From 5544f96241688f8ac6665e88517f258325022ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=AC=C2=BF=C2=A1=C3=B6=C3=8E?= Date: Wed, 11 Dec 2024 23:31:17 +0800 Subject: [PATCH] add code Signed-off-by: lujunxin --- AppScope/app.json5 | 5 +- AppScope/resources/en_US/element/string.json | 8 + .../resources/zh_CN/element/string.json | 4 +- build-profile.json5 | 45 +- common/BuildProfile.ets | 17 + common/build-profile.json5 | 14 +- common/index.ets | 12 +- common/oh-package-lock.json5 | 18 + common/{package.json => oh-package.json5} | 6 +- common/package-lock.json | 5 - .../main/ets/components/AddButton/index.ets | 113 ++ common/src/main/ets/components/Card/index.ets | 109 ++ .../main/ets/components/Clock/ClockHands.ets | 111 ++ common/src/main/ets/components/Clock/Dial.ets | 67 + .../ets/components/Clock/DigitalClock.ets | 100 ++ .../src/main/ets/components/Clock/Scale.ets | 111 ++ .../src/main/ets/components/Clock/index.ets | 230 +++ .../src/main/ets/components/Clock/types.ets | 121 ++ .../components/CommonDialog/CommonDialog.ets | 87 ++ .../CommonDialog/CommonHwDialog.ets | 248 +++ .../CommonDialog/CommonTextInputDialog.ets | 254 ++++ .../components/CommonDialog/ConfirmDialog.ets | 103 ++ .../main/ets/components/CommonGrid/index.ets | 55 +- .../main/ets/components/CommonGrid/types.ets | 40 + .../components/FloatingActionButton/index.ets | 97 ++ .../components/FloatingActionButton/types.ets | 54 + .../components/RingtoneSelection/index.ets | 215 +++ .../components/RingtoneSelection/types.ets | 166 ++ .../main/ets/components/TitleBar/index.ets | 157 ++ common/src/main/ets/components/index.ets | 40 + common/src/main/ets/libs/lottieArkTS.har | Bin 0 -> 138348 bytes common/src/main/ets/manager/AlarmManager.ets | 1064 +++++++++++++ .../main/ets/manager/AlarmStateManager.ets | 157 ++ .../main/ets/manager/BreakpointManager.ets | 136 ++ .../src/main/ets/manager/DatabaseManager.ets | 113 ++ common/src/main/ets/manager/FormManager.ets | 104 ++ .../src/main/ets/manager/PageStateManager.ets | 146 ++ .../main/ets/manager/ResourceAudioManager.ets | 262 ++++ .../src/main/ets/manager/ResourceManager.ets | 151 ++ common/src/main/ets/manager/SnoozeManager.ets | 147 ++ common/src/main/ets/manager/SoundPool.ets | 416 +++++ common/src/main/ets/manager/TimerManager.ets | 163 ++ .../main/ets/manager/TimerStateManager.ets | 84 ++ common/src/main/ets/manager/TsUtilManager.ts | 59 + common/src/main/ets/manager/index.ets | 121 ++ .../src/main/ets/manager/timerSystemTimer.ets | 80 + common/src/main/ets/manager/types.ets | 335 ++++ common/src/main/ets/types.ets | 64 + common/src/main/ets/utils/CommonUtil.ets | 239 +++ common/src/main/ets/utils/ConfigData.ets | 63 - common/src/main/ets/utils/DisplayUtil.ts | 38 + common/src/main/ets/utils/EventReportUtil.ets | 102 ++ common/src/main/ets/utils/EventType.ets | 115 ++ common/src/main/ets/utils/FormUtil.ets | 81 + .../src/main/ets/utils/GlobalContext.ts | 33 +- common/src/main/ets/utils/LogUtil.ts | 47 +- .../ets/utils/NotificationContentUtil.ets | 41 + .../main/ets/utils/RingtoneSelectionUtil.ets | 53 + common/src/main/ets/utils/SettingUtil.ts | 60 + common/src/main/ets/utils/StringUtil.ts | 47 + common/src/main/ets/utils/TimeUtil.ets | 581 +++++++ common/src/main/ets/utils/TimerUtil.ets | 54 - common/src/main/ets/utils/WantAgentUtil.ets | 365 +++++ common/src/main/ets/utils/index.ets | 42 + common/src/main/ets/utils/types.ets | 217 +++ .../main/resources/base/element/color.json | 232 ++- .../main/resources/base/element/float.json | 512 +++++++ .../main/resources/base/element/plural.json | 43 + .../main/resources/base/element/string.json | 48 +- .../src/main/resources/base/media/ic_add.svg | 7 + .../main/resources/base/media/ic_cancel.svg | 13 + .../main/resources/base/media/ic_confirm.svg | 13 + .../resources/base/media/ic_public_ok.svg | 13 + .../main/resources/base/media/ic_reset.svg | 6 + .../main/resources/base/media/ic_ringtone.svg | 8 + .../resources/base/media/ic_stopwatch_lap.svg | 9 + .../base/media/img_clock_dial_scale_day.png | Bin 0 -> 19351 bytes .../main/resources/bo_CN/element/plural.json | 31 + .../main/resources/bo_CN/element/string.json | 40 + .../main/resources/dark/element/color.json | 228 +++ .../main/resources/dark/element/float.json | 508 +++++++ .../main/resources/dark/element/plural.json | 43 + .../main/resources/dark/element/string.json | 48 + .../src/main/resources/dark/media/ic_add.svg | 7 + .../src/main/resources/en/element/plural.json | 43 + .../src/main/resources/en/element/string.json | 40 + .../src/main/resources/ug/element/plural.json | 43 + .../src/main/resources/ug/element/string.json | 40 + .../main/resources/zh_CN/element/plural.json | 31 + .../main/resources/zh_CN/element/string.json | 72 + .../main/resources/zh_HK/element/plural.json | 31 + .../main/resources/zh_HK/element/string.json | 44 + .../main/resources/zh_TW/element/plural.json | 31 + .../main/resources/zh_TW/element/string.json | 44 + .../main/resources/zz_ZX/element/plural.json | 91 ++ .../main/resources/zz_ZX/element/string.json | 48 + feature/{countdown => alarmclock}/.gitignore | 0 feature/alarmclock/BuildProfile.ets | 17 + feature/alarmclock/build-profile.json5 | 17 + .../hvigorfile.ts} | 2 +- feature/alarmclock/index.ets | 43 + feature/alarmclock/oh-package-lock.json5 | 28 + feature/alarmclock/oh-package.json5 | 14 + .../AlarmCard/ObservedAlarmInfo.ets | 71 + .../main/ets/components/AlarmCard/index.ets | 355 +++++ .../main/ets/components/AlarmCard/types.ets | 12 +- .../main/ets/components/ArraySlider/index.ets | 94 ++ .../main/ets/components/ArraySlider/types.ets | 12 +- .../src/main/ets/components/Form/index.ets | 506 +++++++ .../src/main/ets/components/Form/types.ets | 57 + .../main/ets/components/PCBuilder/index.ets | 52 + .../ets/components/SlideCloseButton/index.ets | 110 ++ .../src/main/ets/components/index.ets | 26 + .../main/ets/manager/AlarmServiceManager.ets | 609 ++++++++ .../src/main/ets/manager/AudioManager.ets | 218 +++ .../src/main/ets/manager/AudioPlayer.ts | 248 +++ .../alarmclock/src/main/ets/manager/index.ets | 13 +- .../alarmclock/src/main/ets/manager/types.ets | 56 + .../src/main/ets/pages/BannerAlarm/index.ets | 39 +- .../main/ets/pages/ForegroundPage/index.ets | 33 +- .../main/ets/pages/FullScreenAlarm/index.ets | 360 +++++ .../main/ets/pages/ManageAlarmClock/index.ets | 1343 +++++++++++++++++ .../main/ets/pages/ManageAlarmClock/types.ets | 57 + .../alarmclock/src/main/ets/pages/index.ets | 1295 ++++++++++++++++ .../src/main/ets/utils/AlarmCardUtil.ets | 146 ++ .../src/main/ets/utils/NotificationUtil.ets | 437 ++++++ .../alarmclock/src/main/ets/utils/index.ets | 24 + .../src/main/module.json5 | 3 +- .../main/resources/base/element/color.json | 56 + .../main/resources/base/element/float.json | 320 ++++ .../main/resources/base/element/plural.json | 101 ++ .../main/resources/base/element/string.json | 192 +++ .../resources/base/media/ic_delete_grey.svg | 14 + .../main/resources/bo_CN/element/plural.json | 49 + .../main/resources/bo_CN/element/string.json | 136 ++ .../main/resources/dark/element/float.json | 284 ++++ .../main/resources/dark/element/plural.json | 101 ++ .../main/resources/dark/element/string.json | 156 ++ .../src/main/resources/en/element/plural.json | 101 ++ .../src/main/resources/en/element/string.json | 192 +++ .../src/main/resources/ug/element/plural.json | 69 + .../src/main/resources/ug/element/string.json | 136 ++ .../main/resources/zh_CN/element/plural.json | 71 + .../main/resources/zh_CN/element/string.json | 220 +++ .../main/resources/zh_HK/element/plural.json | 49 + .../main/resources/zh_HK/element/string.json | 140 ++ .../main/resources/zh_TW/element/plural.json | 49 + .../main/resources/zh_TW/element/string.json | 140 ++ .../main/resources/zz_ZX/element/plural.json | 149 ++ .../main/resources/zz_ZX/element/string.json | 192 +++ feature/countdown/package-lock.json | 11 - feature/countdown/package.json | 16 - .../src/main/ets/components/CountdownView.ets | 114 -- .../main/ets/components/TimeSelectView.ets | 130 -- .../ets/controller/CountdownController.ets | 277 ---- .../main/resources/base/element/color.json | 20 - .../main/resources/base/element/float.json | 48 - .../main/resources/zh_CN/element/string.json | 16 - feature/stopwatch/BuildProfile.ets | 17 + .../build-profile.json5 | 0 .../stopwatch/hvigorfile.ts | 2 +- .../stopwatch/index.ets | 12 +- feature/stopwatch/oh-package-lock.json5 | 28 + feature/stopwatch/oh-package.json5 | 14 + .../ets/SoundManager/SoundPoolManager.ets | 159 ++ .../StopwatchDial/StopwatchDialScale.ets | 90 ++ .../ets/components/StopwatchDial/index.ets | 122 ++ .../ets/components/StopwatchDial/types.ets | 21 + .../src/main/ets/components/index.ets | 16 + .../stopwatch/src/main/ets/pages/index.ets | 1188 +++++++++++++++ .../stopwatch/src/main/ets/pages/types.ets | 91 ++ feature/stopwatch/src/main/module.json5 | 10 + .../main/resources/base/element/color.json | 56 + .../main/resources/base/element/float.json | 360 +++++ .../main/resources/base/element/string.json | 16 + .../base/media/ic_stopwatch_button.svg | 9 + .../base/media/img_stopwatch_dial_scale.png | Bin 0 -> 120 bytes .../base/media/img_stopwatch_minutehand.png | Bin 0 -> 120 bytes .../media/img_stopwatch_minutehand_shadow.png | Bin 0 -> 120 bytes .../img_stopwatch_minutes_dial_scale.png | Bin 0 -> 120 bytes .../resources/base/profile/main_pages.json | 0 .../main/resources/dark}/element/color.json | 4 +- .../main/resources/dark/element/float.json | 344 +++++ .../main/resources/dark/element/string.json | 16 + .../resources/dark/profile/main_pages.json | 2 +- .../main/resources/en_US/element/string.json | 16 + .../main/resources/rawfile/pauseToPlay.json | 1 + .../main/resources/rawfile/playToPause.json | 1 + .../main/resources/zh_CN/element/string.json | 12 + feature/timer/BuildProfile.ets | 17 + feature/timer/hvigorfile.ts | 2 + feature/timer/index.ets | 10 +- feature/timer/oh-package-lock.json5 | 28 + feature/timer/oh-package.json5 | 14 + feature/timer/package-lock.json | 11 - feature/timer/package.json | 16 - feature/timer/src/main/ets/common/Utils.ets | 50 + .../src/main/ets/common/preferencesUtil.ets | 141 ++ .../src/main/ets/components/AnalogTimer.ets | 234 +++ .../ets/components/SlideCloseButton/index.ets | 121 ++ .../src/main/ets/components/TimerPicker.ets | 293 ++++ .../main/ets/components/TimerPickerForPC.ets | 277 ++++ .../timer/src/main/ets/components/index.ets | 20 + .../src/main/ets/manager/timerAudioPlayer.ets | 38 + .../src/main/ets/manager/timerManager.ets | 88 ++ feature/timer/src/main/ets/manager/types.ets | 56 + .../timer/src/main/ets/model/DateObject.ets | 32 + feature/timer/src/main/ets/model/Index.ets | 16 + .../main/ets/pages/FullScreenTimer/index.ets | 282 ++++ feature/timer/src/main/ets/pages/index.ets | 1260 ++++++++++++++++ feature/timer/src/main/ets/types/index.ets | 31 + feature/timer/src/main/ets/utils/index.ets | 16 + .../main/ets/utils/timerNotificationUtil.ets | 326 ++++ .../main/resources/base/element/color.json | 81 +- .../main/resources/base/element/float.json | 552 +++++++ .../main/resources/base/element/string.json | 50 +- .../resources/base/media/ic_clock_button.svg | 9 + .../base/media/img_clock_timer_bg.png | Bin 0 -> 173387 bytes .../media/img_clock_timer_dial_secondhand.png | Bin 0 -> 173387 bytes .../resources/base/profile/main_pages.json | 5 + .../main/resources/dark/element/color.json | 41 + .../main/resources/dark/element/float.json | 471 ++++++ .../main/resources/dark}/element/string.json | 16 +- .../dark/media/img_clock_timer_bg.png | Bin 0 -> 225397 bytes .../resources/dark/profile/main_pages.json | 5 + .../main/resources/en_US/element/string.json | 52 + .../main/resources/rawfile/pauseToPlay.json | 1 + .../main/resources/rawfile/playToPause.json | 1 + .../main/resources/zh_CN/element/string.json | 52 + {product/pc => feature/worldclock}/.gitignore | 3 +- feature/worldclock/BuildProfile.ets | 17 + feature/worldclock/build-profile.json5 | 17 + feature/worldclock/hvigorfile.ts | 2 + feature/worldclock/index.ets | 36 + feature/worldclock/oh-package-lock.json5 | 28 + feature/worldclock/oh-package.json5 | 14 + .../main/ets/components/FaClockCard/index.ets | 201 +++ .../ets/components/ListCardForPC/index.ets | 78 + .../ets/components/WorldCityCard/index.ets | 169 +++ .../ets/components/WorldClockCard/index.ets | 235 +++ .../ets/manager/TimeZoneTaskPoolManager.ets | 124 ++ .../main/ets/manager/WorldClockManager.ets | 207 +++ .../worldclock/src/main/ets/manager/index.ets | 18 + .../worldclock/src/main/ets/manager/types.ets | 40 + .../worldclock/src/main/ets/pages/AddCity.ets | 50 + .../src/main/ets/pages/AddWorldClock.ets | 529 +++++++ .../src/main/ets/pages/EditCities.ets | 384 +++++ .../src/main/ets/pages/EditCitiesForPC.ets | 433 ++++++ .../src/main/ets/pages/FaManagerCity.ets | 153 ++ .../src/main/ets/pages/TimeZoneUtil.ets | 199 +++ .../worldclock/src/main/ets/pages/index.ets | 902 +++++++++++ .../src/main/ets/utils/CityClockCardUtil.ets | 186 +++ .../src/main/ets/utils/WorldClockTsUtil.ts | 28 + .../src/main/ets/utils/WorldClockUtil.ets | 132 ++ .../worldclock/src/main/ets/utils/index.ets | 20 + feature/worldclock/src/main/module.json5 | 10 + .../main/resources/base/element/color.json | 24 + .../main/resources/base/element/float.json | 233 +++ .../main/resources/base/element/string.json | 88 ++ .../resources/base/media/ic_back_night.svg | 13 + .../base/media/ic_public_close_filled.svg | 13 + .../base/media/ic_public_drag_handle.svg | 13 + .../main/resources/bo_CN/element/string.json | 64 + .../main/resources/dark/element/color.json | 19 + .../main/resources/dark/element/float.json | 224 +++ .../main/resources/dark/element/string.json | 84 ++ .../resources/dark/media/ic_delete_grey.svg | 14 + .../src/main/resources/en/element/string.json | 84 ++ .../src/main/resources/ug/element/string.json | 64 + .../main/resources/zh_CN/element/string.json | 88 ++ .../main/resources/zh_HK/element/string.json | 64 + .../main/resources/zh_TW/element/string.json | 64 + .../main/resources/zz_ZX/element/string.json | 88 ++ hvigor/hvigor-config.json5 | 8 + local.properties | 8 + oh-package-lock.json5 | 20 + oh-package.json5 | 19 + package.json | 18 - product/pc/build-profile.json5 | 13 - product/pc/package-lock.json | 33 - product/pc/package.json | 18 - .../src/main/ets/MainAbility/MainAbility.ts | 56 - product/pc/src/main/ets/pages/Countdown.ets | 185 --- product/pc/src/main/ets/pages/index.ets | 85 -- .../src/main/ets/pages/timer/TimeOfRecord.ets | 69 - product/pc/src/main/ets/pages/timer/Timer.ets | 70 - .../src/main/ets/pages/timer/TimerControl.ets | 85 -- product/pc/src/main/module.json5 | 43 - .../main/resources/base/element/color.json | 12 - .../main/resources/base/element/float.json | 120 -- .../main/resources/base/element/string.json | 16 - .../ohosTest/ets/TestAbility/TestAbility.ts | 60 - .../ets/TestRunner/OpenHarmonyTestRunner.ts | 79 - .../pc/src/ohosTest/ets/test/Ability.test.ets | 523 ------- product/pc/src/ohosTest/module.json5 | 39 - .../resources/base/element/string.json | 16 - .../ohosTest/resources/base/media/icon.png | Bin 6790 -> 0 bytes product/phone/build-profile.json5 | 23 +- product/phone/oh-package-lock.json5 | 56 + .../phone/{package.json => oh-package.json5} | 14 +- product/phone/package-lock.json | 33 - .../src/main/ets/Application/AbilityStage.ets | 39 + .../ets/BackupExtension/BackupExtension.ets | 73 + .../ets/BackupExtension/BackupMananger.ets | 69 + .../main/ets/BackupExtension/CloneManager.ets | 133 ++ .../src/main/ets/DBBackup/CloneDBHandle.ets | 182 +++ .../src/main/ets/DBBackup/CloneDBManager.ets | 625 ++++++++ .../src/main/ets/DBBackup/CloneFSManager.ets | 76 + .../phone/src/main/ets/DBBackup/DBManager.ets | 496 ++++++ .../src/main/ets/DBBackup/FSMananger.ets | 99 ++ product/phone/src/main/ets/DBBackup/index.ets | 172 +++ .../ets/FaAbility/FaCityManagerAbility.ets | 71 + .../ForegroundAbility/ForegroundAbility.ets | 61 + .../FullScreenAbility/FullScreenAbility.ets | 101 ++ .../main/ets/IntentAbility/CreateAlarm.ets | 129 ++ .../ets/IntentAbility/DelayRingIntent.ets | 78 + .../ets/IntentAbility/DeleteAlarmIntent.ets | 90 ++ .../main/ets/IntentAbility/EntryAbility.ets | 68 + .../ets/IntentAbility/ModifyAlarmIntent.ets | 142 ++ .../main/ets/IntentAbility/QueryAlarmRing.ets | 101 ++ .../ets/IntentAbility/SearchAlarmIntent.ets | 212 +++ .../main/ets/IntentAbility/StopRingIntent.ets | 85 ++ .../src/main/ets/IntentAbility/ViewAlarm.ets | 103 ++ .../src/main/ets/MainAbility/MainAbility.ets | 344 +++++ .../src/main/ets/MainAbility/MainAbility.ts | 56 - .../ets/ServiceExtAbility/AlarmService.ets | 242 +++ .../ets/ServiceExtAbility/TimerService.ets | 195 +++ .../ets/entryformability/EntryFormAbility.ets | 137 ++ product/phone/src/main/ets/pages/AddCity.ets | 33 + .../phone/src/main/ets/pages/AddCityNew.ets | 33 + .../phone/src/main/ets/pages/BannerAlarm.ets | 31 + .../phone/src/main/ets/pages/Countdown.ets | 158 -- .../phone/src/main/ets/pages/EditCities.ets | 33 + .../src/main/ets/pages/EditCitiesForPC.ets | 25 +- .../src/main/ets/pages/FaManagerCity.ets | 29 + .../src/main/ets/pages/ForegroundPage.ets | 31 + .../src/main/ets/pages/FullScreenAlarm.ets | 44 + .../src/main/ets/pages/FullScreenTimer.ets} | 64 +- .../src/main/ets/pages/ManageAlarmClock.ets | 33 + product/phone/src/main/ets/pages/index.ets | 1076 ++++++++++++- product/phone/src/main/ets/pages/index11.ets | 1041 +++++++++++++ .../src/main/ets/pages/timer/TimeOfRecord.ets | 72 - .../phone/src/main/ets/pages/timer/Timer.ets | 79 - .../src/main/ets/pages/timer/TimerControl.ets | 75 - .../ets/subscriber/AlarmInitSubscriber.ets | 67 + .../src/main/ets/widget/pages/WidgetCard.ets | 133 ++ .../src/main/ets/workers/AudioWorker.ts} | 66 +- product/phone/src/main/module.json5 | 167 +- .../main/resources/base/element/color.json | 40 + .../main/resources/base/element/float.json | 487 +++++- .../main/resources/base/element/string.json | 34 +- .../main/resources/base/media/Stateless.svg | 21 + .../main/resources/base/media/app_icon.png} | Bin .../main/resources/base/media/background.png | Bin 0 -> 19351 bytes .../main/resources/base/media/foreground.png | Bin 0 -> 4155 bytes .../main/resources/base/media/ic_alarm.svg | 12 + .../base/media/ic_alarm_activated.svg | 12 + .../resources/base/media/ic_stopwatch.svg | 10 + .../base/media/ic_stopwatch_activated.svg | 10 + .../main/resources/base/media/ic_timer.svg | 9 + .../base/media/ic_timer_activated.svg | 9 + .../resources/base/media/ic_world_clock.svg | 9 + .../base/media/ic_world_clock_activated.svg | 9 + .../resources/base/media/icon_clock_black.svg | 10 + .../resources/base/media/icon_clock_white.svg | 10 + .../src/main/resources/base/media/logo.json | 7 + .../resources/base/media/starting_window.png | Bin 0 -> 120 bytes .../resources/base/profile/backup_config.json | 26 + .../resources/base/profile/form_config.json | 23 + .../base/profile/insight_intent.json | 100 ++ .../base/profile/insight_intent_schema.json | 160 ++ .../resources/base/profile/main_pages.json | 13 +- .../profile/static_subscriber_config.json | 15 + .../main/resources/bo_CN/element/string.json | 12 + .../main/resources/dark/element/color.json | 32 + .../main/resources/dark/element/float.json | 444 ++++++ .../main/resources/dark/element/string.json | 39 + .../main/resources/dark/media/background.png | Bin 0 -> 19351 bytes .../main/resources/dark/media/foreground.png | Bin 0 -> 4155 bytes .../main/resources/dark/media/ic_alarm.svg | 12 + .../dark/media/ic_alarm_activated.svg | 12 + .../resources/dark/media/ic_stopwatch.svg | 10 + .../dark/media/ic_stopwatch_activated.svg | 10 + .../main/resources/dark/media/ic_timer.svg | 9 + .../dark/media/ic_timer_activated.svg | 9 + .../resources/dark/media/ic_world_clock.svg | 9 + .../dark/media/ic_world_clock_activated.svg | 9 + .../src/main/resources/dark/media/icon.png | Bin 0 -> 3149 bytes .../resources/dark/media/icon_clock_black.svg | 10 + .../resources/dark/media/icon_clock_white.svg | 10 + .../src/main/resources/dark/media/logo.json | 7 + .../resources/dark/media/starting_window.png | Bin 0 -> 120 bytes .../src/main/resources/ug/element/string.json | 12 + .../main/resources/zh_CN/element/string.json | 37 +- .../main/resources/zh_HK/element/string.json | 12 + .../main/resources/zh_TW/element/string.json | 12 + .../main/resources/zz_ZX/element/string.json | 44 + .../src/ohosTest/ets/test/Ability.test.ets | 1006 ++++++------ signature/clock.cer | 1 + signature/clock.p12 | Bin 0 -> 8907 bytes signature/clock.p7b | Bin 0 -> 4118 bytes .../ac/c261694e5c98460c8fae5ed342968818 | Bin 0 -> 16 bytes .../ce/ed64ba4c7ae2411494c305b974fae270 | Bin 0 -> 48 bytes .../fd/0/2766e6523cee499eb7cb48c736dfe851 | 1 + .../fd/1/b53e8606966740828855d77874293083 | 1 + .../fd/2/1ae5d39f0d734626b82fb45cddb470df | 1 + 406 files changed, 41655 insertions(+), 3705 deletions(-) create mode 100644 AppScope/resources/en_US/element/string.json rename {product/pc/src/main => AppScope}/resources/zh_CN/element/string.json (60%) create mode 100644 common/BuildProfile.ets create mode 100644 common/oh-package-lock.json5 rename common/{package.json => oh-package.json5} (67%) delete mode 100644 common/package-lock.json create mode 100644 common/src/main/ets/components/AddButton/index.ets create mode 100644 common/src/main/ets/components/Card/index.ets create mode 100644 common/src/main/ets/components/Clock/ClockHands.ets create mode 100644 common/src/main/ets/components/Clock/Dial.ets create mode 100644 common/src/main/ets/components/Clock/DigitalClock.ets create mode 100644 common/src/main/ets/components/Clock/Scale.ets create mode 100644 common/src/main/ets/components/Clock/index.ets create mode 100644 common/src/main/ets/components/Clock/types.ets create mode 100644 common/src/main/ets/components/CommonDialog/CommonDialog.ets create mode 100644 common/src/main/ets/components/CommonDialog/CommonHwDialog.ets create mode 100644 common/src/main/ets/components/CommonDialog/CommonTextInputDialog.ets create mode 100644 common/src/main/ets/components/CommonDialog/ConfirmDialog.ets rename product/phone/src/main/ets/pages/timer/CurrentTimeDisplay.ets => common/src/main/ets/components/CommonGrid/index.ets (36%) create mode 100644 common/src/main/ets/components/CommonGrid/types.ets create mode 100644 common/src/main/ets/components/FloatingActionButton/index.ets create mode 100644 common/src/main/ets/components/FloatingActionButton/types.ets create mode 100644 common/src/main/ets/components/RingtoneSelection/index.ets create mode 100644 common/src/main/ets/components/RingtoneSelection/types.ets create mode 100644 common/src/main/ets/components/TitleBar/index.ets create mode 100644 common/src/main/ets/components/index.ets create mode 100644 common/src/main/ets/libs/lottieArkTS.har create mode 100644 common/src/main/ets/manager/AlarmManager.ets create mode 100644 common/src/main/ets/manager/AlarmStateManager.ets create mode 100644 common/src/main/ets/manager/BreakpointManager.ets create mode 100644 common/src/main/ets/manager/DatabaseManager.ets create mode 100644 common/src/main/ets/manager/FormManager.ets create mode 100644 common/src/main/ets/manager/PageStateManager.ets create mode 100644 common/src/main/ets/manager/ResourceAudioManager.ets create mode 100644 common/src/main/ets/manager/ResourceManager.ets create mode 100644 common/src/main/ets/manager/SnoozeManager.ets create mode 100644 common/src/main/ets/manager/SoundPool.ets create mode 100644 common/src/main/ets/manager/TimerManager.ets create mode 100644 common/src/main/ets/manager/TimerStateManager.ets create mode 100644 common/src/main/ets/manager/TsUtilManager.ts create mode 100644 common/src/main/ets/manager/index.ets create mode 100644 common/src/main/ets/manager/timerSystemTimer.ets create mode 100644 common/src/main/ets/manager/types.ets create mode 100644 common/src/main/ets/types.ets create mode 100644 common/src/main/ets/utils/CommonUtil.ets delete mode 100644 common/src/main/ets/utils/ConfigData.ets create mode 100644 common/src/main/ets/utils/DisplayUtil.ts create mode 100644 common/src/main/ets/utils/EventReportUtil.ets create mode 100644 common/src/main/ets/utils/EventType.ets create mode 100644 common/src/main/ets/utils/FormUtil.ets rename product/phone/src/main/ets/pages/timer/TimerClock.ets => common/src/main/ets/utils/GlobalContext.ts (50%) create mode 100644 common/src/main/ets/utils/NotificationContentUtil.ets create mode 100644 common/src/main/ets/utils/RingtoneSelectionUtil.ets create mode 100644 common/src/main/ets/utils/SettingUtil.ts create mode 100644 common/src/main/ets/utils/StringUtil.ts create mode 100644 common/src/main/ets/utils/TimeUtil.ets delete mode 100644 common/src/main/ets/utils/TimerUtil.ets create mode 100644 common/src/main/ets/utils/WantAgentUtil.ets create mode 100644 common/src/main/ets/utils/index.ets create mode 100644 common/src/main/ets/utils/types.ets create mode 100644 common/src/main/resources/base/element/float.json create mode 100644 common/src/main/resources/base/element/plural.json create mode 100644 common/src/main/resources/base/media/ic_add.svg create mode 100644 common/src/main/resources/base/media/ic_cancel.svg create mode 100644 common/src/main/resources/base/media/ic_confirm.svg create mode 100644 common/src/main/resources/base/media/ic_public_ok.svg create mode 100644 common/src/main/resources/base/media/ic_reset.svg create mode 100644 common/src/main/resources/base/media/ic_ringtone.svg create mode 100644 common/src/main/resources/base/media/ic_stopwatch_lap.svg create mode 100644 common/src/main/resources/base/media/img_clock_dial_scale_day.png create mode 100644 common/src/main/resources/bo_CN/element/plural.json create mode 100644 common/src/main/resources/bo_CN/element/string.json create mode 100644 common/src/main/resources/dark/element/color.json create mode 100644 common/src/main/resources/dark/element/float.json create mode 100644 common/src/main/resources/dark/element/plural.json create mode 100644 common/src/main/resources/dark/element/string.json create mode 100644 common/src/main/resources/dark/media/ic_add.svg create mode 100644 common/src/main/resources/en/element/plural.json create mode 100644 common/src/main/resources/en/element/string.json create mode 100644 common/src/main/resources/ug/element/plural.json create mode 100644 common/src/main/resources/ug/element/string.json create mode 100644 common/src/main/resources/zh_CN/element/plural.json create mode 100644 common/src/main/resources/zh_HK/element/plural.json create mode 100644 common/src/main/resources/zh_HK/element/string.json create mode 100644 common/src/main/resources/zh_TW/element/plural.json create mode 100644 common/src/main/resources/zh_TW/element/string.json create mode 100644 common/src/main/resources/zz_ZX/element/plural.json create mode 100644 common/src/main/resources/zz_ZX/element/string.json rename feature/{countdown => alarmclock}/.gitignore (100%) create mode 100644 feature/alarmclock/BuildProfile.ets create mode 100644 feature/alarmclock/build-profile.json5 rename feature/{countdown/hvigorfile.js => alarmclock/hvigorfile.ts} (63%) create mode 100644 feature/alarmclock/index.ets create mode 100644 feature/alarmclock/oh-package-lock.json5 create mode 100644 feature/alarmclock/oh-package.json5 create mode 100644 feature/alarmclock/src/main/ets/components/AlarmCard/ObservedAlarmInfo.ets create mode 100644 feature/alarmclock/src/main/ets/components/AlarmCard/index.ets rename product/pc/src/ohosTest/ets/test/List.test.ets => feature/alarmclock/src/main/ets/components/AlarmCard/types.ets (78%) create mode 100644 feature/alarmclock/src/main/ets/components/ArraySlider/index.ets rename product/phone/src/main/ets/Application/MyAbilityStage.ts => feature/alarmclock/src/main/ets/components/ArraySlider/types.ets (68%) create mode 100644 feature/alarmclock/src/main/ets/components/Form/index.ets create mode 100644 feature/alarmclock/src/main/ets/components/Form/types.ets create mode 100644 feature/alarmclock/src/main/ets/components/PCBuilder/index.ets create mode 100644 feature/alarmclock/src/main/ets/components/SlideCloseButton/index.ets create mode 100644 feature/alarmclock/src/main/ets/components/index.ets create mode 100644 feature/alarmclock/src/main/ets/manager/AlarmServiceManager.ets create mode 100644 feature/alarmclock/src/main/ets/manager/AudioManager.ets create mode 100644 feature/alarmclock/src/main/ets/manager/AudioPlayer.ts rename product/pc/src/main/ets/Application/MyAbilityStage.ts => feature/alarmclock/src/main/ets/manager/index.ets (68%) create mode 100644 feature/alarmclock/src/main/ets/manager/types.ets rename common/src/main/ets/components/imageComponent.ets => feature/alarmclock/src/main/ets/pages/BannerAlarm/index.ets (56%) rename product/pc/src/main/ets/pages/timer/TimerClock.ets => feature/alarmclock/src/main/ets/pages/ForegroundPage/index.ets (56%) create mode 100644 feature/alarmclock/src/main/ets/pages/FullScreenAlarm/index.ets create mode 100644 feature/alarmclock/src/main/ets/pages/ManageAlarmClock/index.ets create mode 100644 feature/alarmclock/src/main/ets/pages/ManageAlarmClock/types.ets create mode 100644 feature/alarmclock/src/main/ets/pages/index.ets create mode 100644 feature/alarmclock/src/main/ets/utils/AlarmCardUtil.ets create mode 100644 feature/alarmclock/src/main/ets/utils/NotificationUtil.ets create mode 100644 feature/alarmclock/src/main/ets/utils/index.ets rename feature/{countdown => alarmclock}/src/main/module.json5 (68%) create mode 100644 feature/alarmclock/src/main/resources/base/element/color.json create mode 100644 feature/alarmclock/src/main/resources/base/element/float.json create mode 100644 feature/alarmclock/src/main/resources/base/element/plural.json create mode 100644 feature/alarmclock/src/main/resources/base/element/string.json create mode 100644 feature/alarmclock/src/main/resources/base/media/ic_delete_grey.svg create mode 100644 feature/alarmclock/src/main/resources/bo_CN/element/plural.json create mode 100644 feature/alarmclock/src/main/resources/bo_CN/element/string.json create mode 100644 feature/alarmclock/src/main/resources/dark/element/float.json create mode 100644 feature/alarmclock/src/main/resources/dark/element/plural.json create mode 100644 feature/alarmclock/src/main/resources/dark/element/string.json create mode 100644 feature/alarmclock/src/main/resources/en/element/plural.json create mode 100644 feature/alarmclock/src/main/resources/en/element/string.json create mode 100644 feature/alarmclock/src/main/resources/ug/element/plural.json create mode 100644 feature/alarmclock/src/main/resources/ug/element/string.json create mode 100644 feature/alarmclock/src/main/resources/zh_CN/element/plural.json create mode 100644 feature/alarmclock/src/main/resources/zh_CN/element/string.json create mode 100644 feature/alarmclock/src/main/resources/zh_HK/element/plural.json create mode 100644 feature/alarmclock/src/main/resources/zh_HK/element/string.json create mode 100644 feature/alarmclock/src/main/resources/zh_TW/element/plural.json create mode 100644 feature/alarmclock/src/main/resources/zh_TW/element/string.json create mode 100644 feature/alarmclock/src/main/resources/zz_ZX/element/plural.json create mode 100644 feature/alarmclock/src/main/resources/zz_ZX/element/string.json delete mode 100644 feature/countdown/package-lock.json delete mode 100644 feature/countdown/package.json delete mode 100644 feature/countdown/src/main/ets/components/CountdownView.ets delete mode 100644 feature/countdown/src/main/ets/components/TimeSelectView.ets delete mode 100644 feature/countdown/src/main/ets/controller/CountdownController.ets delete mode 100644 feature/countdown/src/main/resources/base/element/color.json delete mode 100644 feature/countdown/src/main/resources/base/element/float.json delete mode 100644 feature/countdown/src/main/resources/zh_CN/element/string.json create mode 100644 feature/stopwatch/BuildProfile.ets rename feature/{countdown => stopwatch}/build-profile.json5 (100%) rename product/pc/hvigorfile.js => feature/stopwatch/hvigorfile.ts (62%) rename product/pc/src/ohosTest/ets/Application/TestAbilityStage.ts => feature/stopwatch/index.ets (68%) create mode 100644 feature/stopwatch/oh-package-lock.json5 create mode 100644 feature/stopwatch/oh-package.json5 create mode 100644 feature/stopwatch/src/main/ets/SoundManager/SoundPoolManager.ets create mode 100644 feature/stopwatch/src/main/ets/components/StopwatchDial/StopwatchDialScale.ets create mode 100644 feature/stopwatch/src/main/ets/components/StopwatchDial/index.ets create mode 100644 feature/stopwatch/src/main/ets/components/StopwatchDial/types.ets create mode 100644 feature/stopwatch/src/main/ets/components/index.ets create mode 100644 feature/stopwatch/src/main/ets/pages/index.ets create mode 100644 feature/stopwatch/src/main/ets/pages/types.ets create mode 100644 feature/stopwatch/src/main/module.json5 create mode 100644 feature/stopwatch/src/main/resources/base/element/color.json create mode 100644 feature/stopwatch/src/main/resources/base/element/float.json create mode 100644 feature/stopwatch/src/main/resources/base/element/string.json create mode 100644 feature/stopwatch/src/main/resources/base/media/ic_stopwatch_button.svg create mode 100644 feature/stopwatch/src/main/resources/base/media/img_stopwatch_dial_scale.png create mode 100644 feature/stopwatch/src/main/resources/base/media/img_stopwatch_minutehand.png create mode 100644 feature/stopwatch/src/main/resources/base/media/img_stopwatch_minutehand_shadow.png create mode 100644 feature/stopwatch/src/main/resources/base/media/img_stopwatch_minutes_dial_scale.png rename {product/pc => feature/stopwatch}/src/main/resources/base/profile/main_pages.json (100%) rename {product/pc/src/ohosTest/resources/base => feature/stopwatch/src/main/resources/dark}/element/color.json (32%) create mode 100644 feature/stopwatch/src/main/resources/dark/element/float.json create mode 100644 feature/stopwatch/src/main/resources/dark/element/string.json rename product/pc/src/ohosTest/resources/base/profile/test_pages.json => feature/stopwatch/src/main/resources/dark/profile/main_pages.json (38%) create mode 100644 feature/stopwatch/src/main/resources/en_US/element/string.json create mode 100644 feature/stopwatch/src/main/resources/rawfile/pauseToPlay.json create mode 100644 feature/stopwatch/src/main/resources/rawfile/playToPause.json create mode 100644 feature/stopwatch/src/main/resources/zh_CN/element/string.json create mode 100644 feature/timer/BuildProfile.ets create mode 100644 feature/timer/hvigorfile.ts create mode 100644 feature/timer/oh-package-lock.json5 create mode 100644 feature/timer/oh-package.json5 delete mode 100644 feature/timer/package-lock.json delete mode 100644 feature/timer/package.json create mode 100644 feature/timer/src/main/ets/common/Utils.ets create mode 100644 feature/timer/src/main/ets/common/preferencesUtil.ets create mode 100644 feature/timer/src/main/ets/components/AnalogTimer.ets create mode 100644 feature/timer/src/main/ets/components/SlideCloseButton/index.ets create mode 100644 feature/timer/src/main/ets/components/TimerPicker.ets create mode 100644 feature/timer/src/main/ets/components/TimerPickerForPC.ets create mode 100644 feature/timer/src/main/ets/components/index.ets create mode 100644 feature/timer/src/main/ets/manager/timerAudioPlayer.ets create mode 100644 feature/timer/src/main/ets/manager/timerManager.ets create mode 100644 feature/timer/src/main/ets/manager/types.ets create mode 100644 feature/timer/src/main/ets/model/DateObject.ets create mode 100644 feature/timer/src/main/ets/model/Index.ets create mode 100644 feature/timer/src/main/ets/pages/FullScreenTimer/index.ets create mode 100644 feature/timer/src/main/ets/pages/index.ets create mode 100644 feature/timer/src/main/ets/types/index.ets create mode 100644 feature/timer/src/main/ets/utils/index.ets create mode 100644 feature/timer/src/main/ets/utils/timerNotificationUtil.ets create mode 100644 feature/timer/src/main/resources/base/element/float.json create mode 100644 feature/timer/src/main/resources/base/media/ic_clock_button.svg create mode 100644 feature/timer/src/main/resources/base/media/img_clock_timer_bg.png create mode 100644 feature/timer/src/main/resources/base/media/img_clock_timer_dial_secondhand.png create mode 100644 feature/timer/src/main/resources/base/profile/main_pages.json create mode 100644 feature/timer/src/main/resources/dark/element/color.json create mode 100644 feature/timer/src/main/resources/dark/element/float.json rename feature/{countdown/src/main/resources/base => timer/src/main/resources/dark}/element/string.json (34%) create mode 100644 feature/timer/src/main/resources/dark/media/img_clock_timer_bg.png create mode 100644 feature/timer/src/main/resources/dark/profile/main_pages.json create mode 100644 feature/timer/src/main/resources/en_US/element/string.json create mode 100644 feature/timer/src/main/resources/rawfile/pauseToPlay.json create mode 100644 feature/timer/src/main/resources/rawfile/playToPause.json create mode 100644 feature/timer/src/main/resources/zh_CN/element/string.json rename {product/pc => feature/worldclock}/.gitignore (66%) create mode 100644 feature/worldclock/BuildProfile.ets create mode 100644 feature/worldclock/build-profile.json5 create mode 100644 feature/worldclock/hvigorfile.ts create mode 100644 feature/worldclock/index.ets create mode 100644 feature/worldclock/oh-package-lock.json5 create mode 100644 feature/worldclock/oh-package.json5 create mode 100644 feature/worldclock/src/main/ets/components/FaClockCard/index.ets create mode 100644 feature/worldclock/src/main/ets/components/ListCardForPC/index.ets create mode 100644 feature/worldclock/src/main/ets/components/WorldCityCard/index.ets create mode 100644 feature/worldclock/src/main/ets/components/WorldClockCard/index.ets create mode 100644 feature/worldclock/src/main/ets/manager/TimeZoneTaskPoolManager.ets create mode 100644 feature/worldclock/src/main/ets/manager/WorldClockManager.ets create mode 100644 feature/worldclock/src/main/ets/manager/index.ets create mode 100644 feature/worldclock/src/main/ets/manager/types.ets create mode 100644 feature/worldclock/src/main/ets/pages/AddCity.ets create mode 100644 feature/worldclock/src/main/ets/pages/AddWorldClock.ets create mode 100644 feature/worldclock/src/main/ets/pages/EditCities.ets create mode 100644 feature/worldclock/src/main/ets/pages/EditCitiesForPC.ets create mode 100644 feature/worldclock/src/main/ets/pages/FaManagerCity.ets create mode 100644 feature/worldclock/src/main/ets/pages/TimeZoneUtil.ets create mode 100644 feature/worldclock/src/main/ets/pages/index.ets create mode 100644 feature/worldclock/src/main/ets/utils/CityClockCardUtil.ets create mode 100644 feature/worldclock/src/main/ets/utils/WorldClockTsUtil.ts create mode 100644 feature/worldclock/src/main/ets/utils/WorldClockUtil.ets create mode 100644 feature/worldclock/src/main/ets/utils/index.ets create mode 100644 feature/worldclock/src/main/module.json5 create mode 100644 feature/worldclock/src/main/resources/base/element/color.json create mode 100644 feature/worldclock/src/main/resources/base/element/float.json create mode 100644 feature/worldclock/src/main/resources/base/element/string.json create mode 100644 feature/worldclock/src/main/resources/base/media/ic_back_night.svg create mode 100644 feature/worldclock/src/main/resources/base/media/ic_public_close_filled.svg create mode 100644 feature/worldclock/src/main/resources/base/media/ic_public_drag_handle.svg create mode 100644 feature/worldclock/src/main/resources/bo_CN/element/string.json create mode 100644 feature/worldclock/src/main/resources/dark/element/color.json create mode 100644 feature/worldclock/src/main/resources/dark/element/float.json create mode 100644 feature/worldclock/src/main/resources/dark/element/string.json create mode 100644 feature/worldclock/src/main/resources/dark/media/ic_delete_grey.svg create mode 100644 feature/worldclock/src/main/resources/en/element/string.json create mode 100644 feature/worldclock/src/main/resources/ug/element/string.json create mode 100644 feature/worldclock/src/main/resources/zh_CN/element/string.json create mode 100644 feature/worldclock/src/main/resources/zh_HK/element/string.json create mode 100644 feature/worldclock/src/main/resources/zh_TW/element/string.json create mode 100644 feature/worldclock/src/main/resources/zz_ZX/element/string.json create mode 100644 hvigor/hvigor-config.json5 create mode 100644 local.properties create mode 100644 oh-package-lock.json5 create mode 100644 oh-package.json5 delete mode 100644 package.json delete mode 100644 product/pc/build-profile.json5 delete mode 100644 product/pc/package-lock.json delete mode 100644 product/pc/package.json delete mode 100644 product/pc/src/main/ets/MainAbility/MainAbility.ts delete mode 100644 product/pc/src/main/ets/pages/Countdown.ets delete mode 100644 product/pc/src/main/ets/pages/index.ets delete mode 100644 product/pc/src/main/ets/pages/timer/TimeOfRecord.ets delete mode 100644 product/pc/src/main/ets/pages/timer/Timer.ets delete mode 100644 product/pc/src/main/ets/pages/timer/TimerControl.ets delete mode 100644 product/pc/src/main/module.json5 delete mode 100644 product/pc/src/main/resources/base/element/color.json delete mode 100644 product/pc/src/main/resources/base/element/float.json delete mode 100644 product/pc/src/main/resources/base/element/string.json delete mode 100644 product/pc/src/ohosTest/ets/TestAbility/TestAbility.ts delete mode 100644 product/pc/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts delete mode 100644 product/pc/src/ohosTest/ets/test/Ability.test.ets delete mode 100644 product/pc/src/ohosTest/module.json5 delete mode 100644 product/pc/src/ohosTest/resources/base/element/string.json delete mode 100644 product/pc/src/ohosTest/resources/base/media/icon.png create mode 100644 product/phone/oh-package-lock.json5 rename product/phone/{package.json => oh-package.json5} (37%) delete mode 100644 product/phone/package-lock.json create mode 100644 product/phone/src/main/ets/Application/AbilityStage.ets create mode 100644 product/phone/src/main/ets/BackupExtension/BackupExtension.ets create mode 100644 product/phone/src/main/ets/BackupExtension/BackupMananger.ets create mode 100644 product/phone/src/main/ets/BackupExtension/CloneManager.ets create mode 100644 product/phone/src/main/ets/DBBackup/CloneDBHandle.ets create mode 100644 product/phone/src/main/ets/DBBackup/CloneDBManager.ets create mode 100644 product/phone/src/main/ets/DBBackup/CloneFSManager.ets create mode 100644 product/phone/src/main/ets/DBBackup/DBManager.ets create mode 100644 product/phone/src/main/ets/DBBackup/FSMananger.ets create mode 100644 product/phone/src/main/ets/DBBackup/index.ets create mode 100644 product/phone/src/main/ets/FaAbility/FaCityManagerAbility.ets create mode 100644 product/phone/src/main/ets/ForegroundAbility/ForegroundAbility.ets create mode 100644 product/phone/src/main/ets/FullScreenAbility/FullScreenAbility.ets create mode 100644 product/phone/src/main/ets/IntentAbility/CreateAlarm.ets create mode 100644 product/phone/src/main/ets/IntentAbility/DelayRingIntent.ets create mode 100644 product/phone/src/main/ets/IntentAbility/DeleteAlarmIntent.ets create mode 100644 product/phone/src/main/ets/IntentAbility/EntryAbility.ets create mode 100644 product/phone/src/main/ets/IntentAbility/ModifyAlarmIntent.ets create mode 100644 product/phone/src/main/ets/IntentAbility/QueryAlarmRing.ets create mode 100644 product/phone/src/main/ets/IntentAbility/SearchAlarmIntent.ets create mode 100644 product/phone/src/main/ets/IntentAbility/StopRingIntent.ets create mode 100644 product/phone/src/main/ets/IntentAbility/ViewAlarm.ets create mode 100644 product/phone/src/main/ets/MainAbility/MainAbility.ets delete mode 100644 product/phone/src/main/ets/MainAbility/MainAbility.ts create mode 100644 product/phone/src/main/ets/ServiceExtAbility/AlarmService.ets create mode 100644 product/phone/src/main/ets/ServiceExtAbility/TimerService.ets create mode 100644 product/phone/src/main/ets/entryformability/EntryFormAbility.ets create mode 100644 product/phone/src/main/ets/pages/AddCity.ets create mode 100644 product/phone/src/main/ets/pages/AddCityNew.ets create mode 100644 product/phone/src/main/ets/pages/BannerAlarm.ets delete mode 100644 product/phone/src/main/ets/pages/Countdown.ets create mode 100644 product/phone/src/main/ets/pages/EditCities.ets rename feature/countdown/index.ets => product/phone/src/main/ets/pages/EditCitiesForPC.ets (64%) create mode 100644 product/phone/src/main/ets/pages/FaManagerCity.ets create mode 100644 product/phone/src/main/ets/pages/ForegroundPage.ets create mode 100644 product/phone/src/main/ets/pages/FullScreenAlarm.ets rename product/{pc/src/ohosTest/ets/TestAbility/pages/index.ets => phone/src/main/ets/pages/FullScreenTimer.ets} (41%) create mode 100644 product/phone/src/main/ets/pages/ManageAlarmClock.ets create mode 100644 product/phone/src/main/ets/pages/index11.ets delete mode 100644 product/phone/src/main/ets/pages/timer/TimeOfRecord.ets delete mode 100644 product/phone/src/main/ets/pages/timer/Timer.ets delete mode 100644 product/phone/src/main/ets/pages/timer/TimerControl.ets create mode 100644 product/phone/src/main/ets/subscriber/AlarmInitSubscriber.ets create mode 100644 product/phone/src/main/ets/widget/pages/WidgetCard.ets rename product/{pc/src/main/ets/pages/timer/CurrentTimeDisplay.ets => phone/src/main/ets/workers/AudioWorker.ts} (31%) create mode 100644 product/phone/src/main/resources/base/element/color.json create mode 100644 product/phone/src/main/resources/base/media/Stateless.svg rename product/{pc/src/main/resources/base/media/icon.png => phone/src/main/resources/base/media/app_icon.png} (100%) create mode 100644 product/phone/src/main/resources/base/media/background.png create mode 100644 product/phone/src/main/resources/base/media/foreground.png create mode 100644 product/phone/src/main/resources/base/media/ic_alarm.svg create mode 100644 product/phone/src/main/resources/base/media/ic_alarm_activated.svg create mode 100644 product/phone/src/main/resources/base/media/ic_stopwatch.svg create mode 100644 product/phone/src/main/resources/base/media/ic_stopwatch_activated.svg create mode 100644 product/phone/src/main/resources/base/media/ic_timer.svg create mode 100644 product/phone/src/main/resources/base/media/ic_timer_activated.svg create mode 100644 product/phone/src/main/resources/base/media/ic_world_clock.svg create mode 100644 product/phone/src/main/resources/base/media/ic_world_clock_activated.svg create mode 100644 product/phone/src/main/resources/base/media/icon_clock_black.svg create mode 100644 product/phone/src/main/resources/base/media/icon_clock_white.svg create mode 100644 product/phone/src/main/resources/base/media/logo.json create mode 100644 product/phone/src/main/resources/base/media/starting_window.png create mode 100644 product/phone/src/main/resources/base/profile/backup_config.json create mode 100644 product/phone/src/main/resources/base/profile/form_config.json create mode 100644 product/phone/src/main/resources/base/profile/insight_intent.json create mode 100644 product/phone/src/main/resources/base/profile/insight_intent_schema.json create mode 100644 product/phone/src/main/resources/base/profile/static_subscriber_config.json create mode 100644 product/phone/src/main/resources/bo_CN/element/string.json create mode 100644 product/phone/src/main/resources/dark/element/color.json create mode 100644 product/phone/src/main/resources/dark/element/float.json create mode 100644 product/phone/src/main/resources/dark/element/string.json create mode 100644 product/phone/src/main/resources/dark/media/background.png create mode 100644 product/phone/src/main/resources/dark/media/foreground.png create mode 100644 product/phone/src/main/resources/dark/media/ic_alarm.svg create mode 100644 product/phone/src/main/resources/dark/media/ic_alarm_activated.svg create mode 100644 product/phone/src/main/resources/dark/media/ic_stopwatch.svg create mode 100644 product/phone/src/main/resources/dark/media/ic_stopwatch_activated.svg create mode 100644 product/phone/src/main/resources/dark/media/ic_timer.svg create mode 100644 product/phone/src/main/resources/dark/media/ic_timer_activated.svg create mode 100644 product/phone/src/main/resources/dark/media/ic_world_clock.svg create mode 100644 product/phone/src/main/resources/dark/media/ic_world_clock_activated.svg create mode 100644 product/phone/src/main/resources/dark/media/icon.png create mode 100644 product/phone/src/main/resources/dark/media/icon_clock_black.svg create mode 100644 product/phone/src/main/resources/dark/media/icon_clock_white.svg create mode 100644 product/phone/src/main/resources/dark/media/logo.json create mode 100644 product/phone/src/main/resources/dark/media/starting_window.png create mode 100644 product/phone/src/main/resources/ug/element/string.json create mode 100644 product/phone/src/main/resources/zh_HK/element/string.json create mode 100644 product/phone/src/main/resources/zh_TW/element/string.json create mode 100644 product/phone/src/main/resources/zz_ZX/element/string.json create mode 100644 signature/clock.cer create mode 100644 signature/clock.p12 create mode 100644 signature/clock.p7b create mode 100644 signature/material/ac/c261694e5c98460c8fae5ed342968818 create mode 100644 signature/material/ce/ed64ba4c7ae2411494c305b974fae270 create mode 100644 signature/material/fd/0/2766e6523cee499eb7cb48c736dfe851 create mode 100644 signature/material/fd/1/b53e8606966740828855d77874293083 create mode 100644 signature/material/fd/2/1ae5d39f0d734626b82fb45cddb470df diff --git a/AppScope/app.json5 b/AppScope/app.json5 index 97024c9..86979fe 100644 --- a/AppScope/app.json5 +++ b/AppScope/app.json5 @@ -6,6 +6,9 @@ "versionName": "1.0.0", "icon": "$media:app_icon", "label": "$string:app_name", - "distributedNotificationEnabled": true + "distributedNotificationEnabled": true, + "minAPIVersion": 11, + "targetAPIVersion": 11, + "debug": false } } diff --git a/AppScope/resources/en_US/element/string.json b/AppScope/resources/en_US/element/string.json new file mode 100644 index 0000000..b490efc --- /dev/null +++ b/AppScope/resources/en_US/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "Clock" + } + ] +} diff --git a/product/pc/src/main/resources/zh_CN/element/string.json b/AppScope/resources/zh_CN/element/string.json similarity index 60% rename from product/pc/src/main/resources/zh_CN/element/string.json rename to AppScope/resources/zh_CN/element/string.json index 4e7d9f8..cba0f84 100644 --- a/product/pc/src/main/resources/zh_CN/element/string.json +++ b/AppScope/resources/zh_CN/element/string.json @@ -1,8 +1,8 @@ { "string": [ { - "name": "MainAbility_label", + "name": "app_name", "value": "时钟" } ] -} \ No newline at end of file +} diff --git a/build-profile.json5 b/build-profile.json5 index 8d28a9a..77e1d25 100644 --- a/build-profile.json5 +++ b/build-profile.json5 @@ -1,29 +1,30 @@ { "app": { "signingConfigs": [ + { + "name": "default", + "material": { + "certpath": "C:/Users/l30051688/.ohos/config/openharmony/default_applications_clock_yy0iSiZbZcuiIAZ_draa2ySNdrJlZeuIW709DtFsmMw=.cer", + "storePassword": "0000001B3E3FFFEB097D828DDACAC6B2465A5EB9E71E8E5D62072028C976C8B3950D94881924EA166BFE54", + "keyAlias": "debugKey", + "keyPassword": "0000001BD31CF455D29CFEB95814C51046D56C3EBBFE72911B6237170C2B5C6C5DCA5F633DCFBDB6D05B30", + "profile": "C:/Users/l30051688/.ohos/config/openharmony/default_applications_clock_yy0iSiZbZcuiIAZ_draa2ySNdrJlZeuIW709DtFsmMw=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "C:/Users/l30051688/.ohos/config/openharmony/default_applications_clock_yy0iSiZbZcuiIAZ_draa2ySNdrJlZeuIW709DtFsmMw=.p12" + } + } ], - "compileSdkVersion": 9, - "compatibleSdkVersion": 9, "products": [ { + "compatibleSdkVersion": "4.1.0(11)", + "targetSdkVersion": "4.1.0(11)", "name": "default", "signingConfig": "default", + "runtimeOS": "HarmonyOS" } ] }, "modules": [ - { - "name": "pc", - "srcPath": "./product/pc", - "targets": [ - { - "name": "default", - "applyToProducts": [ - "default" - ] - } - ] - }, { "name": "phone", "srcPath": "./product/phone", @@ -41,12 +42,20 @@ "srcPath": "./common" }, { - "name": "timer", - "srcPath": "./feature/timer" + "name": "alarmclock", + "srcPath": "./feature/alarmclock" }, { - "name": "countdown", - "srcPath": "./feature/countdown" + "name": "worldclock", + "srcPath": "./feature/worldclock" + }, + { + "name": "stopwatch", + "srcPath": "./feature/stopwatch" + }, + { + "name": "timer", + "srcPath": "./feature/timer" } ] } \ No newline at end of file diff --git a/common/BuildProfile.ets b/common/BuildProfile.ets new file mode 100644 index 0000000..3a501e5 --- /dev/null +++ b/common/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/common/build-profile.json5 b/common/build-profile.json5 index 35dff6d..7374b5c 100644 --- a/common/build-profile.json5 +++ b/common/build-profile.json5 @@ -1,5 +1,17 @@ { "apiType": "stageMode", "buildOption": { - } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + } + } + } + }, + ] } diff --git a/common/index.ets b/common/index.ets index cde2092..cc707e2 100644 --- a/common/index.ets +++ b/common/index.ets @@ -13,10 +13,8 @@ * limitations under the License. */ -export { ImageComponent } from './src/main/ets/components/imageComponent'; - -export { default as ConfigData } from './src/main/ets/utils/ConfigData'; - -export { default as TimerUtil } from './src/main/ets/utils/TimerUtil'; - -export { default as LogUtil } from './src/main/ets/utils/LogUtil'; +export * from './src/main/ets/components/index'; +export * from './src/main/ets/utils/index'; +export * from './src/main/ets/manager/index'; +export * from './src/main/ets/types'; +export const RINGTONE_SELECTION: string = 'RingtoneSelection'; diff --git a/common/oh-package-lock.json5 b/common/oh-package-lock.json5 new file mode 100644 index 0000000..7b39bb6 --- /dev/null +++ b/common/oh-package-lock.json5 @@ -0,0 +1,18 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/lottie@src/main/ets/libs/lottieArkTS.har": "@ohos/lottie@src/main/ets/libs/lottieArkTS.har" + }, + "packages": { + "@ohos/lottie@src/main/ets/libs/lottieArkTS.har": { + "name": "@ohos/lottie", + "version": "2.0.5", + "resolved": "src/main/ets/libs/lottieArkTS.har", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/common/package.json b/common/oh-package.json5 similarity index 67% rename from common/package.json rename to common/oh-package.json5 index 6845131..d845124 100644 --- a/common/package.json +++ b/common/oh-package.json5 @@ -7,8 +7,10 @@ "ohos": { "org": "" }, - "main": "src/main/ets/components/MainPage/MainPage.ets", + "main": "index.ets", "repository": {}, "version": "1.0.0", - "dependencies": {} + "dependencies": { + "@ohos/lottie": "file:./src/main/ets/libs/lottieArkTS.har" + } } diff --git a/common/package-lock.json b/common/package-lock.json deleted file mode 100644 index 01b613f..0000000 --- a/common/package-lock.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@ohos/common", - "version": "1.0.0", - "lockfileVersion": 1 -} diff --git a/common/src/main/ets/components/AddButton/index.ets b/common/src/main/ets/components/AddButton/index.ets new file mode 100644 index 0000000..b3b5389 --- /dev/null +++ b/common/src/main/ets/components/AddButton/index.ets @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import router from '@ohos.router'; +import { + EventReportUtil, + EventName, + CommonUtil +} from '../../utils'; +// import hiSysEvent from '@ohos.hiSysEvent'; +/** + * Add-Button Component + * + * @since 2022-09-26 + */ +export enum ButtonSize { + NORMAL, + LARGER +} + +@Component +export struct AddButton { + @Prop buttonSize: ButtonSize = ButtonSize.NORMAL; + @Prop alarmFlag: boolean = true; + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + onButtonClick?: () => void; + + @Styles + normalButtonStyle() + { + .backgroundColor($r('sys.color.ohos_id_container_color_active')) + } + + @Styles + pressedButtonStyle() + { + .backgroundColor($r('sys.color.interactive_click')) + } + + private getButtonSize(): ResourceStr { + if (this.buttonSize === ButtonSize.NORMAL) { + return $r('app.float.button_size'); + } else if (this.buttonSize === ButtonSize.LARGER) { + return $r('app.float.pc_add_button_size'); + } + return $r('app.float.button_size'); + } + + private addDottingEvent(flag: boolean): void { + if (flag) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_ALARM_CLOCK_ADD) + } else { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.WORLD_CLOCK_ADD_CITY) + } + } + + @Builder + buildAddButtonShadow() { + Column() + .width($r('app.float.pc_add_button_size')) + .height($r('app.float.pc_add_button_size')) + .borderRadius($r('app.float.button_size')) + .shadow({ + radius: $r('app.float.button_shadow_radius'), + color: $r('sys.color.ohos_id_icon_color_active_secondary'), + offsetX: $r('app.float.button_shadow_x'), + offsetY: $r('app.float.button_shadow_y'), + }) + } + + build() { + Stack() { + Button({ type: ButtonType.Circle, stateEffect: true }) { + Image($r('app.media.ic_add')) + .fillColor($r('sys.color.icon_on_primary')) + .width($r('app.float.button_icon_size')) + .height($r('app.float.button_icon_size')) + .draggable(false) + .focusable(true) + } + .id('id_add_button') + .backgroundColor($r('sys.color.ohos_id_container_color_active')) + .width($r('app.float.pc_add_button_size')) + .height($r('app.float.pc_add_button_size')) + .stateStyles({ + pressed: this.pressedButtonStyle, + }) + .shadow({ + radius: $r('app.float.button_shadow_radius'), + color: CommonUtil.changeShadowColor($r('sys.color.ohos_id_container_color_active')), + offsetX: 0, + offsetY: $r('app.float.button_shadow_y2'), + }) + .onClick(() => { + this.onButtonClick && this.onButtonClick() + this.addDottingEvent(this.alarmFlag) + }) + } + .margin({ bottom: this.buttonSize === ButtonSize.LARGER ? 0 : $r('app.float.appbar_fold_icon_margin') }) + } +} \ No newline at end of file diff --git a/common/src/main/ets/components/Card/index.ets b/common/src/main/ets/components/Card/index.ets new file mode 100644 index 0000000..d0dd135 --- /dev/null +++ b/common/src/main/ets/components/Card/index.ets @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@Builder +function dummyFunction() { +}; + +interface ICardBgColorOptions { + defaultBgColor: ResourceColor; + hoverBgColor: ResourceColor; + pressBgColor: ResourceColor; +} + +export enum CardSize { + NORMAL, + LARGER +} + +/** + * Card Component + * + * @since 2022-07-16 + */ +@Component +export struct Card { + @BuilderParam content: () => void = dummyFunction; + @Prop cancelMargin: boolean = false; + @Prop restrictAlarmCardMargin: boolean = false; + @Prop cancelDoubleCardMargin: boolean = false; + @Prop openHoverStatus: boolean = false; + @Prop openPressStatus: boolean = false; + @Prop cardSize: CardSize = CardSize.NORMAL; + @Prop focusRemoveLRMargin: boolean = false; + @State bgColorOptions: ICardBgColorOptions = { + defaultBgColor: $r('sys.color.comp_background_list_card'), + hoverBgColor: $r('sys.color.interactive_hover'), + pressBgColor: $r('sys.color.comp_background_list_card'), + }; + @State bgColor: ResourceColor = this.bgColorOptions.defaultBgColor; + // Whether a content contains multiple items. default value is 1. + private isMultipleSubItems: boolean = false; + private needPressStyle: boolean = true; + private isHorizontalPaddingLeft: boolean = true; + bgHoverHandle = (isHover: boolean) => { + if (!this.openHoverStatus) { + return; + } + if (isHover) { + this.bgColor = this.bgColorOptions.hoverBgColor; + } else { + this.bgColor = this.bgColorOptions.defaultBgColor; + } + } + bgMouseHandle = (event: MouseEvent) => { + if (!this.openPressStatus) { + return; + } + if ((event.action === MouseAction.Press) && (event.button === MouseButton.Left)) { + this.bgColor = this.bgColorOptions.pressBgColor; + } else if (event.action === MouseAction.Hover) { + this.bgColor = this.bgColorOptions.hoverBgColor; + } else if (event.action === MouseAction.Release) { + this.bgColor = this.bgColorOptions.defaultBgColor; + } + } + + build() { + Stack() { + Column() { + this.content() + } + .borderRadius($r('sys.float.corner_radius_level10')) + .padding({ + right: this.cardSize === CardSize.LARGER ? 0 : (this.isMultipleSubItems ? 0 : $r('app.float.card_inner_padding_horizontal')), + left: this.isMultipleSubItems ? 0 : $r('app.float.card_inner_padding_horizontal'), + }) + } + .id('id_card') + .padding({ + top: this.cardSize === CardSize.LARGER ? $r('app.float.card_inner_padding_horizontal') : (this.restrictAlarmCardMargin ? 0 : $r('app.float.card_padding_vertical')), + right: this.cardSize === CardSize.LARGER ? 0 : $r('app.float.card_padding_horizontal'), + bottom: this.cardSize === CardSize.LARGER ? $r('app.float.card_inner_padding_horizontal') : (this.restrictAlarmCardMargin ? 0 : $r('app.float.card_padding_vertical')), + left: this.isHorizontalPaddingLeft ? $r('app.float.card_padding_horizontal') : 0, + }) + .margin({ + left: (this.cancelMargin || this.cancelDoubleCardMargin || this.focusRemoveLRMargin) ? 0 : $r('app.float.card_margin_start'), + right: (this.cancelMargin || this.cancelDoubleCardMargin || this.focusRemoveLRMargin) ? 0 : $r('app.float.card_margin_end'), + top: (this.cancelMargin || this.restrictAlarmCardMargin) ? 0 : $r('app.float.card_margin_top'), + bottom: this.cancelMargin ? $r('app.float.cancel_bottom_margin') : this.restrictAlarmCardMargin ? 0 : $r('app.float.card_margin_bottom'), + }) + .borderRadius($r('sys.float.corner_radius_level10')) + .backgroundColor($r('sys.color.comp_background_list_card')) + .backgroundColor(this.bgColor) + .onHover(this.bgHoverHandle) + .onMouse(this.bgMouseHandle) + } +} diff --git a/common/src/main/ets/components/Clock/ClockHands.ets b/common/src/main/ets/components/Clock/ClockHands.ets new file mode 100644 index 0000000..be032a5 --- /dev/null +++ b/common/src/main/ets/components/Clock/ClockHands.ets @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ACCURACY_ASSURANCE, + DIAL_DIAMETER_RATIO, + HOUR_ANGLE_STEP, + HOUR_HAND_REFRESH_FREQUENCY, + HOUR_HAND_ROTATION_ANGEL_IN_120_SECONDS, + HOUR_HAND_ROTATION_ANGEL_IN_TEN_MINUTES, + MINUTE_ANGLE_STEP, + SECOND_ANGLE_STEP, + CONST_POINTER, + MINUS_HOUR_ANGLE, + ADD_MINUTE_ANGLE +} from './types'; +import { MILLIS_IN_SECOND } from '../../types'; +import { LogUtil } from '../../utils'; + +const TAG: string = 'ClockHands'; +const START_HOUR: number = 6; +const END_HOUR: number = 18; + +/** + * 钟表指针,实现时针、分针、秒针样式,控制指针走动 + * + * @author XiaHan + * @since 2022-07-05 + */ +@Component +struct ClockHands { + @Prop private diameter: number = 0; // 刻度圆环直径 + @State private secondAngle: number = 0; // 秒针相对12点钟方向的偏移角度 + @State private minuteAngle: number = 0; // 分针相对12点钟方向的偏移角度 + @State private hourAngle: number = 0; // 时针相对12点钟方向的偏移角度 + @State isDay: boolean = false; // 白天or黑夜 + @State nowTime: number = new Date().getHours(); + private secondIntervalId: number = -1; // 每秒执行的更新指针方法的定时器实例的 ID,用于页面销毁时清空定时器 + @Consume('currentIndex') @Watch('refreshTime') currentIndexProvider: number; + @State isClockBackShown: boolean = false; + + aboutToAppear(): void { + this.refreshTime(); // 初始化各指针角度 + this.secondIntervalId = setInterval(() => this.refreshTime(), MILLIS_IN_SECOND); + LogUtil.info(TAG, 'diameter:' + this.diameter + (this.diameter * DIAL_DIAMETER_RATIO)); + } + + aboutToDisappear(): void { + if (this.secondIntervalId !== -1) { + clearInterval(this.secondIntervalId); + this.secondIntervalId = -1; + } + } + + /** + * refresh the angle of each pointer + */ + private refreshTime(): void { + const time = new Date(); + this.nowTime = time.getHours(); + if (this.nowTime >= START_HOUR && this.nowTime < END_HOUR) { + this.isDay = true; + } else { + this.isDay = false; + } + this.secondAngle = time.getSeconds() * SECOND_ANGLE_STEP; + this.minuteAngle = (time.getMinutes() * MINUTE_ANGLE_STEP * ACCURACY_ASSURANCE + + time.getSeconds()) / ACCURACY_ASSURANCE; + this.hourAngle = (time.getHours() * HOUR_ANGLE_STEP * ACCURACY_ASSURANCE + time.getMinutes() * + HOUR_HAND_ROTATION_ANGEL_IN_TEN_MINUTES + time.getSeconds() / HOUR_HAND_REFRESH_FREQUENCY * + HOUR_HAND_ROTATION_ANGEL_IN_120_SECONDS) / ACCURACY_ASSURANCE; + } + + private ShowsSetInterval(): Resource { + let pictures: Resource = $r('app.media.img_clock_dial_scale_day') + return pictures + } + + build() { + Stack() { + Image(this.ShowsSetInterval()) + .height(this.diameter * DIAL_DIAMETER_RATIO) + .width(this.diameter * DIAL_DIAMETER_RATIO) + .objectFit(ImageFit.Contain) + .draggable(false) + .interpolation(ImageInterpolation.Low) + .onComplete((event) => { + LogUtil.info(TAG, 'Image onComplete:' + JSON.stringify(event)); + // 返回的状态值为0时,表示图片数据加载成功。返回的状态值为1时,表示图片解码成功。 + if (event && event.loadingStatus === 1) { + this.isClockBackShown = true; + } + }) + } + .visibility(this.isClockBackShown ? Visibility.Visible : Visibility.Hidden) + } +} + +export default ClockHands; diff --git a/common/src/main/ets/components/Clock/Dial.ets b/common/src/main/ets/components/Clock/Dial.ets new file mode 100644 index 0000000..8c12e2a --- /dev/null +++ b/common/src/main/ets/components/Clock/Dial.ets @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BLUR_VALUE, HALF_CIRCLE_ANGLE } from './types'; + +/** + * Clock dial + * + * @since 2022-07-05 + */ +@Component +struct Dial { + @Prop private diameter: number = 0; // Dial diameter + + build() { + Flex({ alignItems: ItemAlign.Center, alignContent: FlexAlign.Center }) { + Column() { + Column() + .width(this.diameter) + .height(this.diameter) + .borderRadius(this.diameter) + .linearGradient({ + angle: HALF_CIRCLE_ANGLE, + direction: GradientDirection.Top, + colors: [ + [$r('app.color.dial_top'), $r('app.float.dial_top_position')], + [$r('app.color.dial_center'), $r('app.float.dial_center_position')], + [$r('app.color.dial_bottom'), $r('app.float.dial_bottom_position')], + ] + }) + .shadow({ + radius: $r('app.float.dial_shadow_radius'), + color: $r('app.color.dial_shadow_inside'), + offsetX: $r('app.float.dial_shadow_inside_x'), + offsetY: $r('app.float.dial_shadow_inside_y'), + }) + } + .width(this.diameter) + .height(this.diameter) + .borderRadius(this.diameter) + .shadow({ + radius: $r('app.float.dial_shadow_radius'), + color: $r('app.color.dial_shadow_outside'), + offsetX: $r('app.float.dial_shadow_outside_x'), + offsetY: $r('app.float.dial_shadow_outside_y'), + }) + } + .width(this.diameter + BLUR_VALUE) // Reserve space for the blurring effect of the dial + .height(this.diameter + BLUR_VALUE) + .borderRadius(this.diameter + BLUR_VALUE) + .blur(BLUR_VALUE) + } +} + +export default Dial; diff --git a/common/src/main/ets/components/Clock/DigitalClock.ets b/common/src/main/ets/components/Clock/DigitalClock.ets new file mode 100644 index 0000000..e264c0e --- /dev/null +++ b/common/src/main/ets/components/Clock/DigitalClock.ets @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogUtil, TimeUtil, TIME_TAG_ID_LIST_IN_ZH } from '../../utils'; +import { MILLIS_IN_SECOND } from '../../types'; +import { ResourceManager } from '../../manager'; + +const TAG: string = 'DigitalClock'; + +/** + * Digital clock for time change every second, morning, afternoon, etc. + * + * @since 2022-09-29 + */ +@Component +export struct DigitalClock { + @State private timeText: string = ''; + private timerId: number = -1; + @State private leftTag: string = ''; + @State private rightTag: string = ''; + private isResourceInit: boolean = false; + @Consume('currentIndex') @Watch('refreshTime') currentIndexProvider: number; + + aboutToAppear() { + this.refreshTime(); + this.timerId = setInterval(() => this.refreshTime(), MILLIS_IN_SECOND); + } + + aboutToDisappear() { + this.cancelTimer(); + } + + private cancelTimer() { + if (this.timerId !== -1) { + clearInterval(this.timerId); + this.timerId = -1; + } + } + + private async refreshTime(): Promise { + if (!this.isResourceInit) { + ResourceManager.preloadStringResourcesSync(TIME_TAG_ID_LIST_IN_ZH); + this.isResourceInit = !this.isResourceInit; + } + let timeObj = TimeUtil.getFullFormattedTimeObj(); + if (timeObj.isTagLeft) { + this.leftTag = timeObj.tag ? timeObj.tag : ''; + this.rightTag = ''; + } else { + this.rightTag = timeObj.tag ? timeObj.tag : ''; + this.leftTag = ''; + } + this.timeText = timeObj.time ? timeObj.time : ''; + } + + build() { + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Column() { + Text(this.leftTag) + .fontSize($r('app.float.digital_clock_text_size_normal_18')) + .fontColor($r('app.color.digital_clock_text_color')) + .fontWeight(FontWeight.Regular) + .margin({ bottom: 8 }) + } + .justifyContent(FlexAlign.End) + .height($r('sys.float.Display_L')) + + Text(this.timeText) + .fontSize($r('app.float.digital_clock_text_size_normal_56')) + .fontColor($r('app.color.digital_clock_text_color')) + .fontWeight(FontWeight.Medium) + .margin({ + left: $r('app.float.digital_clock_text_margin'), + right: $r('app.float.digital_clock_text_margin') + }) + Column() { + Text(this.rightTag) + .fontSize($r('app.float.digital_clock_text_size_normal_18')) + .fontColor($r('app.color.digital_clock_text_color')) + .fontWeight(FontWeight.Bold) + } + .justifyContent(FlexAlign.End) + .height($r('sys.float.Title_L')) + } + .width('100%') + .height('100%') + } +} diff --git a/common/src/main/ets/components/Clock/Scale.ets b/common/src/main/ets/components/Clock/Scale.ets new file mode 100644 index 0000000..68d9420 --- /dev/null +++ b/common/src/main/ets/components/Clock/Scale.ets @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + HALF, + TWICE, + SCALE_UNIT_WIDTH, + SCALE_UNIT_BOLD_WIDTH, + SCALE_UNIT_HEIGHT, + SCALE_UNIT_BOLD_HEIGHT, + SCALE_UNIT_RADIUS, + SCALE_UNIT_BOLD_RADIUS, + HALF_CIRCLE_ANGLE, + FULL_CIRCLE_ANGLE, + SCALE_COLOR, + SCALE_COLOR_BOLD, + SCALE_ANGLES, + SCALE_ANGLES_BOLD, + SCALE_HIDE_CYCLE, + ROTATION_CENTER, +} from './types'; + +/** + * Clock scale + * + * @since 2022-07-05 + */ +@Component +struct Scale { + @Prop @Watch('updateOffset') diameter: number = 0; + // Offset that adjusts the scale to the edge of the scale ring + @State scaleUnitBoldOffset: number = 0; + @State scaleUnitOffset: number = 0; + // Offset that adjusts the scale text next to the scale + @State scaleTextOffset: number = 0; + + aboutToAppear() { + this.updateOffset(); + } + + private updateOffset(): void { + this.scaleUnitBoldOffset = (this.diameter - SCALE_UNIT_BOLD_WIDTH) * HALF; + this.scaleUnitOffset = (this.diameter - SCALE_UNIT_WIDTH) * HALF; + this.scaleTextOffset = -this.diameter * HALF + SCALE_UNIT_BOLD_HEIGHT * TWICE; + } + + private getScaleUnit(isBold: boolean): RectAttribute { + const sliceShape: RectAttribute = new Rect({ + width: isBold ? SCALE_UNIT_BOLD_WIDTH : SCALE_UNIT_WIDTH, + height: isBold ? SCALE_UNIT_BOLD_HEIGHT : SCALE_UNIT_HEIGHT, + radius: isBold ? SCALE_UNIT_BOLD_RADIUS : SCALE_UNIT_RADIUS + }); + return sliceShape.offset({ x: isBold ? this.scaleUnitBoldOffset : this.scaleUnitOffset, y: 0 }); + } + + @Builder + buildScaleUnit(angle: number, isBold: boolean, scaleText: string) { + Stack() { + Column() + .width(this.diameter) + .height(this.diameter) + .borderRadius(this.diameter) + .linearGradient({ + angle: HALF_CIRCLE_ANGLE - angle, + direction: GradientDirection.Bottom, + colors: isBold ? SCALE_COLOR_BOLD : SCALE_COLOR + }) + .clip(this.getScaleUnit(isBold)) // Use the shape of the pointer to clip the background color. + if (isBold) { + Text(scaleText) + .offset({ x: 0, y: -this.diameter * HALF + SCALE_UNIT_BOLD_HEIGHT * TWICE }) + .rotate({ z: 1, angle: FULL_CIRCLE_ANGLE - angle }) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Medium) + } + } + .rotate({ z: ROTATION_CENTER, angle }) + } + + @Builder + buildScale(isBold: boolean) { + ForEach(isBold ? SCALE_ANGLES_BOLD : SCALE_ANGLES, (angle: number, index: number | undefined) => { + if (index && (isBold || index % SCALE_HIDE_CYCLE !== 0)) { + // When there is a bold scale unit, the thin scale unit will not be rendered. + this.buildScaleUnit(angle, isBold, `${index + 1}`) + } + }) + } + + build() { + Stack() { + this.buildScale(true) + this.buildScale(false) + } + } +} + +export default Scale; diff --git a/common/src/main/ets/components/Clock/index.ets b/common/src/main/ets/components/Clock/index.ets new file mode 100644 index 0000000..ff4b451 --- /dev/null +++ b/common/src/main/ets/components/Clock/index.ets @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import window from '@ohos.window'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import { BreakPoint, PageStateManager } from '../../manager'; +import { EventName, EventReportUtil, EventResult, GlobalContext, LogUtil } from '../../utils'; +import ClockHands from './ClockHands'; +import { DigitalClock } from './DigitalClock'; +import { + CLOCK_RATIO_FOR_LG, + CLOCK_RATIO_FOR_NORMAL, + CLOCK_SCALE_DURATION_INCOMING, + CLOCK_SCALE_DURATION_OUTGOING, + CLOCK_SCALE_DELAY, + HALF, + SCALE_DIAMETER_RATIO, + SizeInfo, + UPDATE_WIDTH_DELAY +} from './types'; +import deviceInfo from '@ohos.deviceInfo' + +const TAG = 'Clock(Component)'; +const CLOCK_RATIO_FOR_NORMAL_CUSTOM = 0.65; +const FOLD_SCREEN_DIAMETER_RATIO = 0.767; + + +/** + * An entrance to a clock assembly for stacking dials, scales, and hands to form a clock + * + * Switching between clocks and digital clocks by clicking + * + * @since 2022-07-05 + */ +@Component +export struct Clock { + @Prop getOrientaion: number; + @StorageProp('currentBreakpoint') @Watch('updateClockRatio') currentBreakpoint: string = ''; + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + @StorageProp('windowStandardHeight') windowStandardHeight: number = 0; + @StorageLink('mainWindow') mainWindow: window.Window | undefined = undefined; + @StorageLink('showTextClock') showTextClock: boolean = PageStateManager.getClockShowStateSync(); + @StorageLink('showHandsClock') showHandsClock: boolean = PageStateManager.getClockHandsStateSync(); + @State diameter: number = 0; // Dial diameter (in vp) + @State clockRatio: number = CLOCK_RATIO_FOR_NORMAL; + @State deviceType: string = deviceInfo.deviceType; + @Prop isPortraitOrientation: boolean = true; //纵向 true 横向 false + @State isClicked: boolean = false; + @Prop showBoth: boolean = false + private isFoldCross: number = 2224; + private isFoldVertical: number = 2496; + private minuteCallback?: () => Promise; + + async checkTextAndHands(): Promise { + if (this.showHandsClock === this.showTextClock) { + this.showHandsClock = !this.showTextClock; + await PageStateManager.saveHandsShowToSp(this.showHandsClock); + } + LogUtil.info(TAG, 'checkTextAndHands:', `${this.showHandsClock} ${this.showTextClock}`) + } + + async aboutToAppear(): Promise { + await this.checkTextAndHands(); + this.deviceType = deviceInfo.deviceType + const windowSize: SizeInfo | window.Rect = (this.mainWindow?.getWindowProperties())?.windowRect || { + width: 0, + height: 0 + }; + this.updateClockDiameter(windowSize as ESObject); + this.mainWindow?.on('windowSizeChange', sizeInfo => this.updateClockDiameter(sizeInfo)); + } + + aboutToDisappear(): void { + try { + if (this.mainWindow && this.mainWindow.isWindowShowing()) { + this.mainWindow.off('windowSizeChange'); + } + } catch (error) { + LogUtil.info(TAG, `${JSON.stringify(error)}`) + } + } + + private updateClockRatio(): void { + LogUtil.info(TAG, 'updateClockRatio', `${this.currentBreakpoint}`) + this.clockRatio = this.currentBreakpoint === BreakPoint.LG ? CLOCK_RATIO_FOR_LG : CLOCK_RATIO_FOR_NORMAL; + } + + private async onWindowSizeChange(sizeInfo: SizeInfo): Promise { + if (GlobalContext.getContext().getObject('updateDiameterId')) { + clearTimeout(GlobalContext.getContext().getObject('updateDiameterId') as number); + } + GlobalContext.getContext() + .setObject('updateDiameterId', setTimeout(() => this.updateClockDiameter(sizeInfo), UPDATE_WIDTH_DELAY)); + } + + private updateClockDiameter(sizeInfo: SizeInfo | window.Size): void { + LogUtil.info(TAG, 'updateDiameter, currentBreakpoint:', this.currentBreakpoint); + if (!sizeInfo || !sizeInfo.height || !sizeInfo.width) { + return; + } + const height = sizeInfo.height || this.windowStandardHeight; + const longEdge = Math.max(height, sizeInfo.width); + const shortEdge = Math.min(height, sizeInfo.width); + this.diameter = px2vp(Math.min(longEdge * HALF, shortEdge) * this.clockRatio); + LogUtil.info(TAG, 'updateDiameter, diameter:' + this.diameter); + } + + private getFoldHeight() { + if (this.deviceType == '2in1') { + return this.diameter * CLOCK_RATIO_FOR_NORMAL_CUSTOM + } + if (this.foldAbleScreen === 1) { + return this.getOrientaion === this.isFoldCross ? this.diameter * SCALE_DIAMETER_RATIO : + this.diameter * FOLD_SCREEN_DIAMETER_RATIO + } else { + return this.isPortraitOrientation ? this.diameter * SCALE_DIAMETER_RATIO : + this.diameter * CLOCK_RATIO_FOR_NORMAL_CUSTOM + } + } + + @Builder + buildAnalogClock(): void { + Stack() { + ClockHands({ + diameter: this.getFoldHeight(), + }); + } + .id('id_analog_clock') + .width('100%') + .height(this.showBoth ? '80%' : '100%') + .transition(TransitionEffect.opacity(this.isClicked ? 0 : 1)) + } + + @Builder + buildDigitalClock(): void { + Stack() { + DigitalClock(); + } + .id('id_digital_clock') + .width('100%') + .height(this.deviceType == '2in1' ? '74vp' : (this.showBoth ? '70vp' : '100%')) + .transition(TransitionEffect.opacity(this.isClicked ? 0 : 1)) + } + + @Builder + buildClockContent(): void { + Column() { + // if (this.showBoth) { + // this.buildAnalogClock(); + this.buildDigitalClock(); + // } else { + // Stack() { + // if (this.showTextClock) { + // this.buildDigitalClock(); + // } + // if (this.showHandsClock) { + // // this.buildAnalogClock(); + // } + // } + // } + } + .justifyContent(FlexAlign.Center) + } + + build() { + if (this.diameter) { + Row() { + this.buildClockContent(); + } + .focusable(false) + .onClick(() => { + if (!this.showBoth) { + this.isClicked = true; + const oldShowTextClock = this.showTextClock; + LogUtil.info(TAG, 'onClick showTextClock:' + this.showTextClock); + + // animateTo({ + // duration: CLOCK_SCALE_DURATION_INCOMING, + // delay: CLOCK_SCALE_DELAY, + // curve: Curve.Friction, + // onFinish: () => { + // LogUtil.info(TAG, 'Clock incoming play end'); + // } + // }, () => { + // if (oldShowTextClock) { + // this.showHandsClock = true; + // PageStateManager.saveHandsShowToSp(this.showHandsClock); + // } else { + // this.showTextClock = true; + // PageStateManager.saveClockShowToSp(this.showTextClock); + // } + // }); + + // animateTo({ + // duration: CLOCK_SCALE_DURATION_OUTGOING, + // curve: Curve.Friction, + // onFinish: () => { + // LogUtil.info(TAG, 'Clock outgoing play end'); + // } + // }, () => { + // if (oldShowTextClock) { + // this.showTextClock = false; + // PageStateManager.saveClockShowToSp(this.showTextClock); + // } else { + // this.showHandsClock = false; + // PageStateManager.saveHandsShowToSp(this.showHandsClock); + // } + // }); + } + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.DIGITAL_DIAL_SWITCHING) + this.checkTextAndHands(); + }) + .width('100%') + .height(this.diameter) + .justifyContent(FlexAlign.Center) + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/components/Clock/types.ets b/common/src/main/ets/components/Clock/types.ets new file mode 100644 index 0000000..55e332e --- /dev/null +++ b/common/src/main/ets/components/Clock/types.ets @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const HALF = 0.5; + +export const TWICE = 2; + +export const FULL_CIRCLE_ANGLE = 360; + +export const HALF_CIRCLE_ANGLE = 180; + +// 粗刻度尺寸 +export const SCALE_UNIT_BOLD_WIDTH = 4; + +export const SCALE_UNIT_BOLD_HEIGHT = 6.8; + +export const SCALE_UNIT_BOLD_RADIUS = 2; + +// 细刻度尺寸 +export const SCALE_UNIT_WIDTH = 2; + +export const SCALE_UNIT_HEIGHT = 6.8; + +export const SCALE_UNIT_RADIUS = 2; + +// 刻度数量 +export const SCALE_COUNT = 60; + +export const SCALE_COUNT_BOLD = 12; + +// 粗刻度线文本的偏移量 +export const SCALE_TEXT_OFFSET = -7; + +// 细刻度线隐藏的周期(和粗刻度线重合的周期) +export const SCALE_HIDE_CYCLE = 5; + +export const SCALE_COLOR_BOLD = [ + [$r('app.color.scale_bold_top'), $r('app.float.scale_top_position')], + [$r('app.color.scale_bold_center'), $r('app.float.scale_center_position')], + [$r('app.color.scale_bold_bottom'), $r('app.float.scale_bottom_position')], +]; + +export const SCALE_COLOR = [ + [$r('app.color.scale_top'), $r('app.float.scale_top_position')], + [$r('app.color.scale_bottom'), $r('app.float.scale_bottom_position')], +]; + +export const TEXT_CLOCK_FORMAT_24H = 'HHmmss'; + +export const SECOND_ANGLE_STEP = 6; + +export const ADD_MINUTE_ANGLE = 4; + +export const MINUS_HOUR_ANGLE = 2; + +export const CONST_POINTER = 24; + +export const MINUTE_ANGLE_STEP = 6; + +export const HOUR_ANGLE_STEP = 30; + +export const HOUR_HAND_REFRESH_FREQUENCY = 12; + +export const ACCURACY_ASSURANCE = 10; + +export const HOUR_HAND_ROTATION_ANGEL_IN_TEN_MINUTES = 5; + +export const HOUR_HAND_ROTATION_ANGEL_IN_120_SECONDS = 1; + +// 刻度圆环直径占比 +export const SCALE_DIAMETER_RATIO = 0.85; + +export const DIAL_DIAMETER_RATIO = 1.58; + +export const BLUR_VALUE = 4; + +// 细刻度线、粗刻度线的角度列表,用于 build 方法中循环渲染刻度 +const SCALE_ANGLES: number[] = []; +const SCALE_ANGLES_BOLD: number[] = []; +for (let angle = HOUR_ANGLE_STEP; angle <= FULL_CIRCLE_ANGLE; angle += HOUR_ANGLE_STEP) { + // 为了方便 '小时' 的刻度文字和便宜角度对齐,'小时' 的偏移角度从1点钟方向开始 + SCALE_ANGLES_BOLD.push(angle); +} +for (let angle = 0; angle < FULL_CIRCLE_ANGLE; angle += SECOND_ANGLE_STEP) { + SCALE_ANGLES.push(angle); +} + +export { SCALE_ANGLES, SCALE_ANGLES_BOLD }; + +export interface SizeInfo { + width: number; + height: number; +} + +export const CLOCK_RATIO_FOR_NORMAL = 0.70; + +export const CLOCK_RATIO_FOR_LG = 0.60; + +export const UPDATE_WIDTH_DELAY = 50; + +export const ROTATION_CENTER = 1; + +export const CLOCK_SCALE_VALUE = 0.9; + +export const CLOCK_SCALE_DURATION_INCOMING = 300; + +export const CLOCK_SCALE_DURATION_OUTGOING = 250; + +export const CLOCK_SCALE_DELAY = 150; \ No newline at end of file diff --git a/common/src/main/ets/components/CommonDialog/CommonDialog.ets b/common/src/main/ets/components/CommonDialog/CommonDialog.ets new file mode 100644 index 0000000..fed1c5b --- /dev/null +++ b/common/src/main/ets/components/CommonDialog/CommonDialog.ets @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CommonHwDialog, ActionType } from './CommonHwDialog'; +import { CommonUtil } from '../../utils'; + +@Builder +function dummyFunction() { +}; + +/** + * Common Dialog + * + * @since 2023-01-10 + */ +@CustomDialog +export struct CommonDialog { + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + @BuilderParam content: () => void = dummyFunction; + @Prop firstButtonName: string = ''; + @Prop secondButtonName: string = ''; + private actionType: ActionType = ActionType.TWO_BUTTON_HORIZONTAL; + private title?: string; + private firstButtonCallback: () => void = () => { + }; + private secondButtonCallback: () => void = () => { + }; + private controller: CustomDialogController = new CustomDialogController({ builder: undefined }); + // This variable must be declared when HwDialog is used. Otherwise, an exception occurs when the dialog is opened. + @Provide('contentParam') _: number = 0; + + @Builder + buildDialogForPhone() { + Column() { + Column() { + this.buildDialog() + } + .backgroundColor($r('app.color.component_ultra_thick_dialog')) + } + .id('id_common_dialog_phone') + .backgroundColor($r('app.color.component_ultra_thick_dialog')) + .borderRadius($r('sys.float.corner_radius_level16')) + .width(this.foldAbleScreen === 1 ? $r('app.float.dilog_width') : '') + .clip(true) + .margin({ + left: $r('sys.float.padding_level6'), + right: $r('sys.float.padding_level6'), + bottom: $r('sys.float.padding_level8'), + }) + } + + @Builder + buildDialog() { + CommonHwDialog({ + controller: this.controller, + title: this.title, + actionType: this.actionType, + cancelText: `${this.firstButtonName}`, + cancelCallback: () => this.firstButtonCallback(), + confirmText: `${this.secondButtonName}`, + confirmCallback: () => this.secondButtonCallback(), + content: () => this.content(), + }) + .id('id_common_dialog') + .backgroundColor($r('app.color.component_ultra_thick_dialog')) + } + + build() { + if (CommonUtil.isDevicePhone()) { + this.buildDialogForPhone() + } else { + this.buildDialog() + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/components/CommonDialog/CommonHwDialog.ets b/common/src/main/ets/components/CommonDialog/CommonHwDialog.ets new file mode 100644 index 0000000..f57791a --- /dev/null +++ b/common/src/main/ets/components/CommonDialog/CommonHwDialog.ets @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CommonUtil } from '../../utils'; + +export enum ActionType { + NO_BUTTON = 0, + SINGE_BUTTON_VERTICAL = 1, + SINGE_BUTTON_HORIZONTAL = 2, + TWO_BUTTON_VERTICAL = 3, + TWO_BUTTON_HORIZONTAL = 4, + THREE_BUTTON_VERTICAL = 5, + THREE_BUTTON_HORIZONTAL = 6 +} + + +@Extend(Button) +function dialogButton() { + .height($r('app.float.dialog_button_height')) + .fontSize($r('sys.float.Body_L')) + .flexGrow(1) +} + +const TITTLE_TEXT_LEFT_MARGIN_HAS_ICON: number = 12; +const HUNDRED_PERCENT: string = '100%'; +const OPACITY_ONE: number = 1; +const OPACITY_OTHER: number = 0.4; +const STROKE_WIDTH: number = 0.5; +const NO_PADDING: number = 0; +const DIALOG_TOP_BOTTOM_PADDING: number = 24; + +@Builder +function dummyFunction() { +}; + +/** + * CommonHwDialog + * + * @since 2023-04-27 + */ +@CustomDialog +export struct CommonHwDialog { + @State title: string = ''; + @BuilderParam content: () => void = dummyFunction; + @State confirmEnable: boolean = true; + @State textContent: string = ''; + @State cancelBackground: Resource = $r('app.color.transparent'); + @State confirmBackground: Resource = $r('app.color.transparent'); + @StorageLink('TextInputClose') @Watch('TextInputClose') isTextInputClose: boolean = false; + private controller: CustomDialogController = new CustomDialogController({ builder: undefined }); + actionType: ActionType = ActionType.NO_BUTTON; + hasWarning: boolean = false; + cancelCallback?: () => void; + confirmCallback?: () => void; + textInputCallback: (value: string) => void = (input: string) => { + }; + confirmText?: Resource | string; + cancelText?: Resource | string; + confirmTextColor?: ResourceColor; + + private TextInputClose() { + this.controller.close(); + } + + aboutToAppear(): void { + if (CommonUtil.isDevicePhone()) { + this.cancelBackground = $r('app.color.transparent'); + this.confirmBackground = $r('app.color.transparent'); + } else { + this.cancelBackground = $r('app.color.transparent'); + this.confirmBackground = $r('app.color.transparent'); + } + } + + /** + * handleButtonTouchEvent + * + * @param event TouchEvent + * + */ + private handleButtonTouchEvent(event: TouchEvent): Resource { + if (event.type === TouchType.Down || event.type === TouchType.Move) { + if (CommonUtil.isDevicePhone()) { + return $r('app.color.color_press_text_button'); + } else { + return $r('app.color.color_press_normal_button'); + } + } else { + if (CommonUtil.isDevicePhone()) { + return $r('app.color.id_color_transparent'); + } else { + return $r('sys.color.comp_background_tertiary'); + } + } + } + + /** + * handleButtonHoverEvent + * + * @param isHover + */ + private handleButtonHoverEvent(isHover: boolean): Resource { + if (isHover) { + if (CommonUtil.isDevicePhone()) { + return $r('sys.color.interactive_hover'); + } else { + return $r('app.color.color_hover_normal_button'); + } + } else { + return $r('app.color.id_color_transparent'); + } + } + + @Builder + buildTitle(): void { + Flex({ justifyContent: FlexAlign.Start }) { + Row() { + Text(this.title) + .margin({ left: TITTLE_TEXT_LEFT_MARGIN_HAS_ICON }) + .fontSize($r('sys.float.Subtitle_S')) + .fontColor($r('sys.color.font_primary')) + .fontWeight(FontWeight.Medium) + .margin({ left: 0 }) + } + .alignItems(VerticalAlign.Center) + .constraintSize({ maxWidth: HUNDRED_PERCENT }) + .height($r('app.float.float_title_height')) + .align(Alignment.Start) + .padding({ left: $r('app.float.float_content_padding'), right: $r('app.float.float_content_padding') }) + } + .width(HUNDRED_PERCENT) + } + + @Builder + buildContent(): void { + Column() { + this.content(); + } + .padding({ left: $r('app.float.float_content_padding'), right: $r('app.float.float_content_padding') }) + .margin({ bottom: $r('app.float.float_content_bottom_margin') }) + } + + @Builder + buildButtons(): void { + Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) { + if (this.cancelCallback) { + Button(this.cancelText ? this.cancelText : $r('app.string.cancel')) + .onClick(() => { + this.controller.close(); + this.cancelCallback ? this.cancelCallback() : undefined; + }) + .id('id_cancelText_button') + .dialogButton() + .fontColor($r('sys.color.ohos_id_icon_color_active')) + .backgroundColor($r('sys.color.interactive_hover')) + .onHover((isHover) => { + this.cancelBackground = this.handleButtonHoverEvent(isHover as boolean); + }) + .onTouch((event) => { + this.cancelBackground = this.handleButtonTouchEvent(event as TouchEvent); + }) + } + if (this.actionType === ActionType.TWO_BUTTON_HORIZONTAL) { + if (this.cancelCallback && this.confirmCallback) { + Divider() + .vertical(true) + .strokeWidth(null) + .color($r('sys.color.comp_divider')) + .lineCap(LineCapStyle.Round) + .height($r('app.float.float_divider_height')) + .padding({ left: $r('app.float.float_divider_padding'), right: $r('app.float.float_divider_padding') }) + .visibility(CommonUtil.isDevicePhone() ? Visibility.Visible : Visibility.Hidden) + } + if (this.confirmCallback) { + Button(this.confirmText ? this.confirmText : $r('app.string.confirm_delete')) + .onClick(() => { + this.confirmCallback ? this.confirmCallback() : undefined; + this.controller.close(); + }) + .id('id_confirmText_button') + .fontColor(this.hasWarning ? $r('sys.color.warning') : $r('sys.color.ohos_id_icon_color_active')) + .enabled(this.confirmEnable) + .opacity(this.confirmEnable ? OPACITY_ONE : OPACITY_OTHER) + .dialogButton() + .backgroundColor($r('sys.color.interactive_hover')) + .onHover((isHover) => { + this.confirmBackground = this.handleButtonHoverEvent(isHover as boolean); + }) + .onTouch((event) => { + this.confirmBackground = this.handleButtonTouchEvent(event as TouchEvent); + }) + } + } + } + .height($r('app.float.dialog_button_height')) + .margin({ + bottom: CommonUtil.isDevicePhone() ? $r('app.float.common_text_input_padding_zero') : $r('app.float.confirm_dialog_padding_horizontal') + }) + } + + @Builder + buildDialog(): void { + Column() { + if (this.title !== '') { + this.buildTitle(); + } + this.buildContent(); + this.buildButtons(); + } + .backgroundColor($r('app.color.component_ultra_thick_dialog')) + .borderRadius($r('sys.float.corner_radius_level16')) + .padding({ + left: $r('app.float.confirm_dialog_padding_horizontal'), + right: $r('app.float.confirm_dialog_padding_horizontal'), + top: NO_PADDING, + }) + } + + @Builder + buildDialogForPhone(): void { + Column() { + this.buildDialog(); + } + .margin({ + bottom: $r('sys.float.padding_level8'), + }) + } + + build() { + if (CommonUtil.isDevicePhone()) { + this.buildDialogForPhone(); + } else { + this.buildDialog(); + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/components/CommonDialog/CommonTextInputDialog.ets b/common/src/main/ets/components/CommonDialog/CommonTextInputDialog.ets new file mode 100644 index 0000000..c17a5bd --- /dev/null +++ b/common/src/main/ets/components/CommonDialog/CommonTextInputDialog.ets @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ALARM_NAME_INPUT_MIN, ALARM_NAME_INPUT_LIMIT } from '../../manager'; +import { CommonUtil } from '../../utils'; + +@Extend(Button) +function inputDialogButton() { + .height($r('app.float.dialog_button_height')) + .fontSize($r('sys.float.Body_L')) + .flexGrow(1) +} + +const TITTLE_TEXT_LEFT_MARGIN_HAS_ICON: number = 12; +const HUNDRED_PERCENT: string = '100%'; +const OPACITY_ONE: number = 1; +const OPACITY_OTHER: number = 0.4; +const STROKE_WIDTH: number = 0.5; + +@Builder +function dummyFunction() { +}; + +/** + * CommonTextInputDialog + * + * @since 2023-04-27 + */ +@CustomDialog +export struct CommonTextInputDialog { + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + @State title: string = ''; + @BuilderParam content: () => void = dummyFunction; + @State confirmEnable: boolean = true; + @State textContent: string = ''; + @State cancelBackground: Resource = $r('app.color.transparent'); + @State confirmBackground: Resource = $r('app.color.transparent'); + @StorageLink('TextInputClose') @Watch('TextInputClose') isTextInputClose: boolean = false; + private controller: CustomDialogController = new CustomDialogController({ builder: undefined }); + cancelCallback?: () => void; + confirmCallback?: () => void; + textInputCallback: (value: string) => void = (input: string) => { + }; + confirmText?: Resource | string; + cancelText?: Resource | string; + confirmTextColor?: ResourceColor; + + private TextInputClose() { + this.controller.close(); + } + + aboutToAppear(): void { + if (CommonUtil.isDevicePhone()) { + this.cancelBackground = $r('app.color.transparent'); + this.confirmBackground = $r('app.color.transparent'); + } else { + this.cancelBackground = $r('app.color.transparent'); + this.confirmBackground = $r('app.color.transparent'); + } + } + + /** + * handleButtonTouchEvent + * + * @param event TouchEvent + * + */ + private handleButtonTouchEvent(event: TouchEvent): Resource { + if (event.type === TouchType.Down || event.type === TouchType.Move) { + if (CommonUtil.isDevicePhone()) { + return $r('app.color.color_press_text_button'); + } else { + return $r('app.color.color_press_normal_button'); + } + } else { + if (CommonUtil.isDevicePhone()) { + return $r('app.color.id_color_transparent'); + } else { + return $r('sys.color.comp_background_tertiary'); + } + } + } + + /** + * handleButtonHoverEvent + * + * @param isHover + */ + private handleButtonHoverEvent(isHover: boolean): Resource { + if (isHover) { + if (CommonUtil.isDevicePhone()) { + return $r('sys.color.interactive_hover'); + } else { + return $r('app.color.color_hover_normal_button'); + } + } else { + return $r('app.color.id_color_transparent'); + } + } + + @Builder + buildTitle(): void { + Flex({ justifyContent: FlexAlign.Start }) { + Row() { + Text(this.title) + .margin({ left: TITTLE_TEXT_LEFT_MARGIN_HAS_ICON }) + .fontSize($r('sys.float.Subtitle_S')) + .fontColor($r('sys.color.font_primary')) + .fontWeight(FontWeight.Medium) + .margin({ left: 0 }) + } + .alignItems(VerticalAlign.Center) + .constraintSize({ maxWidth: HUNDRED_PERCENT }) + .height($r('app.float.float_title_height')) + .align(Alignment.Start) + .padding({ left: $r('app.float.float_content_padding'), right: $r('app.float.float_content_padding') }) + } + .width(HUNDRED_PERCENT) + } + + @Builder + buildContent(): void { + Column() { + Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Column }) { + TextInput({ text: this.textContent }) + .backgroundColor($r('app.color.transparent')) + .height($r('app.float.input_height')) + .maxLength(ALARM_NAME_INPUT_LIMIT) + .onChange(async (value: string) => { + if (value?.length === ALARM_NAME_INPUT_MIN) { + this.confirmEnable = false; + this.textContent = ''; + } else { + this.confirmEnable = true; + this.textContent = value; + await this.textInputCallback(value); + } + }) + .padding({ + left: $r('app.float.common_text_input_padding_zero'), + right: $r('app.float.common_text_input_padding_zero'), + }) + .defaultFocus(true) + } + .height($r('app.float.list_item_height')) + + Divider().color($r('sys.color.comp_divider')) + } + .padding({ left: $r('app.float.float_content_padding'), right: $r('app.float.float_content_padding') }) + .margin({ bottom: $r('app.float.float_content_bottom_margin') }) + } + + @Builder + buildButtons(): void { + Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) { + if (this.cancelCallback) { + Button(this.cancelText ? this.cancelText : $r('app.string.cancel')) + .onClick(() => { + this.controller.close(); + this.cancelCallback ? this.cancelCallback() : undefined; + }) + .inputDialogButton() + .fontColor($r('sys.color.ohos_id_icon_color_active')) + .backgroundColor($r('sys.color.interactive_hover')) + .onHover((isHover) => { + this.cancelBackground = this.handleButtonHoverEvent(isHover as boolean); + }) + .onTouch((event) => { + this.cancelBackground = this.handleButtonTouchEvent(event as TouchEvent); + }) + } + if (this.cancelCallback && this.confirmCallback) { + Divider() + .vertical(true) + .strokeWidth(null) + .color($r('sys.color.comp_divider')) + .lineCap(LineCapStyle.Round) + .height($r('app.float.float_divider_height')) + .padding({ left: $r('app.float.float_divider_padding'), right: $r('app.float.float_divider_padding') }) + .visibility(CommonUtil.isDevicePhone() ? Visibility.Visible : Visibility.Hidden) + } + if (this.confirmCallback) { + Button(this.confirmText ? this.confirmText : $r('app.string.confirm_delete')) + .onClick(() => { + this.confirmCallback ? this.confirmCallback() : undefined; + this.controller.close(); + }) + .enabled(this.confirmEnable) + .opacity(this.confirmEnable ? OPACITY_ONE : OPACITY_OTHER) + .inputDialogButton() + .fontColor($r('sys.color.ohos_id_icon_color_active')) + .backgroundColor(this.confirmBackground) + .onHover((isHover) => { + this.confirmBackground = this.handleButtonHoverEvent(isHover as boolean); + }) + .onTouch((event) => { + this.confirmBackground = this.handleButtonTouchEvent(event as TouchEvent); + }) + } + } + .height($r('app.float.dialog_button_height')) + .margin({ + bottom: $r('app.float.confirm_dialog_padding_horizontal') + }) + } + + @Builder + buildDialog(): void { + Column() { + this.buildTitle(); + this.buildContent(); + this.buildButtons(); + } + .backgroundColor($r('app.color.component_ultra_thick_dialog')) + .borderRadius($r('sys.float.corner_radius_level16')) + .width(this.foldAbleScreen === 1 ? $r('app.float.dilog_width') : '') + .padding({ + left: $r('app.float.confirm_dialog_padding_horizontal'), + right: $r('app.float.confirm_dialog_padding_horizontal'), + }) + } + + @Builder + buildDialogForPhone(): void { + Column() { + this.buildDialog(); + } + .margin({ + left: $r('sys.float.padding_level6'), + right: $r('sys.float.padding_level6'), + bottom: $r('sys.float.padding_level6'), + }) + } + + build() { + if (CommonUtil.isDevicePhone()) { + this.buildDialogForPhone(); + } else { + this.buildDialog(); + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/components/CommonDialog/ConfirmDialog.ets b/common/src/main/ets/components/CommonDialog/ConfirmDialog.ets new file mode 100644 index 0000000..54f0e53 --- /dev/null +++ b/common/src/main/ets/components/CommonDialog/ConfirmDialog.ets @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import resmgr from '@ohos.resourceManager'; +import { ActionType, CommonHwDialog } from './CommonHwDialog'; +import { CommonUtil, GlobalContext } from '../../utils'; + +/** + * ConfirmDialog + * + * @since 2022-12-10 + */ +@CustomDialog +export struct ConfirmDialog { + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + private confirmMessage: ResourceStr = ''; + private controller: CustomDialogController = new CustomDialogController({ builder: undefined }); + private cancel?: () => void; + private confirm?: () => void; + private confirmText?: ResourceStr; + private cancelText?: ResourceStr + private hasWarning: boolean = false; + // This variable must be declared when HwDialog is used. Otherwise, an exception occurs when the dialog is opened. + @Provide('contentParam') _: number = 0; + + private getStrFromResource(resourceStr: ResourceStr): string { + if (typeof resourceStr === 'string') { + return resourceStr; + } + return (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringSync(resourceStr.id); + } + + @Builder + buildContent(): void { + Column() { + Text(this.confirmMessage) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Regular) + } + .backgroundColor($r('app.color.component_ultra_thick_dialog')) + .padding({ + top: $r('app.float.confirm_dialog_content_padding_top'), + bottom: $r('app.float.confirm_dialog_content_padding_bottom'), + }) + } + + @Builder + buildDialogForPhone() { + Column() { + Column() { + this.buildDialog() + } + .backgroundColor($r('app.color.component_ultra_thick_dialog')) + } + .backgroundColor($r('app.color.component_ultra_thick_dialog')) + .borderRadius($r('sys.float.corner_radius_level16')) + .clip(true) + .width(this.foldAbleScreen === 1 ? $r('app.float.dilog_width') : '') + .margin({ + left: $r('sys.float.padding_level6'), + right: $r('sys.float.padding_level6'), + bottom: $r('sys.float.padding_level8'), + }) + } + + @Builder + buildDialog() { + CommonHwDialog({ + controller: this.controller, + hasWarning: this.hasWarning, + actionType: ActionType.TWO_BUTTON_HORIZONTAL, + cancelText: this.cancelText ? this.getStrFromResource(this.cancelText) : (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('cancel'), + cancelCallback: () => this.cancel ? this.cancel() : undefined, + confirmText: this.confirmText ? this.getStrFromResource(this.confirmText) : (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('confirm_delete'), + confirmCallback: () => this.confirm ? this.confirm() : undefined, + content: () => this.buildContent(), + }) + } + + build() { + if (CommonUtil.isDevicePhone()) { + this.buildDialogForPhone() + } else { + this.buildDialog() + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/timer/CurrentTimeDisplay.ets b/common/src/main/ets/components/CommonGrid/index.ets similarity index 36% rename from product/phone/src/main/ets/pages/timer/CurrentTimeDisplay.ets rename to common/src/main/ets/components/CommonGrid/index.ets index dd13b6d..a594c4b 100644 --- a/product/phone/src/main/ets/pages/timer/CurrentTimeDisplay.ets +++ b/common/src/main/ets/components/CommonGrid/index.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,34 +13,39 @@ * limitations under the License. */ -import { TimerController } from '@ohos/timer'; -import { TimerUtil, ConfigData } from '@ohos/common'; +import { BreakPoint } from '../../manager'; +import { COMMON_GRID_COL_OPTIONS, LG_8_GRID_COL_OPTIONS } from './types'; + +@Builder +function dummyFunction() { +}; +/** + * Common Grid Component + * + * @since 2023-01-10 + */ @Component -export struct CurrentTimeDisplay { - @State currentTime: number = 0; - @State timerController: TimerController = new TimerController(); - @Prop lastTimeMs: number; +export struct CommonGrid { + @StorageProp('currentBreakpoint') @Watch('updateMargin') currentBreakpoint: string = ''; + @BuilderParam content: () => void = dummyFunction; + @State gridMargin: Resource = this.currentBreakpoint === BreakPoint.LG ? $r('app.float.common_grid_margin') : $r('app.float.common_grid_margin_zero'); + @Prop lg8: boolean = false; - aboutToAppear() { - this.timerController.setTimeUpdateListener((currentTimeMs: number) => { - this.currentTime = currentTimeMs; - }) + private updateMargin(): void { + this.gridMargin = this.currentBreakpoint === BreakPoint.LG ? $r('app.float.common_grid_margin') : $r('app.float.common_grid_margin_zero'); } build() { - Column() { - Text(TimerUtil.timeFormat(this.currentTime)) - .lineHeight($r('app.float.wh_value_32')) - .fontColor($r('app.color.text_color')) - .fontWeight(500) - .fontSize($r('app.float.font_40')) - Text(TimerUtil.timeFormat(this.currentTime - this.lastTimeMs)) - .lineHeight($r('app.float.wh_value_32')) - .fontColor($r('app.color.text_color')) - .opacity($r('app.float.opacity_6')) - .fontSize($r('app.float.font_28')) - .visibility(this.lastTimeMs > 0 ? Visibility.Visible : Visibility.Hidden) - }.width(ConfigData.WH_100_100) + GridRow() { + GridCol(this.lg8 ? LG_8_GRID_COL_OPTIONS : COMMON_GRID_COL_OPTIONS) { + this.content() + } + } + .id('id_common_grid') + .margin({ + left: this.gridMargin, + right: this.gridMargin, + }) } } \ No newline at end of file diff --git a/common/src/main/ets/components/CommonGrid/types.ets b/common/src/main/ets/components/CommonGrid/types.ets new file mode 100644 index 0000000..0fe1c1e --- /dev/null +++ b/common/src/main/ets/components/CommonGrid/types.ets @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const COMMON_GRID_COL_OPTIONS: GridColOptions = { + span: { + xs: 12, + md: 12, + lg: 12, + }, + offset: { + xs: 0, + md: 0, + lg: 2, + }, +}; + +export const LG_8_GRID_COL_OPTIONS: GridColOptions = { + span: { + xs: 12, + md: 12, + lg: 8, + }, + offset: { + xs: 0, + md: 0, + lg: 2, + }, +}; \ No newline at end of file diff --git a/common/src/main/ets/components/FloatingActionButton/index.ets b/common/src/main/ets/components/FloatingActionButton/index.ets new file mode 100644 index 0000000..93e7db8 --- /dev/null +++ b/common/src/main/ets/components/FloatingActionButton/index.ets @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { buttonColorSchemes, FloatingActionButtonColor, ButtonColorScheme, IButtonCustomStyle } from './types'; + +/** + * Floating Action Button Component + * + * @since 2023-09-14 + */ +@Component +export struct FloatingActionButton { + @Prop src: Resource = {} as Resource; + @Prop colorSchemeName: FloatingActionButtonColor = FloatingActionButtonColor.BLUE; + @Prop isEnabled: boolean = true; + @Prop playButton: boolean = false; + @Prop idx: string | number = 0; + @Prop customStyle: IButtonCustomStyle = {} as IButtonCustomStyle; + @State noDraggable: boolean = false; + onButtonClick?: () => void; + private colorScheme = buttonColorSchemes.find(s => s.schemeName === this.colorSchemeName); + + @Styles + normalButtonStyle() + { + .backgroundColor(this.isEnabled ? this.colorScheme!.backgroundColor : (this.customStyle.buttonDefaultBgc || + this.colorScheme!.backgroundColor)) + } + + @Styles + pressedButtonStyle() + { + .backgroundColor(this.colorScheme!.backgroundColorPressed) + } + + @Styles + disabledButtonStyle() + { + .backgroundColor($r('app.color.dialog_background_color')) + } + + @Builder + buildButtonShadow() { + Column() + .width($r('app.float.button_size')) + .height($r('app.float.button_size')) + .borderRadius($r('app.float.button_size')) + .shadow({ + radius: $r('app.float.button_shadow_radius'), + color: $r('app.color.color_fab_shadow'), + offsetX: $r('app.float.button_shadow_x'), + offsetY: $r('app.float.button_shadow_y'), + }) + } + + build() { + Stack() { + if (this.colorSchemeName == FloatingActionButtonColor.BLUE && this.isEnabled) { + this.buildButtonShadow() + } + Button({ type: ButtonType.Circle, stateEffect: true }) { + Image(this.src) + .width(this.playButton ? $r('app.float.button_icon_on') : $r('app.float.button_icon_size')) + .height(this.playButton ? $r('app.float.button_icon_on') : $r('app.float.button_icon_size')) + .fillColor(this.isEnabled ? (this.customStyle.iconPlayColor || this.colorScheme!.iconColor) + : (this.customStyle.iconDefaultColor || this.colorScheme!.iconColorDisabled)) + .draggable(this.noDraggable) + .focusable(true) + } + .id('id_fab_button' + this.idx) + .backgroundColor(this.colorScheme!.backgroundColor) + .width(this.playButton ? $r('app.float.button_back') : $r('app.float.button_size')) + .height(this.playButton ? $r('app.float.button_back') : $r('app.float.button_size')) + .margin(this.playButton ? { top: '2vp', bottom: '2vp' } : 0) + .stateStyles({ + normal: this.normalButtonStyle, + pressed: this.pressedButtonStyle, + disabled: this.disabledButtonStyle, + }) + // .enabled(this.isEnabled) + .onClick(() => this.onButtonClick && this.onButtonClick()) + } + .enabled(this.isEnabled) + } +} \ No newline at end of file diff --git a/common/src/main/ets/components/FloatingActionButton/types.ets b/common/src/main/ets/components/FloatingActionButton/types.ets new file mode 100644 index 0000000..7a1ce7f --- /dev/null +++ b/common/src/main/ets/components/FloatingActionButton/types.ets @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum FloatingActionButtonColor { + BLUE, + WHITE +} + +export interface ButtonColorScheme { + schemeName: FloatingActionButtonColor; + backgroundColor: Resource; + backgroundColorPressed: Resource; + backgroundColorDisabled: Resource; + iconColor: Resource; + iconColorDisabled: Resource; +} + +export interface IButtonCustomStyle { + iconDefaultColor: string | Resource; + iconPlayColor: string | Resource; + buttonDefaultBgc: string | Resource; + buttonPlayBgc: string | Resource; +} + +export const buttonColorSchemes: ButtonColorScheme[] = [ + { + schemeName: FloatingActionButtonColor.BLUE, + backgroundColor: $r('sys.color.ohos_id_icon_color_active'), + backgroundColorPressed: $r('sys.color.ohos_id_icon_color_active'), + backgroundColorDisabled: $r('app.color.color_fab_bg_disabled'), + iconColor: $r('app.color.play_btn'), + iconColorDisabled: $r('sys.color.ohos_id_icon_color_active') + }, + { + schemeName: FloatingActionButtonColor.WHITE, + backgroundColor: $r('app.color.color_white'), + backgroundColorPressed: $r('app.color.color_fab_bg_white_pressed'), + backgroundColorDisabled: $r('app.color.color_fab_bg_white_disabled'), + iconColor: $r('app.color.color_black'), + iconColorDisabled: $r('app.color.color_fab_icon_white_disabled') + } +]; \ No newline at end of file diff --git a/common/src/main/ets/components/RingtoneSelection/index.ets b/common/src/main/ets/components/RingtoneSelection/index.ets new file mode 100644 index 0000000..12da89b --- /dev/null +++ b/common/src/main/ets/components/RingtoneSelection/index.ets @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Card } from '../Card'; +import { + DEFAULT_RINGTONE_ID, + NONE_RINGTONE_ID, + noneRingtone, + recommendedRingtones, + RingtoneItem, + ringtones +} from './types'; +import { ALARM_LAST_SELECTED_RING, APP_CONFIG, ResourceAudioManager, TIMER_SELECTED_RING } from '../../manager'; +import preferencesUtil from '@ohos.data.preferences'; +import { RingtoneSelectionUtil } from '../../utils/RingtoneSelectionUtil'; +import { GlobalContext } from '../../utils' + +const TAG = 'RingtoneSelection'; + +interface PageProps { + isTimerRingtone: boolean, + onRingtoneSelected: (path: string) => void, + selectedPath?: string +} + +/** + * Ringtone Selection page + * + * @since 2022-04-10 + */ +@Component +export struct RingtoneSelection { + @Prop pageProps: PageProps = { + isTimerRingtone: true, + onRingtoneSelected: (path: string) => '', + selectedPath: '' + } + @State selectedRingtone: RingtoneItem = RingtoneSelectionUtil.getDefaultRingtone(); + + async aboutToAppear() { + this.selectedRingtone = await (this.pageProps.isTimerRingtone ? RingtoneSelectionUtil.getTimerRingtone() : + this.pageProps.selectedPath ? RingtoneSelectionUtil.getRingtoneByPath(this.pageProps.selectedPath) : + RingtoneSelectionUtil.getAlarmRingtone()); + } + + async aboutToDisappear() { + this.pageProps.onRingtoneSelected(this.selectedRingtone.path); + ResourceAudioManager.stopPlayerFor(this.selectedRingtone.path); + } + + async onRingtoneSelected(ringtone: RingtoneItem) { + await ResourceAudioManager.stopPlayerFor(this.selectedRingtone.path); + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, APP_CONFIG); + await preferences.put(this.pageProps.isTimerRingtone ? TIMER_SELECTED_RING : + ALARM_LAST_SELECTED_RING, ringtone.path); + await preferences.flush(); + this.selectedRingtone = ringtone; + if (ringtone.path) { + ResourceAudioManager.playFromFileOnce(ringtone.path); + } + } + + @Builder + buildRingtoneItem(item: RingtoneItem, isDefault?: boolean) { + Stack({ alignContent: Alignment.Center }) { + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(item.name + (!this.pageProps.isTimerRingtone && item.id == DEFAULT_RINGTONE_ID ? ' (Default)' : '')) + Radio({ value: item.name, group: isDefault ? 'default_' : '' + 'ringtones' }) + .height($r('app.float.radio_size')) + .width($r('app.float.radio_size')) + .padding(0) + .checked(this.selectedRingtone && this.selectedRingtone.id == item.id) + } + .padding({ + top: $r('app.float.card_inner_padding_horizontal'), + bottom: $r('app.float.card_inner_padding_horizontal'), + right: $r('app.float.card_inner_padding_horizontal'), + left: $r('app.float.card_inner_padding_horizontal'), + }) + + Flex() + .width('100%') + .height('100%') + .onClick(async () => await this.onRingtoneSelected(item)) + } + .height($r('app.float.ringtone_selection_item_height')) + } + + @Builder + buildAlarmSounds() { + Column() { + Text('Recommended Ringtones') + .textCase(TextCase.UpperCase) + .textAlign(TextAlign.Start) + .fontSize($r('app.float.ringtone_selection_section_header_size')) + .margin({ + top: $r('app.float.ringtone_selection_section_header_margin_vertical'), + bottom: $r('app.float.ringtone_selection_section_header_margin_vertical') + }) + .padding({ + left: $r('app.float.ringtone_selection_section_header_margin_horizontal'), + right: $r('app.float.ringtone_selection_section_header_margin_horizontal') + }) + .width('100%') + Card({ isMultipleSubItems: true }) { + ForEach(ringtones.filter((r => recommendedRingtones.some(rr => rr == r.id))), + (item: RingtoneItem, index: number) => { + Column() { + if (index) { + Divider().color($r('sys.color.comp_divider')) + } + this.buildRingtoneItem(item, true) + } + .justifyContent(FlexAlign.Center) + .borderRadius($r('app.float.default_corner_radius_l')) + }) + } + + Text('Classic Ringtones') + .textCase(TextCase.UpperCase) + .textAlign(TextAlign.Start) + .fontSize($r('app.float.ringtone_selection_section_header_size')) + .margin({ + top: $r('app.float.ringtone_selection_section_header_margin_vertical'), + bottom: $r('app.float.ringtone_selection_section_header_margin_vertical') + }) + .padding({ + left: $r('app.float.ringtone_selection_section_header_margin_horizontal'), + right: $r('app.float.ringtone_selection_section_header_margin_horizontal') + }) + .width('100%') + Card({ isMultipleSubItems: true }) { + ForEach(ringtones, (item: RingtoneItem, index: number) => { + if (item.id != NONE_RINGTONE_ID) { + Column() { + if (index) { + Divider().color($r('sys.color.comp_divider')) + } + this.buildRingtoneItem(item) + } + .justifyContent(FlexAlign.Center) + .borderRadius($r('app.float.default_corner_radius_l')) + } + }) + } + } + } + + @Builder + buildTimerSounds() { + Column() { + Text('Preset') + .textCase(TextCase.UpperCase) + .textAlign(TextAlign.Start) + .fontSize($r('app.float.ringtone_selection_section_header_size')) + .margin({ + top: $r('app.float.ringtone_selection_section_header_margin_vertical'), + bottom: $r('app.float.ringtone_selection_section_header_margin_vertical') + }) + .padding({ + left: $r('app.float.ringtone_selection_section_header_margin_horizontal'), + right: $r('app.float.ringtone_selection_section_header_margin_horizontal') + }) + .width('100%') + Card({ isMultipleSubItems: true }) { + ForEach(ringtones, (item: RingtoneItem, index: number) => { + if (item.id != NONE_RINGTONE_ID) { + Column() { + if (index) { + Divider().color($r('sys.color.comp_divider')) + } + this.buildRingtoneItem(item) + } + .justifyContent(FlexAlign.Center) + .borderRadius($r('app.float.default_corner_radius_l')) + } + }) + } + } + } + + build() { + NavDestination() { + Scroll() { + if (this.pageProps.isTimerRingtone) { + this.buildTimerSounds() + } else { + this.buildAlarmSounds() + } + Card({ isMultipleSubItems: true }) { + Column() { + this.buildRingtoneItem(noneRingtone) + } + .justifyContent(FlexAlign.Center) + .borderRadius($r('app.float.default_corner_radius_l')) + } + } + } + .backgroundColor($r('sys.color.ohos_id_background_secondary')) + .title(this.pageProps.isTimerRingtone ? 'Notification Sound' : 'Alarm Sound') + } +} \ No newline at end of file diff --git a/common/src/main/ets/components/RingtoneSelection/types.ets b/common/src/main/ets/components/RingtoneSelection/types.ets new file mode 100644 index 0000000..fa8de46 --- /dev/null +++ b/common/src/main/ets/components/RingtoneSelection/types.ets @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface RingtoneItem { + id: number, + name: string, + path: string +} + +export const DEFAULT_RINGTONE_ID = 8; + +export const NONE_RINGTONE_ID = 0; + +export const noneRingtone: RingtoneItem = { + id: NONE_RINGTONE_ID, + name: 'None', + path: '' +} + +export const ringtones: RingtoneItem[] = [ + { + id: 1, + name: 'Aegean Sea', + path: '/sys_prod/resource/media/audio/alarms/Aegean_Sea.ogg' + }, + { + id: 2, + name: 'Amazing Morning', + path: '/sys_prod/resource/media/audio/alarms/Amazing_Morning.ogg' + }, + { + id: 3, + name: 'Awakening', + path: '/sys_prod/resource/media/audio/alarms/Awakening.ogg' + }, + { + id: 4, + name: 'Creek', + path: '/sys_prod/resource/media/audio/alarms/Creek.ogg' + }, + { + id: 5, + name: 'Dawn', + path: '/sys_prod/resource/media/audio/alarms/Dawn.ogg' + }, + { + id: 6, + name: 'Flourish', + path: '/sys_prod/resource/media/audio/alarms/Flourish.ogg' + }, + { + id: 7, + name: 'Flow', + path: '/sys_prod/resource/media/audio/alarms/Flow.ogg' + }, + { + id: 8, + name: 'Forest Melody', + path: '/sys_prod/resource/media/audio/alarms/Forest_Melody.ogg' + }, + { + id: 9, + name: 'Hawaii', + path: '/sys_prod/resource/media/audio/alarms/Hawaii.ogg' + }, + { + id: 10, + name: 'Ingle', + path: '/sys_prod/resource/media/audio/alarms/Ingle.ogg' + }, + { + id: 11, + name: 'Moment', + path: '/sys_prod/resource/media/audio/alarms/Moment.ogg' + }, + { + id: 12, + name: 'Morning Light', + path: '/sys_prod/resource/media/audio/alarms/Morning_Light.ogg' + }, + { + id: 13, + name: 'Morning Mist', + path: '/sys_prod/resource/media/audio/alarms/Morning_Mist.ogg' + }, + { + id: 14, + name: 'New Day', + path: '/sys_prod/resource/media/audio/alarms/New_Day.ogg' + }, + { + id: 15, + name: 'Ocean Whisper', + path: '/sys_prod/resource/media/audio/alarms/Ocean_Whisper.ogg' + }, + { + id: 16, + name: 'Overture', + path: '/sys_prod/resource/media/audio/alarms/Overture.ogg' + }, + { + id: 17, + name: 'Rays', + path: '/sys_prod/resource/media/audio/alarms/Rays.ogg' + }, + { + id: 18, + name: 'Ripple', + path: '/sys_prod/resource/media/audio/alarms/Ripple.ogg' + }, + { + id: 19, + name: 'Sand Sea', + path: '/sys_prod/resource/media/audio/alarms/Sand_Sea.ogg' + }, + { + id: 20, + name: 'Shimmering', + path: '/sys_prod/resource/media/audio/alarms/Shimmering.ogg' + }, + { + id: 21, + name: 'Sound of the Sea', + path: '/sys_prod/resource/media/audio/alarms/Sound of the Sea.ogg' + }, + { + id: 22, + name: 'Star', + path: '/sys_prod/resource/media/audio/alarms/Star.ogg' + }, + { + id: 23, + name: 'Tap', + path: '/sys_prod/resource/media/audio/alarms/Tap.ogg' + }, + { + id: 24, + name: 'Timer Beep', + path: '/sys_prod/resource/media/audio/alarms/Timer_Beep.ogg' + }, + { + id: 25, + name: 'Wonderful Beginning', + path: '/sys_prod/resource/media/audio/alarms/Wonderful_Beginning.ogg' + }, + { + id: 26, + name: 'Xiaoyi', + path: '/sys_prod/resource/media/audio/alarms/Xiaoyi.ogg' + }, + noneRingtone +] + +export const recommendedRingtones: number[] = [DEFAULT_RINGTONE_ID, 12, 15] \ No newline at end of file diff --git a/common/src/main/ets/components/TitleBar/index.ets b/common/src/main/ets/components/TitleBar/index.ets new file mode 100644 index 0000000..dc72cb9 --- /dev/null +++ b/common/src/main/ets/components/TitleBar/index.ets @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Title Bar + * + * @since 2022-12-12 + */ +import deviceInfo from '@ohos.deviceInfo' +import { + EventReportUtil, + EventName +} from '../../utils'; +// import hiSysEvent from '@ohos.hiSysEvent'; + +interface ICancelBgColorOptions { + defaultBgColor: ResourceColor; + hoverBgColor: ResourceColor; + pressBgColor: ResourceColor; +} + +const cancelBgColorOptions: ICancelBgColorOptions = { + defaultBgColor: $r('app.color.color_hover_disabled'), + hoverBgColor: $r('app.color.color_hover'), + pressBgColor: $r('app.color.color_hover_disabled'), +} +const pcCancelBgColorOptions: ICancelBgColorOptions = { + defaultBgColor: $r('sys.color.comp_background_tertiary'), + hoverBgColor: $r('sys.color.interactive_hover'), + pressBgColor: $r('sys.color.interactive_click'), +} + +@Component +export struct TitleBar { + @Prop titleFontWeight: FontWeight = FontWeight.Medium; + @Prop title: string = ''; + @Prop isHoverInfo: boolean = false; + @Prop isPCView: boolean = false; + @Prop isFocusAble: boolean = false; + @Prop isModelBg: boolean = false; + @Prop modelEditFlag: boolean = false; + @State hoverBackgroundCancel: ResourceColor = this.getCancelBg(); + @State deviceType: string = deviceInfo.deviceType; // 设备类型 + @BuilderParam operationArea?: () => void; // Operation area on the right of the title + + private isPCLg() { + return this.deviceType === '2in1' + } + + private onIconClick?: () => void; + private iconResource?: Resource; + + private getCancelBg() { + if (this.isPCView) { + return pcCancelBgColorOptions.defaultBgColor; + } + return pcCancelBgColorOptions.defaultBgColor; + } + // 取消修改打点事件 + private cancelEdit(flag:boolean) { + if (flag) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_ALARM_ABANDON_SETTING) + } else { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.LONG_PRESS_CANCEL_EDIT_ALARM) + } + } + + build() { + Row() { + if (this.iconResource) { + if (this.isHoverInfo) { + Row() { + Image(this.iconResource) + .fillColor($r('sys.color.icon_primary')) + .width($r('app.float.button_icon_size')) + .height($r('app.float.button_icon_size')) + .id('id_title_bar_image') + .draggable(false) + .focusable(this.isFocusAble) + } + .onClick(() => { + this.cancelEdit(this.modelEditFlag) + this.onIconClick?.() + }) + .margin({ + left: this.isPCView ? 0 : $r('app.float.button_icon_left_padding'), + right: this.isPCView ? $r('sys.float.padding_level4') : $r('app.float.button_icon_right_padding'), + }) + .justifyContent(FlexAlign.Center) + .backgroundColor(this.hoverBackgroundCancel) + .width(this.isPCView ? $r('app.float.pc_button_size') : $r('app.float.pc_button_size')) + .height(this.isPCView ? $r('app.float.pc_button_size') : $r('app.float.pc_button_size')) + .borderRadius(this.isPCView ? $r('app.float.pc_button_size') : $r('app.float.pc_button_size')) + .onMouse((event) => { + if ((event?.action === MouseAction.Press) && (event?.button === MouseButton.Left)) { + this.hoverBackgroundCancel = $r('sys.color.interactive_click'); + } else if (event?.action === MouseAction.Release) { + this.hoverBackgroundCancel = $r('sys.color.interactive_hover'); + } + }) + } else { + Image(this.iconResource) + .fillColor($r('sys.color.ohos_fa_icon_secondary')) + .id('id_title_bar_image') + .width($r('app.float.button_icon_size')) + .height($r('app.float.button_icon_size')) + .draggable(false) + .onClick(() => this.onIconClick?.()) + .margin({ + left: this.isPCView ? 0 : $r('sys.float.padding_level8'), + right: this.isPCView ? $r('sys.float.padding_level4') : $r('sys.float.padding_level8'), + }) + } + } + if (this.iconResource) { + Text(this.title) + .fontWeight(this.titleFontWeight) + .fontSize($r('app.float.header_fontSize_addTitle')) + .fontColor($r('sys.color.icon_primary')) + .lineHeight($r('app.float.header_lineHeight_addTitle')) + } else { + Text(this.title) + .fontWeight(this.titleFontWeight) + .fontSize($r('app.float.header_fontSize')) + .fontColor($r('sys.color.icon_primary')) + .lineHeight($r('app.float.header_lineHeight')) + .height($r('app.float.header_textHeight')) + .fontWeight(FontWeight.Bold) + .margin({ top: this.isPCLg() ? $r('app.float.card_margin_top') : '' }) + } + + Blank() + if (this.operationArea) { + Row() { + this.operationArea() + } + .margin({ right: this.isPCView ? 0 : $r('app.float.button_operation_right_padding') }) + } + } + .backgroundColor(this.isModelBg ? $r('app.color.component_ultra_thick_panel') : $r('sys.color.ohos_id_background_secondary')) + .id('id_title_bar') + .height($r('app.float.header_footer_height')) + .width('100%') + } +} diff --git a/common/src/main/ets/components/index.ets b/common/src/main/ets/components/index.ets new file mode 100644 index 0000000..7e45cf2 --- /dev/null +++ b/common/src/main/ets/components/index.ets @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { AddButton, ButtonSize } from './AddButton/index'; + +export { Card, CardSize } from './Card/index'; + +export { Clock } from './Clock/index'; + +export { DigitalClock } from './Clock/DigitalClock'; + +export { CommonDialog } from './CommonDialog/CommonDialog'; + +export { CommonHwDialog } from './CommonDialog/CommonHwDialog'; + +export { ConfirmDialog } from './CommonDialog/ConfirmDialog'; + +export { TitleBar } from './TitleBar/index'; + +export { CommonGrid } from './CommonGrid'; + +export { CommonTextInputDialog } from './CommonDialog/CommonTextInputDialog'; + +export { ActionType } from './CommonDialog/CommonHwDialog'; + +export { FloatingActionButton } from './FloatingActionButton/index'; + +export { FloatingActionButtonColor } from './FloatingActionButton/types'; diff --git a/common/src/main/ets/libs/lottieArkTS.har b/common/src/main/ets/libs/lottieArkTS.har new file mode 100644 index 0000000000000000000000000000000000000000..c259c08aa4038c06b3568248f5d011ad0bd15515 GIT binary patch literal 138348 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

$-OfM^9_^V0w%d~To`$Nu}KloO%tqq%lzJA!G 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%0D!Rh50IRnbKw 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%0D9<|=_RFD7W(;i`jZCjt$j^3U86(eCZ5yxht< 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 H089)3)V|{^ literal 0 HcmV?d00001 diff --git a/common/src/main/ets/manager/AlarmManager.ets b/common/src/main/ets/manager/AlarmManager.ets new file mode 100644 index 0000000..a34367a --- /dev/null +++ b/common/src/main/ets/manager/AlarmManager.ets @@ -0,0 +1,1064 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import rdb from '@ohos.data.relationalStore'; +import preferencesUtil from '@ohos.data.preferences'; +import resmgr from '@ohos.resourceManager'; +import dataRdb from '@ohos.data.relationalStore'; +import { BusinessError } from '@ohos.base'; +import { ValueType } from '@ohos.data.ValuesBucket'; +import DatabaseManager from './DatabaseManager'; +import SnoozeManager from './SnoozeManager'; +import TimerManager from './TimerManager'; +import ResourceManager from './ResourceManager'; +import { EventName, EventReportUtil, EventResult, GlobalContext, LogUtil, TimeUtil, CommonUtil } from '../utils'; +import { + ALARM_CLOCK_DATA_TABLE, + ALARM_TIME_NEVER, + AlarmInfo, + AlarmInfoToShow, + ALERT_TIME_DEFAULT_DATA, + APP_CONFIG, + DATA_TABLE, + ENABLED_FALSE, + ENABLED_TRUE, + INIT_SNOOZE_COUNT, + IS_FIRST_ENTER, + NEAREST_ALARM_COUNT, + PRESET_ALARMS, + PRESET_ALARMS_TITLE, + SQL_CREATE_TABLE, + TRIGGER_TIME_NEVER, + WAKE_DAYS_TYPE, + IS_FIRST_ENTER_PRESET_CITY_WORLD +} from './types'; +import { MILLIS_IN_SECOND, SECOND_IN_MINUTE } from '../types'; +import { TIME_TAG_ID_LIST_IN_ZH } from '../utils/types'; +// import hiSysEvent from '@ohos.hiSysEvent'; + +const TAG = 'AlarmManager'; + +const AM_HOUR_START = 7; + +const AM_HOUR_END = 12; + +/** + * 闹钟管理工具类 + * + * @since 2022-07-06 + */ +class AlarmManager extends DatabaseManager { + // Whether the default alarm data is set. + private hasInitDefaultAlarms: boolean = false; + + /** + * Modify Alarm Database Data + * isIntentMode 意图框架调用此方法的标识 + * @param alarmInfo alarm object + */ + private async updateAlarmDatabase(alarmInfo: AlarmInfo, isIntentMode?: boolean): Promise { + const rdbStore = await this.getRdbStore(); + try { + rdbStore.beginTransaction(); + const predicates = new rdb.RdbPredicates(DATA_TABLE); + if (alarmInfo && alarmInfo.id) { + predicates.equalTo('ID', alarmInfo.id); + await rdbStore.update(this.getDbBucketFromAlarm(alarmInfo, isIntentMode), predicates); + rdbStore.commit(); + LogUtil.info(TAG, 'updateAlarmDatabase successfully.'); + } + } catch (error) { + LogUtil.error(TAG, 'updateAlarm failed: ', (error as BusinessError).message); + rdbStore.rollBack(); + } + } + + /** + * 获取关系数据库存储对象 + * 如果已初始化过,则直接返回之前获取的对象 + * 如果没有初始化过,则使用接口获取对象,并执行数据库建表命令,如果表未创建,则会创建一张新的表 + * + * @return 关系数据库存储对象 + */ + async getRdbStore(): Promise { + const rdbStore = await super.getRdbStore(SQL_CREATE_TABLE); + await this.initDefaultAlarms(rdbStore); + return rdbStore; + } + + initAlarmStatus(alarmList: Array) { + const firstOpenStatusAlarm = alarmList.find(item => (item.enabled)); + if (firstOpenStatusAlarm) { + this.updateAlarm(firstOpenStatusAlarm); + TimeUtil.getLeftTimeToRingTime(true); + } + } + + async getFirstEnterApp(worldClockListLength: number): Promise { + try { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, APP_CONFIG); + const isFirstEnter = await preferences.get(IS_FIRST_ENTER_PRESET_CITY_WORLD, true); + LogUtil.info(TAG, 'AlarmManager isFirstEnter: ' + isFirstEnter); + LogUtil.info(TAG, 'AlarmManager worldClockListLength: ' + worldClockListLength); + if (isFirstEnter && worldClockListLength) { + await preferences.put(IS_FIRST_ENTER_PRESET_CITY_WORLD, false); + await preferences.flush(); + return false; + } else if (!isFirstEnter && worldClockListLength) { + await preferences.put(IS_FIRST_ENTER_PRESET_CITY_WORLD, false); + await preferences.flush(); + return false; + } + if (Boolean(isFirstEnter)) { + await preferences.put(IS_FIRST_ENTER_PRESET_CITY_WORLD, false); + await preferences.flush(); + return true; + } + } catch (error) { + LogUtil.error(TAG, 'getFirstEnterApp failed: ', (error as BusinessError).message); + } + return false; + } + + async initDefaultAlarms(rdbStore: rdb.RdbStore): Promise { + if (this.hasInitDefaultAlarms) { + return; + } + this.hasInitDefaultAlarms = true; + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, APP_CONFIG); + const isFirstEnter = await preferences.get(IS_FIRST_ENTER, true); + let allAlarms = await this.getAllAlarms(); + LogUtil.info(TAG, 'read all alarms', JSON.stringify(allAlarms.length)); + LogUtil.info(TAG, `isFirstEnter: ${isFirstEnter}`); + if (isFirstEnter && allAlarms.length) { // 如果是安装时钟后首次进入,判断数据库是否有数据,如果有数据可能是双升单或者克隆场景,不创建默认数据 + this.initAlarmStatus(allAlarms); + await preferences.put(IS_FIRST_ENTER, false); + await preferences.flush(); + return; + } + if (Boolean(isFirstEnter)) { + await preferences.put(IS_FIRST_ENTER, false); + await preferences.flush(); + const defaultAlarm1: AlarmInfo = { + title: '', + hour: 7, + minute: 30, + second: 0, + daysOfWeek: [1, 2, 3, 4, 5], + daysOfWakeType: 3, + enabled: false, + ringDuration: 5, + snoozeDuration: 10, + snoozeTimes: 3, + } + const defaultAlarm2: AlarmInfo = { + title: '', + hour: 9, + minute: 0, + second: 0, + daysOfWeek: [6, 0], + daysOfWakeType: 3, + enabled: false, + ringDuration: 5, + snoozeDuration: 10, + snoozeTimes: 3, + } + try { + rdbStore.beginTransaction(); + defaultAlarm1.alarmTimeIntent = TimeUtil.getAlarmTime(defaultAlarm1) + defaultAlarm2.alarmTimeIntent = TimeUtil.getAlarmTime(defaultAlarm2) + const firstAlarmId = await rdbStore.insert(DATA_TABLE, this.getDbBucketFromAlarm(defaultAlarm1)); + const twoAlarmId = await rdbStore.insert(DATA_TABLE, this.getDbBucketFromAlarm(defaultAlarm2)); + const preSetAlarmIds = firstAlarmId + ',' + twoAlarmId; + await preferences.put(PRESET_ALARMS, preSetAlarmIds); + await preferences.put(PRESET_ALARMS_TITLE + firstAlarmId, ''); + await preferences.put(PRESET_ALARMS_TITLE + twoAlarmId, ''); + await preferences.flush(); + rdbStore.commit(); + } catch (error) { + LogUtil.error(TAG, 'add default alarm failed: ', (error as BusinessError).message); + rdbStore.rollBack(); + } + let alarmListByDB = await this.getAllAlarms(); + this.initAlarmStatus(alarmListByDB); + } + } + + // 根据闹钟id查询数据 + async getIDAlarmFrom(alarmId: string | undefined): Promise { + const rdbStore = await this.getRdbStore(); + const predicates = new dataRdb.RdbPredicates(ALARM_CLOCK_DATA_TABLE); + predicates.equalTo('ID', alarmId); + let resultSet: rdb.ResultSet = await rdbStore.query(predicates); + try { + return this.getAlarmListFromResultSet(resultSet); + } catch (error) { + LogUtil.error(TAG, 'getAllAlarms failed: ', (error as BusinessError).message); + } finally { + resultSet && resultSet.close(); + } + return []; + } + + /** + * 获取所有闹钟列表 + * + * @return 闹钟列表 + */ + async getAllAlarms(): Promise { + const rdbStore = await this.getRdbStore(); + const predicates = new rdb.RdbPredicates(DATA_TABLE); + predicates.orderByAsc('HOUR'); + predicates.orderByAsc('MINUTE'); + let resultSet: rdb.ResultSet = await rdbStore.query(predicates); + try { + return this.getAlarmListFromResultSet(resultSet); + } catch (error) { + LogUtil.error(TAG, 'getAllAlarms failed: ', (error as BusinessError).message); + } finally { + resultSet && resultSet.close(); + } + return []; + } + + async getAllTimerAlarms(): Promise { + const timerTitle = ResourceManager.getStringById($r('app.string.timer_title_new').id); + const rdbStore = await this.getRdbStore(); + let resultSet: rdb.ResultSet = {} as rdb.ResultSet; + try { + const predicates = new dataRdb.RdbPredicates(ALARM_CLOCK_DATA_TABLE); + predicates.equalTo('TITLE', timerTitle); + predicates.orderByAsc('HOUR'); + predicates.orderByAsc('MINUTE'); + predicates.orderByAsc('SECOND'); + resultSet = await rdbStore.query(predicates); + return this.getAlarmListFromResultSet(resultSet); + } catch (error) { + LogUtil.error(TAG, 'getAllAlarms failed: ', error?.message); + } finally { + resultSet && resultSet.close(); + } + return []; + } + + /** + * 获取用于页面展示的所有闹钟列表 + * 在数据库返回的数据上,做进一步加工,用于页面展示 + * + * @return 用于页面展示的闹钟列表 + */ + async getAllAlarmsToShow(): Promise { + const alarmClockListFromDatabase: AlarmInfo[] = await this.getAllAlarms(); + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, APP_CONFIG); + const preSetAlarmIds = await preferences.get(PRESET_ALARMS, ''); + const snoozedIds = await SnoozeManager.getSnoozedAlarmId(); + const noFind = -1; + await ResourceManager.preloadStringResources(TIME_TAG_ID_LIST_IN_ZH); + return alarmClockListFromDatabase.map(alarmInfo => { + alarmInfo.enabled = alarmInfo.enabled || snoozedIds.includes(alarmInfo.id as string); + const resourceManager = GlobalContext.getContext().getObject('resourceManager') as resmgr.ResourceManager; + const daysOfWeekDesc = TimeUtil.getDaysOfWeekDesc(resourceManager, alarmInfo.daysOfWeek); + const formatTime = TimeUtil.getFormattedTimeObj(alarmInfo.hour, alarmInfo.minute); + if (String(preSetAlarmIds).indexOf(alarmInfo.id as string) !== noFind && alarmInfo.title === '') { + alarmInfo.title = resourceManager.getStringSync($r('app.string.default_alarm_label').id); + } + const alarmInfoToShow: AlarmInfoToShow = { + id: alarmInfo.id, + second: alarmInfo.second, + title: alarmInfo.title, + hour: alarmInfo.hour, + minute: alarmInfo.minute, + daysOfWeek: alarmInfo.daysOfWeek, + daysOfWakeType: alarmInfo.daysOfWakeType, + alarmTime: alarmInfo.alarmTime, + enabled: alarmInfo.enabled, + ringDuration: alarmInfo.ringDuration, + snoozeDuration: alarmInfo.snoozeDuration, + snoozeTimes: alarmInfo.snoozeTimes, + snoozeCount: alarmInfo.snoozeCount, + tag: formatTime.tag, + time: formatTime.time, + isTagLeft: formatTime.isTagLeft, + daysOfWeekDesc, + alarmTimeIntent: alarmInfo.alarmTimeIntent, + reStart: alarmInfo.reStart + }; + return alarmInfoToShow; + }); + } + + /** + * 判断是否获取默认闹钟名称 + * + * @param alarmId 闹钟Id + */ + async getDefaultAlarmTitle(alarmId: string, hour: number): Promise { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, APP_CONFIG); + const preSetAlarmIds = await preferences.get(PRESET_ALARMS, ''); + const preSetAlarmName = await preferences.get(PRESET_ALARMS_TITLE + alarmId, ''); + const noFind = -1; + const defaultTitleFirst = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringSync($r('app.string.alarm_clock') + .id); + const defaultTitleSecond = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringSync($r('app.string.default_alarm_label_new') + .id); + let isDefaultTitle: boolean = ['', defaultTitleFirst, defaultTitleSecond].includes(preSetAlarmName as string) + if (String(preSetAlarmIds).indexOf(alarmId) !== noFind && isDefaultTitle) { + if (hour >= AM_HOUR_START && hour < AM_HOUR_END) { + return (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringSync($r('app.string.default_alarm_label_new') + .id); + } else { + return (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringSync($r('app.string.alarm_clock').id); + } + } + return ''; + } + + /** + * 保存预置闹钟名称 + * + * @param alarmId 闹钟Id + * @param alarmName 闹钟Id + */ + async saveDefaultAlarmTitle(alarmId: string, alarmTitle: string): Promise { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, APP_CONFIG); + const preSetAlarmIds = await preferences.get(PRESET_ALARMS, ''); + const noFind = -1; + if (String(preSetAlarmIds).indexOf(alarmId) !== noFind) { + await preferences.put(PRESET_ALARMS_TITLE + alarmId, alarmTitle); + await preferences.flush(); + } + } + + /** + * 新增闹钟 + * + * @param alarmInfo 闹钟对象 + */ + async addAlarm(alarmInfo: AlarmInfo, isIntentMode?: boolean, isCloneMode?: boolean): Promise { + const rdbStore = await this.getRdbStore(); + try { + rdbStore.beginTransaction(); + if (!isCloneMode) { + alarmInfo.enabled = true; + } + LogUtil.info(TAG, 'before addAlarm: ' + JSON.stringify(alarmInfo)) + const ret = await rdbStore.insert(DATA_TABLE, this.getDbBucketFromAlarm(alarmInfo, isIntentMode)); + LogUtil.info(TAG, 'addAlarm: ' + ret); + rdbStore.commit(); + await this.updateTimer(); + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_ALARM_CLOCK_ADD) + } catch (error) { + LogUtil.error(TAG, 'addAlarm failed: ', (error as BusinessError).message); + rdbStore.rollBack(); + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_ALARM_CLOCK_ADD) + } + } + + /** + * 修改闹钟,同时更新闹钟的提醒信息 + * isIntentMode 意图框架调用此方法的标识 + * @param alarmInfo 闹钟对象 + */ + async updateAlarm(alarmInfo: AlarmInfo, isIntentMode?: boolean): Promise { + if (!alarmInfo.id) { + LogUtil.error(TAG, 'Execute method updateAlarm failed, params is incorrect.'); + return; + } + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_ALARM_SAVE_SETTING) + await this.updateAlarmDatabase(alarmInfo, isIntentMode); + await this.updateTimer(alarmInfo.id, alarmInfo.reStart); + } + + /** + * 停止提醒,并根据重复模式更新闹钟状态 + * + * @param alarmInfo 闹钟对象 + */ + async stopAlarm(alarmInfo: AlarmInfo): Promise { + alarmInfo.enabled = (alarmInfo.daysOfWeek || []).length !== 0 || alarmInfo.daysOfWakeType === 4; + // 重复模式为“不重复时”,同时关闭闹钟 + await this.updateAlarm(alarmInfo) + } + + /** + * 延迟闹钟,并返回根据再响配置更新过下次响铃时间的闹钟对象 + * + * @param alarmInfo 闹钟对象 + * @param isAuto 是否为自动延迟响铃,不传视为否,自动延迟的场景,会更新闹钟延迟次数的计时器 + * @return 根据再响配置更新过下次响铃时间的闹钟对象 + */ + async delayAlarm(alarmInfo: AlarmInfo, isAuto?: boolean): Promise { + if (isAuto) { + await SnoozeManager.increaseSnoozedTimes(alarmInfo.id as string); + } else { + await SnoozeManager.clearSnoozeTimes(alarmInfo.id as string); + } + const nextAlertTime = new Date().getTime() + alarmInfo.snoozeDuration * MILLIS_IN_SECOND * SECOND_IN_MINUTE; + await SnoozeManager.saveSnoozedAlarmData(alarmInfo.id as string, nextAlertTime); + return nextAlertTime; + } + + /** + * Obtains the real start time of the alarm. + * + * @param alarmInfo AlarmInfo + * @return Real start time of the alarm in milliseconds. + */ + async getRealAlertTime(alarmInfo: AlarmInfo): Promise { + if (!alarmInfo?.id) { + LogUtil.error(TAG, 'Execute method getRealAlertTime failed, params is incorrect.'); + return ALERT_TIME_DEFAULT_DATA; + } + const delayedAlarmTime = await SnoozeManager.getNextAlertTime(alarmInfo.id); + LogUtil.info(TAG, `Real alert time is: ${delayedAlarmTime}`); + if (delayedAlarmTime !== ALERT_TIME_DEFAULT_DATA) { + return delayedAlarmTime; + } + if (alarmInfo?.alarmTime) { + return alarmInfo.alarmTime; + } + return ALERT_TIME_DEFAULT_DATA; + } + + /** + * 删除闹钟 + * + * @param alarmInfo 闹钟对象 + */ + async removeAlarm(alarmInfo: AlarmInfo): Promise { + const rdbStore = await this.getRdbStore(); + try { + rdbStore.beginTransaction(); + const predicates = new rdb.RdbPredicates(DATA_TABLE); + predicates.equalTo('ID', alarmInfo.id as string); + await rdbStore.delete(predicates); + rdbStore.commit(); + await this.updateTimer(); + LogUtil.info(TAG, 'removeAlarm successfully.'); + } catch (error) { + LogUtil.error(TAG, 'removeAlarm failed: ', (error as BusinessError).message); + rdbStore.rollBack(); + } + } + + /** + * 批量删除闹钟 + * + * @param alarmIdSet 要删除的闹钟id集合 + */ + async removeAlarms(alarmIdSet: Set): Promise { + const idList = Array.from(alarmIdSet); + const rdbStore = await this.getRdbStore(); + try { + rdbStore.beginTransaction(); + const predicates = new rdb.RdbPredicates(DATA_TABLE); + predicates.in('ID', idList); + await rdbStore.delete(predicates); + rdbStore.commit(); + await this.updateTimer(); + LogUtil.info(TAG, 'remove alarms successfully.'); + } catch (error) { + LogUtil.error(TAG, 'remove alarms failed because: ', (error as BusinessError).message); + rdbStore.rollBack(); + } + } + + /** + * 将前台使用的闹钟对象,转换为新增修改数据库的入参对象 + * isIntentMode 意图框架调用此方法的标识 意图框架的alarmTime为时间戳 + * @param alarmInfo 前台定义的闹钟对象 + * @return 新增、修改数据的入参对象 + */ + getDbBucketFromAlarm(alarmInfo: AlarmInfo, isIntentMode?: boolean): rdb.ValuesBucket { + LogUtil.info(TAG, 'before getDbBucketFromAlarm alarm is : ' + JSON.stringify(alarmInfo) + isIntentMode) + if (!isIntentMode) { + return { + 'TITLE': alarmInfo.title, + 'HOUR': alarmInfo.hour, + 'MINUTE': alarmInfo.minute as ValueType, + 'SECOND': alarmInfo.second || 0, + 'DAYS_OF_WEEK': new Uint8Array(alarmInfo.daysOfWeek || []), + 'DAYS_OF_WAKE_TYPE': alarmInfo.daysOfWakeType || 0, + 'ALARM_TIME': alarmInfo.enabled ? TimeUtil.getAlarmTime(alarmInfo) : ALARM_TIME_NEVER, + 'ENABLED': alarmInfo.enabled ? ENABLED_TRUE : ENABLED_FALSE, + 'RING_DURATION': alarmInfo.ringDuration as ValueType, + 'SNOOZE_DURATION': alarmInfo.snoozeDuration, + 'SNOOZE_TIMES': alarmInfo.snoozeTimes, + 'SNOOZE_COUNT': alarmInfo.snoozeCount || INIT_SNOOZE_COUNT, + 'ALARM_TIME_INTENT': alarmInfo.alarmTimeIntent as number === 0 ? TimeUtil.getAlarmTime(alarmInfo) + : alarmInfo.alarmTimeIntent as number, + 'RESTART': alarmInfo.reStart ? alarmInfo.reStart : '' + }; + } + + LogUtil.info(TAG, 'intentMode before getDbBucketFromAlarm alarm is : ' + alarmInfo.alarmTime) + return { + 'TITLE': alarmInfo.title, + 'HOUR': alarmInfo.hour, + 'MINUTE': alarmInfo.minute as ValueType, + 'SECOND': alarmInfo.second || 0, + 'DAYS_OF_WEEK': new Uint8Array(alarmInfo.daysOfWeek || []), + 'DAYS_OF_WAKE_TYPE': alarmInfo.daysOfWakeType || 0, + 'ALARM_TIME': alarmInfo.alarmTime!, + 'ENABLED': alarmInfo.enabled ? ENABLED_TRUE : ENABLED_FALSE, + 'RING_DURATION': alarmInfo.ringDuration as ValueType, + 'SNOOZE_DURATION': alarmInfo.snoozeDuration, + 'SNOOZE_TIMES': alarmInfo.snoozeTimes, + 'SNOOZE_COUNT': alarmInfo.snoozeCount || INIT_SNOOZE_COUNT, + 'ALARM_TIME_INTENT': alarmInfo.alarmTimeIntent as number, + 'RESTART': alarmInfo.reStart ? alarmInfo.reStart : '' + }; + } + + /** + * 从数据库查询的返回对象中,获取前台可用的闹钟信息列表 + * + * @param resultSet 数据库返回的结果集对象 + * @return 闹钟信息列表 + */ + async getAlarmListFromResultSet(resultSet: rdb.ResultSet): Promise { + const alarmClockList: AlarmInfo[] = []; + while (resultSet.goToNextRow()) { + const daysOfWeekNative: Uint8Array = resultSet.getBlob(resultSet.getColumnIndex('DAYS_OF_WEEK')); + const daysOfWeek: number[] = []; + daysOfWeekNative.forEach(day => { + daysOfWeek.push(day); + }); + const alarmInfo: AlarmInfo = { + daysOfWeek, + id: resultSet.getString(resultSet.getColumnIndex('ID')), + hour: resultSet.getLong(resultSet.getColumnIndex('HOUR')), + minute: resultSet.getLong(resultSet.getColumnIndex('MINUTE')), + second: resultSet.getLong(resultSet.getColumnIndex('SECOND')), + enabled: !!resultSet.getLong(resultSet.getColumnIndex('ENABLED')), + alarmTime: resultSet.getLong(resultSet.getColumnIndex('ALARM_TIME')), + title: resultSet.getString(resultSet.getColumnIndex('TITLE')), + ringDuration: resultSet.getLong(resultSet.getColumnIndex('RING_DURATION')), + snoozeDuration: resultSet.getLong(resultSet.getColumnIndex('SNOOZE_DURATION')), + snoozeTimes: resultSet.getLong(resultSet.getColumnIndex('SNOOZE_TIMES')), + snoozeCount: resultSet.getLong(resultSet.getColumnIndex('SNOOZE_COUNT')), + daysOfWakeType: resultSet.getColumnIndex('DAYS_OF_WAKE_TYPE') > -1 ? resultSet.getLong(resultSet.getColumnIndex('DAYS_OF_WAKE_TYPE')) : 0, + alarmTimeIntent: resultSet.getColumnIndex('ALARM_TIME_INTENT') > -1 ? resultSet.getLong(resultSet.getColumnIndex('ALARM_TIME_INTENT')) : 0, + reStart: resultSet.getString(resultSet.getColumnIndex('RESTART')) ? resultSet.getString(resultSet.getColumnIndex('RESTART')) : '' + } + alarmClockList.push(alarmInfo); + } + return alarmClockList; + } + + /** + * 获取最近一次需要提醒的闹钟 + * 搜索条件是已开启的闹钟,只返回一个结果,按提醒时间排序 + * + * @return 最近一次需要提醒的闹钟 + */ + async getNearestAlarm(): Promise { + const rdbStore = await this.getRdbStore(); + const predicates = new rdb.RdbPredicates(DATA_TABLE); + const snoozedIds = await SnoozeManager.getSnoozedAlarmId(); + if (snoozedIds?.length > 0) { + predicates.in('ID', snoozedIds).or().equalTo('ENABLED', ENABLED_TRUE); + } else { + predicates.equalTo('ENABLED', ENABLED_TRUE); + } + let resultSet: rdb.ResultSet = await rdbStore.query(predicates); + try { + const alarmList = await this.getAlarmListFromResultSet(resultSet); + const currentTime = new Date().getTime(); + let nearestAlarm: AlarmInfo | undefined = undefined; + for (const alarm of alarmList) { + let tempAlarmTime: number | undefined = undefined; + if (snoozedIds.includes(alarm.id as string)) { + tempAlarmTime = alarm.alarmTime; + alarm.alarmTime = await SnoozeManager.getNextAlertTime(alarm.id as string); + } + if (alarm.alarmTime && alarm.alarmTime < currentTime) { + await SnoozeManager.deleteSnoozedAlarmId(alarm.id as string); + if (alarm.daysOfWeek.length <= 0 && alarm.daysOfWakeType !== WAKE_DAYS_TYPE) { + alarm.alarmTime = tempAlarmTime; + alarm.enabled = false; + await this.updateAlarmDatabase(alarm); + continue; + } + if (alarm.daysOfWeek.length > 0 || alarm.daysOfWakeType === WAKE_DAYS_TYPE) { + alarm.alarmTime = TimeUtil.getAlarmTime(alarm); + await this.updateAlarmDatabase(alarm); + } + } + if (!nearestAlarm || (nearestAlarm.alarmTime && alarm.alarmTime && alarm.alarmTime < nearestAlarm.alarmTime)) { + nearestAlarm = alarm; + } + } + return nearestAlarm; + } catch (error) { + LogUtil.error(TAG, 'getNextAlarm failed: ', (error as BusinessError).message); + } finally { + resultSet && resultSet.close(); + } + return undefined; + } + + /** + * Query the latest alarm data from the database. + * + * @param the latest alarm data + */ + async getLatestAlarmFromDb(alarmId: string | number): Promise { + if (!alarmId) { + LogUtil.info(TAG, 'get latest alarm from db occurs error, alarmId is empty'); + return undefined; + } + const rdbStore = await this.getRdbStore(); + const predicates = new rdb.RdbPredicates(DATA_TABLE); + predicates.equalTo('ID', alarmId); + predicates.limitAs(NEAREST_ALARM_COUNT); + let resultSet: rdb.ResultSet = await rdbStore.query(predicates); + try { + const alarmList = await this.getAlarmListFromResultSet(resultSet); + const snoozedIds = await SnoozeManager.getSnoozedAlarmId(); + if (alarmList[0] && snoozedIds && snoozedIds.includes(alarmId.toString())) { + alarmList[0].alarmTime = await SnoozeManager.getNextAlertTime(alarmId.toString()); + } + if (alarmList[0].title === '') { + let list = await this.getAllAlarmsToShow() + list.forEach((item) => { + if (item.id === alarmList[0].id) { + alarmList[0].title = item.title + } + }) + } + LogUtil.info(TAG, `alarmList==> ${JSON.stringify(alarmList)}}`) + return alarmList[0]; + } catch (error) { + LogUtil.error(TAG, `getLatestAlarmDataInBb failed: ${JSON.stringify(error)}`); + } finally { + resultSet && resultSet.close(); + } + return undefined; + } + + /** + * 根据最新的最近一次需要触发的闹钟,更新计时器 + * 如果时间相对之前没有变化,则不更新 + * 如果时间变化(包括从无到有,从有到无),则需要更新记录计时器的缓存 + */ + async updateTimer(changedAlarmId?: string | number, reStart?: string): Promise { + const triggerTime = await TimerManager.getTriggerTime(); + const triggerAlarmID = await TimerManager.getTriggerTimeID(); + const nearestAlarm = await this.getNearestAlarm(); + if (changedAlarmId && reStart && reStart !== '') { + const restartAlarmList = await this.getAlarmById(changedAlarmId as string); + if (restartAlarmList && restartAlarmList.length !== 0) { + const restartAlarm = restartAlarmList[0]; + await TimerManager.startRepeatAlarm(restartAlarm); + } + const timeStamp = await TimerManager.getTriggerTime(); + CommonUtil.refreshNearestAlarmTime(timeStamp); + return; + } + if (!nearestAlarm) { + LogUtil.info(TAG, 'No alarm enabled!'); + // 数据库中不再有最近闹钟,但是还有开启的计时器,需要停止计时器 + triggerTime && triggerTime !== TRIGGER_TIME_NEVER && await TimerManager.stopAlarm(); + CommonUtil.refreshNearestAlarmTime(0); + return; + } + const isDifferentTime = (triggerTime !== nearestAlarm.alarmTime); + const isDifferentId = (triggerAlarmID !== Number(nearestAlarm.id) || + (changedAlarmId && Number(changedAlarmId) !== Number(nearestAlarm.id))); + if (nearestAlarm.alarmTime && (isDifferentTime || isDifferentId)) { + LogUtil.info(TAG, 'The latest start time has changed.'); + await TimerManager.startAlarm(nearestAlarm); + const timeStamp = await TimerManager.getTriggerTime(); + CommonUtil.refreshNearestAlarmTime(timeStamp); + return; + } + // If the parameters of the latest alarm are modified, the timer still needs to be updated. + if (changedAlarmId && changedAlarmId === nearestAlarm.id) { + LogUtil.info(TAG, 'nearestAlarm alarm parameters has changed, still need to startAlarm'); + await TimerManager.startAlarm(nearestAlarm); + const timeStamp = await TimerManager.getTriggerTime(); + CommonUtil.refreshNearestAlarmTime(timeStamp); + return; + } + LogUtil.info(TAG, 'nearestAlarm alarm has no change'); + } + + /** + * Refresh the timer immediately regardless of whether the latest start time changes. + */ + async updateTimerImmediately(): Promise { + const triggerTime = await TimerManager.getTriggerTime(); + const nearestAlarm = await this.getNearestAlarm(); + if (!nearestAlarm) { + LogUtil.info(TAG, 'No alarms enabled!'); + triggerTime && triggerTime !== TRIGGER_TIME_NEVER && await TimerManager.stopAlarm(); + return; + } + if (nearestAlarm.alarmTime) { + LogUtil.info(TAG, 'Update timer immediately.'); + await TimerManager.startAlarm(nearestAlarm); + return; + } + LogUtil.info(TAG, `nearestAlarm alarmTime is incorrect: ${nearestAlarm.alarmTime}`); + } + + /** + * Queries and disables all alarms that are unique and + * whose start time is the same as that of alarmInfo in the alarm list. + * Query and reset the alarm time of all duplicate alarms in the alarm list. + * The alarm time is the same as that of alarmInfo. + * + * @param alarmTime alarm clock that is being started + */ + async closeNoRepeatAlarms(alarmInfo: AlarmInfo): Promise { + const rdbStore = await this.getRdbStore(); + rdbStore.beginTransaction(); + const predicates = new rdb.RdbPredicates(DATA_TABLE); + predicates.equalTo('ALARM_TIME', alarmInfo.alarmTime as number); + predicates.equalTo('ENABLED', ENABLED_TRUE); + let resultSet: rdb.ResultSet = await rdbStore.query(predicates); + try { + rdbStore.commit(); + const alarmList = await this.getAlarmListFromResultSet(resultSet); + alarmList.forEach(async alarmInfo => { + if (alarmInfo.daysOfWeek.length === 0 && alarmInfo.daysOfWakeType !== WAKE_DAYS_TYPE) { + alarmInfo.enabled = false; + } else { + alarmInfo.alarmTime = TimeUtil.getAlarmTime(alarmInfo); + } + await this.updateAlarmDatabase(alarmInfo); + }); + await this.updateTimer(); + } catch (error) { + LogUtil.error(TAG, 'closeNoRepeatAlarms failed: ', (error as BusinessError).message); + rdbStore.rollBack(); + } finally { + resultSet && resultSet.close(); + } + } + + async closeNoRepeatAlarmsDB(alarmTime: number): Promise { + const rdbStore = await this.getRdbStore(); + rdbStore.beginTransaction(); + const predicates = new rdb.RdbPredicates(DATA_TABLE); + predicates.equalTo('ALARM_TIME', alarmTime); + predicates.equalTo('ENABLED', ENABLED_TRUE); + let resultSet: rdb.ResultSet = await rdbStore.query(predicates); + try { + rdbStore.commit(); + const alarmList = await this.getAlarmListFromResultSet(resultSet); + alarmList.forEach(async alarmInfo => { + LogUtil.info(TAG, 'closeNoRepeatAlarmsDB before:' + JSON.stringify(alarmInfo)); + if (alarmInfo.daysOfWeek.length === 0 && alarmInfo.daysOfWakeType !== WAKE_DAYS_TYPE) { + alarmInfo.enabled = false; + } else { + alarmInfo.alarmTime = TimeUtil.getAlarmTime(alarmInfo); + } + LogUtil.info(TAG, 'closeNoRepeatAlarmsDB after:' + JSON.stringify(alarmInfo)); + await this.updateAlarmDatabase(alarmInfo); + }); + } catch (error) { + LogUtil.error(TAG, 'closeNoRepeatAlarmsDB failed: ', (error as BusinessError).message); + rdbStore.rollBack(); + } finally { + resultSet && resultSet.close(); + } + } + + /** + * Has the nap limit been reached + * + * @return true if reached the upper limit + */ + async hasReachedLimitedSnoozeTimes(alarmInfo: AlarmInfo): Promise { + const hasSnoozedTimes = await SnoozeManager.getSnoozedTimes(alarmInfo.id as string); + return hasSnoozedTimes >= alarmInfo.snoozeTimes; + } + + /** + * Clear snooze data in sharedPreference by alarm id. + * + * @param alarmInfo Alarm info + * @param isEditMode Whether user proactively edits the alarm + * @return cleared alarm ids + */ + async clearSnoozedData(alarmInfo: AlarmInfo, isEditMode: boolean): Promise { + // The following situations will walk up here + // 1: User proactively edits the alarm + // 2: Close the alarm + // 3: User clicks the button for closing the notification bar + if (isEditMode) { + await SnoozeManager.deleteSnoozedAlarmId(alarmInfo.id as string); + await SnoozeManager.clearSnoozeTimes(alarmInfo.id as string); + } + if (!alarmInfo.enabled) { + return []; + } + const clearedAlarmIds: string[] = []; + if (alarmInfo.id) { + clearedAlarmIds.push(alarmInfo.id); + } + const snoozeIds = await SnoozeManager.getSnoozedAlarmId(); + for (const id of snoozeIds) { + const alertTime = await SnoozeManager.getNextAlertTime(id); + // Snoozed alert time is later than current edited alarm. in this case, we need to clear snoozed data. + if (alertTime !== ALERT_TIME_DEFAULT_DATA && alertTime > TimeUtil.getAlarmTime(alarmInfo)) { + await SnoozeManager.deleteSnoozedAlarmId(id); + await SnoozeManager.clearSnoozeTimes(id); + clearedAlarmIds.push(id); + } + } + return clearedAlarmIds; + } + + /** + * Refresh all alarms ALARM_TIME in database. + */ + async recalculateAllAlarmsAlarmTime(isIntentMode?: boolean): Promise { + const alarmList = await this.getAllAlarms(); + if (!alarmList || alarmList.length <= 0) { + LogUtil.info(TAG, 'There is no alarm, no need to recalculate'); + return; + } + const rdbStore = await this.getRdbStore(); + try { + rdbStore.beginTransaction(); + for (const alarmInfo of alarmList) { + const predicates = new rdb.RdbPredicates(DATA_TABLE); + predicates.equalTo('ID', alarmInfo.id as string); + const valuesBucket = this.getDbBucketFromAlarm(alarmInfo, isIntentMode); + await rdbStore.update(valuesBucket, predicates); + } + rdbStore.commit(); + LogUtil.info(TAG, 'recalculate successfully.'); + } catch (error) { + LogUtil.error(TAG, `recalculate failed:${(error as BusinessError).message}`); + rdbStore.rollBack(); + } + } + + /** + * 通过id删除闹钟 + * + * @param alarmID 闹钟id + */ + async removeAlarmById(alarmID: string): Promise { + const rdbStore = await this.getRdbStore(); + try { + rdbStore.beginTransaction(); + const predicates = new rdb.RdbPredicates(DATA_TABLE); + predicates.equalTo('ID', alarmID as string); + let deleteResult = await rdbStore.delete(predicates); + rdbStore.commit(); + await this.updateTimer(); + if (deleteResult) { + LogUtil.info(TAG, 'removeAlarm successfully.'); + return 'success' + } + LogUtil.info(TAG, 'removeAlarm failed , no this alarmId'); + return 'failed' + } catch (error) { + LogUtil.error(TAG, 'removeAlarm failed: ', (error as BusinessError).message); + rdbStore.rollBack(); + } + return 'error' + } + + /** + * 根据条件查询对应的闹钟 + * id + * + * @return 当前条件对应的闹钟 + */ + async getAlarmById(alarmID: string): Promise { + if (!alarmID) { + LogUtil.info(TAG, 'get alarm by id failed, alarmId is empty') + return undefined + } + const rdbStore = await this.getRdbStore(); + const predicates = new rdb.RdbPredicates(DATA_TABLE); + predicates.equalTo('ID', alarmID) + let resultSet: rdb.ResultSet = await rdbStore.query(predicates); + try { + let alarmList = await this.getAlarmListFromResultSet(resultSet); + return alarmList + } catch (error) { + LogUtil.error(TAG, 'get alarm by id failed: ', (error as BusinessError).message); + } finally { + resultSet && resultSet.close(); + } + return undefined; + } + + /** + * 根据id排序闹钟列表 + * sortAlarmListById 根据id排序闹钟列表 + * + * @return 当前条件对应的闹钟 + */ + async sortAlarmListById(): Promise { + const alarmList = await this.getAllAlarmsToShow(); + LogUtil.info(TAG, 'intentExecutor createAlarm alarmList is : ' + JSON.stringify(alarmList)) + let idList = alarmList.map((alarmInfo) => { + return alarmInfo.id + }) + let sortedIdList = idList.sort() + let resultId = sortedIdList[sortedIdList.length - 1] + let sortByIdList = alarmList.filter((alarmInfo) => { + return alarmInfo.id == resultId + }) + LogUtil.info(TAG, 'intentExecutor createAlarm sortByIdList is : ' + JSON.stringify(sortByIdList)) + return sortByIdList + } + + /** + * 并列查询 支持一起查 id、闹钟名称、启用状态、响铃时间段 + * + * + * @return 当前条件对应的闹钟列表 + */ + async searchAlarmInfo(entityId?: string, title?: string, enabled?: boolean, alarmTimeArr?: string[]): + Promise { + const rdbStore = await this.getRdbStore(); + let predicates = new dataRdb.RdbPredicates(ALARM_CLOCK_DATA_TABLE); + + if (entityId) { + predicates = predicates.equalTo('ID', entityId) + if (title) { + predicates = predicates.contains('TITLE', title) + if (enabled && enabled == true || enabled == false) { + predicates = predicates.equalTo('ENABLED', enabled) + if (alarmTimeArr && alarmTimeArr.length === 2) { + predicates.between('ALARM_TIME_INTENT', alarmTimeArr[0], alarmTimeArr[1]) + } + } + } else { + if (enabled && enabled == true || enabled == false) { + predicates = predicates.equalTo('ENABLED', enabled) + if (alarmTimeArr && alarmTimeArr.length === 2) { + predicates.between('ALARM_TIME_INTENT', alarmTimeArr[0], alarmTimeArr[1]) + } + } + } + } + + if (title) { + predicates = predicates.contains('TITLE', title) + if (entityId) { + predicates = predicates.equalTo('ID', entityId) + + if (enabled && enabled == true || enabled == false) { + predicates = predicates.equalTo('ENABLED', enabled) + if (alarmTimeArr && alarmTimeArr.length === 2) { + predicates.between('ALARM_TIME_INTENT', alarmTimeArr[0], alarmTimeArr[1]) + } + } + } else { + if (enabled && enabled == true || enabled == false) { + predicates = predicates.equalTo('ENABLED', enabled) + if (alarmTimeArr && alarmTimeArr.length === 2) { + predicates.between('ALARM_TIME_INTENT', alarmTimeArr[0], alarmTimeArr[1]) + } + } + } + } + + if (enabled && enabled == true || enabled == false) { + predicates = predicates.equalTo('ENABLED', enabled) + if (entityId) { + predicates = predicates.equalTo('ID', entityId) + + if (title) { + predicates = predicates.contains('TITLE', title) + if (alarmTimeArr && alarmTimeArr.length === 2) { + predicates.between('ALARM_TIME_INTENT', alarmTimeArr[0], alarmTimeArr[1]) + } + } + } else { + if (title) { + predicates = predicates.contains('TITLE', title) + if (alarmTimeArr && alarmTimeArr.length === 2) { + predicates.between('ALARM_TIME_INTENT', alarmTimeArr[0], alarmTimeArr[1]) + } + } + } + } + + if (alarmTimeArr && alarmTimeArr.length === 2) { + predicates.between('ALARM_TIME_INTENT', alarmTimeArr[0], alarmTimeArr[1]) + if (entityId) { + predicates = predicates.equalTo('ID', entityId) + + if (title) { + predicates = predicates.contains('TITLE', title) + if (enabled && enabled == true || enabled == false) { + predicates = predicates.equalTo('ENABLED', enabled) + } + } + } else { + if (title) { + predicates = predicates.contains('TITLE', title) + if (enabled && enabled == true || enabled == false) { + predicates = predicates.equalTo('ENABLED', enabled) + } + } + } + } + let resultSet: rdb.ResultSet = await rdbStore.query(predicates); + try { + let alarmList = await this.getAlarmListFromResultSet(resultSet); + return alarmList + } catch (error) { + LogUtil.error(TAG, 'get alarms failed: ', (error as BusinessError).message); + } finally { + resultSet && resultSet.close(); + } + return undefined; + } + + /** + * 根据条件查询对应的闹钟 + * alarmTimeIntent 只有意图框架才能用这个方法 + * + * @return 当前条件对应的闹钟 + */ + async getAlarmByAlarmTimeIntent(alarmTimeIntent: number): Promise { + const rdbStore = await this.getRdbStore(); + let predicates = new dataRdb.RdbPredicates(ALARM_CLOCK_DATA_TABLE); + predicates = predicates.equalTo('ALARM_TIME_INTENT', alarmTimeIntent); + let resultSet: rdb.ResultSet = await rdbStore.query(predicates); + try { + const alarmList = await this.getAlarmListFromResultSet(resultSet); + return alarmList + } catch (error) { + LogUtil.error(TAG, 'get alarm by alarmTimeIntent failed: ', (error as BusinessError).message); + } finally { + resultSet && resultSet.close(); + } + return undefined; + } +} + +export default new AlarmManager(); \ No newline at end of file diff --git a/common/src/main/ets/manager/AlarmStateManager.ets b/common/src/main/ets/manager/AlarmStateManager.ets new file mode 100644 index 0000000..32ebe1d --- /dev/null +++ b/common/src/main/ets/manager/AlarmStateManager.ets @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import preferencesUtil from '@ohos.data.preferences'; +import { LogUtil } from '../utils'; +import { GlobalContext } from '../utils'; +import { APP_CONFIG, STATE_FIRING, STATE_SNOOZE, STATE_IDLE, STATE_STOP, DEFAULT_ALARM_ID, } from './types'; + +const KEY_ALARM_STATE = 'alarm_state'; + +const KEY_ALARM_STATE_ID = 'alarm_state_id'; + +const TAG = 'AlarmStateManager'; + +type Preferences = preferencesUtil.Preferences; + +/** + * Manager Alarm state Tool Class + * + * @since 2022-11-24 + */ +class AlarmStateManager { + private preferences: Preferences | undefined = undefined; + + /** + * Get AlarmStateManager Preferences instance. + * + * @return Preferences instance + */ + private async getPreferences(): Promise { + if (this.preferences) { + return this.preferences; + } + this.preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, APP_CONFIG); + return this.preferences as Preferences; + } + + /** + * set alarm state + * + * @param currentState alarm state + * @param alarmId alarm id + */ + private async setState(currentState: number, alarmId: string): Promise { + const preferences = await this.getPreferences(); + await preferences.put(KEY_ALARM_STATE, currentState); + if (currentState === STATE_FIRING) { + await preferences.put(KEY_ALARM_STATE_ID, alarmId); + } + await preferences.flush(); + } + + /** + * get current alarm state + * + * @return alarm state + */ + async getState(): Promise { + const preferences = await this.getPreferences(); + const alarmState = await preferences.get(KEY_ALARM_STATE, STATE_IDLE); + return Number(alarmState); + } + + /** + * get alarm id which causes a state change + * + * @return alarm id + */ + async getAlarmId(): Promise { + const preferences = await this.getPreferences(); + const alarmState = await preferences.get(KEY_ALARM_STATE_ID, DEFAULT_ALARM_ID); + return Number(alarmState); + } + + /** + * set alarm state firing + * + * @param alarmId alarm id + */ + async setStateFiring(alarmId: string): Promise { + if (!alarmId) { + LogUtil.error(TAG, 'Execute method setStateFiring failed, params is incorrect.'); + return; + } + await this.setState(STATE_FIRING, alarmId); + } + + /** + * set alarm state firing + * + * @param alarmId alarm id + */ + async setStateSnooze(alarmId: string): Promise { + if (!alarmId) { + LogUtil.error(TAG, 'Execute method setStateSnooze failed, params is incorrect.'); + return; + } + await this.setState(STATE_SNOOZE, alarmId); + } + + /** + * set alarm state stop + * + * @param alarmId alarm id + */ + async setStateStop(alarmId: string): Promise { + if (!alarmId) { + LogUtil.error(TAG, 'Execute method setStateStop failed, params is incorrect.'); + return; + } + const isFiring = await this.isFiring(); + const isFiringId = await this.getAlarmId(); + LogUtil.info(TAG, `setStateStop isFiring:${isFiring} isFiringId:${isFiringId} alarmId:${alarmId}`); + if (isFiring && isFiringId === Number(alarmId)) { + await this.setState(STATE_STOP, alarmId); + } + } + + /** + * is current firing + */ + async isFiring(): Promise { + const currentState = await this.getState(); + return currentState === STATE_FIRING; + } + + /** + * is current snoozed + */ + async isSnoozed(): Promise { + const currentState = await this.getState(); + return currentState === STATE_SNOOZE; + } + + /** + * is current stopped + */ + async isStopped(): Promise { + const currentState = await this.getState(); + return currentState === STATE_STOP; + } +} + +export default new AlarmStateManager(); \ No newline at end of file diff --git a/common/src/main/ets/manager/BreakpointManager.ets b/common/src/main/ets/manager/BreakpointManager.ets new file mode 100644 index 0000000..270955c --- /dev/null +++ b/common/src/main/ets/manager/BreakpointManager.ets @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import mediaQuery from '@ohos.mediaquery'; +import display from '@ohos.display'; +import { DisplayUtil, LogUtil } from '../utils'; +import { Callback, BusinessError } from '@ohos.base'; +import { BreakPointListener, MatchRule, BreakPoint, MIN_LG_WIDTH, MIN_MD_WIDTH } from './types'; +import deviceInfo from '@ohos.deviceInfo'; + +const TAG: string = 'BreakpointManager'; + +/** + * Manager Alarm state Tool Class + * + * @since 2023-01-10 + */ +class BreakpointManager { + private currentBreakpoint: string = ''; + private foldAbleScreen: number = 0; + private orientaion: number = 0; + private listenerList: BreakPointListener[] = [ + { + listener: mediaQuery.matchMediaSync(MatchRule.SM), + breakPoint: BreakPoint.SM, + }, + { + listener: mediaQuery.matchMediaSync(MatchRule.MD), + breakPoint: BreakPoint.MD, + }, + { + listener: mediaQuery.matchMediaSync(MatchRule.LG), + breakPoint: BreakPoint.LG, + }, + ]; + + /** + * Do not modify the folding method + */ + private getScreen() { + let callback: Callback = (data: display.FoldStatus) => { + const getfoldAbleScreen = display.getFoldStatus(); + this.foldAbleScreen = getfoldAbleScreen + AppStorage.SetOrCreate('currentAbleScreen', this.foldAbleScreen); + }; + try { + display.on('foldStatusChange', callback); + } catch (exception) { + LogUtil.error(TAG, 'get screen result' + JSON.stringify(exception)); + } + } + + /** + * Do not modify the folding method + */ + private getOrientation() { + display.getAllDisplay((err: BusinessError, data: Array) => { + const errCode: number = err.code; + if (errCode) { + LogUtil.error(TAG, 'Failed to obtain all the display objects. Code: ' + JSON.stringify(err)); + return; + } + this.orientaion = data[0].width; + AppStorage.SetOrCreate('setOrientaion', this.orientaion); + }); + } + + /** + * Do not modify the folding method + */ + private updateCurrentBreakpoint(breakpoint: string): void { + if (this.currentBreakpoint !== breakpoint) { + this.currentBreakpoint = (breakpoint === 'lg' && deviceInfo.deviceType !== '2in1') ? 'md' : breakpoint; + AppStorage.SetOrCreate('currentBreakpoint', this.currentBreakpoint); + } + } + + /** + * Initializes breakpoint based on the input window width. + * + * @param windowWidth Window width + */ + public async initBreakpointFromWidth(windowWidth: number): Promise { + this.getScreen(); + this.getOrientation(); + const dftDisplay = await display.getDefaultDisplay(); + const dpi = dftDisplay.densityDPI; + if (windowWidth > DisplayUtil.vp2pxInTs(MIN_LG_WIDTH, dpi)) { + this.updateCurrentBreakpoint(BreakPoint.LG); + return; + } + if (windowWidth > DisplayUtil.vp2pxInTs(MIN_MD_WIDTH, dpi)) { + this.updateCurrentBreakpoint(BreakPoint.MD); + return; + } + this.updateCurrentBreakpoint(BreakPoint.SM); + } + + /** + * Register breakpoint listening. + */ + public register(): void { + for (const item of this.listenerList) { + if (item.listener.matches && this.currentBreakpoint !== item.breakPoint) { + this.updateCurrentBreakpoint(item.breakPoint); + break; + } + item.listener.on('change', data => { + data.matches && this.updateCurrentBreakpoint(item.breakPoint); + }); + } + } + + /** + * Unregister breakpoint listening. + */ + public unregister(): void { + for (const item of this.listenerList) { + item.listener.off('change'); + } + } +} + +export default new BreakpointManager(); \ No newline at end of file diff --git a/common/src/main/ets/manager/DatabaseManager.ets b/common/src/main/ets/manager/DatabaseManager.ets new file mode 100644 index 0000000..4da9143 --- /dev/null +++ b/common/src/main/ets/manager/DatabaseManager.ets @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dataRdb from '@ohos.data.relationalStore'; +import { BusinessError } from '@ohos.base'; +import { GlobalContext, LogUtil } from '../utils'; +import { AlarmInfo, STORE_CONFIG } from './types'; +import AlarmManager from './AlarmManager'; + +const TAG = 'DatabaseManager'; + +const DATA_TABLE = 'ALARM_CLOCK'; + +let IS_FIRST_OTA = true; + +/** + * 数据库管理基类 + * 通过继承实现具体能力,如: 被 AlarmManager 继承,通过 AlarmManager 进一步封装对外提供闹钟管理功能 + * + * @author XiaHan + * @since 2022-07-06 + */ +export default abstract class DatabaseManager { + private rdbStore: dataRdb.RdbStore | undefined = undefined; + + /** + * 获取关系数据库存储对象 + * 如果已初始化过,则直接返回之前获取的对象 + * 如果没有初始化过,则使用接口获取对象,并执行数据库建表命令,如果表未创建,则会创建一张新的表 + * + * @param createTableSql 创建新表使用的 SQL + * @return 关系数据库存储对象 + */ + async getRdbStore(createTableSql: string): Promise { + if (this.rdbStore) { + return this.rdbStore; + } + try { + this.rdbStore = await dataRdb.getRdbStore(GlobalContext.getContext() + .getObject('clockContext') as Context, STORE_CONFIG); + + let noNewColumn = await this.hasNoNewColumns(); + if (noNewColumn && IS_FIRST_OTA) { + LogUtil.info(TAG, 'noNewColumn: ' + noNewColumn) + IS_FIRST_OTA = false; + let allOldAlarms: AlarmInfo[] = await AlarmManager.getAllAlarms(); + LogUtil.info(TAG, 'allOldAlarms:' + JSON.stringify(allOldAlarms)); + + const SQL_DROP_TABLE = `DROP TABLE IF EXISTS ${DATA_TABLE}`; + await this.rdbStore.executeSql(SQL_DROP_TABLE); + LogUtil.info(TAG, 'SQL_DROP_TABLE'); + this.rdbStore.commit(); + + await this.rdbStore.executeSql(createTableSql); + this.rdbStore.commit(); + LogUtil.info(TAG, 'createTableSql'); + + if (allOldAlarms.length > 0) { + LogUtil.info(TAG, 'being ota insert'); + this.rdbStore.beginTransaction(); + allOldAlarms.map(async item => { + if (item.daysOfWeek.length > 0) { + // WAKE_DAYS = 4, USER_DEFINED = 3, ONES = 0 + item.daysOfWakeType = 3; + } + if (this.rdbStore) { + await this.rdbStore.insert(DATA_TABLE, AlarmManager.getDbBucketFromAlarm(item)); + this.rdbStore.commit(); + LogUtil.info(TAG, 'do insert, item=' + JSON.stringify(item) + ', after=' + JSON.stringify(AlarmManager.getDbBucketFromAlarm(item))); + } + }); + + await AlarmManager.updateTimer(); + LogUtil.info(TAG, 'updateTimer end'); + } + } else { + await this.rdbStore.executeSql(createTableSql); + } + LogUtil.info(TAG, 'Get RdbStore successfully.'); + } catch (error) { + LogUtil.error(TAG, 'Get RdbStore failed, err:', (error as BusinessError).message); + } + return this.rdbStore as dataRdb.RdbStore; + } + + async hasNoNewColumns(): Promise { + if (!this.rdbStore) { + return false; + } + const predicates = new dataRdb.RdbPredicates(DATA_TABLE); + let resultSet: dataRdb.ResultSet = await this.rdbStore.query(predicates); + let columnNames = JSON.stringify(resultSet.columnNames); + LogUtil.info(TAG, 'resultSet:' + JSON.stringify(resultSet) + ', columns:' + JSON.stringify(resultSet.columnNames)); + + if (columnNames && columnNames !== '[]' && (columnNames.indexOf('DAYS_OF_WAKE_TYPE') === -1 || + columnNames.indexOf('ALARM_TIME_INTENT') === -1 || columnNames.indexOf('RESTART') === -1)) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/common/src/main/ets/manager/FormManager.ets b/common/src/main/ets/manager/FormManager.ets new file mode 100644 index 0000000..31534bf --- /dev/null +++ b/common/src/main/ets/manager/FormManager.ets @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import preferencesUtil from '@ohos.data.preferences'; +import { FormInfo, FROM_DATA_STORE } from './types'; +import { GlobalContext, LogUtil } from '../utils'; +import { TsUtilManager } from './TsUtilManager'; + +type Preferences = preferencesUtil.Preferences; +const TAG: string = 'FormManager'; + +/** + * FormManager + * + * @since 2023-06-08 + */ +class FormManager { + private preferences: Preferences | undefined = undefined; + + /** + * Get FormManager Preferences instance. + * + * @return Preferences instance + */ + private async getPreferences(): Promise { + preferencesUtil.removePreferencesFromCache(GlobalContext.getContext() + .getObject('clockContext') as Context, FROM_DATA_STORE); + this.preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, FROM_DATA_STORE); + return this.preferences as Preferences; + } + + /** + * 从 sp 中获取所有卡片信息 + * + * @return 卡片信息数组 + */ + public async fetchAllFormsFromSp(): Promise { + const preferences = await this.getPreferences(); + let formList: FormInfo[] = []; + let obj: Object = await preferences.getAll(); + formList = TsUtilManager.transferObjectToFormInfo(obj); + return formList; + } + + /** + * get FromInfo from Sp ById + * + * @return FromInfo + */ + public async getFromInfoSpById(fromId: string): Promise { + try { + const preferences = await this.getPreferences(); + let formInfoString = await preferences.get(fromId, ''); + if (String(formInfoString) !== '') { + const formInfo: FormInfo = JSON.parse(String(formInfoString)); + return formInfo; + } else { + return undefined; + } + } catch (error) { + LogUtil.error(TAG, 'getFromNameSpById error:' + JSON.stringify(error)); + } + return undefined; + } + + /** + * save formInfo to preferences + * + * @param formInfo 被保存的卡片信息 + */ + public async saveFormToSp(formInfo: FormInfo): Promise { + const preferences = await this.getPreferences(); + LogUtil.info(TAG, 'saveFormToSp formInfo:' + JSON.stringify(formInfo)); + await preferences.put(formInfo.formId, JSON.stringify(formInfo)); + await preferences.flush(); + } + + /** + * delete formInfo to preferences + * + * @param formId 删除的卡片信息ID + */ + public async deleteFormToSp(formId: string): Promise { + const preferences = await this.getPreferences(); + LogUtil.info(TAG, 'deleteFormToSp formId:' + formId); + await preferences.delete(formId); + await preferences.flush(); + } +} + +export default new FormManager(); \ No newline at end of file diff --git a/common/src/main/ets/manager/PageStateManager.ets b/common/src/main/ets/manager/PageStateManager.ets new file mode 100644 index 0000000..f978ad2 --- /dev/null +++ b/common/src/main/ets/manager/PageStateManager.ets @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import preferencesUtil from '@ohos.data.preferences'; +import { PAGE_STATE_STORE } from './types'; +import { GlobalContext, LogUtil } from '../utils'; + +type Preferences = preferencesUtil.Preferences; +const tabIndexKey: string = 'tabIndex'; +const showTextClockKey: string = 'showTextClock'; +const showHandsClockKey: string = 'showHandsClock'; +const TAG: string = 'PageStateManager'; + +class PageStateManager { + private preferences: Preferences | undefined = undefined; + + /** + * Get PageStateManager Preferences instance. + * + * @return Preferences instance + */ + private async getPreferences(): Promise { + if (this.preferences) { + return this.preferences; + } + this.preferences = await preferencesUtil.getPreferences((GlobalContext.getContext() + .getObject('clockContext') as Context), PAGE_STATE_STORE); + return this.preferences as Preferences; + } + + private getPreferencesSync(): Preferences { + if (this.preferences) { + return this.preferences; + } + this.preferences = preferencesUtil.getPreferencesSync((GlobalContext.getContext() + .getObject('clockContext') as Context), { name: PAGE_STATE_STORE }); + return this.preferences as Preferences; + } + + /** + * Obtains tab index. + * + * @return tabIndex + */ + async getTabIndex(): Promise { + const preferences = await this.getPreferences(); + const tabIndex = await preferences.get(tabIndexKey, 0); + LogUtil.info(TAG, 'get tabIndex from Sp:' + tabIndex); + return Number(tabIndex); + } + + /** + * save tab Index to preferences + * + * @param tabIndex current tabIndex + */ + public async saveTabIndexToSp(tabIndex: number): Promise { + const preferences = await this.getPreferences(); + LogUtil.info(TAG, 'save tabIndex toSp:' + tabIndex); + await preferences.put(tabIndexKey, tabIndex); + await preferences.flush(); + } + + /** + * Obtains showTextClock value. + * + * @return showTextClock + */ + async getClockShowState(): Promise { + const preferences = await this.getPreferences(); + const showTextClock = await preferences.get(showTextClockKey, false); + LogUtil.info(TAG, 'get showTextClock from Sp:' + showTextClock); + return Boolean(showTextClock); + } + + getClockShowStateSync(): boolean { + const preferences = this.getPreferencesSync(); + const showTextClock = preferences.getSync(showTextClockKey, false); + LogUtil.info(TAG, 'get showTextClock from Sp:' + showTextClock); + return Boolean(showTextClock); + } + + /** + * Obtains showHandsClock value. + * + * @return showHandsClock + */ + async getClockHandsState(): Promise { + const preferences = await this.getPreferences(); + const showHandsClock = await preferences.get(showHandsClockKey, true); + LogUtil.info(TAG, 'get showHandsClock from Sp:' + showHandsClock); + return Boolean(showHandsClock); + } + + getClockHandsStateSync(): boolean { + // const preferences = this.getPreferencesSync(); + // const showHandsClock = preferences.getSync(showHandsClockKey, true); + // LogUtil.info(TAG, 'get showHandsClock from Sp:' + showHandsClock); + return true; + } + + /** + * save showTextClock to preferences + * + * @param showTextClock showTextClock value + */ + public async saveClockShowToSp(showTextClock: boolean): Promise { + const preferences = await this.getPreferences(); + LogUtil.info(TAG, 'save showTextClock toSp:' + showTextClock); + await preferences.put(showTextClockKey, showTextClock); + await preferences.flush(); + } + + /** + * save saveHandsShowToSp to preferences + * + * @param showHandsClock value + */ + public async saveHandsShowToSp(showHandsClock: boolean): Promise { + const preferences = await this.getPreferences(); + LogUtil.info(TAG, 'save showHandsClock toSp:' + showHandsClock); + await preferences.put(showHandsClockKey, showHandsClock); + await preferences.flush(); + } + + public async changeTabsToIndex(tabIndex: number): Promise { + await this.saveTabIndexToSp(tabIndex); + const isTabChange = AppStorage.get('isTabChange'); + AppStorage.setOrCreate('isTabChange', isTabChange ? false : true); + LogUtil.info(TAG, 'changeTabsToIndex tabIndex:' + tabIndex); + } +} + +export default new PageStateManager(); diff --git a/common/src/main/ets/manager/ResourceAudioManager.ets b/common/src/main/ets/manager/ResourceAudioManager.ets new file mode 100644 index 0000000..bd64a54 --- /dev/null +++ b/common/src/main/ets/manager/ResourceAudioManager.ets @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import media from '@ohos.multimedia.media'; +import HashMap from '@ohos.util.HashMap'; +import fs from '@ohos.file.fs' +import audio from '@ohos.multimedia.audio'; +import { LogUtil } from '../utils'; +import resmgr from '@ohos.resourceManager'; +import { GlobalContext } from '../../../../src/main/ets/utils/index'; +import { BusinessError } from '@ohos.base'; + +const TAG: string = 'ResourceAudioManager'; + +/** + * Internal Audio Manager which works with audio resources + * + * @since 24-09-2023 + */ +interface FileDescriptor { + fd: string | number, + offset: string | number, + length: string | number +} + +const TICK_PLAYER = 'TICK_PLAYER'; + + +class ResourceAudioManager { + private players: HashMap = new HashMap(); + + async tickPlayerStateHandle(player: media.AVPlayer) { + if (!player) { + return; + } + player.on('stateChange', async (state) => { + switch (state) { + case 'initialized': { + player.prepare(); + break; + } + case 'prepared': { + player.loop = false; + player.play(); + break; + } + case 'completed': { + break; + } + case 'stopped': { + break; + } + + case 'playing': { + break; + } + case 'paused': { + break; + } + default: { + + } + } + }) + } + + async createTickPlayer(resourceFileName = ''): Promise { + let tickPlayer = await media.createAVPlayer(); + const resourceManager = (GlobalContext.getContext().getObject('resourceManager') as resmgr.ResourceManager); + const fileDescriptor = await resourceManager.getRawFd(resourceFileName); + let avFileDescriptor: media.AVFileDescriptor = { + fd: fileDescriptor.fd, + offset: fileDescriptor.offset, + length: fileDescriptor.length + }; + tickPlayer.fdSrc = avFileDescriptor; + return tickPlayer; + } + + async tickPlayerPlaying(resourceFileName = '') { + let tickPlayer = this.players.get(TICK_PLAYER); + if (tickPlayer) { + if (tickPlayer.state === 'completed') { + tickPlayer.play(); + } + return; + } + tickPlayer = await this.createTickPlayer(resourceFileName); + this.addPlayer(TICK_PLAYER, tickPlayer); + this.tickPlayerStateHandle(tickPlayer); + } + + async playOnce(resourceFileName = '') { + LogUtil.info(TAG, 'play once ' + resourceFileName, String(this.players.hasKey(resourceFileName))); + if (this.players.hasKey(resourceFileName)) { + let p = this.players.get(resourceFileName); + if (p) { + p.play(); + } + return; + } + let player = await this.createPlayer(resourceFileName, false); + this.addPlayer(resourceFileName, player); + + } + + async playLoop(resourceFileName: string) { + LogUtil.info(TAG, 'play loop 99' + resourceFileName); + let player = await this.createPlayer(resourceFileName, true); + LogUtil.info(TAG, 'play loop 99' + player, JSON.stringify(player)); + this.addPlayer(resourceFileName, player); + } + + async playFromFileOnce(filePath: string) { + LogUtil.info(TAG, 'play file at ' + filePath); + let player = await this.createFilePlayer(filePath, false); + this.addPlayer(filePath, player); + } + + async playFromFileLoop(filePath: string) { + LogUtil.info(TAG, 'play file at ' + filePath); + let player = await this.createFilePlayer(filePath, true); + this.addPlayer(filePath, player); + } + + private addPlayer(resourceFileName: string, player: media.AVPlayer) { + this.players.set(resourceFileName, player); + } + + private async createFilePlayer(filePath: string, loop: boolean): Promise { + let player = await media.createAVPlayer(); + + let fdPath = 'fd://'; + let file = await fs.open(filePath); + fdPath = fdPath + '' + file.fd; + LogUtil.info(TAG, 'opening file at ' + fdPath); + + this.setCallback(player, loop, filePath); + player.url = fdPath; + + return player; + } + + private async createPlayer(resourceFileName: string, loop: boolean): Promise { + let player = await media.createAVPlayer(); + + let fileDescriptor: media.AVFileDescriptor = {} as media.AVFileDescriptor; + await (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getRawFd(resourceFileName).then((value) => { + fileDescriptor = { fd: value.fd, offset: value.offset, length: value.length } + }) + + this.setCallback(player, loop, resourceFileName); + + player.fdSrc = fileDescriptor; + + return player; + } + + async pausePlayerFor(resourceFileName: string) { + let player = await media.createAVPlayer() + if (this.players.hasKey(resourceFileName)) { + player = this.players.get(resourceFileName); + } + if (player) { + player.pause(); + } + } + + async stopPlayerFor(filePath: string) { + let player: media.AVPlayer | undefined = undefined + + + if (this.players.hasKey(filePath)) { + player = this.players.get(filePath); + } + + + if (player) { + await this.stopPlayer(player); + this.players.remove(filePath); + + } + } + + async stopPlayer(player: media.AVPlayer) { + + if (player) { + + await player.stop(); + player.release(); + } + } + + release() { + this.players.forEach((p) => { + if (p) { + this.stopPlayer(p); + } + }) + this.players.clear(); + } + + setCallback(player: media.AVPlayer, loop: boolean, resourceFileName: string = '') { + player.on('stateChange', async (state) => { + LogUtil.info(TAG, 'setCallback stateChange:' + state); + switch (state) { + case 'initialized': { + let audioRendererInfo: audio.AudioRendererInfo = { + content: audio.ContentType.CONTENT_TYPE_UNKNOWN, + usage: audio.StreamUsage.STREAM_USAGE_ALARM, + rendererFlags: 0 + }; + player.audioRendererInfo = audioRendererInfo; + player.prepare(); + break; + } + case 'prepared': { + player.setVolume(1.0); + player.loop = loop; + player.play(); + break; + } + case 'completed': { + player.stop(); + break; + } + case 'stopped': { + player.release(); + break; + } + } + }); + player.on('error', async (error: BusinessError) => { + LogUtil.error(TAG, 'audio player error:' + JSON.stringify(error)); + }); + player.on('audioInterrupt', async (info: audio.InterruptEvent) => { + LogUtil.info(TAG, 'audioInterrupt:' + resourceFileName + ' ' + JSON.stringify(info)); + if (info.hintType === audio.InterruptHint.INTERRUPT_HINT_STOP) { + try { + this.stopPlayerFor(resourceFileName); + } catch (error) { + LogUtil.error(TAG, 'Failed to stopPlayerFor:' + JSON.stringify(error)); + } + } + }); + } +} + +export default new ResourceAudioManager(); \ No newline at end of file diff --git a/common/src/main/ets/manager/ResourceManager.ets b/common/src/main/ets/manager/ResourceManager.ets new file mode 100644 index 0000000..70c92ff --- /dev/null +++ b/common/src/main/ets/manager/ResourceManager.ets @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import i18n from '@ohos.i18n'; +import resmgr from '@ohos.resourceManager'; +import { BusinessError } from '@ohos.base'; +import { GlobalContext, LogUtil } from '../utils'; + +const TAG = 'ResourceManager'; + +/** + * 资源相关工具类 + * + * @author XiaHan + * @since 2022-07-06 + */ +class ResourceManager { + // 缓存资源信息,对于字符串资源,key 是 语言编码 + 资源 ID,value 是语言和资源对应的字符串 + private resourceCache: Map = new Map(); + + /** + * 读取字符串资源,放入缓存中 + * 与 getStringById 配合使用,通过提前加载字符串资源,做到在同步方法中使用字符串资源 + * + * @param resourceIds 需要预加载的字符串资源的 ID + */ + async preloadStringResources(resourceIds: number[]): Promise { + this.resourceCache = this.resourceCache || new Map(); + LogUtil.info(TAG, `resourceCache==> ${this.resourceCache}`); + for (const id of resourceIds) { + const stringKey = `${i18n.getSystemLocale()}_${id}`; + if (!this.resourceCache.has(stringKey)) { + await this.getStringByIdAsync(id); + } + } + } + + preloadStringResourcesSync(resourceIds: number[]): void { + this.resourceCache = this.resourceCache || new Map(); + for (const id of resourceIds) { + const stringKey = `${i18n.getSystemLocale()}_${id}`; + if (!this.resourceCache.has(stringKey)) { + this.getStringByIdSync(id); + } + } + } + + getStringByIdSync(id: number): string { + const stringKey = `${i18n.getSystemLocale()}_${id}`; + if (this.resourceCache.has(stringKey)) { + return this.getStringById(id); + } + let result = ''; + try { + result = (GlobalContext.getContext().getObject('resourceManager') as resmgr.ResourceManager).getStringSync(id); + const stringKey = `${i18n.getSystemLocale()}_${id}`; + this.resourceCache.set(stringKey, result); + LogUtil.info(TAG, 'getString successfully!'); + } catch (error) { + LogUtil.error(TAG, 'getString error:', (error as BusinessError).message); + } + return result; + } + + /** + * 获取字符串资源的同步方法 + * 使用前需要通过 preloadStringResources 预加载相关资源 + * + * @param id 字符串资源的 ID + * @return 资源 ID 对应的当前语言下的字符串 + */ + getStringById(id: number): string { + LogUtil.info(TAG, `getStringById ID==> ${id}`); + const stringKey = `${i18n.getSystemLocale()}_${id}`; + LogUtil.info(TAG, `stringKey==> ${stringKey}`); + return this.resourceCache.get(stringKey) as string; + } + + /** + * 获取字符串资源的异步方法 + * 如果缓存中存在对应资源,则直接返回,否则通过资源管理器获取相关资源,并存入缓存 + * + * @param id 字符串资源的 ID + * @return 资源 ID 对应的当前语言下的字符串 + */ + async getStringByIdAsync(id: number): Promise { + const stringKey = `${i18n.getSystemLocale()}_${id}`; + if (this.resourceCache.has(stringKey)) { + LogUtil.info(TAG, `getStringByIdAsync ID==> ${id}`); + return this.getStringById(id); + } + let result = ''; + try { + result = await (GlobalContext.getContext().getObject('resourceManager') as resmgr.ResourceManager).getString(id); + LogUtil.info(TAG, `stringKey=> ${stringKey}`, `result=> ${result}`); + this.resourceCache.set(stringKey, result); + LogUtil.info(TAG, 'getString successfully!'); + } catch (error) { + LogUtil.error(TAG, 'getString error:', (error as BusinessError).message); + } + LogUtil.info(TAG, `result===> ${result}`); + return result; + } + + /** + * 获取区分单复数的字符串资源的异步方法 + * 此处直接透传 resourceManager 的 getPluralString 方法 + * 暂不做任何【缓存处理】 和 对【同步引用方式】的适配 + * + * @param id 字符串资源的 ID + * @param num 数量 + * @return 资源 ID 和 对应数量,在当前语言下组合后的字符串 + */ + async getPluralStringAsync(id: number, num: number): Promise { + return await (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getPluralString(id, num); + } + + /** + * 获取string资源值 + * @param resource Resource资源类 + */ + getStringSync(resource: ResourceStr | undefined): string { + if (!resource) { + LogUtil.error(TAG, 'resource is null'); + return ''; + } + if (typeof resource === 'string') { + return resource; + } + let resourceManager = GlobalContext.getContext().getObject('resourceManager') as resmgr.ResourceManager; + if (!resourceManager) { + return ''; + } + return resourceManager.getStringSync(resource.id); + } +} + +export default new ResourceManager(); \ No newline at end of file diff --git a/common/src/main/ets/manager/SnoozeManager.ets b/common/src/main/ets/manager/SnoozeManager.ets new file mode 100644 index 0000000..e9bc770 --- /dev/null +++ b/common/src/main/ets/manager/SnoozeManager.ets @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import preferencesUtil from '@ohos.data.preferences'; +import { GlobalContext } from '../utils'; +import { + ALARM_CLOCK_SNOOZE_STORE, + ALERT_TIME_DEFAULT_DATA, + SNOOZE_COUNT_STEP, + SNOOZE_ID, + SNOOZE_SEPARATOR, +} from './types'; + +type Preferences = preferencesUtil.Preferences; + +/** + * Nap Management Tool Class + * + * @since 2022-10-27 + */ +class SnoozeManager { + private preferences: Preferences | undefined = undefined; + + /** + * Get SnoozeManager Preferences instance. + * + * @return Preferences instance + */ + private async getPreferences(): Promise { + if (this.preferences) { + return this.preferences; + } + this.preferences = await preferencesUtil.getPreferences((GlobalContext.getContext() + .getObject('clockContext') as Context), ALARM_CLOCK_SNOOZE_STORE); + return this.preferences as Preferences; + } + + /** + * Obtains the number of naps. + * + * @param alarmId alarm id + * @return number of naps + */ + async getSnoozedTimes(alarmId: string): Promise { + const preferences = await this.getPreferences(); + const snoozedTimes = await preferences.get(alarmId, 0); + return Number(snoozedTimes); + } + + /** + * The number of naps increases once. + * + * @param alarmId alarm id + */ + async increaseSnoozedTimes(alarmId: string): Promise { + const snoozedTimes = await this.getSnoozedTimes(alarmId); + const preferences = await this.getPreferences(); + await preferences.put(alarmId, snoozedTimes + SNOOZE_COUNT_STEP); + await preferences.flush(); + } + + /** + * Clear the number of naps + * + * @param alarmId alarm id + */ + async clearSnoozeTimes(alarmId: string): Promise { + const preferences = await this.getPreferences(); + await preferences.delete(alarmId); + await preferences.flush(); + } + + /** + * Save Next Nap Timestamp + * + * @param alarmId alarm id + * @param nextAlertTime next Nap Timestamp + */ + async saveSnoozedAlarmData(alarmId: string, nextAlertTime: number): Promise { + const preferences = await this.getPreferences(); + const snoozeIds = await preferences.get(SNOOZE_ID, ''); + let snoozeArray: string[] = []; + if (snoozeIds) { + snoozeArray = snoozeIds.toString().split(SNOOZE_SEPARATOR); + if (!snoozeArray.includes(alarmId)) { + snoozeArray.push(alarmId); + } + } else { + snoozeArray.push(alarmId); + } + await preferences.put(SNOOZE_ID, snoozeArray.join(SNOOZE_SEPARATOR)); + await preferences.put(`${SNOOZE_ID}${alarmId}`, nextAlertTime); + await preferences.flush(); + } + + /** + * Obtains the ids of all snooze alarms. + * + * @param snooze alarms ids + * @return all snoozed alarm ids + */ + async getSnoozedAlarmId(): Promise { + const preferences = await this.getPreferences(); + const snoozeIds = await preferences.get(SNOOZE_ID, ''); + return snoozeIds ? snoozeIds.toString().split(SNOOZE_SEPARATOR) : []; + } + + /** + * Delete the nap data + * + * @param alarmId alarm id + */ + async deleteSnoozedAlarmId(alarmId: string): Promise { + const snoozeIdsArray = await this.getSnoozedAlarmId(); + const newSnoozeIdsArray = snoozeIdsArray.filter(id => alarmId !== id); + const preferences = await this.getPreferences(); + await preferences.put(SNOOZE_ID, newSnoozeIdsArray.join(SNOOZE_SEPARATOR)); + await preferences.delete(SNOOZE_ID + alarmId); + await preferences.flush(); + } + + /** + * Obtains the next start time. + * + * @param alarmId alarm id + * @return next alert time + */ + async getNextAlertTime(alarmId: string): Promise { + const preferences = await this.getPreferences(); + const nextAlertTime = await preferences.get(SNOOZE_ID + alarmId, ALERT_TIME_DEFAULT_DATA); + return Number(nextAlertTime); + } +} + +export default new SnoozeManager(); \ No newline at end of file diff --git a/common/src/main/ets/manager/SoundPool.ets b/common/src/main/ets/manager/SoundPool.ets new file mode 100644 index 0000000..58ccace --- /dev/null +++ b/common/src/main/ets/manager/SoundPool.ets @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import audio from '@ohos.multimedia.audio'; +import media from '@ohos.multimedia.media'; +import { LogUtil, GlobalContext } from '../utils'; +import resmgr from '@ohos.resourceManager'; +import resourceManager from '@ohos.resourceManager'; +import common from '@ohos.app.ability.common'; +import { BusinessError } from '@ohos.base'; + +const TICK_FILE_NAME_TIME: string = 'timer_tick.ogg'; +const TICK_FILE_NAME_STOPWATCH: string = 'stopwatch_tick.ogg'; +const TICK_FILR_TIME_PICK: string = 'timepicker.ogg'; +const LAP_FILE_NAME_STOPWATCH: string = 'stopwatch_lap.wav'; +const TAG = 'soundPoolNew'; +let count = 0; +let playTime = 0; + +interface Resource { + page: string, + file: string, + soundId: number, + streamId: number +} + +interface PlayParameters { + loop: number, + rate: audio.AudioRendererRate.RENDER_RATE_NORMAL, + leftVolume: number, + rightVolume: number, + priority: number, + parallelPlayFlag: boolean +}; + +export interface IdItems { + soundId: number, + streamId: number +} + +export const resourceList: Resource[] = [ + { page: 'stopwatch', file: TICK_FILE_NAME_STOPWATCH, soundId: -1, streamId: -1 }, + { page: 'time', file: TICK_FILE_NAME_TIME, soundId: -1, streamId: -1 }, + { page: 'timePick', file: TICK_FILR_TIME_PICK, soundId: -1, streamId: -1 } +]; + +const idArr: IdItems[] = [ + { soundId: -1, streamId: -1 }, + { soundId: -1, streamId: -1 }, + { soundId: -1, streamId: -1 }, + { soundId: -1, streamId: -1 }, + { soundId: -1, streamId: -1 } +]; +const maxStreams: number = 8; + +class SoundPool { + public soundPool: media.SoundPool = {} as media.SoundPool; + private soundId: number = -1; + private streamId: number = -1; + private playTimerClear: number = -1; + private playStopwatchClear: number = -1; + private audioRendererInfo: audio.AudioRendererInfo = { + content: audio.ContentType.CONTENT_TYPE_RINGTONE, + usage: audio.StreamUsage.STREAM_USAGE_ALARM, + rendererFlags: 1, + }; + private playParams: PlayParameters = { + loop: -1, + rate: audio.AudioRendererRate.RENDER_RATE_NORMAL, + leftVolume: 1, + rightVolume: 1, + priority: 0, + parallelPlayFlag: true + }; + + async create(tips: string): Promise { + LogUtil.info(TAG, `create entry tips=${tips}`); + try { + this.soundPool = await media.createSoundPool(maxStreams, this.audioRendererInfo); + this.loadCallback(); + this.finishPlayCallback(); + this.setErrorCallback(); + LogUtil.info(TAG, `create success`); + } catch (error) { + LogUtil.error(TAG, `create fail. code: ${error.code}, message: ${error.message}`); + } + } + + async openResource(url: string): Promise { + const resourceManager = (GlobalContext.getContext().getObject('resourceManager') as resmgr.ResourceManager); + const fileDescriptor = await resourceManager.getRawFd(url); + return fileDescriptor; + } + + async load(fileDescriptor: resourceManager.RawFileDescriptor): Promise { + let soundId = await this.soundPool.load(fileDescriptor.fd, fileDescriptor.offset, fileDescriptor.length); + LogUtil.info(TAG, `load success`); + return soundId; + } + + async loadResource(step: number, cur: number): Promise { + LogUtil.info(TAG, `entry loadResource,step=${step},cur=${cur}`); + try { + const fileDescriptor1 = await this.openResource(resourceList[0].file); + resourceList[0].soundId = await this.load(fileDescriptor1); + const fileDescriptor2 = await this.openResource(resourceList[1].file); + resourceList[1].soundId = await this.load(fileDescriptor2); + const fileDescriptor3 = await this.openResource(resourceList[2].file); + resourceList[2].soundId = await this.load(fileDescriptor3); + await this.loadRepeat(); + } catch (error) { + LogUtil.error(TAG, `load fail. code: ${error.code}, message: ${error.message}`); + } + } + + async loadRepeat() { + try { + const FILDESCRIPTOR_LAP = await this.openResource(LAP_FILE_NAME_STOPWATCH); + idArr[0].soundId = await this.soundPool.load(FILDESCRIPTOR_LAP.fd, FILDESCRIPTOR_LAP.offset, + FILDESCRIPTOR_LAP.length); + idArr[1].soundId = await this.soundPool.load(FILDESCRIPTOR_LAP.fd, FILDESCRIPTOR_LAP.offset, + FILDESCRIPTOR_LAP.length); + idArr[2].soundId = await this.soundPool.load(FILDESCRIPTOR_LAP.fd, FILDESCRIPTOR_LAP.offset, + FILDESCRIPTOR_LAP.length); + idArr[3].soundId = await this.soundPool.load(FILDESCRIPTOR_LAP.fd, FILDESCRIPTOR_LAP.offset, + FILDESCRIPTOR_LAP.length); + idArr[4].soundId = await this.soundPool.load(FILDESCRIPTOR_LAP.fd, FILDESCRIPTOR_LAP.offset, + FILDESCRIPTOR_LAP.length); + } catch (error) { + LogUtil.error(TAG, `loadRepeat fail. code: ${error.code}, message: ${error.message}`); + } + } + + async playForRepeat(): Promise { + try { + const soundId = idArr[count].soundId; + LogUtil.info(TAG, 'play', String(count), String(soundId)); + this.soundPool.play(soundId, { + loop: 0, + rate: audio.AudioRendererRate.RENDER_RATE_NORMAL, + leftVolume: 1, + rightVolume: 1, + priority: 0 + }, (error: BusinessError, streamId: number) => { + if (error) { + console.info(`play sound Error: errCode is ${error.code}, errMessage is ${error.message}`) + } else { + // streamID = streamId; + console.info('play success soundid:' + streamId); + } + }); + count++; + if (count >= 5) { + count = 0; + } + LogUtil.info(TAG, 'play success'); + } catch (error) { + LogUtil.error(TAG, `playForRepeat fail. code: ${error.code}, message: ${error.message}`); + } + } + + playStopwatchForDelay() { + this.playStopwatchClear !== -1 && clearTimeout(this.playStopwatchClear); + this.playStopwatchClear = setTimeout(() => { + this.soundPool.play(resourceList[0].soundId, this.playParams).then((streamid) => { + resourceList[0].streamId = streamid; + }).catch((err: BusinessError) => { + LogUtil.info(TAG, `playStopwatchForDelay failed,code=${err.code},msg=${err.message}`); + }) + }, 200); + } + + playTimeForDelay() { + this.playTimerClear !== -1 && clearTimeout(this.playTimerClear); + this.playTimerClear = setTimeout(() => { + this.soundPool.play(resourceList[1].soundId, this.playParams).then((streamid) => { + resourceList[1].streamId = streamid; + }).catch((err: BusinessError) => { + LogUtil.info(TAG, `playTimeForDelay failed,code=${err.code},msg=${err.message}`); + }) + }, 200); + } + + async playSoundPool(page: string): Promise { + LogUtil.info(TAG, `playSoundPool entry`); + switch (page) { + case 'stopwatch': + try { + let streamId = await this.soundPool.play(resourceList[0].soundId, this.playParams); + resourceList[0].streamId = streamId; + LogUtil.info(TAG, `play success for stopwatch`); + } catch (error) { + LogUtil.error(TAG, `play failed for stopwatch,code=${error.code},msg=${error.message}`); + } + break; + case 'time': + try { + LogUtil.info(TAG, `playTime entry resourceList[1].soundId=${resourceList[1].soundId}`); + let streamId: number = await this.soundPool.play(resourceList[1].soundId, this.playParams); + resourceList[1].streamId = streamId + LogUtil.info(TAG, `play success for time`); + } catch (error) { + LogUtil.error(TAG, `play failed for time,code=${error.code},msg=${error.message}`); + } + break; + case 'timePick': + try { + this.soundPool.play(resourceList[2].soundId, { + loop: 0, + rate: audio.AudioRendererRate.RENDER_RATE_NORMAL, + leftVolume: 1, + rightVolume: 1, + priority: 0 + }, (error: BusinessError, streamId: number) => { + if (error) { + console.info(`play sound Error: errCode is ${error.code}, errMessage is ${error.message}`) + } else { + // streamID = streamId; + resourceList[2].streamId = streamId; + console.info('play success soundid:' + streamId); + } + }); + LogUtil.info(TAG, `play timePick success`); + } catch { + LogUtil.error(TAG, `play timePick failed`); + } + break; + default: + break; + } + } + + async stopForPageHide(page: string): Promise { + switch (page) { + case 'stopwatch': + try { + resourceList[0].streamId !== -1 && await this.soundPool.stop(resourceList[0].streamId); + this.setErrorCallback(); + if (this.playStopwatchClear !== -1) { + clearTimeout(this.playStopwatchClear); + this.playStopwatchClear = -1; + } + LogUtil.info(TAG, `stopForPageHide stopwatch success`); + } catch { + LogUtil.error(TAG, `stopForPageHide stopwatch failed`); + } + break; + case 'time': + try { + resourceList[1].streamId !== -1 && await this.soundPool.stop(resourceList[1].streamId); + this.setErrorCallback(); + if (this.playTimerClear !== -1) { + clearTimeout(this.playTimerClear); + this.playTimerClear = -1; + } + LogUtil.info(TAG, `stopForPageHide time success`); + } catch { + LogUtil.error(TAG, `stopForPageHide time failed`); + } + break; + case 'timePick': + try { + LogUtil.info(TAG, `stopForPageHide resourceList[2].streamId=${resourceList[2].streamId}`); + resourceList[2].streamId !== -1 && await this.soundPool.stop(resourceList[2].streamId); + this.setErrorCallback(); + LogUtil.info(TAG, `stopForPageHide timePick success`); + } catch { + LogUtil.error(TAG, `stopForPageHide timePick failed`); + } + break; + default: + break; + } + } + + async stopForRepeat(): Promise { + try { + idArr[0].streamId !== -1 && await this.soundPool.stop(idArr[0].streamId); + idArr[1].streamId !== -1 && await this.soundPool.stop(idArr[1].streamId); + idArr[2].streamId !== -1 && await this.soundPool.stop(idArr[2].streamId); + idArr[3].streamId !== -1 && await this.soundPool.stop(idArr[3].streamId); + idArr[4].streamId !== -1 && await this.soundPool.stop(idArr[4].streamId); + } catch { + LogUtil.info(TAG, `stopForRepeat failed`); + } + } + + async stopAllSoundPool(): Promise { + try { + resourceList[0].streamId !== -1 && await this.soundPool.stop(resourceList[0].streamId); + resourceList[1].streamId !== -1 && await this.soundPool.stop(resourceList[1].streamId); + resourceList[2].streamId !== -1 && await this.soundPool.stop(resourceList[2].streamId); + if (this.playStopwatchClear !== -1) { + clearTimeout(this.playStopwatchClear); + this.playStopwatchClear = -1; + } + if (this.playTimerClear !== -1) { + clearTimeout(this.playTimerClear); + this.playTimerClear = -1; + } + } catch { + LogUtil.info(TAG, `stop all failed`); + } + } + + async unloadForRepeat(): Promise { + try { + idArr[0].soundId != -1 && idArr[0].streamId != -1 && await this.soundPool.unload(idArr[0].soundId); + idArr[1].soundId != -1 && idArr[1].streamId != -1 && await this.soundPool.unload(idArr[1].soundId); + idArr[2].soundId != -1 && idArr[2].streamId != -1 && await this.soundPool.unload(idArr[2].soundId); + idArr[3].soundId != -1 && idArr[3].streamId != -1 && await this.soundPool.unload(idArr[3].soundId); + idArr[4].soundId != -1 && idArr[4].streamId != -1 && await this.soundPool.unload(idArr[4].soundId); + LogUtil.info(TAG, `unloadForRepeat success`); + } catch (err) { + LogUtil.error(TAG, `unloadForRepeat failed,code=${err.code},msg=${err.message}`); + } + } + + async unloadAllSoundPool(): Promise { + try { + resourceList[0].soundId != -1 && resourceList[0].streamId != -1 && + await this.soundPool.unload(resourceList[0].soundId); + LogUtil.info(TAG, `unload stopwatch success`); + } catch (err) { + LogUtil.error(TAG, `unload stopwatch failed,code=${err.code},msg=${err.message}`); + } + try { + resourceList[1].soundId != -1 && resourceList[1].streamId != -1 && + await this.soundPool.unload(resourceList[1].soundId); + LogUtil.info(TAG, `unload time success`); + } catch (err) { + LogUtil.error(TAG, `unload time failed,code=${err.code},msg=${err.message}`); + } + try { + resourceList[2].soundId != -1 && resourceList[2].streamId != -1 && + await this.soundPool.unload(resourceList[2].soundId); + LogUtil.info(TAG, `unload timePick success`); + } catch (err) { + LogUtil.error(TAG, `unload timePick failed,code=${err.code},msg=${err.message}`); + } + } + + async releaseAll(): Promise { + try { + await this.stopAllSoundPool(); + await this.stopForRepeat(); + await this.unloadAllSoundPool(); + await this.unloadForRepeat(); + LogUtil.info(TAG, `unloadAll success`); + this.setOffCallback(); + } catch (err) { + LogUtil.info(TAG, `unloadAll all failed,code=${err.code},msg=${err.message}`); + } + } + + async release(): Promise { + this.soundPool.stop(this.streamId); + await this.soundPool.unload(this.soundId); + this.setOffCallback(); + await this.soundPool.release(); + } + + async setPlayParams(streamId: number, loop: number, volume?: number) { + // 设置循环播放次数 + this.soundPool && await this.soundPool.setLoop(streamId, loop); // 循环 + // 设置对应流的优先级 + this.soundPool && await this.soundPool.setPriority(streamId, 0); + // 设置播放倍速 + this.soundPool && await this.soundPool.setRate(streamId, audio.AudioRendererRate.RENDER_RATE_NORMAL); // 半倍速播放 + // 设置音量 + this.soundPool && await this.soundPool.setVolume(streamId, volume || 1, volume || 1); + } + + //关闭监听 + setOffCallback() { + this.soundPool.off('loadComplete'); + this.soundPool.off('playFinished'); + this.soundPool.off('error'); + } + + async loadCallback(): Promise { + // 加载完成回调 + this.soundPool.on('loadComplete', (soundId_: number) => { + LogUtil.info(TAG, `loadComplete message`); + }); + } + + //设置播放完成监听 + async finishPlayCallback(): Promise { + // 播放完成回调 + this.soundPool.on('playFinished', async () => { + LogUtil.info(TAG, `recive play finished message`); + }); + } + + //设置错误类型监听 + setErrorCallback(): void { + this.soundPool.on('error', (error) => { + LogUtil.info(TAG, `error happened,message is: ${error.message},code=${error.code},name=${error.name}`); + }); + } +} + +export default new SoundPool(); \ No newline at end of file diff --git a/common/src/main/ets/manager/TimerManager.ets b/common/src/main/ets/manager/TimerManager.ets new file mode 100644 index 0000000..dfaf047 --- /dev/null +++ b/common/src/main/ets/manager/TimerManager.ets @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// import systemTimer from '@ohos.systemTimer'; +import preferencesUtil from '@ohos.data.preferences'; +import { AlarmServiceType, GlobalContext, LogUtil, WantAgentUtil } from '../utils'; +import { + ALARM_CLOCK_TIMER_ID, + ALARM_CLOCK_TIMER_STORE, + ALARM_CLOCK_TRIGGER_TIME, + ALARM_CLOCK_TRIGGER_TIME_ID, + AlarmInfo, + NO_TIMER, + TRIGGER_TIME_NEVER, +} from './types'; +import { BusinessError } from '@ohos.base'; +import { ValueType } from '@ohos.data.ValuesBucket'; + +const TAG = 'TimerManager'; + +type Preferences = preferencesUtil.Preferences; + +/** + * 定时器管理工具类 + * + * @since 2022-07-06 + */ +class TimerManager { + private preferences: Preferences | undefined = undefined; // 用于持久化计时器数据的轻量级存储对象 + + /** + * 获取操作内部存储的对象 + * + * @return 操作内部存储的对象 + */ + async getPreferences(): Promise { + if (this.preferences) { + return this.preferences; + } + this.preferences = await preferencesUtil.getPreferences((GlobalContext.getContext() + .getObject('clockContext') as Context), ALARM_CLOCK_TIMER_STORE); + return this.preferences as Preferences; + } + + /** + * 获取当前生效的定时器的 ID + * + * @return 当前生效的定时器的 ID + */ + async getTimerId(): Promise { + const preferences = await this.getPreferences(); + const timerIdFromStorage = await preferences.get(ALARM_CLOCK_TIMER_ID, NO_TIMER); + return Number(timerIdFromStorage); + } + + /** + * 获取当前生效的定时器的触发时间 + * + * @return 当前生效的定时器的触发时间 + */ + async getTriggerTime(): Promise { + const preferences = await this.getPreferences(); + const triggerTimeFromStorage = await preferences.get(ALARM_CLOCK_TRIGGER_TIME, TRIGGER_TIME_NEVER); + return Number(triggerTimeFromStorage); + } + + /** + * 获取当前生效的定时器的触发时间 + * + * @return 当前生效的定时器的触发时间 + */ + async getTriggerTimeID(): Promise { + const preferences = await this.getPreferences(); + const triggerTimeIdFromStorage = await preferences.get(ALARM_CLOCK_TRIGGER_TIME_ID, TRIGGER_TIME_NEVER); + return Number(triggerTimeIdFromStorage); + } + + /** + * 发起闹钟计时 + */ + async startAlarm(alarmInfo: AlarmInfo): Promise { + try { + await this.stopAlarm(); // 拉起 timer 前,先停止之前的 timer(如果有) + const timerId: number = 0; + LogUtil.info(TAG, 'timerId:' + timerId + ' alarmId:' + alarmInfo.id); + // LogUtil.info(TAG, 'systemTimer alarmTime:' + alarmInfo.alarmTime); + // await systemTimer.startTimer(timerId, alarmInfo.alarmTime as number); + + + // 将 timerId 和 triggerTime 放入内部存储中,持久化相关字段 + const preferences = await this.getPreferences(); + await preferences.put(ALARM_CLOCK_TIMER_ID, timerId); + await preferences.put(ALARM_CLOCK_TRIGGER_TIME, alarmInfo.alarmTime as ValueType); + await preferences.put(ALARM_CLOCK_TRIGGER_TIME_ID, alarmInfo.id); + await preferences.flush(); + } catch (error) { + LogUtil.error(TAG, 'Start timer failed: ', (error as BusinessError).message); + } + } + + /** + * 发起闹钟计时 + */ + async startRepeatAlarm(alarmInfo: AlarmInfo): Promise { + try { + await this.stopAlarm(); // 拉起 timer 前,先停止之前的 timer(如果有) + const timerId: number = 0; + const alarmTime = new Date(); + alarmTime.setHours(alarmInfo.hour); + alarmTime.setMinutes(alarmInfo.minute); + alarmTime.setSeconds(0); + alarmTime.setMilliseconds(0); + alarmInfo.alarmTime = alarmTime.getTime(); + LogUtil.info(TAG, 'repeatTimerId:' + timerId + ' alarmId:' + alarmInfo.id + + 'repeatSystemTimer alarmTime:' + alarmInfo.alarmTime); + // await systemTimer.startTimer(timerId, alarmInfo.alarmTime as number); + + // 将 timerId 和 triggerTime 放入内部存储中,持久化相关字段 + const preferences = await this.getPreferences(); + await preferences.put(ALARM_CLOCK_TIMER_ID, timerId); + await preferences.put(ALARM_CLOCK_TRIGGER_TIME, alarmInfo.alarmTime as ValueType); + await preferences.put(ALARM_CLOCK_TRIGGER_TIME_ID, alarmInfo.id); + await preferences.flush(); + } catch (error) { + LogUtil.error(TAG, 'Start timer failed: ', (error as BusinessError).message); + } + } + + /** + * 停止闹钟计时 + */ + async stopAlarm(): Promise { + const timerId = await this.getTimerId(); + if (!timerId || timerId === NO_TIMER) { + return; + } + try { + const preferences = await this.getPreferences(); + await preferences.delete(ALARM_CLOCK_TRIGGER_TIME); + await preferences.delete(ALARM_CLOCK_TRIGGER_TIME_ID); + await preferences.delete(ALARM_CLOCK_TIMER_ID); + await preferences.flush(); + // await systemTimer.stopTimer(timerId); + LogUtil.info(TAG, 'Stop timerId: ' + timerId); + } catch (error) { + LogUtil.error(TAG, 'Stop timer failed: ', (error as BusinessError).message); + } + } +} + +export default new TimerManager(); \ No newline at end of file diff --git a/common/src/main/ets/manager/TimerStateManager.ets b/common/src/main/ets/manager/TimerStateManager.ets new file mode 100644 index 0000000..eec7897 --- /dev/null +++ b/common/src/main/ets/manager/TimerStateManager.ets @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import preferencesUtil from '@ohos.data.preferences'; +import { LogUtil } from '../utils'; +import { GlobalContext } from '../utils'; +import { APP_CONFIG, STATE_FIRING, STATE_SNOOZE, STATE_IDLE, STATE_STOP, DEFAULT_ALARM_ID, } from './types'; + +const KEY_TIMER_STATE = 'timerOut_state'; + +const KEY_TIMER_STATE_ID = 'timerOut_state_id'; + +const TAG = 'timerStateManager'; + +type Preferences = preferencesUtil.Preferences; + +class TimerStateManager { + private preferences: Preferences | undefined = undefined; + + private async getPreferences(): Promise { + if (this.preferences) { + return this.preferences; + } + this.preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, APP_CONFIG); + return this.preferences as Preferences; + } + + private async setState(currentState: number, alarmId: string): Promise { + const preferences = await this.getPreferences(); + await preferences.put(KEY_TIMER_STATE, currentState); + if (currentState === STATE_FIRING) { + await preferences.put(KEY_TIMER_STATE_ID, alarmId); + } + await preferences.flush(); + } + + async getState(): Promise { + const preferences = await this.getPreferences(); + const alarmState = await preferences.get(KEY_TIMER_STATE, STATE_IDLE); + return Number(alarmState); + } + + async getAlarmId(): Promise { + const preferences = await this.getPreferences(); + const alarmState = await preferences.get(KEY_TIMER_STATE_ID, DEFAULT_ALARM_ID); + return Number(alarmState); + } + + async setStateFiring(alarmId: string): Promise { + if (!alarmId) { + LogUtil.error(TAG, 'Execute method setStateFiring failed, params is incorrect.'); + return; + } + await this.setState(STATE_FIRING, alarmId); + } + + async setStateStop(alarmId: string): Promise { + if (!alarmId) { + LogUtil.error(TAG, 'Execute method setStateStop failed, params is incorrect.'); + return; + } + await this.setState(STATE_STOP, alarmId); + } + + async isFiring(): Promise { + const currentState = await this.getState(); + return currentState === STATE_FIRING; + } +} + +export default new TimerStateManager(); \ No newline at end of file diff --git a/common/src/main/ets/manager/TsUtilManager.ts b/common/src/main/ets/manager/TsUtilManager.ts new file mode 100644 index 0000000..d05805f --- /dev/null +++ b/common/src/main/ets/manager/TsUtilManager.ts @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +interface FormInfo { + formId: string; + formName: string; + parameters?: string[]; +} + +/** + * TsUtil + * + * @since 2023-09-21 + */ +export class TsUtilManager { + /** + * 将object类型的数据转化为fromInfo类型 + * + * @return fromInfo[] + */ + public static transferObjectToFormInfo(data): FormInfo[] { + let result: FormInfo[] = []; + Object.keys(data).forEach((value, index) => { + let formInfo = JSON.parse(data[value]); + result.push(formInfo); + }); + return result; + } + + public static hasItsProperty(data, value): boolean { + if (data && value) { + return Object.prototype.hasOwnProperty.call(data, value); + } else { + return false; + } + } + + public static getItsProperty(data, value): string { + if (TsUtilManager.hasItsProperty(data, value)) { + return data[value]; + } else { + return ''; + } + } +} + +export default new TsUtilManager(); diff --git a/common/src/main/ets/manager/index.ets b/common/src/main/ets/manager/index.ets new file mode 100644 index 0000000..86513b0 --- /dev/null +++ b/common/src/main/ets/manager/index.ets @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ResourceManager from './ResourceManager'; +import AlarmManager from './AlarmManager'; +import TimerManager from './TimerManager'; +import AlarmStateManager from './AlarmStateManager'; +import DatabaseManager from './DatabaseManager'; +import BreakpointManager from './BreakpointManager'; +import FormManager from './FormManager'; +import PageStateManager from './PageStateManager'; +import ResourceAudioManager from './ResourceAudioManager'; +import SoundPool from './SoundPool'; +import TimerStateManager from './TimerStateManager' +import TimerSystemTimer from './timerSystemTimer'; + +export { + ResourceManager, + AlarmManager, + TimerManager, + AlarmStateManager, + TimerStateManager, + DatabaseManager, + BreakpointManager, + FormManager, + PageStateManager, + TimerSystemTimer, + ResourceAudioManager, + SoundPool +}; + +export { STORE_CONFIG, + RDB_VERSION, + FormInfo, + AlarmInfo, + AlarmInfoToShow, + AlarmInfoProperty, + DATA_TABLE, + SQL_CREATE_TABLE, + ENABLED_TRUE, + ENABLED_FALSE, + ALARM_TIME_NEVER, + NEAREST_ALARM_COUNT, + TRIGGER_TIME_NEVER, + SNOOZE_COUNT_STEP, + INIT_SNOOZE_COUNT, + ALARM_CLOCK_TIMER_STORE, + ALARM_CLOCK_TIMER_ID, + ALARM_CLOCK_TRIGGER_TIME, + ALARM_CLOCK_TRIGGER_TIME_ID, + ALARM_CLOCK_SNOOZE_STORE, + PAGE_STATE_STORE, + CLOCK_SHOW_STATE_STORE, + FROM_DATA_STORE, + STOP_WATCH_DATA_STORE, + TIMER_DATA_STORE, + SNOOZE_ID, + SNOOZE_SEPARATOR, + NO_TIMER, + APP_CONFIG, + IS_FIRST_ENTER, + PRESET_ALARMS, + PRESET_ALARMS_TITLE, + EVENT_ID_ALARM_RING, + EVENT_ID_CHANGE_ALARM, + EVENT_ID_DELETE_ALARM, + EVENT_ID_FINISH_ABILITY, + EVENT_ID_CHANGE_WORLD_CLOCK, + EVENT_ID_EDIT_WORLD_CLOCK, + ALERT_TIME_DEFAULT_DATA, + DEFAULT_ALARM_ID, + KEY_ALARM_RING_TIME, + PROMPT_TIME, + ALARM_NAME_INPUT_LIMIT, + ALARM_NAME_INPUT_MIN, + STATE_FIRING, + STATE_SNOOZE, + STATE_STOP, + STATE_IDLE, + BreakPoint, + MatchRule, + MIN_LG_WIDTH, + MIN_MD_WIDTH, + COMMON_GRID_COL_OPTIONS, + BreakPointListener, + ALARM_LAST_SELECTED_RING, + TIMER_SELECTED_RING, + EVENT_ID_TIMEPICK_FORPC, + EVENT_ID_FULL_SCREEN_ALARM, + EVENT_ID_BANNER_ALARM, + EVENT_ID_KILL_ALARM_TIMEOUT_MESSAGE, + EVENT_ID_TIMER_FULL_SCREEN_CLOSE, + EVENT_ID_WORLDCLOCK_FORPC, + TIME_PAUSE_OR_CONTINUE_ID, + TIMEOUT_FULL_SCREEN_ID, + TIMEOUT_FULL_SCREEN_CLOSE_ID, + TIMEOUT_FULL_SCREEN_REPATE_ID, + TIMEOUT_FLAG_ID, + FULLSCREEN_SLIPUP_ID, + TIMER_Toggle_FRONT_AND_BACKEND, + EVENT_ID_SEND_ALARM, + TIMER_PREFERENCES_CLOCK, + START_DATE_TIMER_PREFERENCES_CLOCK, + LEFT_TIME_TIMER_PREFERENCES_CLOCK, + TOTAL_TIME_TIMER_PREFERENCES_CLOCK, + PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK, +} from './types'; + +export { TsUtilManager } from './TsUtilManager' \ No newline at end of file diff --git a/common/src/main/ets/manager/timerSystemTimer.ets b/common/src/main/ets/manager/timerSystemTimer.ets new file mode 100644 index 0000000..5d13736 --- /dev/null +++ b/common/src/main/ets/manager/timerSystemTimer.ets @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogUtil, WantAgentUtil, TimerServiceType, TIMER_NOTICE_ID } from '../utils'; +// import systemTimer from '@ohos.systemTimer'; +import { BusinessError } from '@ohos.base'; + +const TAG = 'SystemTimerManager'; +const DELAY_TIME = 300 + +class TimerSystemTimer { + private timerId: number = -1; + + async create(delay: number) { + try { + // const _options: systemTimer.TimerOptions = { + // type: systemTimer.TIMER_TYPE_EXACT | systemTimer.TIMER_TYPE_WAKEUP, + // repeat: false, + // wantAgent: await WantAgentUtil.timerWantAgent(TimerServiceType.Firing, TIMER_NOTICE_ID, 0) + // } + const _delay: number = Date.now() + delay + DELAY_TIME; + this.destory(); + // await systemTimer.createTimer(_options).then((timerId: number) => { + // LogUtil.info(TAG, `SystemTimer Create Success: ${timerId}`); + // this.timerId = timerId; + // this.start(_delay); + // }).catch((error: BusinessError) => { + // LogUtil.info(TAG, `SystemTimer Create Fail: ${error.message}, code: ${error.code}`); + // }); + } catch (error) { + LogUtil.info(TAG, `SystemTimer Create Anomalies: ${error.message}, code: ${error.code}`); + } + } + + async start(_delay: number) { + try { + // await systemTimer.startTimer(this.timerId, _delay).then(() => { + // LogUtil.info(TAG, `SystemTimer Start Success`); + // }).catch((error: BusinessError) => { + // LogUtil.info(TAG, `SystemTimer Start Fail: ${error.message}, code: ${error.code}`); + // }); + } catch (error) { + LogUtil.info(TAG, `SystemTimer Start Anomalies: ${error.message}, code: ${error.code}`); + } + } + + async destory() { + try { + if (this.timerId > -1) { + // await systemTimer.destroyTimer(this.timerId).then(() => { + // LogUtil.info(TAG, `SystemTimer Destroy Success`); + // this.timerId = -1; + // }).catch((error: BusinessError) => { + // LogUtil.info(TAG, `SystemTimer Destroy Fail: ${error.message}, code: ${error.code}`); + // }); + } else { + return; + } + } catch (error) { + LogUtil.info(TAG, `SystemTimer Destroy Anomalies: ${error.message}, code: ${error.code}`); + } + + } +} + +export default new TimerSystemTimer(); + + diff --git a/common/src/main/ets/manager/types.ets b/common/src/main/ets/manager/types.ets new file mode 100644 index 0000000..5a30df6 --- /dev/null +++ b/common/src/main/ets/manager/types.ets @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import mediaQuery from '@ohos.mediaquery'; +import rdb from '@ohos.data.relationalStore'; + +import { FormatTime } from '../utils'; + +// RDB 数据库的初始化配置 +export const STORE_CONFIG: rdb.StoreConfig = { name: 'Clock.db', securityLevel: 1 }; + +// RDB 数据库的版本 +export const RDB_VERSION: number = 3; + +export interface FormInfo { + formId: string; + formName: string; + parameters?: string[]; +} + +// 闹钟信息类型定义 +export interface AlarmInfo { + id?: string, + title: string, + hour: number, + minute: number, + second?: number, + reStart?: string, + daysOfWeek: number[], // 设置每个星期哪一天重复提醒,范围从 1 到 7 + daysOfWakeType?: number, //设置单次或者法定工作日 + alarmTime?: number, // 最近一次响铃时间,使用 Date 的 getTime 方法获取 + enabled: boolean, // 是否打开 + ringPath?: string, + ringDuration: number, // 响铃时间 + snoozeDuration: number, // 再响间隔 + snoozeTimes: number, // 再响次数 + snoozeCount?: number, // 某次响铃中,已再响的次数,与 snoozeTimes 相等时,不再重新响铃 + alarmTimeIntent?: number //意图框架模式下的最近一次响铃时间 时间戳 +} + +// 用于前台展示的闹钟对象,在 AlarmInfo 的基础上,加入 时间段标签、组装后的时间 和 每周重复时间的文字版本 +export interface AlarmInfoToShow extends AlarmInfo, FormatTime { + daysOfWeekDesc?: string, // 每周重复时间的文字版本,如 "周一 周二 周六" +} + +// 用于表单展示的闹钟对象,在 AlarmInfo 的基础上,加入 用于表单展示再响设置的文字描述 和 用于表单展示响铃时长的文字描述 +export interface AlarmInfoProperty extends AlarmInfo { + snoozeDesc?: string, // 用于表单展示再响设置的文字描述 + ringDurationDesc?: string, // 用于表单展示响铃时长的文字描述 +} + +// 数据库表名 +export const ALARM_CLOCK_DATA_TABLE = 'ALARM_CLOCK'; + +export const WORLD_CLOCK_DATA_TABLE = 'WORLD_CLOCK'; + +// 数据库表名 +export const DATA_TABLE = 'ALARM_CLOCK'; + +// 数据库的建表 SQL +export const SQL_CREATE_TABLE = ` + CREATE TABLE IF NOT EXISTS ${DATA_TABLE} ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + REMINDER_ID INTEGER, + HOUR INTEGER NOT NULL, + MINUTE INTEGER NOT NULL, + SECOND INTEGER NOT NULL, + ALARM_TIME INTERGER NOT NULL, + ENABLED INTEGER NOT NULL, + TITLE TEXT NOT NULL, + RING_DURATION INTEGER NOT NULL, + SNOOZE_DURATION INTEGER NOT NULL, + SNOOZE_TIMES INTEGER NOT NULL, + SNOOZE_COUNT INTEGER NOT NULL, + DAYS_OF_WEEK BLOB, + DAYS_OF_WAKE_TYPE INTEGER, + ALARM_TIME_INTENT INTEGER, + RESTART INTEGER + ) +`; + +// 数据库的建表 SQL +export const CREATE_ALARM_CLOCK_TABLE_QUERY = ` + CREATE TABLE IF NOT EXISTS ${ALARM_CLOCK_DATA_TABLE} ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + REMINDER_ID INTEGER, + HOUR INTEGER NOT NULL, + MINUTE INTEGER NOT NULL, + SECOND INTEGER NOT NULL, + ALARM_TIME INTEGER NOT NULL, + ENABLED INTEGER NOT NULL, + TITLE TEXT NOT NULL, + RING_DURATION INTEGER NOT NULL, + SNOOZE_DURATION INTEGER NOT NULL, + SNOOZE_TIMES INTEGER NOT NULL, + SNOOZE_COUNT INTEGER NOT NULL, + DAYS_OF_WEEK BLOB, + ALARM_TIME_INTENT INTEGER, + RESTART TEXT + ) +`; + +export const CREATE_WORLD_CLOCK_TABLE_QUERY = ` + CREATE TABLE IF NOT EXISTS ${WORLD_CLOCK_DATA_TABLE} ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + SORT_ORDER INTEGER NOT NULL, + CITY_INDEX TEXT NOT NULL, + TIMEZONE TEXT NOT NULL, + CITY TEXT NOT NULL, + OFFSET INTEGER NOT NULL + );`; + +// ENABLED 字段在数据库中代表 true 和 false 的值 +export const ENABLED_TRUE = 1; + +export const ENABLED_FALSE = 0; + +// 闹钟不重复,且未激活时,其 ALARM_TIME 的字段值 +export const ALARM_TIME_NEVER = 0; + +// 查询最近闹钟的个数,通常只查一个,即最快被触发的闹钟 +export const NEAREST_ALARM_COUNT = 1; + +// 没有计时器在触发时,trigger time 的取值 +export const TRIGGER_TIME_NEVER = -1; + +// 再响次数计数器步长 +export const SNOOZE_COUNT_STEP = 1; + +// 再响次数计数器初始值 +export const INIT_SNOOZE_COUNT = 0; + +// 计时器使用的内部数据存储名称 +export const ALARM_CLOCK_TIMER_STORE = 'ALARM_CLOCK_TIMER_STORE'; + +// 计时器使用的内部数据存储中,TIMER ID 的键值 +export const ALARM_CLOCK_TIMER_ID = 'ALARM_CLOCK_TIMER_ID'; + +// 计时器使用的内部数据存储中,起闹时间 的键值 +export const ALARM_CLOCK_TRIGGER_TIME = 'ALARM_CLOCK_TRIGGER_TIME'; + +export const ALARM_CLOCK_TRIGGER_TIME_ID = 'ALARM_CLOCK_TRIGGER_TIME_ID'; + +// Nap-related data +export const ALARM_CLOCK_SNOOZE_STORE = 'ALARM_CLOCK_SNOOZE_STORE'; + +export const PAGE_STATE_STORE = 'PAGE_STATE_STORE'; + +export const CLOCK_SHOW_STATE_STORE = 'CLOCK_SHOW_STATE_STORE'; + +export const FROM_DATA_STORE = 'FROM_DATA_STORE'; + +export const STOP_WATCH_DATA_STORE = 'STOP_WATCH_DATA_STORE'; + +export const TIMER_DATA_STORE = 'TIMER_DATA_STORE'; + +// Snoozed alarm ids data key saved in sharePreference +export const SNOOZE_ID = 'SNOOZE_ID'; + +// Separator of the Snoozed ID +export const SNOOZE_SEPARATOR = ','; + +// 如果在内部存储中获取不到 TIMER ID,则返回这个值 +export const NO_TIMER = -1; + +// UserConfigs +export const APP_CONFIG = 'APP_CONFIG'; + +// Key for first enter app +export const IS_FIRST_ENTER = 'IS_FIRST_ENTER'; + +// Key for PRESET_ALARMS +export const PRESET_ALARMS = 'PRESET_ALARMS'; + +// Key for PRESET_ALARMS_NAME +export const PRESET_ALARMS_TITLE = 'PRESET_ALARMS_TITLE_'; + +// Subscribe to event alarm start eventId +export const EVENT_ID_ALARM_RING = 1; + +// Subscribe finish ability eventId +export const EVENT_ID_FINISH_ABILITY = 2; + +// Subscribe add or modify alarm eventId +export const EVENT_ID_CHANGE_ALARM = 3; + +// Subscribe Delete alarm eventId +export const EVENT_ID_DELETE_ALARM = 4; + +//Subscribe edit world clock eventId +export const EVENT_ID_EDIT_WORLD_CLOCK = 5; + +//Subscribe add or modify world clock eventId +export const EVENT_ID_CHANGE_WORLD_CLOCK = 6; + +//Subscribe alarm service kill alarm timeout message eventId +export const EVENT_ID_KILL_ALARM_TIMEOUT_MESSAGE = 7; + +export const EVENT_ID_TIMER_FULL_SCREEN_CLOSE = 8 + +// Subscribe full screen alarm eventId +export const EVENT_ID_FULL_SCREEN_ALARM = 9527; + +export const EVENT_ID_BANNER_ALARM = 9528; + +export const EVENT_ID_TIMEPICK_FORPC = 999099 + +export const EVENT_ID_WORLDCLOCK_FORPC = 999098 + +export const EVENT_ID_SEND_ALARM = 999097 + +export const TIME_PAUSE_OR_CONTINUE_ID = 666669 + +export const TIMEOUT_FULL_SCREEN_ID = 666666 + +export const TIMEOUT_FULL_SCREEN_CLOSE_ID = 666664 + +export const TIMEOUT_FULL_SCREEN_REPATE_ID = 666662 + +export const TIMEOUT_FLAG_ID = 666000 + +export const FULLSCREEN_SLIPUP_ID = 666601 + +export const TIMER_Toggle_FRONT_AND_BACKEND = 666110 + +// Next start time default data +export const ALERT_TIME_DEFAULT_DATA = -1; + +// Key for alarm id default value +export const DEFAULT_ALARM_ID = -1; + +// alarm ring time key +export const KEY_ALARM_RING_TIME = 'alarmRingTime'; + +// toast show time +export const PROMPT_TIME = 2000; + +// Maximum length of the alarm name +export const ALARM_NAME_INPUT_LIMIT = 200; + +// Min length of the alarm name +export const ALARM_NAME_INPUT_MIN = 0; + +// Alarm is Alerting +export const STATE_FIRING = 1; + +// Alarm is Snoozed +export const STATE_SNOOZE = 2; + +// Alarm is Stopped and before AlarmService is destroyed +export const STATE_STOP = 3; + +// AlarmService is destroyed after STATE_STOP +export const STATE_IDLE = 0; + +export enum BreakPoint { + SM = 'sm', + MD = 'md', + LG = 'lg', +} + +export enum MatchRule { + SM = '(320vp<=width<520vp)', + MD = '(520vp<=width<840vp)', + LG = '(840vp<=width)', +} + +export const MIN_LG_WIDTH = 840; + +export const MIN_MD_WIDTH = 520; + +export const COMMON_GRID_COL_OPTIONS: GridColOptions = { + span: { + xs: 12, + md: 12, + lg: 8, + }, + offset: { + xs: 0, + md: 0, + lg: 2, + }, +}; + +export interface BreakPointListener { + listener: mediaQuery.MediaQueryListener, + breakPoint: BreakPoint, +} + +export const RING_FILE_NAME = 'ring.wav'; + +export const READ_INTERVAL = 16; + +export const FILE_SEPARATOR = '/'; + +export const WRITE_MODE = 'w+'; + +export const TIMER_SELECTED_RING = 'TIMER_SELECTED_RING'; + +export const ALARM_LAST_SELECTED_RING = 'ALARM_LAST_SELECTED_RING'; + +export const CURRENT_DATABASE_VERSION = 'CURRENT_DATABASE_VERSION'; + +export const WAKE_DAYS_TYPE = 4; + +export const ONES_TYPE = 0; + +export const TIMER_PREFERENCES_CLOCK = 'TIMER_PREFERENCES_CLOCK'; + +export const START_DATE_TIMER_PREFERENCES_CLOCK = 'START_DATE_TIMER_PREFERENCES_CLOCK'; + +export const LEFT_TIME_TIMER_PREFERENCES_CLOCK = 'LEFT_TIME_TIMER_PREFERENCES_CLOCK'; + +export const TOTAL_TIME_TIMER_PREFERENCES_CLOCK = 'TOTAL_TIME_TIMER_PREFERENCES_CLOCK'; + +export const PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK = 'PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK'; + +export const IS_FIRST_ENTER_PRESET_CITY_WORLD = 'IS_FIRST_ENTER_PRESET_CITY_WORLD'; + + + + + diff --git a/common/src/main/ets/types.ets b/common/src/main/ets/types.ets new file mode 100644 index 0000000..9f5fff3 --- /dev/null +++ b/common/src/main/ets/types.ets @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const STR_PLACE_HOLDER = '%s'; + +export const SECOND_IN_MINUTE = 60; + +export const MINUTE_IN_HOUR = 60; + +export const MILLIS_IN_SECOND = 1000; + +export const SECOND_IN_HOUR = SECOND_IN_MINUTE * MINUTE_IN_HOUR; + +export const HOURS_IN_ONE_DAY = 24; + +export const FIRST_PARAM = '%1$s'; + +export const SECOND_PARAM = '%2$s'; + +// 刻度圆环直径占比 +export const SCALE_DIAMETER_RATIO = 0.92; + +export const CLOCK_SCALE_VALUE = 0.9; + +export const DIAL_DIAMETER_RATIO = 1.67; + +export const CLOCK_RATIO_FOR_NORMAL = 0.60; + +export const CLOCK_RATIO_FOR_LG = 0.60; + +export const HALF = 0.5; + +export const UPDATE_WIDTH_DELAY = 50; + +export const CLOCK_RATIO_FOR_NORMAL_CUSTOM = 0.60; + +export const SPECAIL_CLOCK_RATIO_FOR_NORMAL_CUSTOM = 0.70; + +export const SPECAIL_CLOCK_TIMER_NORMAL_CUSTOM = 0.70; + +export const CLOCK_RATIO_FOR_LG_CUSTOM = 0.50; + +// Window Shadow Collection +export const EVENT_ID_WINDOW_SHADOW = 666; + +export const EVENT_ID_SWITCH_PAGE = 667; + + +export interface SizeInfo { + width: number; + height: number; +} \ No newline at end of file diff --git a/common/src/main/ets/utils/CommonUtil.ets b/common/src/main/ets/utils/CommonUtil.ets new file mode 100644 index 0000000..552c036 --- /dev/null +++ b/common/src/main/ets/utils/CommonUtil.ets @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import i18n from '@ohos.i18n'; +import deviceInfo from '@ohos.deviceInfo'; +import resmgr from '@ohos.resourceManager'; +import util from '@ohos.util'; +import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager'; +import common from '@ohos.app.ability.common'; + +import { GlobalContext, LogUtil, SettingUtil } from '.'; +import wallpaper from '@ohos.wallpaper'; +import image from '@ohos.multimedia.image'; +import { AlarmStateManager } from '../manager'; +import SnoozeManager from '../manager/SnoozeManager'; +import notificationManager from '@ohos.notificationManager'; +import { BusinessError } from '@ohos.base'; + +const DEVICE_TYPE_PHONE = 'phone'; +const NAME = 'nearestAlarmingTime'; +const TAG = 'CommonUtil'; +// type ServiceExtensionContext = common.ServiceExtensionContext; +const UNICODE: string = 'utf-8'; + +/** + * Common Util + * + * @since 2023-01-10 + */ +export class CommonUtil { + /** + * Check whether the current device is a mobile phone. + * + * @return Whether the current device is a mobile phone. + */ + static isDevicePhone(): boolean { + return deviceInfo.deviceType === DEVICE_TYPE_PHONE; + } + + /** + * Set button shadow color. + * + * @return button shadow color. + */ + static changeShadowColor(resource: Resource): string | undefined { + let context = GlobalContext.getContext().getObject('clockContext') as Context; + if (context) { + let backgroundColor: string = context.resourceManager.getColorSync(resource).toString(16); + let colorSize = 6; + let shadowColor = '#' + '4d' + backgroundColor.substring(backgroundColor.length - colorSize); + LogUtil.info('changeShadowColor' + shadowColor); + return shadowColor; + } + return; + } + + /** + * Get common dialog controller configurations + * + * @return Common dialog controller configurations + */ + static getCommonDialogConfig(): CustomDialogControllerOptions { + return { + builder: undefined, + autoCancel: true, + alignment: CommonUtil.isDevicePhone() ? DialogAlignment.Bottom : undefined, + customStyle: CommonUtil.isDevicePhone(), + }; + } + + /** + * Query which day of the week by given the date. + * + * @param date Date + * @return The description of the queried day of a week in the corresponding language. + */ + static getDayOfWeekByDateNotHO(date: Date): string { + if (!date) { + return ''; + } + const options: Intl.DateTimeFormatOptions = { weekday: 'short' }; + return new Date(date.getFullYear(), date.getMonth(), + date.getDate()).toLocaleDateString(i18n.getSystemLanguage(), options); + } + + public static refreshNearestAlarmTime(timeStamp: number): boolean { + const oldData: string = SettingUtil.getValueSync(NAME, '-1'); + const insertData = (timeStamp >= 0 ? timeStamp.toString() : '0'); + if (oldData === '-1' || oldData !== insertData) { + let isSuccess = SettingUtil.setValueSync(NAME, insertData); + LogUtil.info(TAG, 'nearestAlarmingTime refresh timestamp=' + insertData + ' isSuccess=' + isSuccess); + return isSuccess; + } else { + LogUtil.info(TAG, 'nearestAlarmingTime same timestamp=' + insertData); + return true; + } + } + + public static getNearestAlarmTime(): string { + return SettingUtil.getValueSync(NAME, '-1'); + } + + public static requestTimerResources(): void { + try { + // backgroundTaskManager.resetAllEfficiencyResources(); + // let request: backgroundTaskManager.EfficiencyResourcesRequest = { + // resourceTypes: backgroundTaskManager.ResourceType.TIMER, + // isApply: true, // 申请资源 + // timeOut: 0, + // reason: 'apply', + // isPersist: true, + // isProcess: false, + // }; + // backgroundTaskManager.applyEfficiencyResources(request); + LogUtil.info(TAG, 'Succeeded in invoking requestTimerResources.'); + } catch (err) { + LogUtil.error(TAG, `requestTimerResources Failed: ${JSON.stringify(err as BusinessError)}`) + } + } + + public static requestCPUResources(): void { + try { + // let request: backgroundTaskManager.EfficiencyResourcesRequest = { + // resourceTypes: backgroundTaskManager.ResourceType.CPU, + // isApply: true, // 申请资源 + // timeOut: 0, + // reason: 'apply', + // isPersist: true, + // isProcess: false, + // }; + // backgroundTaskManager.applyEfficiencyResources(request); + LogUtil.info(TAG, 'Succeeded in requestCPUResources.'); + } catch (err) { + LogUtil.error(TAG, `requestCPUResources Failed: ${JSON.stringify(err as BusinessError)}`) + } + } + + public static releaseCPUResources(): void { + try { + // let request: backgroundTaskManager.EfficiencyResourcesRequest = { + // resourceTypes: backgroundTaskManager.ResourceType.CPU, + // isApply: false, // 释放资源 + // timeOut: 0, + // reason: 'apply', + // isPersist: true, + // isProcess: false, + // }; + // backgroundTaskManager.applyEfficiencyResources(request); + LogUtil.info(TAG, 'Succeeded in releaseCPUResources.'); + } catch (err) { + LogUtil.error(TAG, `releaseCPUResources Failed: ${JSON.stringify(err as BusinessError)}`) + } + } + + public static async terminateAlarmService(): Promise { + try { + const isFiring = await AlarmStateManager.isFiring(); + const snoozeIds = await SnoozeManager.getSnoozedAlarmId(); + const notificationCnt = await notificationManager.getActiveNotificationCount(); + LogUtil.info(TAG, `terminateAlarmService isFiring:${isFiring} cnt:${notificationCnt} snoozeIds:${JSON.stringify(snoozeIds)} `); + if (!isFiring && snoozeIds.length === 0) { + LogUtil.info(TAG, `terminateAlarmService begin`); + // await (GlobalContext.getContext().getObject('AlarmServiceContext') as ServiceExtensionContext).terminateSelf(); + } + } catch (err) { + LogUtil.error(TAG, `terminateAlarmService Failed: ${JSON.stringify(err as BusinessError)}`) + } + } + + public static terminateFullScreenAbility(): void { + try { + const isTerminated: boolean = GlobalContext.getContext() + .getObject('terminateFullScreenAbilityContext') as boolean; + LogUtil.info(TAG, 'terminateFullScreenAbility isTerminated=' + isTerminated); + if (!isTerminated) { + LogUtil.info(TAG, 'terminateFullScreenAbility begin'); + GlobalContext.getContext().setObject('terminateFullScreenAbilityContext', true); + // (GlobalContext.getContext() + // .getObject('FullScreenAbilityContext') as ServiceExtensionContext).terminateSelf((error: object) => { + // LogUtil.info(TAG, `terminateFullScreenAbility finish error: ${JSON.stringify(error)}`); + // }); + GlobalContext.getContext().setObject('FullScreenAbilityContext', undefined); + } + } catch (err) { + LogUtil.error(TAG, `terminateFullScreenAbility Failed: ${JSON.stringify(err as BusinessError)}`) + } + } + + public static async getWallpaper(): Promise { + try { + LogUtil.info(TAG, `getWallpaper begin`); + // let wallpaperImage: image.PixelMap = await wallpaper.getImage(wallpaper.WallpaperType.WALLPAPER_LOCKSCREEN); + // GlobalContext.getContext().setObject('wallpaperImage', wallpaperImage); + // LogUtil.info(TAG, `getWallpaper: ${wallpaperImage}`); + } catch (err) { + LogUtil.error(TAG, `getWallpaper Failed: ${JSON.stringify(err as BusinessError)}`); + } + } + + /** + * 获取指定JSON文件的对象 + * @param context 上下文 + */ + public static getStringByFile(resourceManager: resmgr.ResourceManager | undefined, + fileName: string): Promise { + return new Promise((resolve, reject) => { + LogUtil.info(TAG, `getFileContent. fileName: ${fileName}`); + if (!resourceManager || !fileName) { + LogUtil.error(TAG, `getFileContent failed. resourceManager or fileName is null. fileName:${fileName}`); + reject(); + return; + } + try { + resourceManager.getRawFileContent(fileName).then((value) => { + let content = util.TextDecoder.create(UNICODE).decodeWithStream(value); + resolve(content); + }).catch((err: BusinessError) => { + LogUtil.error(TAG, `getFileContent of ${fileName} failed. ${err?.code} ${err?.message}`); + reject(err); + }); + } catch (jsonError) { + LogUtil.error(TAG, `JSON parse error:${jsonError?.code} ${jsonError?.message}`); + reject(); + } + }); + } +} \ No newline at end of file diff --git a/common/src/main/ets/utils/ConfigData.ets b/common/src/main/ets/utils/ConfigData.ets deleted file mode 100644 index 046b180..0000000 --- a/common/src/main/ets/utils/ConfigData.ets +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -class ConfigData { - ONE_SECOND_TIME = 1000; - ONE_MINUTE_TIME = 60000; - ONE_HOUR_TIME = 3600000; - ONE_DAY_TIME = 86400000; - SCALE_NUM = 120; - COLOR_R = 98; - COLOR_G_START = 175; - COLOR_G_END = 125; - COLOR_B = 255; - HOUR_SELECT = 100; - MINUTE_SELECT = 60; - SECOND_SELECT = 60; - NOTIFY_INTERVAL_MS = 50; - STANDARD_SIZE = 280; - DIALS_CENTER_OFFSET_X = 1 / 2; - MINUTE_DIALS_CENTER_OFFSET_Y = 5 / 16; - SECOND_DIALS_CENTER_OFFSET_Y = 1 / 2; - SHADOW_RADIUS = 20; - CIRCLE_WIDTH_COLOR = '#FFFFFF'; - SECOND_HAND_COLOR = '#007DFF'; - TICK_MARK_COLOR = '#66182431'; - HOUR_TICK_MARK_COLOR = '#22182431'; - TICK_MARK_WIDTH = 2; - HOUR_TICK_MARK_WIDTH = 4; - SECOND_DISTANCE = 5; - SECOND_POINTER_OVER = 12; - SECOND_CIRCLE_WIDTH = 6; - SECOND_FONT_SIZE = 16; - SECOND_HAND_WIDTH = 2; - SECOND_TICK_MARK_DISTANCE = 12; - HOUR_SECOND_TICK_MARK_DISTANCE = 16; - HOUR_SECOND_TIMER_DISTANCE = 28; - MINUTE_DISTANCE = 1; - MINUTE_TICK_MARK_WIDTH = 1.5; - MINUTE_TICK_MARK_DISTANCE = 6; - MINUTE_POINTER_DISTANCE = 20; - MINUTE_CIRCLE_WIDTH = 4; - MINUTE_CENTER_CIRCLE_WIDTH = 2.5; - MINUTE_FONT_SIZE = 12; - HOUR_MINUTE_TIMER_DISTANCE = 14; - WH_100_100 = '100%'; - WH_33_100 = '33%'; -} - -let configData = new ConfigData(); - -export default configData as ConfigData; \ No newline at end of file diff --git a/common/src/main/ets/utils/DisplayUtil.ts b/common/src/main/ets/utils/DisplayUtil.ts new file mode 100644 index 0000000..d7246ad --- /dev/null +++ b/common/src/main/ets/utils/DisplayUtil.ts @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const BASE_DPI = 160; + +/** + * Display Util + * + * @since 2023-01-10 + */ +export class DisplayUtil { + /** + * Convert the value in unit of vp to px. + * + * The api named 'vp2px' provided by the framework cannot be used in the TS file, so we need this function. + * The function is applicable only to TS file. + * For UI components or pages, use vp2px provided by the framework. + * + * @param vpValue Value in the unit of vp + * @param deviceDpi DPI of the device, which can be obtained from the display object. + * @return vpValue Value in the unit of px + */ + static vp2pxInTs(vpValue: number, deviceDpi: number): number { + return vpValue * deviceDpi / BASE_DPI; + } +} \ No newline at end of file diff --git a/common/src/main/ets/utils/EventReportUtil.ets b/common/src/main/ets/utils/EventReportUtil.ets new file mode 100644 index 0000000..894594f --- /dev/null +++ b/common/src/main/ets/utils/EventReportUtil.ets @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import hiAppEvent from '@ohos.hiviewdfx.hiAppEvent'; +import { LogUtil } from './LogUtil'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import { BusinessError } from '@ohos.base'; +import bundleManager from '@ohos.bundle.bundleManager'; + +const TAG = 'Common_EventReport'; + +interface ReportParams { + PNAMEID: string; + PVERSIONID: string; + TIME?: number; + VALUE?:string; +} + +/** + * Event report util + * + * @since 2022-12-15 + */ +export class EventReportUtil { + private static applicationVersion: string; + + static reportEvent(eventName: string, time?: number, value?:string): void { + if (!EventReportUtil.applicationVersion) { + EventReportUtil.getApplicationVersion(); + } + LogUtil.info(TAG, `eventName : ${eventName} `); + let params: ReportParams = { + PNAMEID: 'com.ohos.clock', + PVERSIONID: EventReportUtil.applicationVersion, + TIME: time, + VALUE: value + } + LogUtil.info(TAG, 'eventName:', eventName, JSON.stringify(params)); + // hiSysEvent.write({ + // domain: 'CLOCK_UE', + // name: eventName, + // eventType: eventType, + // params: params + // }).then( + // (val) => { + // LogUtil.info(TAG, `${eventName} success.`); + // } + // ).catch( + // (err: BusinessError) => { + // LogUtil.error(TAG, `${eventName} error. code: ${err.code} ,message: ${err.message}`); + // } + // ) + return; + } + + static getApplicationVersion() { + let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT; + try { + bundleManager.getBundleInfoForSelf(bundleFlags, (err, data) => { + if (err) { + LogUtil.error(`${TAG},'getBundleInfoForSelf failed', message: ${err.message}`); + } else { + LogUtil.info(`${TAG},'getBundleInfoForSelf successfully', message: ${JSON.stringify(data)}`); + EventReportUtil.applicationVersion = data?.versionName; + } + }); + } catch (err) { + let message = (err as BusinessError).message; + LogUtil.error(`${TAG},'getBundleInfoForSelf failed: %{public}s', message: ${message}`); + } + } + + static write(eventName: string, eventType: number, params: Record): void { + if (!eventName || !eventType || !params) { + LogUtil.error(TAG, 'EventReportUtil write failed, params is incorrect'); + return; + } + const appEventInfo: hiAppEvent.AppEventInfo = { + domain: 'clock', + name: eventName, + eventType: eventType, + params: params + }; + hiAppEvent.write(appEventInfo).then(data => { + LogUtil.info(TAG, `HiAppEvent success to write event: ${eventName}`); + }).catch((error: object) => { + LogUtil.info(TAG, `HiAppEvent failed to write event because ${JSON.stringify(error)}`); + }); + } +} \ No newline at end of file diff --git a/common/src/main/ets/utils/EventType.ets b/common/src/main/ets/utils/EventType.ets new file mode 100644 index 0000000..04f09ce --- /dev/null +++ b/common/src/main/ets/utils/EventType.ets @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum EventName { + CLICK_ALARM_CLOCK_TAB = 'CLICK_ALARM_CLOCK_TAB', + CLICK_ALARM_CLOCK_ADD = 'CLICK_ALARM_CLOCK_ADD', + ENTER_ALARM_EDIT_PAGE = 'ENTER_ALARM_EDIT_PAGE', + EDIT_ALARM_CLOCK_TIME = 'EDIT_ALARM_CLOCK_TIME', + CLOCK_SET_ALARM_NAME = 'CLOCK_SET_ALARM_NAME', + CLOCK_SET_ALARM_NAME_CONFIRM = 'CLOCK_SET_ALARM_NAME_CONFIRM', + CLOCK_SET_ALARM_NAME_CANCEL = 'CLOCK_SET_ALARM_NAME_CANCEL', + SET_ALARM_RING_DURATION = 'SET_ALARM_RING_DURATION', + CLOCK_EDIT_ALARM_RING_DURATION = 'CLOCK_EDIT_ALARM_RING_DURATION', + CLOCK_CANCEL_RING_DURATION = 'CLOCK_CANCEL_RING_DURATION', + SET_ALARM_SNOOZE_DURATION = 'SET_ALARM_SNOOZE_DURATION', + CLOCK_CONFIRM_SNOOZE_DURATION = 'CLOCK_CONFIRM_SNOOZE_DURATION', + CLOCK_CANCEL_SNOOZE_DURATION = 'CLOCK_CANCEL_SNOOZE_DURATION', + CLOCK_ALARM_SAVE_SETTING = 'CLOCK_ALARM_SAVE_SETTING', + CLOCK_ALARM_ABANDON_SETTING = 'CLOCK_ALARM_ABANDON_SETTING', + CLOCK_ALARM_SWITCH = 'CLOCK_ALARM_SWITCH', + SINGLE_ALARM_SWIPE_LEFT_DEL = 'SINGLE_ALARM_SWIPE_LEFT_DEL', + LONG_PRESS_EDIT_ALARM_LIST = 'LONG_PRESS_EDIT_ALARM_LIST', + LONG_PRESS_CANCEL_EDIT_ALARM = 'LONG_PRESS_CANCEL_EDIT_ALARM', + DEL_SELECTED_ALARM_ITEM = 'DEL_SELECTED_ALARM_ITEM', + CLOCK_EDIT_ALARM_DEL = 'CLOCK_EDIT_ALARM_DEL', + CLOCK_EDIT_ALARM_DEL_CONFIRM = 'CLOCK_EDIT_ALARM_DEL_CONFIRM', + ALL_SELECT_ALARM_LIST = 'ALL_SELECT_ALARM_LIST', + ALL_UNSELECT_ALARM_LIST = 'ALL_UNSELECT_ALARM_LIST', + CLOCK_ALARM_IS_ON = 'CLOCK_ALARM_IS_ON', + CLOCK_LOCK_SCREEN_ALARM = 'CLOCK_LOCK_SCREEN_ALARM', + CLOCK_LOCK_SCREEN_REMIND_LATER = 'CLOCK_LOCK_SCREEN_REMIND_LATER', + NOTIFICATION_BAR_REMIND_LATER = 'NOTIFICATION_BAR_REMIND_LATER', + LOCK_SCREEN_SWIPE_CLOSE_ALARM = 'LOCK_SCREEN_SWIPE_CLOSE_ALARM', + NOTIFICATION_REMIND_LATER = 'NOTIFICATION_REMIND_LATER', + NOTIFICATION_CLOSE_ALARM = 'NOTIFICATION_CLOSE_ALARM', + NOTIFICATION_BANNER_CLOSE_ALARM = 'NOTIFICATION_BANNER_CLOSE_ALARM', + NOTIFICATION_CENTER_CLOSE_ALARM = 'NOTIFICATION_CENTER_CLOSE_ALARM', + CLICK_WORLD_CLOCK_TAB = 'CLICK_WORLD_CLOCK_TAB', + WORLD_CLOCK_ADD_CITY = 'WORLD_CLOCK_ADD_CITY', + CITY_SWIPE_LEFT_DEL = 'CITY_SWIPE_LEFT_DEL', + WORLD_CLOCK_LONG_PRESS_EDIT = 'WORLD_CLOCK_LONG_PRESS_EDIT', + WORLD_CLOCK_CITY_DEL = 'WORLD_CLOCK_CITY_DEL', + WORLD_CLOCK_EDIT_CONFIRM = 'WORLD_CLOCK_EDIT_CONFIRM', + WORLD_CLOCK_EDIT_CANCEL = 'WORLD_CLOCK_EDIT_CANCEL', + DIGITAL_DIAL_SWITCHING = 'DIGITAL_DIAL_SWITCHING', + CLICK_NEW_CLOCK_LABEL_OK = 'CLICK_NEW_CLOCK_LABEL_OK', + CLICK_NEW_CLOCK_REPEAT_CHOICE = 'CLICK_NEW_CLOCK_REPEAT_CHOICE', + CLICK_CLOCK_SETTINGS_DURATION_CHOICE = 'CLICK_CLOCK_SETTINGS_DURATION_CHOICE', + CLICK_CLOCK_SETTINGS_FREQUENCY_OK = 'CLICK_CLOCK_SETTINGS_FREQUENCY_OK', + CLICK_NEW_CLOCK_REPEAT_CANCEL = 'CLICK_NEW_CLOCK_REPEAT_CANCEL', + NOTIFICATION_SNOOZE_ALARM = 'NOTIFICATION_SNOOZE_ALARM', + NOTIFICATION_CANCEL_ALARM_SNOOZE = 'NOTIFICATION_CANCEL_ALARM_SNOOZE', + CLICK_NEW_CLOCK_LABEL_CANCEL = 'CLICK_NEW_CLOCK_LABEL_CANCEL', + CLICK_CLOCK_SETTINGS_DURATION_CANCEL = 'CLICK_CLOCK_SETTINGS_DURATION_CANCEL', + CLICK_CLOCK_SETTINGS_FREQUENCY_CANCEL = 'CLICK_CLOCK_SETTINGS_FREQUENCY_CANCEL', + + CLICK_NEW_CLOCK_REPEAT = 'CLICK_NEW_CLOCK_REPEAT', + CLICK_NEW_CLOCK_LABEL = 'CLICK_NEW_CLOCK_LABEL', + CLICK_CLOCK_SETTINGS_DURATION = 'CLICK_CLOCK_SETTINGS_DURATION', + CLICK_CLOCK_SETTINGS_FREQUENCY = 'CLICK_CLOCK_SETTINGS_FREQUENCY', + FULL_SCREEN_CLOSE_ALARM = 'FULL_SCREEN_CLOSE_ALARM', + + CLICK_STOPWATCH_TAB = 'CLICK_STOPWATCH_TAB', + CLICK_TIMER_TAB = 'CLICK_TIMER_TAB', + CLICK_START_TIMER = 'CLICK_START_TIMER', + CLICK_TIMER_PAUSE = 'CLICK_TIMER_PAUSE', + CLICK_TIMER_RESET = 'CLICK_TIMER_RESET', + CLICK_START_STOPWATCH = 'CLICK_START_STOPWATCH', + CLICK_STOPWATCH_COUNT = 'CLICK_STOPWATCH_COUNT', + CLICK_STOPWATCH_PAUSE = 'CLICK_STOPWATCH_PAUSE', + CLICK_STOPWATCH_RESET = 'CLICK_STOPWATCH_RESET', + CLICK_TIMER_DIAL_UPDATE = 'CLICK_TIMER_DIAL_UPDATE', + CLICK_TIMER_MUSIC = 'CLICK_TIMER_MUSIC', + TIMER_ALERT = 'TIMER_ALERT', + CLICK_TIMER_CLOSE = 'CLICK_TIMER_CLOSE', + CLICK_TIMER_AGAIN = 'CLICK_TIMER_AGAIN', + +} + +export enum EventResult { + EVENT_RESULT_SUCCESS = 0, + EVENT_RESULT_FAILED = 1, + ADD_NEW_CLOCK_SUCCESS = 0, + ADD_NEW_CLOCK_FAILED = 1, + CLICK_ALARM_CLOCK_TAB = 0, + CLICK_WORLD_CLOCK_TAB = 0, + ADD_WORLD_CLOCK_SUCCESS = 0, + ADD_WORLD_CLOCK_FAILED = 1, + ENTER_EDIT_CITIES_PAGE = 0, + ENTER_EDIT_ALARM_PAGE = 0, + ALARM_SWITCH_CHANGED_SUCCESS = 0, + WORLD_CLOCK_EDIT_DELETE = 0, + WORLD_CLOCK_EDIT_DELETE_OK = 0, + WORLD_CLOCK_EDIT_CANCEL = 0, + ALARM_ALERT_SUCCESS = 0, + NOTIFICATION_CLOSE_ALARM = 0, + NOTIFICATION_SNOOZE_ALARM = 0, + FULL_SCREEN_SNOOZE_ALARM = 0, + FULL_SCREEN_CLOSE_ALARM = 0, + CLICK_STOPWATCH_TAB = 0, + CLICK_TIMER_TAB = 0 + +} \ No newline at end of file diff --git a/common/src/main/ets/utils/FormUtil.ets b/common/src/main/ets/utils/FormUtil.ets new file mode 100644 index 0000000..8d3818f --- /dev/null +++ b/common/src/main/ets/utils/FormUtil.ets @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import formProvider from '@ohos.app.form.formProvider'; +import formBindingData from '@ohos.app.form.formBindingData'; +import { FormManager } from '../manager'; +import { FormInfo } from '../manager/types'; +import { LogUtil } from './LogUtil'; +import { GlobalContext } from './GlobalContext'; + +const TAG: string = 'FormUtil'; + +/** + * 卡片工具类 + * + * @since 2023-06-08 + */ +export class FormUtil { + /** + * notify FormData Changed + * + * @param formId formId + * @param formData formData + */ + public static async notifyFormDataChanged(formId: string, formData: Object): Promise { + try { + let bindData = formBindingData.createFormBindingData(formData); + LogUtil.info(TAG, `notifyFormDataChanged --> formId: ${formId}, formData = ${JSON.stringify(bindData)}`); + formProvider.updateForm(formId, bindData); + } catch (error) { + LogUtil.error(TAG, `notifyFormDataChanged error: ${error}`); + } + } + + /** + * notify FormBindData Changed + * + * @param formId formId + * @param formBindData formBindData + */ + public static async notifyFormBindDataChanged(formId: string, formBindData: formBindingData.FormBindingData): + Promise { + try { + LogUtil.info(TAG, `notifyFormBindDataChanged --> formId: ${formId}, formBindData = ${JSON.stringify(formBindData)}`); + formProvider.updateForm(formId, formBindData); + } catch (error) { + LogUtil.error(TAG, `notifyFormBindDataChanged error: ${error}`); + } + } + + /** + * Notify All Alarm Card Changed + * + */ + public static async notifyClockCardChanged(formData: Object, formName: string): Promise { + try { + let bindData = formBindingData.createFormBindingData(formData); + const formList: FormInfo[] = await FormManager.fetchAllFormsFromSp(); + LogUtil.info(TAG, 'notifyClockCardChanged formList=' + JSON.stringify(formList)) + formList.forEach((value, index) => { + if (value.formName === formName) { + FormUtil.notifyFormBindDataChanged(value.formId, bindData); + } + }); + } catch (error) { + LogUtil.error(TAG, `notifyClockCardChanged error: ${error}`); + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/timer/TimerClock.ets b/common/src/main/ets/utils/GlobalContext.ts similarity index 50% rename from product/phone/src/main/ets/pages/timer/TimerClock.ets rename to common/src/main/ets/utils/GlobalContext.ts index b66502c..e5120ad 100644 --- a/product/phone/src/main/ets/pages/timer/TimerClock.ets +++ b/common/src/main/ets/utils/GlobalContext.ts @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,24 +13,25 @@ * limitations under the License. */ -import { TimerView, TimerController } from '@ohos/timer'; +export class GlobalContext { + private constructor() { + } -const CLOCK_SIZE = 373.5; + private static instance: GlobalContext; + private _objects = new Map(); -@Component -export struct TimerClock { - @State currentTime: number = 0; - @State timerController: TimerController = new TimerController(); + public static getContext(): GlobalContext { + if (!GlobalContext.instance) { + GlobalContext.instance = new GlobalContext(); + } + return GlobalContext.instance; + } - aboutToAppear() { - this.timerController.setClockUpdateListener((currentTimeMs: number) => { - this.currentTime = currentTimeMs; - }) + getObject(value: string): Object | undefined { + return this._objects.get(value); } - build() { - Column() { - TimerView({ sSize: CLOCK_SIZE, currentTime: this.currentTime }) - } + setObject(key: string, objectClass?: Object): void { + this._objects.set(key, objectClass as Object); } } \ No newline at end of file diff --git a/common/src/main/ets/utils/LogUtil.ts b/common/src/main/ets/utils/LogUtil.ts index e320097..11f4d4d 100644 --- a/common/src/main/ets/utils/LogUtil.ts +++ b/common/src/main/ets/utils/LogUtil.ts @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,32 +13,33 @@ * limitations under the License. */ -import HiLog from '@ohos.hilog'; +import hilog from '@ohos.hilog'; -const DOMAIN = 0x0500; -const TAG = '[Clock]'; +const DOMAIN = 0x66EE; +const FORMAT = '%{public}s'; +const PREFIX = '[Clock]'; +const SEPARATOR = ' '; /** - * log package tool class + * 日志打印工具类 + * + * @author XiaHan + * @since 2022-07-06 */ -export default class LogUtil { - static debug(msg): void { - HiLog.debug(DOMAIN, TAG, msg); - } - - static log(msg): void { - HiLog.info(DOMAIN, TAG, msg); - } +export class LogUtil { + static debug(tag, ...args: string[]): void { + hilog.debug(DOMAIN, PREFIX, FORMAT, `tag: ${tag} --> ${args.join(SEPARATOR)}`); + } - static info(msg): void { - HiLog.info(DOMAIN, TAG, msg); - } + static info(tag, ...args: string[]): void { + hilog.info(DOMAIN, PREFIX, FORMAT, `tag: ${tag} --> ${args.join(SEPARATOR)}`); + } - static warn(msg): void { - HiLog.warn(DOMAIN, TAG, msg); - } + static warn(tag, ...args: string[]): void { + hilog.warn(DOMAIN, PREFIX, FORMAT, `tag: ${tag} --> ${args.join(SEPARATOR)}`); + } - static error(msg): void { - HiLog.error(DOMAIN, TAG, msg); - } + static error(tag, ...args: string[]): void { + hilog.error(DOMAIN, PREFIX, FORMAT, `tag: ${tag} --> ${args.join(SEPARATOR)}`); + } } \ No newline at end of file diff --git a/common/src/main/ets/utils/NotificationContentUtil.ets b/common/src/main/ets/utils/NotificationContentUtil.ets new file mode 100644 index 0000000..e9c8bd5 --- /dev/null +++ b/common/src/main/ets/utils/NotificationContentUtil.ets @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import image from '@ohos.multimedia.image' +import { GlobalContext, LogUtil } from './index'; +import resmgr from '@ohos.resourceManager'; + +const TAG = 'NotificationContentUtil'; + +export class NotificationContentUtil { + static async createPixelMap(name: string): Promise { + const resourceMgr = (GlobalContext.getContext().getObject('resourceManager') as resmgr.ResourceManager) + const fileData = await resourceMgr.getRawFileContent(name + '.svg') + LogUtil.info(TAG, `${JSON.stringify(resourceMgr)}`) + const imageSource = image.createImageSource(fileData.buffer as ESObject); + const pixSize = (name === 'timer_capsule' || name === 'alarm_capsule') ? 63 : 140; + let decodingOptions: image.DecodingOptions = { + editable: true, + desiredPixelFormat: 3, + desiredSize: { + width: pixSize, + height: pixSize + } + } + // 创建pixelMap并进行简单的旋转和缩放 + const pixelMap = await imageSource.createPixelMap(decodingOptions); + return pixelMap + } +} \ No newline at end of file diff --git a/common/src/main/ets/utils/RingtoneSelectionUtil.ets b/common/src/main/ets/utils/RingtoneSelectionUtil.ets new file mode 100644 index 0000000..c6a2627 --- /dev/null +++ b/common/src/main/ets/utils/RingtoneSelectionUtil.ets @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DEFAULT_RINGTONE_ID, noneRingtone, ringtones } from '../components/RingtoneSelection/types'; +import preferencesUtil from '@ohos.data.preferences'; +import { ALARM_LAST_SELECTED_RING, APP_CONFIG, TIMER_SELECTED_RING } from '../manager/types'; +import { GlobalContext } from './index'; + +interface RingtoneItem { + id: number, + name: string, + path: string +} + +export class RingtoneSelectionUtil { + static getRingtoneById(id: number) { + return ringtones.find(r => r.id == id) ?? RingtoneSelectionUtil.getDefaultRingtone() + } + + static getRingtoneByPath(path: string) { + return ringtones.find(r => r.path === path) ?? RingtoneSelectionUtil.getDefaultRingtone() + } + + static getDefaultRingtone() { + return ringtones.find(r => r.id == DEFAULT_RINGTONE_ID) ?? noneRingtone + } + + static async getAlarmRingtone(): Promise { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, APP_CONFIG); + let ringPath = String(await preferences.get(ALARM_LAST_SELECTED_RING, '')); + return RingtoneSelectionUtil.getRingtoneByPath(ringPath); + } + + static async getTimerRingtone(): Promise { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, APP_CONFIG); + let ringPath = String(await preferences.get(TIMER_SELECTED_RING, '')); + return RingtoneSelectionUtil.getRingtoneByPath(ringPath); + } +} \ No newline at end of file diff --git a/common/src/main/ets/utils/SettingUtil.ts b/common/src/main/ets/utils/SettingUtil.ts new file mode 100644 index 0000000..a5657d8 --- /dev/null +++ b/common/src/main/ets/utils/SettingUtil.ts @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import settings from '@ohos.settings'; +import type common from '@ohos.app.ability.common'; +import { GlobalContext } from './GlobalContext'; +import { LogUtil } from './LogUtil'; + +const TAG: string = 'SettingUtil'; + +/** + * Settings工具类 + */ +export class SettingUtil { + /** + * 设置settings数据(同步方法) + * @param name 名称 + * @param value 值 + * @return true:设置成功;false:设置失败 + */ + public static setValueSync(name: string, value: string): boolean { + let clockContext: common.Context = GlobalContext.getContext().getObject('clockContext') as common.Context; + if (clockContext) { + let resultGlobal = settings.setValueSync(clockContext, name, value); + LogUtil.info(TAG, `setValueSync global name:${name} value:${value} result:${resultGlobal}`); + return resultGlobal; + } + LogUtil.error(TAG, 'setValueSync context is null'); + return false; + } + + /** + * 获取settings数据(同步方法) + * @param name 名称 + * @param defValue 默认值 + * @return 值 + */ + public static getValueSync(name: string, defValue: string): string { + let clockContext: common.Context = GlobalContext.getContext().getObject('clockContext') as common.Context; + if (clockContext) { + let resultGlobal = settings.getValueSync(clockContext, name, defValue); + LogUtil.info(TAG, `getValueSync global name:${name} value:${resultGlobal}`); + return resultGlobal; + } + LogUtil.error(TAG, 'getValueSync context is null'); + return ''; + } +} diff --git a/common/src/main/ets/utils/StringUtil.ts b/common/src/main/ets/utils/StringUtil.ts new file mode 100644 index 0000000..a55d8c3 --- /dev/null +++ b/common/src/main/ets/utils/StringUtil.ts @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const TAG: string = 'SettingUtil'; + +export const EMPTY_IDENTIFY: string = 'empty'; // 空字符串标识符 + +/** + * 字符串工具类 + */ +export class StringUtil { + /** + * 判断字串是否是空字串 + * @param stringValue 字符串 + */ + public static isEmpty(stringValue: string | null | undefined): boolean { + return stringValue === undefined || stringValue === null || stringValue.length === 0; + } + + /** + * 判断字串是否是非空字串 + * @param stringValue 字符串 + */ + public static isNotEmpty(stringValue: string | null | undefined): boolean { + return stringValue !== undefined && stringValue !== null && stringValue.length !== 0; + } + + /** + * 判断资源字符是否是非空字串 + * @param stringValue 字符串 + */ + public static isNotEmptyRes(stringValue: string | null | undefined): boolean { + return StringUtil.isNotEmpty(stringValue) && stringValue !== EMPTY_IDENTIFY; + } +} diff --git a/common/src/main/ets/utils/TimeUtil.ets b/common/src/main/ets/utils/TimeUtil.ets new file mode 100644 index 0000000..67ebe39 --- /dev/null +++ b/common/src/main/ets/utils/TimeUtil.ets @@ -0,0 +1,581 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import i18n from '@ohos.i18n'; +import Intl from '@ohos.intl'; +import intl from '@ohos.intl'; +import resmgr from '@ohos.resourceManager'; +// import systemTimer from '@ohos.systemTimer'; +import SnoozeManager from '../manager/SnoozeManager' +import { AlarmInfo, AlarmManager, ResourceManager, TimerManager, TRIGGER_TIME_NEVER } from '../manager'; +import preferencesUtil from '@ohos.data.preferences'; +import { BusinessError } from '@ohos.base'; +import { + AFTERNOON_START_POINT, + CalendarFiled, + DAYS_OF_WEEK_COUNT, + DAYS_OF_WEEK_WORK_DAY_COUNT, + DEFAULT_DAY, + DIGIT_TIME_FORMAT, + FormatTime, + HOUR_12_TIME_FORMAT_REG_EXP, + HOUR_12_TIME_FORMAT_REG_EXP_WITH_SECOND, + INDEX_FRIDAY, + INDEX_OFFSET, + INDEX_SATURDAY, + LANGUAGE_ZH, + LAST_TAG_ID, + LESS_THAN_ONE_MINUTE_FLAG, + NO_ALARM_ENABLED, + ONE_DAY, + ONE_MINUTE, + SINGLE_NUMBER_END_POINT, + TIME_TAG_GROUP_SIZE, + TIME_TAG_ID_LIST_IN_ZH, + TimeFormatIndex, + WEEK_DAY_BASIC_MONTH, + WEEK_DAY_BASIC_YEAR, + WeekDayFormat, + WAKE_DAYS_TYPE, + SUNDAY, + SATURDAY, + MONTH_LIST, + CACHE_HOLIDAYS_LIST, + HolidaysType, + NetHolidaysType +} from './types'; +import { HOURS_IN_ONE_DAY, MILLIS_IN_SECOND, MINUTE_IN_HOUR, SECOND_IN_MINUTE } from '../types'; +import { LogUtil } from './LogUtil'; +import { GlobalContext } from '../utils'; +import { CommonUtil } from './CommonUtil'; +import http from '@ohos.net.http'; + +const TAG: string = 'TimeUtil'; +let nextDay: number = 0; + +export interface urlType { + url: string, +} + +/** + * Time processing tool class + * + * @since 2022-07-06 + */ +export class TimeUtil { + private static daysOfWeekLabelCache: Map = new Map(); + public static getDayOfWeekByDate: (date: Date) => string = CommonUtil.getDayOfWeekByDateNotHO; + + /** + * Get the tag of the time segment based on the hour in Chinese. + * If 23 is transferred, "半夜" is returned. + * + * @param hour Hour to be queried + * @return The tag of the time segment based on the hour in Chinese + */ + static getChinaTimeTag(hour: number): string { + if (hour === 0) { + // 0 点取最后一个时间段(即 23 点 到 0 点)的标签 + return ResourceManager.getStringById(LAST_TAG_ID); + } + const index = Math.floor((hour - INDEX_OFFSET) / TIME_TAG_GROUP_SIZE); // 将小时转换为时间段标签的下标 + return ResourceManager.getStringById(TIME_TAG_ID_LIST_IN_ZH[index]); + } + + /** + * Get the formatted time information in the current language and time system based on the hour and minute. + * + * For example, if 23:16 is transferred in Chinese and the time format is 12 hours, + * the following information will be returned: + * { time: '11:16', tag: '半夜', isTagLeft: true } + * + * @param hour + * @param minute + * @param second + * @return The formatted time information + */ + static getFormattedTimeObj(hour: number, minute: number, second ?: number): FormatTime { + const is24HourClock = i18n.is24HourClock(); + let option: intl.DateTimeOptions; + let timeObj: Date; + const currentDate = new Date(); + if (second || second === 0) { + option = { + hour: DIGIT_TIME_FORMAT, + minute: DIGIT_TIME_FORMAT, + second: DIGIT_TIME_FORMAT, + hour12: !is24HourClock, + }; + timeObj = new Date(currentDate.getFullYear(), currentDate.getMonth(), DEFAULT_DAY, hour, minute, second); + } else { + option = { hour: DIGIT_TIME_FORMAT, minute: DIGIT_TIME_FORMAT, hour12: !is24HourClock }; + timeObj = new Date(currentDate.getFullYear(), currentDate.getMonth(), DEFAULT_DAY, hour, minute); + } + let locale = new Intl.Locale(i18n.System.getSystemLanguage()); + const timeWithFormat = new Intl.DateTimeFormat(locale.toString(), option).format(timeObj); + let hours = hour; + if (hour === 0 && !is24HourClock) { + hours = AFTERNOON_START_POINT; + } else if (hour > AFTERNOON_START_POINT) { + hours = hour - AFTERNOON_START_POINT; + } + if (is24HourClock) { + return { + time: (hour < SINGLE_NUMBER_END_POINT ? `0${hour}` : hour) + timeWithFormat.substring(TimeFormatIndex.HOUR, timeWithFormat.length), + }; + } + const parts = timeWithFormat.match(second || second === 0 ? HOUR_12_TIME_FORMAT_REG_EXP_WITH_SECOND : + HOUR_12_TIME_FORMAT_REG_EXP); + const leftTag: string = parts ? parts[TimeFormatIndex.LEFT_TAG].trim() : ''; + const isChinaTime: boolean = i18n.getSystemLanguage().startsWith(LANGUAGE_ZH); + const time: string = parts ? hours + parts.slice(TimeFormatIndex.MINUTE - 1, second || second === 0 ? + TimeFormatIndex.RIGHT_TAG_WITH_SECOND : TimeFormatIndex.RIGHT_TAG) + .join('') : ''; + const rightTag: string = parts ? parts[second || second === 0 ? TimeFormatIndex.RIGHT_TAG_WITH_SECOND : TimeFormatIndex.RIGHT_TAG].trim() : ''; + return { + time: time, + tag: isChinaTime ? TimeUtil.getChinaTimeTag(hour) : (leftTag || rightTag), + isTagLeft: !!leftTag, + } + } + + /** + * Get formatted time + * + * @param time Time to format. If this parameter is undefined or zero, get the current time. + * @return Formatted time like '8:10 AM' in English or '早上8:10' in Chinese. + */ + static getFormattedTime(time?: number): string { + const nowDate = time ? new Date(time) : new Date(); + const timeObj = TimeUtil.getFormattedTimeObj(nowDate.getHours(), nowDate.getMinutes()); + if (timeObj.tag) { + return timeObj.isTagLeft ? `${timeObj.tag}${timeObj.time}` : `${timeObj.time} ${timeObj.tag}`; + } + return timeObj.time as string; + } + + /** + * Get formatted time of specific timezone ID + * + * @param timeZone + * @return The formatted time information. + */ + static getFormattedTimeZoneTimeObjByTimeZone(timeZone: string): FormatTime { + const calendar = i18n.getCalendar(i18n.getSystemLocale()); + calendar.setTimeZone(timeZone); + let hour = calendar.get(CalendarFiled.Hour_OF_Day) + let minute = calendar.get(CalendarFiled.Minute) + let second = calendar.get(CalendarFiled.Second); + const timeObj = TimeUtil.getFormattedTimeObj(hour, minute); + return { + time: timeObj.time, + tag: timeObj.isTagLeft == undefined ? '' : timeObj.tag, + isTagLeft: timeObj.isTagLeft, + } + } + + /** + * Get the formatted time information in the current language and time system based on the hour and minute. + * + * For example, if 23:16:00 is transferred in Chinese and the time format is 12 hours, + * the following information will be returned: + * { time: '11:16:00', tag: '半夜', isTagLeft: true } + * + * @param time Time to format. If this parameter is undefined or zero, get the current time. + * @return The formatted time information + */ + static getFullFormattedTimeObj(time?: number): FormatTime { + const nowDate = time ? new Date(time) : new Date(); + const timeObj = TimeUtil.getFormattedTimeObj(nowDate.getHours(), nowDate.getMinutes(), nowDate.getSeconds()); + return { + time: timeObj.time, + tag: timeObj.isTagLeft == undefined ? '' : timeObj.tag, + isTagLeft: timeObj.isTagLeft, + } + } + + /** + * Get current formatted date with week day + * + * @return Current formatted date with week day like 'Friday, August 19' + */ + static getCurrentFormattedDateWithWeek(): string { + const nowDate = new Date(); + return nowDate.toLocaleDateString(i18n.getSystemLanguage(), { + weekday: 'long', + month: 'long', + day: 'numeric', + }); + } + + /** + * Query which day of the week the Nth day is. + * + * @param day Index of the queried week day + * @param weekDayFormat The format of week day, the default value is WeekDayFormat.Short + * @return The description of the queried day of a week in the corresponding language. + */ + static getDayOfWeek(day: number, weekDayFormat: WeekDayFormat): string { + const options: intl.DateTimeOptions = { weekday: weekDayFormat || WeekDayFormat.Short }; + const date = new Date(WEEK_DAY_BASIC_YEAR, WEEK_DAY_BASIC_MONTH); + date.setDate(day + 1); + let locale = new Intl.Locale(i18n.System.getSystemLanguage()); + const timeWithFormat = new Intl.DateTimeFormat(locale.toString(), options).format(date); + return timeWithFormat; + } + + /** + * Get the description of each day of a week in the corresponding language. + * + * @return The description of each day of a week in the corresponding language. + */ + static getDaysOfWeekLabels(): string[] { + const language = i18n.getSystemLanguage(); + let weekLabels = TimeUtil.daysOfWeekLabelCache.get(language); + if (weekLabels) { + return weekLabels; + } + // In Chinese, '周 X' is always used instead of '星期 X', therefore, the short format is used here. + const weekDayFormat = language.startsWith(LANGUAGE_ZH) ? WeekDayFormat.Short : WeekDayFormat.Long; + weekLabels = [0, 1, 2, 3, 4, 5, 6].map(weekDay => TimeUtil.getDayOfWeek(weekDay, weekDayFormat)); + TimeUtil.daysOfWeekLabelCache.set(language, weekLabels); + return weekLabels; + } + + /** + * Convert the Days-of-week information of an alarm to the repetition information displayed in the alarm list. + * + * @param daysOfWeek Days-of-week information for repetition + * @return The repetition information to display + */ + static getDaysOfWeekDesc(resourceManager: resmgr.ResourceManager, daysOfWeek: number[]): string { + const daysOfWeekLength = daysOfWeek.length; + if (daysOfWeekLength === 0) { + return resourceManager.getStringSync($r('app.string.only_ring_once').id); + } + if (daysOfWeekLength === DAYS_OF_WEEK_COUNT) { + return resourceManager.getStringSync($r('app.string.every_day').id); + } + if (daysOfWeekLength === DAYS_OF_WEEK_WORK_DAY_COUNT && + daysOfWeek[daysOfWeekLength - 1] === INDEX_FRIDAY && daysOfWeek[0] === 1) { + return resourceManager.getStringSync($r('app.string.monday_to_friday').id); + } + if (daysOfWeekLength > 1 && daysOfWeek[daysOfWeek.length - 1] === INDEX_SATURDAY) { + daysOfWeek.pop(); + daysOfWeek.unshift(INDEX_SATURDAY); + } + return daysOfWeek.map(weekDay => { + return TimeUtil.getDayOfWeek(weekDay, WeekDayFormat.Short); + }).join(' '); + } + + /** + * Gets the number of days until the next alarm + * + * @param daysOfWeek Days-of-week information for repetition + * @param nearWeekDay The nearest day of week that the alarm may ring + * @return The number of days until the next alarm + */ + static getDaysToNextAlarm(daysOfWeek: number[], nearWeekDay: number): number { + let dayCount = 0; + for (; dayCount < DAYS_OF_WEEK_COUNT; dayCount++) { + const day = (nearWeekDay + dayCount) % DAYS_OF_WEEK_COUNT; + if (daysOfWeek.some(item => item === day)) { + break; + } + } + return dayCount; + } + + /** + * Get the next alarm time + * + * @param daysOfWeek Days-of-week information for repetition + * @return The next alarm time + */ + static getAlarmTime(alarmInfo: AlarmInfo): number { + const daysOfWeek: number[] = alarmInfo.daysOfWeek; + const hour: number = alarmInfo.hour; + const minute: number = alarmInfo.minute; + + const alarmTime = new Date(); + alarmTime.setHours(hour); + alarmTime.setMinutes(minute); + alarmTime.setSeconds(0); + alarmTime.setMilliseconds(0); + + const now = new Date(); + if (hour < now.getHours() || hour === now.getHours() && minute <= now.getMinutes()) { + // If the alarm time of the current day has passed, the next alarm time must ring at least one day later. + alarmTime.setDate(alarmTime.getDate() + ONE_DAY); + } + + if (alarmInfo.daysOfWakeType === WAKE_DAYS_TYPE) { //选择法定工作日 + return TimeUtil.getFassDay(alarmInfo); + } + + if (daysOfWeek.length === 0) { + // The alarm is not repeated + return alarmTime.getTime(); + } + + // If the alarm is repeated, + // needs to find the next alarm time according to the days-of-week information for repetition. + const daysToAdd = TimeUtil.getDaysToNextAlarm(daysOfWeek, alarmTime.getDay()); + alarmTime.setDate(alarmTime.getDate() + daysToAdd); + return alarmTime.getTime(); + } + + /** + * Get the next workdays alarm time + * + * @param Current alarmInfo + * @return The next alarm time + */ + static getFassDay(alarmInfo: AlarmInfo) { + let cacheHolidaysList = AppStorage.get(CACHE_HOLIDAYS_LIST); + LogUtil.info(TAG, ' getFassDay cacheHolidaysList:' + JSON.stringify(cacheHolidaysList)); + let nowDay: number = TimeUtil.getNumberDay() - ONE_DAY; + LogUtil.info(TAG, ' getFassDay nowDay:' + nowDay); + const hour: number = alarmInfo.hour; + const minute: number = alarmInfo.minute; + const now = new Date(); + + const alarmTime = new Date(); + alarmTime.setHours(hour); + alarmTime.setMinutes(minute); + alarmTime.setSeconds(0); + alarmTime.setMilliseconds(0); + + let freeDay: number[] = cacheHolidaysList?.freeday || []; + let workday: number[] = cacheHolidaysList?.workday || []; + + let tomorrowDay: number = 0; + if (hour < now.getHours() || hour === now.getHours() && minute <= now.getMinutes()) { + tomorrowDay = 1; + } + + // Network holidays data is not exit. + if (!freeDay || !freeDay.length) { + alarmTime.setDate(alarmTime.getDate() + tomorrowDay); + return alarmTime.getTime(); + } + + TimeUtil.getNextDay(nowDay + tomorrowDay, freeDay); + alarmTime.setDate(alarmTime.getDate() + (nextDay - nowDay)); + + // Is saturday and not work, set alarm day add 1. + if (alarmTime.getDay() === SATURDAY && !workday.includes(TimeUtil.getNumberDay(alarmTime.getTime()) - ONE_DAY)) { + alarmTime.setDate(alarmTime.getDate() + ONE_DAY); + } + + // Is sunday and not work, set alarm day add 1. + if (alarmTime.getDay() === SUNDAY && !workday.includes(TimeUtil.getNumberDay(alarmTime.getTime()) - ONE_DAY)) { + alarmTime.setDate(alarmTime.getDate() + ONE_DAY); + } + + LogUtil.info(TAG, ' getFassDay alarmTime.getTime:' + JSON.stringify(new Date(alarmTime.getTime()))); + return alarmTime.getTime(); + } + + + static getNextDay(nowDay: number, freeDay: number[]) { + if (freeDay.indexOf(nowDay) !== -1) { + nowDay++; + TimeUtil.getNextDay(nowDay, freeDay); + } else { + nextDay = nowDay; + } + } + + /** + * Get the number of the current day in a year. + * + * @param a timestamp + * @return Number of days corresponding to the current timestamp + */ + static getNumberDay(time?: number): number { + let dateArr = MONTH_LIST; + let date = time ? new Date(time) : new Date(); + let day = date.getDate(); + let month = date.getMonth(); + let year = date.getFullYear(); + let netYear = AppStorage.get(CACHE_HOLIDAYS_LIST)?.year || new Date().getFullYear(); + let leapYear = (year % 4 === 0 && year % 100 !== 0) || year % 4 === 0; + let result = 0; + for (let i = 0; i < month; i++) { + result += dateArr[i] + } + result += day; + // Check whether the year is a leap year. + if (month > 1 && leapYear) { + result += 1 + } + + if (netYear > year) { + result = leapYear ? result - 366 : result - 365; + } + + return result; + } + + /** + * Gets the description of the time between the current and next ringing + * + * @return The description of the time between the current and next ringing like '3 days 2 hours 1 minute” + */ + static async getLeftTimeToRingTime(isTimeChanged?: boolean): Promise { + let ringTimeInMs = await TimerManager.getTriggerTime(); + LogUtil.info(TAG, 'getLeftTimeToRingTime isTimeChanged:' + isTimeChanged) + if (isTimeChanged) { + LogUtil.info(TAG, 'start to deal TimeChanged event'); + const currentDate = new Date(); + const leftTimeInMs = ringTimeInMs - currentDate.getTime(); + if (leftTimeInMs <= 0) { + LogUtil.info(TAG, 'leftTimeInMs less than zero, before ringTimeInMs:' + ringTimeInMs); + await AlarmManager.updateTimer(); + ringTimeInMs = await TimerManager.getTriggerTime(); + } + } + const snoozeAlarmId = await SnoozeManager.getSnoozedAlarmId(); + LogUtil.info(TAG, 'snoozeAlarmId = ' + snoozeAlarmId); + let alarmList = await AlarmManager.getAllAlarms(); + let alarmEnable = false; + for (let i = 0; i < alarmList.length; i++) { + if (alarmList[i].enabled === true) { + alarmEnable = true; + } + } + if (alarmEnable === false && snoozeAlarmId && snoozeAlarmId.length === 0) { + return NO_ALARM_ENABLED; + } + return await TimeUtil.calculateRingTime(ringTimeInMs); + } + + /** + * Get Next Alarm Time + * + * @returns the next alarm time in ms + */ + static async getNextAlarmTime(): Promise { + const ringTimeInMs = await TimerManager.getTriggerTime(); + return ringTimeInMs; + } + + /** + * Calculate remaining ringing time + * + * @return The description of the time between the current and next ringing like '3 days 2 hours 1 minute” + */ + static async calculateRingTime(ringTimeInMs: number): Promise { + const currentDate = new Date(); + const leftTimeInMs = ringTimeInMs - currentDate.getTime(); + if (leftTimeInMs <= 0) { + return NO_ALARM_ENABLED; + } + const leftTime = new Date(leftTimeInMs); + const leftMinutes = leftTime.getUTCMinutes(); + const leftHours = leftTime.getUTCHours(); + const leftDays = leftTime.getUTCDate() - ONE_DAY; + if (leftDays === 0 && leftHours === 0 && leftMinutes < ONE_MINUTE) { + return LESS_THAN_ONE_MINUTE_FLAG; + } + const leftTimeStrParts: string[] = []; + if (leftDays != 0) { + // If the number of remaining days is not 0, the description of remaining days needs to be assembled. + const leftDayStr = await ResourceManager.getPluralStringAsync($r('app.plural.day').id, leftDays); + leftTimeStrParts.push(leftDayStr); + } + if (leftHours != 0) { + // If the number of remaining hours is not 0, the description of remaining hours needs to be assembled. + const leftHourStr = await ResourceManager.getPluralStringAsync($r('app.plural.hour').id, leftHours); + leftTimeStrParts.push(leftHourStr); + } + if (leftMinutes != 0) { + // If the number of remaining minutes is not 0, the description of the remaining minutes needs to be assembled. + const leftMinuteStr = await ResourceManager.getPluralStringAsync($r('app.plural.minute').id, leftMinutes); + leftTimeStrParts.push(leftMinuteStr); + } + return `${leftTimeStrParts.join(' ')}`; + } + + static async getUrl(): Promise { + let urlString: string = await CommonUtil.getStringByFile(GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager, 'config.json') + let urlJson: urlType = JSON.parse(urlString); + LogUtil.info(TAG, 'getUrl:' + urlJson.url); + return urlJson.url; + } + + /** + * Get Network Data on Official Workdays. + * + * Description: The obtained data is stored in AppStorage. + * + * @returns void + */ + static async getWorkDaysNetData(): Promise { + const url = await TimeUtil.getUrl(); + let httpRequest = http.createHttp(); + return httpRequest.request( + url, + { + method: http.RequestMethod.GET, + header: { + 'Content-Type': 'application/json' + }, + readTimeout: 60000, + connectTimeout: 60000 + }).then((data) => { + LogUtil.info(TAG, 'httpSeccuss' + JSON.stringify(data)); + let resultList: NetHolidaysType = JSON.parse(data.result.toString()); + if (!resultList) { + return false; + } + let holidaysData: HolidaysType = { + year: resultList.year, + version: resultList.version, + freeday: resultList.freeday, + workday: resultList.workday + }; + let yearData: HolidaysType | undefined = resultList?.allRecess?.find(item => item?.year === resultList?.year); + let newYearHolidaysData: number[] = yearData?.freeday?.filter(item => item < 0) || []; + holidaysData.freeday = holidaysData.freeday?.concat(newYearHolidaysData).sort((a, b) => a - b); + preferencesUtil.removePreferencesFromCache(GlobalContext.getContext() + .getObject('clockContext') as Context, 'WORK_DAY'); + TimeUtil.PutFreeday(JSON.stringify(holidaysData)); + AppStorage.SetOrCreate(CACHE_HOLIDAYS_LIST, holidaysData); + return true; + }).catch((err: Error) => { + LogUtil.info(TAG, 'httpReqError' + JSON.stringify(err)); + return false; + }) + } + + static async PutFreeday(list: string) { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, 'WORK_DAY'); + await preferences.put(CACHE_HOLIDAYS_LIST, list); + await preferences.flush(); + } + + static async getFreeday() { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, 'WORK_DAY'); + let list = await preferences.get(CACHE_HOLIDAYS_LIST, '{}') + LogUtil.info(TAG, 'getFreeday' + list); + AppStorage.SetOrCreate(CACHE_HOLIDAYS_LIST, JSON.parse(list as string) as HolidaysType); + } +} \ No newline at end of file diff --git a/common/src/main/ets/utils/TimerUtil.ets b/common/src/main/ets/utils/TimerUtil.ets deleted file mode 100644 index 34c2fb3..0000000 --- a/common/src/main/ets/utils/TimerUtil.ets +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import ConfigData from './ConfigData'; - -export default class TimerUtil { - /** - * Time format conversion - * - * @param timestamp - */ - static timeFormat(timestamp: number): string { - if (timestamp <= 0) { - return `00:00.00`; - } - let timeStr = ``; - let day = Math.floor(timestamp / ConfigData.ONE_DAY_TIME); - if (day > 0) { - timeStr += `${this.addZero(day)}:`; - } - let hour = Math.floor(timestamp % ConfigData.ONE_DAY_TIME / ConfigData.ONE_HOUR_TIME); - if (hour > 0 || day > 0) { - timeStr += `${this.addZero(hour)}:`; - } - let min = Math.floor(timestamp % ConfigData.ONE_HOUR_TIME / ConfigData.ONE_MINUTE_TIME); - timeStr += `${this.addZero(min)}:`; - let sec = Math.floor(timestamp % ConfigData.ONE_MINUTE_TIME / ConfigData.ONE_SECOND_TIME); - timeStr += `${this.addZero(sec)}.`; - let mSec = Math.floor(timestamp % ConfigData.ONE_SECOND_TIME / 10); - timeStr += this.addZero(mSec); - return timeStr; - } - - /** - * If the number is less than 10, add '0". - * - * @param num - */ - static addZero(num): string { - return (num < 10 ? `0${num}` : num).toString(); - } -} \ No newline at end of file diff --git a/common/src/main/ets/utils/WantAgentUtil.ets b/common/src/main/ets/utils/WantAgentUtil.ets new file mode 100644 index 0000000..9564c9a --- /dev/null +++ b/common/src/main/ets/utils/WantAgentUtil.ets @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { WantAgent } from '@ohos.wantAgent'; +import wantAgent from '@ohos.app.ability.wantAgent' +import { AlarmInfo } from '../manager'; +import { LogUtil } from './LogUtil'; +import { + AGENT_REQUEST_CODE_NORMAL, + AlarmServiceType, + TimerServiceType, + MAIN_ABILITY, + TIMER_ALERT_ABILITY, + TIMER_ALERT_BUNDLE, + TIMER_TIMER_ABILITY, + FULL_SCREEN_ABILITY, + FullScreenType +} from './types'; +import { BusinessError } from '@ohos.base'; +import { GlobalContext } from './GlobalContext'; +import common from '@ohos.app.ability.common'; +import Want from '@ohos.app.ability.Want'; + +const TAG = 'WantAgentUtil'; +// type ServiceExtensionContext = common.ServiceExtensionContext; + +/** + * WantAgent Util + * + * @since 2022-08-29 + */ +export class WantAgentUtil { + /** + * Get the WantAgent used by the timer to start the alarm ringing service. + * + * @param alarmInfo AlarmInfo + * @param serviceType AlarmServiceType Enum value + * @param isNotificationCloseButton Whether is button for closing the notification bar + * @return WantAgent used by the timer to start the alarm ringing service + */ + static async getAlarmWantAgent(alarmInfo: AlarmInfo, serviceType: AlarmServiceType, + isNotificationCloseButton?: boolean, isSnooze?: boolean): + Promise { + LogUtil.info(TAG, 'getAlarmWantAgent with serviceType:', serviceType); + try { + const wantAgentInfo: wantAgent.WantAgentInfo = { + wants: [ + { + bundleName: TIMER_ALERT_BUNDLE, + abilityName: TIMER_ALERT_ABILITY, + parameters: { + alarmInfo: alarmInfo, + serviceType: serviceType, + isNotificationCloseButton: isNotificationCloseButton as Object, + isSnooze: isSnooze as Object, + }, + } + ], + operationType: wantAgent.OperationType.START_ABILITY, + wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG], + requestCode: AGENT_REQUEST_CODE_NORMAL, + } + return await wantAgent.getWantAgent(wantAgentInfo); + } catch (error) { + LogUtil.error(TAG, 'getAlarmWantAgent failed: ', (error as BusinessError).message); + return; + } + } + + /** + * Get the WantAgent used by the AlarmInitSubscriber to start the alarm service. + * + * @param forceRefreshTimer The timer needs to be reset during startup. + * @return WantAgent used by the timer to start the alarm ringing service. + */ + static async getCommonEventWantAgent(forceRefreshTimer?: boolean): Promise { + try { + const wantAgentInfo: wantAgent.WantAgentInfo = { + wants: [ + { + bundleName: TIMER_ALERT_BUNDLE, + abilityName: TIMER_ALERT_ABILITY, + parameters: { + serviceType: AlarmServiceType.Update, + forceRefreshTimer: forceRefreshTimer as Object + }, + } + ], + operationType: wantAgent.OperationType.START_ABILITY, + wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG], + requestCode: AGENT_REQUEST_CODE_NORMAL, + } + return await wantAgent.getWantAgent(wantAgentInfo); + } catch (error) { + LogUtil.error(TAG, `getCommonEventWantAgent failed: ${JSON.stringify(error)}`); + return; + } + } + + /** + * Get the WantAgent used to start the full screen alarm page. + * + * @param alarmInfo AlarmInfo + * @return WantAgent used to start the full screen alarm page. + */ + static async getToggleFullScreenWantAgent(alarmInfo: AlarmInfo): Promise { + try { + const wantAgentInfo: wantAgent.WantAgentInfo = { + wants: [ + { + bundleName: TIMER_ALERT_BUNDLE, + abilityName: FULL_SCREEN_ABILITY, + parameters: { + fullScreenType: FullScreenType.Alarm, + alarmInfo: alarmInfo, + }, + } + ], + operationType: wantAgent.OperationType.START_ABILITY, + wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG], + requestCode: AGENT_REQUEST_CODE_NORMAL, + } + return await wantAgent.getWantAgent(wantAgentInfo); + } catch (error) { + LogUtil.error(TAG, `getToggleFullScreenWantAgent failed: ${JSON.stringify(error)}`); + return; + } + } + + /** + * Obtaining the WantAgent for Encapsulating the Home Page + * + * @return WantAgent used to start main page + */ + static async getMainPageWantAgent(alarmId?: number | string, isMissed?: boolean): Promise { + try { + const wantAgentInfo: wantAgent.WantAgentInfo = { + wants: [ + { + bundleName: TIMER_ALERT_BUNDLE, + abilityName: MAIN_ABILITY, + parameters: { + alarmId: alarmId ? alarmId : '', + isMissed: isMissed ? true : false + }, + } + ], + operationType: wantAgent.OperationType.START_ABILITY, + wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG], + requestCode: AGENT_REQUEST_CODE_NORMAL, + } + return await wantAgent.getWantAgent(wantAgentInfo); + } catch (error) { + LogUtil.error(TAG, `getMainPageWantAgent failed: ${JSON.stringify(error)}`); + return; + } + } + + /** + * Trigger the alarm to turn off + * + * @param alarmInfo AlarmInfo + */ + static async triggerCloseAlarm(alarmInfo: AlarmInfo): Promise { + if (!alarmInfo) { + LogUtil.error(TAG, 'Trigger close alarm failed, params is incorrect.'); + return; + } + const triggerWantAgent = await WantAgentUtil.getAlarmWantAgent(alarmInfo, AlarmServiceType.Close); + if (triggerWantAgent) { + wantAgent.trigger(triggerWantAgent, { + code: AGENT_REQUEST_CODE_NORMAL, + }, data => { + LogUtil.info(TAG, `trigger response: ${JSON.stringify(data)}`); + }); + } + } + + static async triggerDelayAlarm(alarmInfo: AlarmInfo): Promise { + if (!alarmInfo) { + LogUtil.error(TAG, 'Trigger Delay alarm failed, params is incorrect.'); + return; + } + const triggerWantAgent = await WantAgentUtil.getAlarmWantAgent(alarmInfo, AlarmServiceType.Delay); + if (triggerWantAgent) { + wantAgent.trigger(triggerWantAgent, { + code: AGENT_REQUEST_CODE_NORMAL, + }, data => { + LogUtil.info(TAG, `trigger response: ${JSON.stringify(data)}`); + }); + } + } + + static async getTimerMainPageWantAgent(tabActive: string): Promise { + try { + const wantAgentInfo: wantAgent.WantAgentInfo = { + wants: [ + { + bundleName: TIMER_ALERT_BUNDLE, + abilityName: MAIN_ABILITY, + parameters: { + tab_active: tabActive + }, + } + ], + operationType: wantAgent.OperationType.START_ABILITY, + wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG], + requestCode: AGENT_REQUEST_CODE_NORMAL, + } + return await wantAgent.getWantAgent(wantAgentInfo); + } catch (error) { + LogUtil.error(TAG, `getTimerMainPageWantAgent failed: ${JSON.stringify(error)}`); + return; + } + } + + static async getTimerFullScreenWantAgent(): Promise { + try { + const wantAgentInfo: wantAgent.WantAgentInfo = { + wants: [ + { + bundleName: TIMER_ALERT_BUNDLE, + abilityName: FULL_SCREEN_ABILITY, + parameters: { + fullScreenType: FullScreenType.Timer, + }, + } + ], + operationType: wantAgent.OperationType.START_ABILITY, + wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG], + requestCode: AGENT_REQUEST_CODE_NORMAL, + } + return await wantAgent.getWantAgent(wantAgentInfo); + } catch (error) { + LogUtil.error(TAG, `getTimerFullScreenWantAgent failed: ${JSON.stringify(error)}`); + return; + } + } + + static async timerWantAgent(serviceType: TimerServiceType, timerId: number | string, + alarmAlertTime?: number | string): + Promise { + LogUtil.info(TAG, 'timerWantAgent with serviceType:', serviceType); + try { + const wantAgentInfo: wantAgent.WantAgentInfo = { + wants: [ + { + bundleName: TIMER_ALERT_BUNDLE, + abilityName: TIMER_TIMER_ABILITY, + parameters: { + timerId: timerId, + alarmAlertTime: alarmAlertTime ? alarmAlertTime : 0, + serviceType: serviceType, + }, + } + ], + operationType: wantAgent.OperationType.START_ABILITY, + wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG], + requestCode: AGENT_REQUEST_CODE_NORMAL, + } + return await wantAgent.getWantAgent(wantAgentInfo); + } catch (error) { + LogUtil.error(TAG, 'timerWantAgent failed: ', (error as BusinessError).message); + return; + } + } + + static async triggerTimerMainPage(tabActive: string): Promise { + LogUtil.info(TAG, `triggerTimerMainPage`); + const triggerWantAgent = await WantAgentUtil.getTimerMainPageWantAgent(tabActive); + if (triggerWantAgent) { + wantAgent.trigger( + triggerWantAgent, + { code: AGENT_REQUEST_CODE_NORMAL }, + (data) => { + LogUtil.info(TAG, `triggerTimerMainPage res ${JSON.stringify(data)}`); + } + ); + } + } + + static async triggerStartTimer(timerId: number | string, alarmAlertTime: number | string): Promise { + LogUtil.info(TAG, `triggerStartTimer`); + const triggerWantAgent = await WantAgentUtil.timerWantAgent(TimerServiceType.Start, timerId, alarmAlertTime); + if (triggerWantAgent) { + wantAgent.trigger(triggerWantAgent, { + code: AGENT_REQUEST_CODE_NORMAL, + }, data => { + LogUtil.info(TAG, `triggerStartTimer response: ${JSON.stringify(data)}`); + }); + } + } + + static async triggerPauseTimer(timerId: number | string, alarmAlertTime: number | string): Promise { + LogUtil.info(TAG, `triggerPauseTimer`); + const triggerWantAgent = await WantAgentUtil.timerWantAgent(TimerServiceType.Pause, timerId, alarmAlertTime); + if (triggerWantAgent) { + wantAgent.trigger(triggerWantAgent, { + code: AGENT_REQUEST_CODE_NORMAL, + }, data => { + LogUtil.info(TAG, `triggerPauseTimer response: ${JSON.stringify(data)}`); + }); + } + } + + + static async triggerCloseTimer(timerId: number | string, alarmAlertTime?: number | string): Promise { + LogUtil.info(TAG, `triggerCloseTimer`); + const triggerWantAgent = await WantAgentUtil.timerWantAgent(TimerServiceType.Close, timerId); + if (triggerWantAgent) { + wantAgent.trigger(triggerWantAgent, { + code: AGENT_REQUEST_CODE_NORMAL, + }, data => { + LogUtil.info(TAG, `triggerCloseTimer response: ${JSON.stringify(data)}`); + }); + } + } + + static async triggerFiringTimer(timerId: number | string, alarmAlertTime: number | string): Promise { + LogUtil.info(TAG, `triggerFiringTimer`); + const triggerWantAgent = await WantAgentUtil.timerWantAgent(TimerServiceType.Firing, timerId, alarmAlertTime); + if (triggerWantAgent) { + wantAgent.trigger(triggerWantAgent, { + code: AGENT_REQUEST_CODE_NORMAL, + }, data => { + LogUtil.info(TAG, `triggerFiringTimer response: ${JSON.stringify(data)}`); + }); + } + } + + static async triggerFullScreenAbility(fullScreenType: FullScreenType, timerId?: number | string, + alarmAlertTime?: number | string): Promise { + LogUtil.info(TAG, `triggerFullScreenAbility fullScreenType=${fullScreenType}`); + try { + // const context = GlobalContext.getContext().getObject('clockContext') as ServiceExtensionContext; + // const contextType = GlobalContext.getContext().getObject('clockContextType') as string; + // if (context && contextType) { + // await context.startAbility({ + // bundleName: TIMER_ALERT_BUNDLE, + // abilityName: FULL_SCREEN_ABILITY, + // parameters: { + // fullScreenType: fullScreenType, + // timerId: timerId ? timerId : -1, + // alarmAlertTime: alarmAlertTime ? alarmAlertTime : 0, + // }, + // }); + // } + } catch (error) { + LogUtil.error(TAG, 'triggerFullScreenAbility failed: ', (error as BusinessError).message); + return; + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/utils/index.ets b/common/src/main/ets/utils/index.ets new file mode 100644 index 0000000..a192342 --- /dev/null +++ b/common/src/main/ets/utils/index.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { TimeUtil } from './TimeUtil'; + +export { LogUtil } from './LogUtil'; + +export { FormUtil } from './FormUtil'; + +export { WantAgentUtil } from './WantAgentUtil'; + +export { EventReportUtil } from './EventReportUtil'; + +export { DisplayUtil } from './DisplayUtil'; + +export { CommonUtil } from './CommonUtil'; + +export { GlobalContext } from './GlobalContext'; + +export { RingtoneSelectionUtil } from './RingtoneSelectionUtil'; + +export { SettingUtil } from './SettingUtil'; + +export { StringUtil } from './StringUtil'; + +export { NotificationContentUtil } from './NotificationContentUtil'; + +export * from './types'; + +export { EventName, EventResult } from './EventType'; \ No newline at end of file diff --git a/common/src/main/ets/utils/types.ets b/common/src/main/ets/utils/types.ets new file mode 100644 index 0000000..1528db8 --- /dev/null +++ b/common/src/main/ets/utils/types.ets @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// 时段标签的资源ID列表,当前只有中文有时段区分,英文只区分 AM、PM +export const TIME_TAG_ID_LIST_IN_ZH = [ + $r('app.string.very_early_morning').id, // 凌晨 1 ~ 4 点,4 个小时,此列表一格占两个小时,"凌晨"重复两次 + $r('app.string.very_early_morning').id, + $r('app.string.dawn').id, // 清晨 5 ~ 6 点 + $r('app.string.early_morning').id, // 早上 7 ~ 8 点 + $r('app.string.morning').id, // 上午 9 ~ 10 点 + $r('app.string.midday').id, // 中午 11 ~ 12 点 + $r('app.string.afternoon').id, // 下午 13 ~ 16 点,4 个小时,此列表一格占两个小时,"下午"重复两次 + $r('app.string.afternoon').id, + $r('app.string.evening').id, // 傍晚 17 ~ 18 点 + $r('app.string.night').id, // 晚上 19 ~ 22 点,4 个小时,此列表一格占两个小时,"晚上"重复两次 + $r('app.string.night').id, + $r('app.string.midnight').id, // 半夜,23点到0点 +]; + +// 中文的语言编码 +export const LANGUAGE_ZH = 'zh'; + +// 时间段描述分组大小,此处取值为2,表示 TIME_TAG_ID_LIST 列表中一个元素代表两个小时 +export const TIME_TAG_GROUP_SIZE = 2; + +// 时间段描述的下标偏移量,此处取值为1,则下标为 0 的时间段,对应的开始时间为 0 + 1 点 +export const INDEX_OFFSET = 1; + +// 最后一个时间段的资源 ID,用于对 0 点的特殊处理 +export const LAST_TAG_ID = TIME_TAG_ID_LIST_IN_ZH[TIME_TAG_ID_LIST_IN_ZH.length - 1]; + +// Days of a week +export const DAYS_OF_WEEK_COUNT = 7; + +// Number of working days of the week +export const DAYS_OF_WEEK_WORK_DAY_COUNT = 5; + +// Friday index in daysOfWeek +export const INDEX_FRIDAY = 5; + +// Number of days off in the week +export const DAYS_OF_WEEK_FREE_DAY_COUNT = 2; + +// daysOfWeek数组内周六的索引 +export const INDEX_SATURDAY = 6; + +export const AFTERNOON_START_POINT = 12; + +export const SINGLE_NUMBER_END_POINT = 10; + +export const ONE_DAY = 1; + +export const ONE_MINUTE = 1; + +// 距离响铃时间少于一分钟的标记,用于前台展示时,读取不同的字符串资源 +export const LESS_THAN_ONE_MINUTE_FLAG = 'LESS_THAN_ONE_MINUTE_FLAG'; + +export const NO_ALARM_ENABLED = 'NO_ALARM_ENABLED'; + +// 时间相关的资源ID列表,前台资源预加载时传入,预加载后即可在同步方法中获取相关字段 +const timeDescStringIdList: number[] = [ + $r('app.string.only_ring_once').id, + $r('app.string.every_day').id, + $r('app.string.monday_to_friday').id, + $r('app.string.default_alarm_label').id, + $r('app.string.default_alarm_label_new').id, +] +for (let i = 0; i < TIME_TAG_ID_LIST_IN_ZH.length; ++i) { + timeDescStringIdList.push(TIME_TAG_ID_LIST_IN_ZH[i]) +} + +export const TIME_DESC_STRING_ID_LIST = timeDescStringIdList + +// 获取 “某个数字对应的是周几” 时,调用 JS 接口使用的格式(资料推荐使用 JS 接口查询,而不是提翻译) +export enum WeekDayFormat { + Short = 'short', // 中文是周一、周二、周三等,英文是Mon、Tue、Web等,小语种按照业界通用方式翻译 + Long = 'long', // 中文是星期一、星期二、星期三等,英文是Monday、Tuesday、Wednesday等,小语种按照业界通用方式翻译 +} + +// 获取 “某个数字对应的是周几” 时,使用的基准时间(相关JS 接口,年、月传 0,日传入 daysOfWeek 中存放的数字,即可完成周几的本地化转换) +export const WEEK_DAY_BASIC_YEAR = 2023; + +export const WEEK_DAY_BASIC_MONTH = 0; + +// 填充 Date 对象入参的默认年、月、日,此处的年和月 +export const DEFAULT_YEAR = 0; + +export const DEFAULT_MONTH = 0; + +export const DEFAULT_DAY = 0; + +// 拆分12 小时制格式化时间各部分的正则表达式 +export const HOUR_12_TIME_FORMAT_REG_EXP: RegExp = new RegExp('(\\D*)(\\d+)(.)(\\d+)(.*)'); + +// The regular expression for splitting each part of the 12-hour format time, +// for example:"zh: 9:30:00 a.m. en:9:30:00 AM." +export const HOUR_12_TIME_FORMAT_REG_EXP_WITH_SECOND: RegExp = new RegExp('(\\D*)(\\d+)(.)(\\d+)(.)(\\d+)(.*)'); + +// 格式化时间拆分后的下标 +export enum TimeFormatIndex { + + LEFT_TAG = 1, + HOUR = 2, + MINUTE = 4, + RIGHT_TAG = 5, + RIGHT_TAG_WITH_SECOND = 7, +} + +// 格式化时间返回的对象类型 +export interface FormatTime { + tag?: string, // 时间段标签,凌晨、清晨、早上等 + time: string, // 时间,用于前台展示,如 08:10 + isTagLeft?: boolean, // 时间段标签是否在左边 +} + +type DigitTimeFormat = '2-digit' | 'numeric'; + +// 数字时间格式 (个位数时,在前面填一个零) +export const DIGIT_TIME_FORMAT: DigitTimeFormat = '2-digit'; + +// 闹钟触发时,拉起的 ability 归属的包名 +export const TIMER_ALERT_BUNDLE = 'com.ohos.clock'; + +// 闹钟触发时,拉起的 ability 的名称 +export const TIMER_ALERT_ABILITY = 'com.ohos.clock.AlarmService'; + +// 定时器触发时,拉起的 ability 的名称 +export const TIMER_TIMER_ABILITY = 'com.ohos.clock.TimerService'; + +export const FULL_SCREEN_ABILITY = 'com.ohos.clock.FullScreenAbility'; + +// Ability to pull up when full screen starts +export const FULL_SCREEN_ALARM_ABILITY = 'com.ohos.clock.ForegroundAbility'; + +// Name of the home page. +export const MAIN_ABILITY = 'com.ohos.clock.phone'; + +// want agent 的 request code +export const AGENT_REQUEST_CODE_NORMAL = 0; + +export enum AlarmServiceType { + Start = 'Start', + Close = 'Close', + ReStart = 'ReStart', + Delay = 'Delay', + Update = 'Update', + ToggleFullScreen = 'ToggleFullScreen', +} + +export enum TimerServiceType { + Start = 'Start', + Pause = 'Pause', + Close = 'Close', + Firing = 'Firing', + ToggleFullScreen = 'ToggleFullScreen', +} + +export enum FullScreenType { + Timer = 'Timer', + Alarm = 'Alarm', +} + +export enum CalendarFiled { + Hour_OF_Day = 'hour_of_day', + Minute = 'minute', + Second = 'second', + Date = 'date', + Year = 'year', + Month = 'month', +} + +export const WAKE_DAYS_TYPE = 4; + +export const ONES_TYPE = 0; + +export const MONTH_LIST = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +export const SATURDAY = 6; + +export const SUNDAY = 0; + +export const CACHE_HOLIDAYS_LIST = 'cache_holidays_list'; + +export interface HolidaysType { + year: number, + version: String, + workday: number[], + freeday: number[] +} + +export interface NetHolidaysType { + year: number, + version: String, + workday: number[], + freeday: number[], + allRecess: HolidaysType[] +} + +export const TIMER_NOTICE_ID = '99999' + +export const PRESET_CITY_BEIJING: string = 'Beijing'; + +export const PRESET_CITY_LONDON: string = 'London'; + +export const PRESET_CITY_PARIS: string = 'Paris'; diff --git a/common/src/main/resources/base/element/color.json b/common/src/main/resources/base/element/color.json index 2176617..3464cf8 100644 --- a/common/src/main/resources/base/element/color.json +++ b/common/src/main/resources/base/element/color.json @@ -1,24 +1,240 @@ { "color": [ { - "name": "white", + "name": "dial_top", + "value": "#CCE6E9F0" + }, + { + "name": "dial_center", + "value": "#CCF0F2F7" + }, + { + "name": "dial_bottom", "value": "#FFFFFF" }, { - "name": "background_color", - "value": "#F1F3F5" + "name": "scale_bold_top", + "value": "#3319326C" + }, + { + "name": "scale_bold_center", + "value": "#408295AB" + }, + { + "name": "scale_bold_bottom", + "value": "#59CED8E6" + }, + { + "name": "scale_top", + "value": "#33203973" + }, + { + "name": "scale_bottom", + "value": "#4DD1DAE6" + }, + { + "name": "dial_shadow_inside", + "value": "#99FFFFFF" + }, + { + "name": "dial_shadow_outside", + "value": "#086B7794" + }, + { + "name": "divider_color", + "value": "#0D000000" + }, + { + "name": "divider_text_input_color", + "value": "#000000" + }, + { + "name": "color_control_activated", + "value": "#0A59F7" + }, + { + "name": "transparent", + "value": "#00000000" + }, + { + "name": "color_fab_shadow", + "value": "#4D0070E5" + }, + { + "name": "color_fab_bg", + "value": "#0A59F7" + }, + { + "name": "color_fab_t_bg", + "value": "#564AF7" + }, + { + "name": "color_fab_bg_onclick", + "value": "#0070E5" + }, + { + "name": "digital_clock_text_color", + "value": "#E5000000" + }, + { + "name": "dialog_delete_button", + "value": "#E84026" + }, + { + "name": "dialog_background_color", + "value": "#FFFFFF" + }, + { + "name": "dialog_background_caret_color", + "value": "#00FFFFFF" + }, + { + "name": "color_black", + "value": "#666666" + }, + { + "name": "id_color_transparent", + "value": "#00000000" + }, + { + "name": "ohos_id_color_component_activated", + "value": "#330A59F7" + }, + { + "name": "ohos_id_color_component_activated_disabled", + "value": "#140A59F7" + }, + { + "name": "ohos_id_color_component_activated2", + "value": "#660A59F7" + }, + { + "name": "ohos_id_color_component_activated2_disabled", + "value": "#290A59F7" + }, + { + "name": "ohos_id_color_foreground_contrary", + "value": "#66FFFFFF" + }, + { + "name": "ohos_id_color_foreground_contrary_disabled", + "value": "#29FFFFFF" + }, + { + "name": "ohos_id_color_hover", + "value": "#0C000000" + }, + { + "name": "color_hover_normal_button", + "value": "#19000000" + }, + { + "name": "ohos_id_color_button_normal_disabled", + "value": "#05000000" + }, + { + "name": "color_press_text_button", + "value": "#19000000" + }, + { + "name": "color_press_normal_button", + "value": "#26000000" + }, + { + "name": "color_normal_button_waiting", + "value": "#66000000" + }, + { + "name": "color_normal_button_waiting_disabled", + "value": "#29000000" + }, + { + "name": "color_press_emphasize_button", + "value": "#0A59F7" + }, + { + "name": "color_button_text_normal_disabled", + "value": "#8caeee" + }, + { + "name": "color_button_text_normal", + "value": "#0A59F7" + }, + { + "name": "color_hover_emphasize_button", + "value": "#0954EA" + }, + { + "name": "ohos_id_color_emphasize", + "value": "#0A59F7" + }, + { + "name": "color_warning_text_disabled", + "value": "#66FA2A2D" + }, + { + "name": "color_normal_text", + "value": "#E5000000" }, { - "name": "background_color_white", + "name": "color_normal_text_disabled", + "value": "#5C000000" + }, + { + "name": "color_emphasize_text", + "value": "#FFFFFF" + }, + { + "name": "color_emphasize_text_disabled", + "value": "#FAFAFA" + }, + { + "name": "color_white", + "value": "#FFFFFF" + }, + { + "name": "color_fab_bg_white_pressed", + "value": "#DADBDD" + }, + { + "name": "color_fab_bg_white_disabled", + "value": "#F7F8FA" + }, + { + "name": "color_fab_icon_white_disabled", + "value": "#D3D4D6" + }, + { + "name": "color_fab_bg_disabled", + "value": "#93B6F8" + }, + { + "name": "color_fab_icon_disabled", + "value": "#B9D1F7" + }, + { + "name": "color_hover_disabled", + "value": "#00000000" + }, + { + "name": "color_hover", + "value": "#0D000000" + }, + { + "name": "play_btn", "value": "#FFFFFF" }, { - "name": "text_color", - "value": "#182431" + "name": "color_hover_shadow", + "value": "#1A000000" }, { - "name": "selected_text_color", - "value": "#007DFF" + "name": "component_ultra_thick_panel", + "value": "#F1F3F5" + }, + { + "name": "component_ultra_thick_dialog", + "value": "#FFFFFF" } ] } \ No newline at end of file diff --git a/common/src/main/resources/base/element/float.json b/common/src/main/resources/base/element/float.json new file mode 100644 index 0000000..d93def3 --- /dev/null +++ b/common/src/main/resources/base/element/float.json @@ -0,0 +1,512 @@ +{ + "float": [ + { + "name": "dial_top_position", + "value": "0" + }, + { + "name": "dial_center_position", + "value": "0.38" + }, + { + "name": "dial_bottom_position", + "value": "1" + }, + { + "name": "dial_shadow_radius", + "value": "28vp" + }, + { + "name": "dial_shadow_inside_x", + "value": "0vp" + }, + { + "name": "dial_shadow_inside_y", + "value": "-20vp" + }, + { + "name": "dial_shadow_outside_x", + "value": "0vp" + }, + { + "name": "dial_shadow_outside_y", + "value": "23vp" + }, + { + "name": "scale_top_position", + "value": "0" + }, + { + "name": "scale_center_position", + "value": "0.5" + }, + { + "name": "scale_bottom_position", + "value": "1" + }, + { + "name": "card_padding_vertical", + "value": "4vp" + }, + { + "name": "card_padding_horizontal", + "value": "4vp" + }, + { + "name": "card_inner_padding_horizontal", + "value": "8vp" + }, + { + "name": "card_inner_margin_horizontal", + "value": "3vp" + }, + { + "name": "timer_pick_padding_vertical", + "value": "16vp" + }, + { + "name": "card_margin_start", + "value": "12vp" + }, + { + "name": "card_margin_end", + "value": "12vp" + }, + { + "name": "build_time_picker", + "value": "4vp" + }, + { + "name": "card_margin_top", + "value": "8vp" + }, + { + "name": "card_margin_bottom", + "value": "4vp" + }, + { + "name": "default_corner_radius_l", + "value": "16vp" + }, + { + "name": "card_content_height", + "value": "64vp" + }, + { + "name": "card_tag_margin_end", + "value": "4vp" + }, + { + "name": "alarmCard_24", + "value": "24dp" + }, + { + "name": "card_title_line_margin", + "value": "2vp" + }, + { + "name": "hour_shadow_offset_x", + "value": "5vp" + }, + { + "name": "hour_shadow_offset_y", + "value": "1vp" + }, + { + "name": "minute_shadow_offset_x", + "value": "0vp" + }, + { + "name": "minute_shadow_offset_y", + "value": "3vp" + }, + { + "name": "second_shadow_offset_x", + "value": "-4vp" + }, + { + "name": "second_shadow_offset_y", + "value": "-3vp" + }, + { + "name": "text_size_headline3", + "value": "60fp" + }, + { + "name": "right_icon_height", + "value": "24vp" + }, + { + "name": "right_icon_width", + "value": "12vp" + }, + { + "name": "header_footer_height", + "value": "56vp" + }, + { + "name": "header_textHeight", + "value": "40vp" + }, + { + "name": "header_lineHeight", + "value": "35vp" + }, + { + "name": "header_lineHeight_addTitle", + "value": "28vp" + }, + { + "name": "header_fontSize", + "value": "30sp" + }, + { + "name": "header_fontSize_addTitle", + "value": "20sp" + }, + { + "name": "header_fontSizes", + "value": "20sp" + }, + { + "name": "dialog_padding_horizontal", + "value": "24vp" + }, + { + "name": "dialog_button_height", + "value": "40vp" + }, + { + "name": "dialog_content_padding_vertical", + "value": "8vp" + }, + { + "name": "dialog_button_divider_margin", + "value": "4vp" + }, + { + "name": "dialog_button_divider_height", + "value": "24vp" + }, + { + "name": "dialog_button_divider_width", + "value": "2vp" + }, + { + "name": "dialog_margin_bottom", + "value": "-16vp" + }, + { + "name": "input_height", + "value": "48vp" + }, + { + "name": "button_size", + "value": "56vp" + }, + { + "name": "new_button_size", + "value": "40vp" + }, + { + "name": "button_shadow_radius", + "value": "8vp" + }, + { + "name": "button_shadow_x", + "value": "0vp" + }, + { + "name": "button_shadow_y", + "value": "6vp" + }, + { + "name": "button_shadow_y2", + "value": "8vp" + }, + { + "name": "button_icon_size", + "value": "24vp" + }, + { + "name": "button_click_size", + "value": "48vp" + }, + { + "name": "button_icon_on", + "value": "32vp" + }, + { + "name": "digital_clock_text_size_normal", + "value": "18dp" + }, + { + "name": "digital_clock_text_size_normal_18", + "value": "18dp" + }, + { + "name": "digital_clock_text_size_normal_56", + "value": "56dp" + }, + { + "name": "digital_clock_text_margin", + "value": "4vp" + }, + { + "name": "confirm_dialog_padding_horizontal", + "value": "16vp" + }, + { + "name": "confirm_dialog_content_padding_top", + "value": "24vp" + }, + { + "name": "dilog_width", + "value": "400vp" + }, + { + "name": "confirm_dialog_content_padding_bottom", + "value": "8vp" + }, + { + "name": "dialog_button_divider_margin_vertical", + "value": "8vp" + }, + { + "name": "confirm_dialog_border", + "value": "20vp" + }, + { + "name": "confirm_dialog_divider_height", + "value": "24vp" + }, + { + "name": "confirm_dialog_divider_width", + "value": "1vp" + }, + { + "name": "set_alarm_card_height", + "value": "48vp" + }, + { + "name": "button_back", + "value": "56vp" + }, + { + "name": "common_grid_margin", + "value": "12vp" + }, + { + "name": "common_grid_margin_zero", + "value": "0vp" + }, + { + "name": "common_text_input_padding_zero", + "value": "0vp" + }, + { + "name": "float_content_bottom_margin", + "value": "8vp" + }, + { + "name": "float_divider_height", + "value": "24vp" + }, + { + "name": "float_divider_padding", + "value": "7vp" + }, + { + "name": "float_title_height", + "value": "56vp" + }, + { + "name": "float_content_padding", + "value": "8vp" + }, + { + "name": "float_container_padding", + "value": "16vp" + }, + { + "name": "ringtone_selection_item_height", + "value": "48vp" + }, + { + "name": "ringtone_selection_section_header_size", + "value": "12fp" + }, + { + "name": "ringtone_selection_section_header_margin_vertical", + "value": "8vp" + }, + { + "name": "ringtone_selection_section_header_margin_horizontal", + "value": "24vp" + }, + { + "name": "radio_size", + "value": "20vp" + }, + { + "name": "appbar_icon_margin", + "value": "16vp" + }, + { + "name": "appbar_fold_icon_margin", + "value": "26vp" + }, + { + "name": "button_icon_hover_padding", + "value": "12vp" + }, + { + "name": "button_icon_left_padding", + "value": "16vp" + }, + { + "name": "button_icon_right_padding", + "value": "8vp" + }, + { + "name": "button_operation_right_padding", + "value": "12vp" + }, + { + "name": "cancel_bottom_margin", + "value": "12vp" + }, + { + "name": "delete_padding_left", + "value": "104.5vp" + }, + { + "name": "delete_padding_right", + "value": "104.5vp" + }, + { + "name": "large_digital_clock_height", + "value": "70vp" + }, + { + "name": "large_btn_height", + "value": "56vp" + }, + { + "name": "large_btn_width", + "value": "56vp" + }, + { + "name": "list_item_height", + "value": "48vp" + }, + { + "name": "card_double_height", + "value": "110vp" + }, + { + "name": "pc_button_size", + "value": "40vp" + }, + { + "name": "pc_add_button_size", + "value": "56vp" + }, + { + "name": "pc_padding", + "value": "56vp" + }, + { + "name": "pc_list_scroll_padding", + "value": "4vp" + }, + { + "name": "fold_first_top", + "value": "-2vp" + }, + { + "name": "fold_first_right", + "value": "4vp" + }, + { + "name": "fold_cancel_right", + "value": "-4vp" + }, + { + "name": "fold_first_bottom", + "value": "6vp" + }, + { + "name": "fold_second_bottom", + "value": "18vp" + }, + { + "name": "pc_button_icon_size", + "value": "18vp" + }, + { + "name": "list_item_padding", + "value": "13vp" + }, + { + "name": "menu_content_padding", + "value": "4vp" + }, + { + "name": "menu_item_icon", + "value": "25vp" + }, + { + "name": "dial_margin_50", + "value": "50vp" + }, + { + "name": "dial_margin_47", + "value": "47vp" + }, + { + "name": "dial_margin_32", + "value": "32vp" + }, + { + "name": "dial_margin_29", + "value": "29vp" + }, + { + "name": "dial_margin_24", + "value": "24vp" + }, + { + "name": "canvas_size", + "value": "45vp" + }, + { + "name": "canvas_border_radius", + "value": "45vp" + }, + { + "name": "card_inner_padding_10", + "value": "10vp" + }, + { + "name": "card_inner_padding_11", + "value": "11vp" + }, + { + "name": "date_info_margin_bottom", + "value": "16vp" + }, + { + "name": "card_item_margin_horizontal__8", + "value": "-8vp" + }, + { + "name": "pc_digital_clock_height", + "value": "74vp" + }, + { + "name": "phone_show_both_digital_clock_height", + "value": "70vp" + } + ] +} diff --git a/common/src/main/resources/base/element/plural.json b/common/src/main/resources/base/element/plural.json new file mode 100644 index 0000000..74cf5fb --- /dev/null +++ b/common/src/main/resources/base/element/plural.json @@ -0,0 +1,43 @@ +{ + "plural":[ + { + "name": "minute", + "value": [ + { + "quantity":"one", + "value":"%d minute" + }, + { + "quantity":"other", + "value":"%d minutes" + } + ] + }, + { + "name": "hour", + "value": [ + { + "quantity":"one", + "value":"%d hour" + }, + { + "quantity":"other", + "value":"%d hours" + } + ] + }, + { + "name": "day", + "value": [ + { + "quantity":"one", + "value":"%d day" + }, + { + "quantity":"other", + "value":"%d days" + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/base/element/string.json b/common/src/main/resources/base/element/string.json index d2dab5d..b5e218e 100644 --- a/common/src/main/resources/base/element/string.json +++ b/common/src/main/resources/base/element/string.json @@ -1,20 +1,48 @@ { - "string": [ + "string":[ { - "name": "page_show", - "value": "page from npm package" + "name":"only_ring_once", + "value": "Custom" }, { - "name": "alarm_clock", - "value": "Alarm clock" + "name":"every_day", + "value":"Every day" }, { - "name": "stopwatch", - "value": "stopwatch" + "name":"cancel", + "value":"Cancel" }, { - "name": "timer_clock", - "value": "hour meter" + "name":"ok", + "value":"OK" + }, + { + "name":"alarm_clock", + "value":"Alarm clock" + }, + { + "name":"monday_to_friday", + "value":"Monday to Friday" + }, + { + "name":"default_alarm_label", + "value":"Good morning" + }, + { + "name":"default_alarm_label_new", + "value":"Good morning" + }, + { + "name":"text_with_separate", + "value":"%1$s, %2$s" + }, + { + "name": "timer_title_new", + "value": "Timer" + }, + { + "name": "confirm_delete", + "value": "Delete" } ] -} +} \ No newline at end of file diff --git a/common/src/main/resources/base/media/ic_add.svg b/common/src/main/resources/base/media/ic_add.svg new file mode 100644 index 0000000..9b57855 --- /dev/null +++ b/common/src/main/resources/base/media/ic_add.svg @@ -0,0 +1,7 @@ + + + ic_add_filled + + + + \ No newline at end of file diff --git a/common/src/main/resources/base/media/ic_cancel.svg b/common/src/main/resources/base/media/ic_cancel.svg new file mode 100644 index 0000000..89c3012 --- /dev/null +++ b/common/src/main/resources/base/media/ic_cancel.svg @@ -0,0 +1,13 @@ + + + ic_public_cancel + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/base/media/ic_confirm.svg b/common/src/main/resources/base/media/ic_confirm.svg new file mode 100644 index 0000000..10d8477 --- /dev/null +++ b/common/src/main/resources/base/media/ic_confirm.svg @@ -0,0 +1,13 @@ + + + ic_public_ok + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/base/media/ic_public_ok.svg b/common/src/main/resources/base/media/ic_public_ok.svg new file mode 100644 index 0000000..10d8477 --- /dev/null +++ b/common/src/main/resources/base/media/ic_public_ok.svg @@ -0,0 +1,13 @@ + + + ic_public_ok + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/base/media/ic_reset.svg b/common/src/main/resources/base/media/ic_reset.svg new file mode 100644 index 0000000..fc01a22 --- /dev/null +++ b/common/src/main/resources/base/media/ic_reset.svg @@ -0,0 +1,6 @@ + + ic_clock_timer2备份 + + + + \ No newline at end of file diff --git a/common/src/main/resources/base/media/ic_ringtone.svg b/common/src/main/resources/base/media/ic_ringtone.svg new file mode 100644 index 0000000..b953896 --- /dev/null +++ b/common/src/main/resources/base/media/ic_ringtone.svg @@ -0,0 +1,8 @@ + + + diff --git a/common/src/main/resources/base/media/ic_stopwatch_lap.svg b/common/src/main/resources/base/media/ic_stopwatch_lap.svg new file mode 100644 index 0000000..94a3248 --- /dev/null +++ b/common/src/main/resources/base/media/ic_stopwatch_lap.svg @@ -0,0 +1,9 @@ + + ic_clock_timer2备份 + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/base/media/img_clock_dial_scale_day.png b/common/src/main/resources/base/media/img_clock_dial_scale_day.png new file mode 100644 index 0000000000000000000000000000000000000000..0ae8816b4cb7a1fc6528e536a93b2c35220d4927 GIT binary patch literal 19351 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 QPyhe`07*qoM6N<$g0vQ=y#N3J literal 0 HcmV?d00001 diff --git a/common/src/main/resources/bo_CN/element/plural.json b/common/src/main/resources/bo_CN/element/plural.json new file mode 100644 index 0000000..a7b2f5d --- /dev/null +++ b/common/src/main/resources/bo_CN/element/plural.json @@ -0,0 +1,31 @@ +{ + "plural":[ + { + "name":"hour", + "value":[ + { + "quantity":"other", + "value":"ཆུ་ཚོད་ %d" + } + ] + }, + { + "name":"day", + "value":[ + { + "quantity":"other", + "value":"ཉིན་ %d" + } + ] + }, + { + "name":"minute", + "value":[ + { + "quantity":"other", + "value":"སྐར་མ་ %d" + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/bo_CN/element/string.json b/common/src/main/resources/bo_CN/element/string.json new file mode 100644 index 0000000..b436fbb --- /dev/null +++ b/common/src/main/resources/bo_CN/element/string.json @@ -0,0 +1,40 @@ +{ + "string":[ + { + "name":"monday_to_friday", + "value":"གཟའ་ཟླ་བ་ནས་གཟའ་པ་སངས་བར།" + }, + { + "name":"default_alarm_label_new", + "value":"ཞོགས་པ་བདེ་ལེགས།" + }, + { + "name":"default_alarm_label", + "value":"དཔེ་མཚོན། ཞོགས་པ་བདེ་ལེགས།" + }, + { + "name":"cancel", + "value":"འདོར་བ།" + }, + { + "name":"text_with_separate", + "value":"%1$s %2$s" + }, + { + "name":"timer_title_new", + "value":"དུས་རྩིས་ཆས།" + }, + { + "name":"alarm_clock", + "value":"ཐིར་ལྡན་ཆུ་ཚོད།" + }, + { + "name":"every_day", + "value":"ཉིན་རེར།" + }, + { + "name":"ok", + "value":"གཏན་འཁེལ།" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/dark/element/color.json b/common/src/main/resources/dark/element/color.json new file mode 100644 index 0000000..6605b64 --- /dev/null +++ b/common/src/main/resources/dark/element/color.json @@ -0,0 +1,228 @@ +{ + "color": [ + { + "name": "dial_top", + "value": "#CCE6E9F0" + }, + { + "name": "dial_center", + "value": "#CCF0F2F7" + }, + { + "name": "dial_bottom", + "value": "#FFFFFF" + }, + { + "name": "scale_bold_top", + "value": "#3319326C" + }, + { + "name": "scale_bold_center", + "value": "#408295AB" + }, + { + "name": "scale_bold_bottom", + "value": "#59CED8E6" + }, + { + "name": "scale_top", + "value": "#33203973" + }, + { + "name": "scale_bottom", + "value": "#4DD1DAE6" + }, + { + "name": "dial_shadow_inside", + "value": "#99FFFFFF" + }, + { + "name": "dial_shadow_outside", + "value": "#086B7794" + }, + { + "name": "divider_color", + "value": "#0D000000" + }, + { + "name": "divider_text_input_color", + "value": "#000000" + }, + { + "name": "color_control_activated", + "value": "#5291FF" + }, + { + "name": "transparent", + "value": "#00000000" + }, + { + "name": "color_fab_shadow", + "value": "#4D0070E5" + }, + { + "name": "color_fab_bg", + "value": "#317AF7" + }, + { + "name": "color_fab_t_bg", + "value": "#4796C4" + }, + { + "name": "color_fab_bg_onclick", + "value": "#317AF7" + }, + { + "name": "digital_clock_text_color", + "value": "#E5FFFFFF" + }, + { + "name": "dialog_delete_button", + "value": "#E84026" + }, + { + "name": "dialog_background_color", + "value": "#FFFFFF" + }, + { + "name": "color_black", + "value": "#99FFFFFF" + }, + { + "name": "id_color_transparent", + "value": "#00000000" + }, + { + "name": "ohos_id_color_component_activated", + "value": "#330A59F7" + }, + { + "name": "ohos_id_color_component_activated_disabled", + "value": "#140A59F7" + }, + { + "name": "ohos_id_color_component_activated2", + "value": "#660A59F7" + }, + { + "name": "ohos_id_color_component_activated2_disabled", + "value": "#290A59F7" + }, + { + "name": "ohos_id_color_foreground_contrary", + "value": "#66FFFFFF" + }, + { + "name": "ohos_id_color_foreground_contrary_disabled", + "value": "#29FFFFFF" + }, + { + "name": "ohos_id_color_hover", + "value": "#0C000000" + }, + { + "name": "color_hover_normal_button", + "value": "#19000000" + }, + { + "name": "color_press_text_button", + "value": "#19000000" + }, + { + "name": "color_press_normal_button", + "value": "#26000000" + }, + { + "name": "color_normal_button_waiting", + "value": "#66000000" + }, + { + "name": "color_normal_button_waiting_disabled", + "value": "#29000000" + }, + { + "name": "color_press_emphasize_button", + "value": "#0A59F7" + }, + { + "name": "color_button_text_normal_disabled", + "value": "#33317AF7" + }, + { + "name": "color_button_text_normal", + "value": "#0A59F7" + }, + { + "name": "color_hover_emphasize_button", + "value": "#0954EA" + }, + { + "name": "ohos_id_color_emphasize", + "value": "#0A59F7" + }, + { + "name": "color_warning_text_disabled", + "value": "#66FA2A2D" + }, + { + "name": "color_normal_text", + "value": "#E5000000" + }, + { + "name": "color_normal_text_disabled", + "value": "#5C000000" + }, + { + "name": "color_emphasize_text", + "value": "#FFFFFF" + }, + { + "name": "color_emphasize_text_disabled", + "value": "#FAFAFA" + }, + { + "name": "color_white", + "value": "#19FFFFFF" + }, + { + "name": "color_fab_bg_white_pressed", + "value": "#DADBDD" + }, + { + "name": "color_fab_bg_white_disabled", + "value": "#F7F8FA" + }, + { + "name": "color_fab_icon_white_disabled", + "value": "#33FFFFFF" + }, + { + "name": "color_fab_bg_disabled", + "value": "#33317AF7" + }, + { + "name": "color_fab_icon_disabled", + "value": "#19FFFFFF" + }, + { + "name": "color_hover_disabled", + "value": "#00000000" + }, + { + "name": "color_hover", + "value": "#0D000000" + }, + { + "name": "play_btn", + "value": "#FFFFFF" + }, + { + "name": "component_ultra_thick_panel", + "value": "#202224" + }, + { + "name": "component_ultra_thick_dialog", + "value": "#202224" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/dark/element/float.json b/common/src/main/resources/dark/element/float.json new file mode 100644 index 0000000..071c97d --- /dev/null +++ b/common/src/main/resources/dark/element/float.json @@ -0,0 +1,508 @@ +{ + "float": [ + { + "name": "dial_top_position", + "value": "0" + }, + { + "name": "dial_center_position", + "value": "0.38" + }, + { + "name": "dial_bottom_position", + "value": "1" + }, + { + "name": "dial_shadow_radius", + "value": "28vp" + }, + { + "name": "dial_shadow_inside_x", + "value": "0vp" + }, + { + "name": "dial_shadow_inside_y", + "value": "-20vp" + }, + { + "name": "dial_shadow_outside_x", + "value": "0vp" + }, + { + "name": "dial_shadow_outside_y", + "value": "23vp" + }, + { + "name": "scale_top_position", + "value": "0" + }, + { + "name": "scale_center_position", + "value": "0.5" + }, + { + "name": "scale_bottom_position", + "value": "1" + }, + { + "name": "card_padding_vertical", + "value": "4vp" + }, + { + "name": "card_padding_horizontal", + "value": "4vp" + }, + { + "name": "card_inner_padding_horizontal", + "value": "8vp" + }, + { + "name": "card_inner_margin_horizontal", + "value": "3vp" + }, + { + "name": "timer_pick_padding_vertical", + "value": "16vp" + }, + { + "name": "card_margin_start", + "value": "12vp" + }, + { + "name": "card_margin_end", + "value": "12vp" + }, + { + "name": "build_time_picker", + "value": "4vp" + }, + { + "name": "card_margin_top", + "value": "8vp" + }, + { + "name": "card_margin_bottom", + "value": "4vp" + }, + { + "name": "default_corner_radius_l", + "value": "16vp" + }, + { + "name": "card_content_height", + "value": "64vp" + }, + { + "name": "card_tag_margin_end", + "value": "4vp" + }, + { + "name": "alarmCard_24", + "value": "24dp" + }, + { + "name": "card_title_line_margin", + "value": "2vp" + }, + { + "name": "hour_shadow_offset_x", + "value": "5vp" + }, + { + "name": "hour_shadow_offset_y", + "value": "1vp" + }, + { + "name": "minute_shadow_offset_x", + "value": "0vp" + }, + { + "name": "minute_shadow_offset_y", + "value": "3vp" + }, + { + "name": "second_shadow_offset_x", + "value": "-4vp" + }, + { + "name": "second_shadow_offset_y", + "value": "-3vp" + }, + { + "name": "text_size_headline3", + "value": "60fp" + }, + { + "name": "right_icon_height", + "value": "24vp" + }, + { + "name": "right_icon_width", + "value": "12vp" + }, + { + "name": "header_footer_height", + "value": "56vp" + }, + { + "name": "header_textHeight", + "value": "40vp" + }, + { + "name": "header_lineHeight", + "value": "35vp" + }, + { + "name": "header_lineHeight_addTitle", + "value": "28vp" + }, + { + "name": "header_fontSize", + "value": "30sp" + }, + { + "name": "header_fontSize_addTitle", + "value": "20sp" + }, + { + "name": "header_fontSizes", + "value": "20sp" + }, + { + "name": "dialog_padding_horizontal", + "value": "24vp" + }, + { + "name": "dialog_button_height", + "value": "40vp" + }, + { + "name": "dialog_content_padding_vertical", + "value": "8vp" + }, + { + "name": "dialog_button_divider_margin", + "value": "4vp" + }, + { + "name": "dialog_button_divider_height", + "value": "24vp" + }, + { + "name": "dialog_button_divider_width", + "value": "2vp" + }, + { + "name": "dialog_margin_bottom", + "value": "-16vp" + }, + { + "name": "input_height", + "value": "48vp" + }, + { + "name": "button_size", + "value": "56vp" + }, + { + "name": "new_button_size", + "value": "40vp" + }, + { + "name": "button_shadow_radius", + "value": "8vp" + }, + { + "name": "button_shadow_x", + "value": "0vp" + }, + { + "name": "button_shadow_y", + "value": "6vp" + }, + { + "name": "button_shadow_y2", + "value": "8vp" + }, + { + "name": "button_icon_size", + "value": "24vp" + }, + { + "name": "button_click_size", + "value": "48vp" + }, + { + "name": "button_icon_on", + "value": "32vp" + }, + { + "name": "digital_clock_text_size_normal", + "value": "18dp" + }, + { + "name": "digital_clock_text_size_normal_18", + "value": "18dp" + }, + { + "name": "digital_clock_text_size_normal_56", + "value": "56dp" + }, + { + "name": "digital_clock_text_margin", + "value": "4vp" + }, + { + "name": "confirm_dialog_padding_horizontal", + "value": "16vp" + }, + { + "name": "confirm_dialog_content_padding_top", + "value": "24vp" + }, + { + "name": "dilog_width", + "value": "400vp" + }, + { + "name": "confirm_dialog_content_padding_bottom", + "value": "8vp" + }, + { + "name": "dialog_button_divider_margin_vertical", + "value": "8vp" + }, + { + "name": "confirm_dialog_border", + "value": "20vp" + }, + { + "name": "confirm_dialog_divider_height", + "value": "24vp" + }, + { + "name": "confirm_dialog_divider_width", + "value": "1vp" + }, + { + "name": "set_alarm_card_height", + "value": "48vp" + }, + { + "name": "button_back", + "value": "56vp" + }, + { + "name": "common_grid_margin", + "value": "12vp" + }, + { + "name": "common_grid_margin_zero", + "value": "0vp" + }, + { + "name": "common_text_input_padding_zero", + "value": "0vp" + }, + { + "name": "float_content_bottom_margin", + "value": "8vp" + }, + { + "name": "float_divider_height", + "value": "24vp" + }, + { + "name": "float_divider_padding", + "value": "7vp" + }, + { + "name": "float_title_height", + "value": "56vp" + }, + { + "name": "float_content_padding", + "value": "8vp" + }, + { + "name": "float_container_padding", + "value": "16vp" + }, + { + "name": "ringtone_selection_item_height", + "value": "48vp" + }, + { + "name": "ringtone_selection_section_header_size", + "value": "12fp" + }, + { + "name": "ringtone_selection_section_header_margin_vertical", + "value": "8vp" + }, + { + "name": "ringtone_selection_section_header_margin_horizontal", + "value": "24vp" + }, + { + "name": "radio_size", + "value": "20vp" + }, + { + "name": "appbar_icon_margin", + "value": "16vp" + }, + { + "name": "appbar_fold_icon_margin", + "value": "26vp" + }, + { + "name": "button_icon_hover_padding", + "value": "12vp" + }, + { + "name": "button_icon_left_padding", + "value": "16vp" + }, + { + "name": "button_icon_right_padding", + "value": "8vp" + }, + { + "name": "button_operation_right_padding", + "value": "12vp" + }, + { + "name": "cancel_bottom_margin", + "value": "12vp" + }, + { + "name": "delete_padding_left", + "value": "104.5vp" + }, + { + "name": "delete_padding_right", + "value": "104.5vp" + }, + { + "name": "large_digital_clock_height", + "value": "70vp" + }, + { + "name": "large_btn_height", + "value": "56vp" + }, + { + "name": "large_btn_width", + "value": "56vp" + }, + { + "name": "list_item_height", + "value": "48vp" + }, + { + "name": "card_double_height", + "value": "110vp" + }, + { + "name": "pc_button_size", + "value": "40vp" + }, + { + "name": "pc_add_button_size", + "value": "56vp" + }, + { + "name": "pc_padding", + "value": "56vp" + }, + { + "name": "pc_list_scroll_padding", + "value": "4vp" + }, + { + "name": "fold_first_top", + "value": "-2vp" + }, + { + "name": "fold_first_right", + "value": "4vp" + }, + { + "name": "fold_cancel_right", + "value": "-4vp" + }, + { + "name": "fold_first_bottom", + "value": "6vp" + }, + { + "name": "fold_second_bottom", + "value": "18vp" + }, + { + "name": "pc_button_icon_size", + "value": "18vp" + }, + { + "name": "list_item_padding", + "value": "13vp" + }, + { + "name": "menu_content_padding", + "value": "4vp" + }, + { + "name": "menu_item_icon", + "value": "25vp" + }, + { + "name": "dial_margin_50", + "value": "50vp" + }, + { + "name": "dial_margin_47", + "value": "47vp" + }, + { + "name": "dial_margin_32", + "value": "32vp" + }, + { + "name": "dial_margin_29", + "value": "29vp" + }, + { + "name": "dial_margin_24", + "value": "24vp" + }, + { + "name": "canvas_size", + "value": "45vp" + }, + { + "name": "canvas_border_radius", + "value": "45vp" + }, + { + "name": "card_inner_padding_10", + "value": "10vp" + }, + { + "name": "card_inner_padding_11", + "value": "11vp" + }, + { + "name": "date_info_margin_bottom", + "value": "16vp" + }, + { + "name": "pc_digital_clock_height", + "value": "74vp" + }, + { + "name": "phone_show_both_digital_clock_height", + "value": "70vp" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/dark/element/plural.json b/common/src/main/resources/dark/element/plural.json new file mode 100644 index 0000000..74cf5fb --- /dev/null +++ b/common/src/main/resources/dark/element/plural.json @@ -0,0 +1,43 @@ +{ + "plural":[ + { + "name": "minute", + "value": [ + { + "quantity":"one", + "value":"%d minute" + }, + { + "quantity":"other", + "value":"%d minutes" + } + ] + }, + { + "name": "hour", + "value": [ + { + "quantity":"one", + "value":"%d hour" + }, + { + "quantity":"other", + "value":"%d hours" + } + ] + }, + { + "name": "day", + "value": [ + { + "quantity":"one", + "value":"%d day" + }, + { + "quantity":"other", + "value":"%d days" + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/dark/element/string.json b/common/src/main/resources/dark/element/string.json new file mode 100644 index 0000000..53b22db --- /dev/null +++ b/common/src/main/resources/dark/element/string.json @@ -0,0 +1,48 @@ +{ + "string":[ + { + "name":"only_ring_once", + "value": "Custom" + }, + { + "name":"every_day", + "value":"Every day" + }, + { + "name":"cancel", + "value":"Cancel" + }, + { + "name":"ok", + "value":"OK" + }, + { + "name":"alarm_clock", + "value":"Alarm" + }, + { + "name":"monday_to_friday", + "value":"Monday to Friday" + }, + { + "name":"default_alarm_label", + "value":"Good morning" + }, + { + "name":"default_alarm_label_new", + "value":"Good morning" + }, + { + "name":"text_with_separate", + "value":"%1$s, %2$s" + }, + { + "name": "timer_title_new", + "value": "Timer" + }, + { + "name": "confirm_delete", + "value": "Delete" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/dark/media/ic_add.svg b/common/src/main/resources/dark/media/ic_add.svg new file mode 100644 index 0000000..9b57855 --- /dev/null +++ b/common/src/main/resources/dark/media/ic_add.svg @@ -0,0 +1,7 @@ + + + ic_add_filled + + + + \ No newline at end of file diff --git a/common/src/main/resources/en/element/plural.json b/common/src/main/resources/en/element/plural.json new file mode 100644 index 0000000..74cf5fb --- /dev/null +++ b/common/src/main/resources/en/element/plural.json @@ -0,0 +1,43 @@ +{ + "plural":[ + { + "name": "minute", + "value": [ + { + "quantity":"one", + "value":"%d minute" + }, + { + "quantity":"other", + "value":"%d minutes" + } + ] + }, + { + "name": "hour", + "value": [ + { + "quantity":"one", + "value":"%d hour" + }, + { + "quantity":"other", + "value":"%d hours" + } + ] + }, + { + "name": "day", + "value": [ + { + "quantity":"one", + "value":"%d day" + }, + { + "quantity":"other", + "value":"%d days" + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/en/element/string.json b/common/src/main/resources/en/element/string.json new file mode 100644 index 0000000..71771ef --- /dev/null +++ b/common/src/main/resources/en/element/string.json @@ -0,0 +1,40 @@ +{ + "string": [ + { + "name": "only_ring_once", + "value": "Custom" + }, + { + "name": "every_day", + "value": "Every day" + }, + { + "name": "cancel", + "value": "CANCEL" + }, + { + "name": "ok", + "value": "OK" + }, + { + "name": "alarm_clock", + "value": "Alarm" + }, + { + "name": "monday_to_friday", + "value": "Monday to Friday" + }, + { + "name": "default_alarm_label", + "value": "Good morning" + }, + { + "name": "default_alarm_label_new", + "value": "Good morning" + }, + { + "name": "text_with_separate", + "value": "%1$s, %2$s" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/ug/element/plural.json b/common/src/main/resources/ug/element/plural.json new file mode 100644 index 0000000..01c050c --- /dev/null +++ b/common/src/main/resources/ug/element/plural.json @@ -0,0 +1,43 @@ +{ + "plural":[ + { + "name":"hour", + "value":[ + { + "quantity":"one", + "value":"%d سائەت" + }, + { + "quantity":"other", + "value":"%d سائەت" + } + ] + }, + { + "name":"day", + "value":[ + { + "quantity":"other", + "value":"%d كۈن" + }, + { + "quantity":"one", + "value":"%d كۈن" + } + ] + }, + { + "name":"minute", + "value":[ + { + "quantity":"other", + "value":"%d مىنۇت" + }, + { + "quantity":"one", + "value":"%d مىنۇت" + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/ug/element/string.json b/common/src/main/resources/ug/element/string.json new file mode 100644 index 0000000..11e5876 --- /dev/null +++ b/common/src/main/resources/ug/element/string.json @@ -0,0 +1,40 @@ +{ + "string":[ + { + "name":"monday_to_friday", + "value":"دۈشەنبىدىن جۈمەگىچە" + }, + { + "name":"default_alarm_label_new", + "value":"خەيرىلىك سەھەر" + }, + { + "name":"default_alarm_label", + "value":"ئۆرنەك: تىنچلىقمۇ؟" + }, + { + "name":"cancel", + "value":"بىكار قىلىش" + }, + { + "name":"text_with_separate", + "value":"%1$s، %2$s" + }, + { + "name":"timer_title_new", + "value":"تەتۈر ساناق" + }, + { + "name":"alarm_clock", + "value":"قوڭغۇراق سائەت" + }, + { + "name":"every_day", + "value":"كۈندە" + }, + { + "name":"ok", + "value":"جەزملەش" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/zh_CN/element/plural.json b/common/src/main/resources/zh_CN/element/plural.json new file mode 100644 index 0000000..9315ddc --- /dev/null +++ b/common/src/main/resources/zh_CN/element/plural.json @@ -0,0 +1,31 @@ +{ + "plural":[ + { + "name": "minute", + "value": [ + { + "quantity":"other", + "value":"%d 分钟" + } + ] + }, + { + "name": "hour", + "value": [ + { + "quantity":"other", + "value":"%d 小时" + } + ] + }, + { + "name": "day", + "value": [ + { + "quantity":"other", + "value":"%d 天" + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/zh_CN/element/string.json b/common/src/main/resources/zh_CN/element/string.json index 2e3b29c..b07d44e 100644 --- a/common/src/main/resources/zh_CN/element/string.json +++ b/common/src/main/resources/zh_CN/element/string.json @@ -1,5 +1,57 @@ { "string": [ + { + "name": "only_ring_once", + "value": "自定义" + }, + { + "name": "every_day", + "value": "每天" + }, + { + "name": "very_early_morning", + "value": "凌晨" + }, + { + "name": "dawn", + "value": "清晨" + }, + { + "name": "early_morning", + "value": "早上" + }, + { + "name": "morning", + "value": "上午" + }, + { + "name": "midday", + "value": "中午" + }, + { + "name": "afternoon", + "value": "下午" + }, + { + "name": "evening", + "value": "傍晚" + }, + { + "name": "night", + "value": "晚上" + }, + { + "name": "midnight", + "value": "半夜" + }, + { + "name": "cancel", + "value": "取消" + }, + { + "name": "ok", + "value": "确定" + }, { "name": "alarm_clock", "value": "闹钟" @@ -11,6 +63,26 @@ { "name": "timer_clock", "value": "计时器" + }, + { + "name": "monday_to_friday", + "value": "周一至周五" + }, + { + "name": "default_alarm_label", + "value": "示例:早上好" + }, + { + "name": "default_alarm_label_new", + "value": "早上好" + }, + { + "name": "text_with_separate", + "value": "%1$s,%2$s" + }, + { + "name": "confirm_delete", + "value": "删除" } ] } \ No newline at end of file diff --git a/common/src/main/resources/zh_HK/element/plural.json b/common/src/main/resources/zh_HK/element/plural.json new file mode 100644 index 0000000..f6654a2 --- /dev/null +++ b/common/src/main/resources/zh_HK/element/plural.json @@ -0,0 +1,31 @@ +{ + "plural":[ + { + "name":"hour", + "value":[ + { + "quantity":"other", + "value":"%d 小時" + } + ] + }, + { + "name":"day", + "value":[ + { + "quantity":"other", + "value":"%d 日" + } + ] + }, + { + "name":"minute", + "value":[ + { + "quantity":"other", + "value":"%d 分鐘" + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/zh_HK/element/string.json b/common/src/main/resources/zh_HK/element/string.json new file mode 100644 index 0000000..d87f06a --- /dev/null +++ b/common/src/main/resources/zh_HK/element/string.json @@ -0,0 +1,44 @@ +{ + "string":[ + { + "name":"monday_to_friday", + "value":"週一至週五" + }, + { + "name":"default_alarm_label_new", + "value":"早晨" + }, + { + "name":"default_alarm_label", + "value":"示例:早上好" + }, + { + "name":"cancel", + "value":"取消" + }, + { + "name":"text_with_separate", + "value":"%1$s,%2$s" + }, + { + "name":"timer_title_new", + "value":"計時器" + }, + { + "name":"alarm_clock", + "value":"鬧鐘" + }, + { + "name":"every_day", + "value":"每日" + }, + { + "name":"confirm_delete", + "value":"刪除" + }, + { + "name":"ok", + "value":"確定" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/zh_TW/element/plural.json b/common/src/main/resources/zh_TW/element/plural.json new file mode 100644 index 0000000..6e712e0 --- /dev/null +++ b/common/src/main/resources/zh_TW/element/plural.json @@ -0,0 +1,31 @@ +{ + "plural":[ + { + "name":"hour", + "value":[ + { + "quantity":"other", + "value":"%d 小時" + } + ] + }, + { + "name":"day", + "value":[ + { + "quantity":"other", + "value":"%d 天" + } + ] + }, + { + "name":"minute", + "value":[ + { + "quantity":"other", + "value":"%d 分鐘" + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/zh_TW/element/string.json b/common/src/main/resources/zh_TW/element/string.json new file mode 100644 index 0000000..e50df26 --- /dev/null +++ b/common/src/main/resources/zh_TW/element/string.json @@ -0,0 +1,44 @@ +{ + "string":[ + { + "name":"monday_to_friday", + "value":"週一至週五" + }, + { + "name":"default_alarm_label_new", + "value":"早安" + }, + { + "name":"default_alarm_label", + "value":"範例:早安" + }, + { + "name":"cancel", + "value":"取消" + }, + { + "name":"text_with_separate", + "value":"%1$s,%2$s" + }, + { + "name":"timer_title_new", + "value":"計時器" + }, + { + "name":"alarm_clock", + "value":"鬧鐘" + }, + { + "name":"every_day", + "value":"每天" + }, + { + "name":"confirm_delete", + "value":"刪除" + }, + { + "name":"ok", + "value":"確定" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/zz_ZX/element/plural.json b/common/src/main/resources/zz_ZX/element/plural.json new file mode 100644 index 0000000..ece36c8 --- /dev/null +++ b/common/src/main/resources/zz_ZX/element/plural.json @@ -0,0 +1,91 @@ +{ + "plural":[ + { + "name":"hour", + "value":[ + { + "quantity":"one", + "value":"[TS_794231]_%d hour" + }, + { + "quantity":"other", + "value":"[TS_794249]_%d hours" + }, + { + "quantity":"zero", + "value":"[TS_794260]_%d hours" + }, + { + "quantity":"few", + "value":"[TS_794257]_%d hours" + }, + { + "quantity":"many", + "value":"[TS_794238]_%d hours" + }, + { + "quantity":"two", + "value":"[TS_794247]_%d hours" + } + ] + }, + { + "name":"day", + "value":[ + { + "quantity":"other", + "value":"[TS_794244]_%d days" + }, + { + "quantity":"one", + "value":"[TS_794258]_%d day" + }, + { + "quantity":"zero", + "value":"[TS_794252]_%d days" + }, + { + "quantity":"few", + "value":"[TS_794250]_%d days" + }, + { + "quantity":"many", + "value":"[TS_794214]_%d days" + }, + { + "quantity":"two", + "value":"[TS_794216]_%d days" + } + ] + }, + { + "name":"minute", + "value":[ + { + "quantity":"other", + "value":"[TS_794235]_%d minutes" + }, + { + "quantity":"one", + "value":"[TS_794220]_%d minute" + }, + { + "quantity":"zero", + "value":"[TS_794237]_%d minutes" + }, + { + "quantity":"few", + "value":"[TS_794253]_%d minutes" + }, + { + "quantity":"many", + "value":"[TS_794264]_%d minutes" + }, + { + "quantity":"two", + "value":"[TS_794215]_%d minutes" + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/zz_ZX/element/string.json b/common/src/main/resources/zz_ZX/element/string.json new file mode 100644 index 0000000..c0c307e --- /dev/null +++ b/common/src/main/resources/zz_ZX/element/string.json @@ -0,0 +1,48 @@ +{ + "string": [ + { + "name": "cancel", + "value": "[TS_794206]_Cancel" + }, + { + "name": "text_with_separate", + "value": "[TS_794217]_%1$s, %2$s" + }, + { + "name": "monday_to_friday", + "value": "[TS_810239]_Monday to Friday" + }, + { + "name": "default_alarm_label_new", + "value": "[TS_810225]_Good morning" + }, + { + "name": "default_alarm_label", + "value": "[TS_810232]_Good morning" + }, + { + "name": "every_day", + "value": "[TS_794243]_Every day" + }, + { + "name": "only_ring_once", + "value": "[TS_794254]_Custom" + }, + { + "name": "ok", + "value": "[TS_794211]_OK" + }, + { + "name": "alarm_clock", + "value": "[TS_794240]_Alarm" + }, + { + "name": "timer_title_new", + "value": "[TS_882459]_Timer" + }, + { + "name": "confirm_delete", + "value": "[TS_794251]_Delete" + } + ] +} \ No newline at end of file diff --git a/feature/countdown/.gitignore b/feature/alarmclock/.gitignore similarity index 100% rename from feature/countdown/.gitignore rename to feature/alarmclock/.gitignore diff --git a/feature/alarmclock/BuildProfile.ets b/feature/alarmclock/BuildProfile.ets new file mode 100644 index 0000000..3a501e5 --- /dev/null +++ b/feature/alarmclock/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/feature/alarmclock/build-profile.json5 b/feature/alarmclock/build-profile.json5 new file mode 100644 index 0000000..7374b5c --- /dev/null +++ b/feature/alarmclock/build-profile.json5 @@ -0,0 +1,17 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + } + } + } + }, + ] +} diff --git a/feature/countdown/hvigorfile.js b/feature/alarmclock/hvigorfile.ts similarity index 63% rename from feature/countdown/hvigorfile.js rename to feature/alarmclock/hvigorfile.ts index 42ed4b4..29ad39f 100644 --- a/feature/countdown/hvigorfile.js +++ b/feature/alarmclock/hvigorfile.ts @@ -1,3 +1,3 @@ // Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. -module.exports = require('@ohos/hvigor-ohos-plugin').harTasks +module.exports = require('@ohos/hvigor-ohos-plugin').harTasks; diff --git a/feature/alarmclock/index.ets b/feature/alarmclock/index.ets new file mode 100644 index 0000000..4906add --- /dev/null +++ b/feature/alarmclock/index.ets @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { AlarmClock } from './src/main/ets/pages/index'; + +export { BannerAlarm } from './src/main/ets/pages/BannerAlarm'; + +export { ForegroundView } from './src/main/ets/pages/ForegroundPage'; + +export { FullScreenAlarm } from './src/main/ets/pages/FullScreenAlarm'; + +export { ManageAlarmClock } from './src/main/ets/pages/ManageAlarmClock/index'; + +export { NotificationUtil, + AlarmCardUtil, + MANAGE_NEW_ALARM, + MANAGE_EDIT_ALARM, + DELETE_ALARM_CLOCK } from './src/main/ets/utils'; + +export { AlarmServiceManager, AudioManager } from './src/main/ets/manager'; + +export { AlarmCard, + ObservedAlarmInfo, + AlarmCardType, + ArraySlider, + Form, + InputType, + FormValue, + FormField, + FormOption, + FormDiyCallbackInfo } from './src/main/ets/components/index'; \ No newline at end of file diff --git a/feature/alarmclock/oh-package-lock.json5 b/feature/alarmclock/oh-package-lock.json5 new file mode 100644 index 0000000..688e757 --- /dev/null +++ b/feature/alarmclock/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@hmos/common@../../common": "@hmos/common@../../common", + "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har": "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har" + }, + "packages": { + "@hmos/common@../../common": { + "name": "@ohos/common", + "version": "1.0.0", + "resolved": "../../common", + "registryType": "local", + "dependencies": { + "@ohos/lottie": "file:./src/main/ets/libs/lottieArkTS.har" + } + }, + "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har": { + "name": "@ohos/lottie", + "version": "2.0.5", + "resolved": "../../common/src/main/ets/libs/lottieArkTS.har", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/feature/alarmclock/oh-package.json5 b/feature/alarmclock/oh-package.json5 new file mode 100644 index 0000000..e71e670 --- /dev/null +++ b/feature/alarmclock/oh-package.json5 @@ -0,0 +1,14 @@ +{ + "license": "ISC", + "types": "", + "devDependencies": { + "@hmos/common": "file:../../common" + }, + "name": "@hmos/alarmclock", + "description": "a npm package which contains pages of alarm clock", + "main": "index.ets", + "repository": {}, + "version": "1.0.0", + "dynamicDependencies": {}, + "dependencies": {} +} diff --git a/feature/alarmclock/src/main/ets/components/AlarmCard/ObservedAlarmInfo.ets b/feature/alarmclock/src/main/ets/components/AlarmCard/ObservedAlarmInfo.ets new file mode 100644 index 0000000..31fc598 --- /dev/null +++ b/feature/alarmclock/src/main/ets/components/AlarmCard/ObservedAlarmInfo.ets @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AlarmInfoToShow } from '@hmos/common'; + +/** + * AlarmInfoToShow decorated with @Observed + * + * When the variable of the normal AlarmInfoToShow object changes, the subcomponent cannot be refreshed. + * Object instantiated from this class can do so with the parameter of the subcomponent, + * which is decorated with @ObjectLink. + */ +@Observed +export class ObservedAlarmInfo implements AlarmInfoToShow { + public title: string; + public time: string; + public tag?: string; + public isTagLeft?: boolean; + public daysOfWeekDesc: string; + public enabled: boolean; + public id?: string; + public reStart?: string; + public hour: number; + public minute: number; + public second: number; + public daysOfWeek: number[]; + public daysOfWakeType?: number; + public alarmTime?: number; + public ringDuration: number; + public snoozeDuration: number; + public snoozeTimes: number; + public snoozeCount?: number; + + /** + * Constructor + * + * @param alarmInfoToShow The normal AlarmInfoToShow object + */ + constructor(alarmInfoToShow: AlarmInfoToShow) { + this.title = alarmInfoToShow.title!; + this.second = alarmInfoToShow.second!; + this.time = alarmInfoToShow.time!; + this.tag = alarmInfoToShow.tag; + this.isTagLeft = alarmInfoToShow.isTagLeft; + this.daysOfWeekDesc = alarmInfoToShow.daysOfWeekDesc!; + this.enabled = alarmInfoToShow.enabled!; + this.id = alarmInfoToShow.id; + this.hour = alarmInfoToShow.hour!; + this.minute = alarmInfoToShow.minute!; + this.daysOfWeek = alarmInfoToShow.daysOfWeek!; + this.daysOfWakeType = alarmInfoToShow.daysOfWakeType || 0; + this.alarmTime = alarmInfoToShow.alarmTime; + this.ringDuration = alarmInfoToShow.ringDuration!; + this.snoozeDuration = alarmInfoToShow.snoozeDuration!; + this.snoozeTimes = alarmInfoToShow.snoozeTimes!; + this.snoozeCount = alarmInfoToShow.snoozeCount; + this.reStart = alarmInfoToShow.reStart; + } +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/components/AlarmCard/index.ets b/feature/alarmclock/src/main/ets/components/AlarmCard/index.ets new file mode 100644 index 0000000..37ac090 --- /dev/null +++ b/feature/alarmclock/src/main/ets/components/AlarmCard/index.ets @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Card, + CardSize, + CommonGrid, + LogUtil, + GlobalContext, + ResourceManager, + EventName, + EventReportUtil +} from '@hmos/common'; +import { ObservedAlarmInfo, AlarmCardType } from './types'; +import resmgr from '@ohos.resourceManager'; +import i18n from '@ohos.i18n'; +// import hiSysEvent from '@ohos.hiSysEvent'; + +const TAG = 'AlarmCard'; + +/** + * 双框架迁移数据 dayOfWeekType 值 + * 状态值 : + * 4: 法定工作日 + * 3: 用户自定义 + * 2: 每天 + * 1: 周一到周五 + * 0: 单次 + * + * 单框架逻辑 处理合并 1、2、3 都为用户自定义 3 + */ +enum StateType { + WAKE_DAYS = 4, // 法定工作日 + USER_DEFINED = 3, // 自定义响铃 + ONES = 0 // 只响一次 +} + + +/** + * Font in the first line of the card + * Time and period labels (morning, afternoon, etc.) + * Rendering in two styles + * + * @param isTag Indicates whether the label is a time segment. + */ +@Extend(Text) +function formatTimeText(isTag: boolean) { + .fontSize(isTag ? $r('sys.float.Body_L') : $r('sys.float.Title_M')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.font_primary')) +} + +@Extend(Text) +function formatTimeTextPrivate(isTag: boolean) { + .fontSize(isTag ? $r('sys.float.Subtitle_M') : $r('sys.float.Title_M')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.font_primary')) +} + +/** + * Font in the second line of the card + */ +@Extend(Text) +function secondaryText() { + .fontSize($r('sys.float.Body_M')) + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) +} + +/** + * Card Font Transparency + */ + +/** + * Alarm clock card + * + * @author XiaHan + * @since 2022-07-16 + */ +@Component +export struct AlarmCard { + @ObjectLink @Watch('refreshAlarmCard') alarmInfo: ObservedAlarmInfo; + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + @Prop alarmInfoList: ObservedAlarmInfo[] = []; + @Prop @Watch('refreshAlarmCardInfo') refreshAlarmClockInfo: boolean = false; + @Prop cancelDoubleCardMargin: boolean = false; + @Prop isPCView: boolean = false; + @State refreshTrigger: boolean = false; + @Prop isDateTypeClock: boolean = i18n.System.is24HourClock(); + @State isRepeatMenu: boolean = true; + @StorageLink('isRepeat') isRepeat: boolean = false; + public cardType?: AlarmCardType; + // Type of the alarm card. Whether the card is used in main page or delete-alarm-clock page. + public onRightBtnChange?: (isOn?: boolean) => void; // Callback of the click event of the button on the right. + public isChecked?: boolean; // Whether the card is checked. Valid only in the card of the delete-alarm-clock page. + public selectList = [(GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('once_alarm'), + (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('customize'), + (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('days_of_wake_type') + ]; + + private refreshAlarmCardInfo() { + LogUtil.info(TAG, 'alarmInfoList:' + JSON.stringify(this.alarmInfoList)); + for (let i = 0; i < this.alarmInfoList.length - 1; i++) { + if (this.alarmInfoList[i].id == this.alarmInfo.id) { + this.alarmInfo.title = this.alarmInfoList[i].title!; + this.alarmInfo.time = this.alarmInfoList[i].time!; + this.alarmInfo.tag = this.alarmInfoList[i].tag; + this.alarmInfo.isTagLeft = this.alarmInfoList[i].isTagLeft; + this.alarmInfo.daysOfWeekDesc = this.alarmInfoList[i].daysOfWeekDesc!; + this.alarmInfo.daysOfWakeType = this.alarmInfoList[i].daysOfWakeType!; + this.alarmInfo.hour = this.alarmInfoList[i].hour!; + this.alarmInfo.minute = this.alarmInfoList[i].minute!; + if (this.alarmInfoList[i].daysOfWeek.toString() !== this.alarmInfo.daysOfWeek.toString()) { + this.alarmInfo.daysOfWeek = this.alarmInfoList[i].daysOfWeek!; + } + // alarmTime no need to update,because it will be recalculate + this.alarmInfo.ringDuration = this.alarmInfoList[i].ringDuration!; + this.alarmInfo.snoozeDuration = this.alarmInfoList[i].snoozeDuration!; + this.alarmInfo.snoozeTimes = this.alarmInfoList[i].snoozeTimes!; + this.alarmInfo.snoozeCount = this.alarmInfoList[i].snoozeCount; + this.alarmInfo.enabled = this.alarmInfoList[i].enabled!; + this.alarmInfo.reStart = this.alarmInfoList[i].reStart; + } + } + } + + private refreshAlarmCard() { + this.refreshTrigger = !this.refreshTrigger; // To trigger the refresh of the component and its subcomponents. + } + + private getAlarm(alarmInfo?: ObservedAlarmInfo) { + LogUtil.info(TAG, `daysOfWakeType, ${alarmInfo?.daysOfWakeType}`) + if (alarmInfo?.daysOfWakeType === StateType.WAKE_DAYS) { // 法定工作日 + return `${alarmInfo?.title},${this.selectList[2]}` + } else if (alarmInfo?.daysOfWakeType === StateType.ONES) { // 只响一次 + return `${alarmInfo?.title},${this.selectList[0]}` + } else { // 1 2 3 用户自定义 + return `${alarmInfo?.title},${alarmInfo?.daysOfWeekDesc}` + } + } + + /** + * Do not modify the folding method + */ + private getAlarmPrivate(alarmInfo?: ObservedAlarmInfo) { + if (alarmInfo?.daysOfWakeType === StateType.WAKE_DAYS) { // 法定工作日 + return `${this.selectList[2]}`; + } else if (alarmInfo?.daysOfWakeType === StateType.ONES) { // 只响一次 + return `${this.selectList[0]}`; + } else { // 1 2 3 用户自定义 + return `${alarmInfo?.daysOfWeekDesc}`; + } + } + + @Builder + buildAlarmBasicInfoPrivate() { + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Baseline }) { + Text(this.alarmInfo.tag) + .formatTimeTextPrivate(this.alarmInfo.isTagLeft as boolean) + .fontSize($r('sys.float.Subtitle_M')) + .margin({ + top: $r('app.float.fold_first_top'), + right: $r('app.float.fold_first_right') + }) + Text(this.alarmInfo.time) + .fontSize($r('sys.float.Title_M')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.font_primary')) + .margin(this.isDateTypeClock ? { left: $r('app.float.fold_cancel_right') } : {}) + } + .margin({ bottom: $r('app.float.fold_first_bottom') }) + + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + Text(this.alarmInfo.title) + .fontSize($r('sys.float.Subtitle_S')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.font_secondary')) + } + .margin({ bottom: $r('app.float.fold_second_bottom') }) + + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + Text(this.getAlarmPrivate(this.alarmInfo)) + .fontSize($r('sys.float.Subtitle_S')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.font_secondary')) + } + } + + /** + * Basic information on the left of the alarm card + */ + @Builder + buildAlarmBasicInfo() { + Column() { + Column().height($r('app.float.card_content_padding_height')) + if (this.foldAbleScreen === 1 && this.cancelDoubleCardMargin) { + this.buildAlarmBasicInfoPrivate() + } else { + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Baseline }) { + Text(this.alarmInfo.isTagLeft ? this.alarmInfo.tag : this.alarmInfo.time) + .margin({ right: $r('app.float.card_tag_margin_end') }) + .formatTimeText(this.alarmInfo.isTagLeft as boolean) + Text(this.alarmInfo.isTagLeft ? this.alarmInfo.time : this.alarmInfo.tag) + .formatTimeText(!this.alarmInfo.isTagLeft) + .focusable(true) + } + .margin({ bottom: $r('app.float.card_title_line_margin') }) + + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + if (this.alarmInfo.reStart === undefined || this.alarmInfo.reStart === '') { + Text(this.getAlarm(this.alarmInfo)) + .secondaryText() + } else { + Text(this.getAlarm(this.alarmInfo) + this.alarmInfo.reStart) + .secondaryText() + } + } + } + } + .padding({ + bottom: $r('app.float.card_inner_padding_horizontal'), + }) + } + + /** + * Button on the right of the alarm card + */ + @Builder + buildRightButton() { + Row() { + Toggle({ type: ToggleType.Switch, isOn: this.alarmInfo.enabled }) + .onChange(async (isOn: boolean) => { + + LogUtil.info(TAG, 'AlarmCard onChange id:' + this.alarmInfo.id + ' isOn:' + isOn + ' item.enable:' + this.alarmInfo.enabled); + if (isOn !== this.alarmInfo.enabled) { + this.onRightBtnChange!(isOn) + } + let switchFlag: string = isOn ? 'on' : 'off'; + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_ALARM_SWITCH, 0, switchFlag) + }) + .hoverEffect(HoverEffect.None) + .id('id_switch_toggle') + .selectedColor($r('sys.color.comp_background_emphasize')) + .width($r('app.float.switch_width')) + .onHover((isHover: boolean, event: HoverEvent) => { + if (event && event.stopPropagation) { + event.stopPropagation(); + } + }) + .onMouse((event: MouseEvent) => { + if (event && event.stopPropagation) { + event.stopPropagation(); + } + }) + } + .bindMenu(this.repeatMenu, { + placement: Placement.BottomRight, + }) + .hitTestBehavior(HitTestMode.None) + } + + @Builder + repeatMenu() { + if (this.cardType === AlarmCardType.MainPageCard && this.isRepeatMenu === true) { + Column() { + Text($r('app.string.close_only_once')) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + .focusable(true) + .tabIndex(1) + .padding({ + left: $r('app.float.card_margin_start'), + right: $r('app.float.card_margin_start'), + top: $r('app.float.card_margin_start'), + bottom: $r('app.float.card_margin_start') + }) + .onClick(() => { + this.isRepeat = false; + AppStorage.setOrCreate('isRepeat', false); + this.onRightBtnChange!(false); + }) + Text($r('app.string.close_repeating_alarm')) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + .focusable(true) + .tabIndex(1) + .padding({ + left: $r('app.float.card_margin_start'), + right: $r('app.float.card_margin_start'), + top: $r('app.float.card_margin_start'), + bottom: $r('app.float.card_margin_start') + }) + .onClick(() => { + this.isRepeat = true; + AppStorage.setOrCreate('isRepeat', true); + this.onRightBtnChange!(false); + }) + } + .alignItems(HorizontalAlign.Start) + .width($r('app.float.repeat_menu_width')) + } + } + + @Builder + buildContent() { + Column() { + Card({ + openHoverStatus: this.isPCView, + openPressStatus: this.isPCView, + cancelDoubleCardMargin: this.cancelDoubleCardMargin, + restrictAlarmCardMargin: true, + focusRemoveLRMargin: this.isPCView && true, + cardSize: this.isPCView ? CardSize.LARGER : CardSize.NORMAL + }) { + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) { + this.buildAlarmBasicInfo() + } + .constraintSize({ + minHeight: this.cancelDoubleCardMargin ? $r('app.float.card_double_height') : $r('app.float.card_content_height') + }) + + this.buildRightButton() + } + .constraintSize({ + minHeight: this.cancelDoubleCardMargin ? $r('app.float.card_double_height') : $r('app.float.card_content_height') + }) + } + } + } + + build() { + Column() { + Column().backgroundColor(this.refreshTrigger ? $r('app.color.transparent') : $r('app.color.color_black')) + Column() { + this.buildContent() + } + } + } +} diff --git a/product/pc/src/ohosTest/ets/test/List.test.ets b/feature/alarmclock/src/main/ets/components/AlarmCard/types.ets similarity index 78% rename from product/pc/src/ohosTest/ets/test/List.test.ets rename to feature/alarmclock/src/main/ets/components/AlarmCard/types.ets index a75fa4e..74e3df8 100644 --- a/product/pc/src/ohosTest/ets/test/List.test.ets +++ b/feature/alarmclock/src/main/ets/components/AlarmCard/types.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -12,9 +12,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import abilityTest from './Ability.test' -export default function testsuite() { - abilityTest() +export { ObservedAlarmInfo } from './ObservedAlarmInfo'; + +export enum AlarmCardType { + MainPageCard, } \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/components/ArraySlider/index.ets b/feature/alarmclock/src/main/ets/components/ArraySlider/index.ets new file mode 100644 index 0000000..c08b1fc --- /dev/null +++ b/feature/alarmclock/src/main/ets/components/ArraySlider/index.ets @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MIN_INDEX_STEP, INDEX_STEP } from './types'; + +// The font style of the label of the slider bar +@Extend(Text) +function labelText() { + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) +} + +/** + * The slider which the to-be-selected values can be transferred through an array + * + * @since 2022-07-19 + */ +@Component +export struct ArraySlider { + private label: ResourceStr = ''; + private value: number = 0; + private scales: number[] = []; + private testKey: string = ''; // Key for unit testing + private onChange?: (value: number) => void; + + /** + * Description label of the slider bar + */ + @Builder + buildLabel() { + Row() { + Text(this.label).fontSize($r('app.float.slider_label_size')).labelText() + } + .width('100%') + .justifyContent(FlexAlign.Start) + .align(Alignment.Center) + } + + /** + * Builder of the slider bar + */ + @Builder + buildSlider() { + Slider({ + value: this.scales.findIndex(step => step === this.value), + min: MIN_INDEX_STEP, + max: this.scales.length - INDEX_STEP, + step: INDEX_STEP, + style: SliderStyle.OutSet, + }) + .key(this.testKey) + .blockColor($r('sys.color.comp_background_primary_contrary')) + .selectedColor($r('sys.color.comp_background_emphasize')) + .onChange((index: number) => this.onChange!(this.scales[index.toFixed()])) + .margin({ + top: $r('app.float.snooze_setting_margin_vertical_max'), + bottom: $r('app.float.snooze_setting_margin_vertical_min'), + left: $r('app.float.slider_margin_horizontal'), + }) + .offset({ x: $r('app.float.slider_margin_offset_left') }) + } + + /** + * Scales of the slider bar + */ + @Builder + buildScales() { + Flex({ justifyContent: FlexAlign.SpaceBetween }) { + ForEach(this.scales, (scale: number) => { + Text(scale.toString()).fontSize($r('app.float.slider_step_label_size')).labelText() + }) + } + } + + build() { + Column() { + this.buildLabel() + this.buildSlider() + this.buildScales() + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/Application/MyAbilityStage.ts b/feature/alarmclock/src/main/ets/components/ArraySlider/types.ets similarity index 68% rename from product/phone/src/main/ets/Application/MyAbilityStage.ts rename to feature/alarmclock/src/main/ets/components/ArraySlider/types.ets index 0a9efc1..7abc1aa 100644 --- a/product/phone/src/main/ets/Application/MyAbilityStage.ts +++ b/feature/alarmclock/src/main/ets/components/ArraySlider/types.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,10 +13,6 @@ * limitations under the License. */ -import AbilityStage from "@ohos.application.AbilityStage" +export const MIN_INDEX_STEP = 0; -export default class MyAbilityStage extends AbilityStage { - onCreate() { - console.log("[Demo] MyAbilityStage onCreate") - } -} \ No newline at end of file +export const INDEX_STEP = 1; \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/components/Form/index.ets b/feature/alarmclock/src/main/ets/components/Form/index.ets new file mode 100644 index 0000000..51cad11 --- /dev/null +++ b/feature/alarmclock/src/main/ets/components/Form/index.ets @@ -0,0 +1,506 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import promptAction from '@ohos.promptAction'; +import resmgr from '@ohos.resourceManager'; +import { + ActionType, + AlarmInfo, + AlarmInfoProperty, + AlarmInfoToShow, + ALARM_NAME_INPUT_LIMIT, + ALARM_NAME_INPUT_MIN, + Card, + CommonDialog, + CommonTextInputDialog, + CommonUtil, + EventName, + EventReportUtil, + EventResult, + GlobalContext, + PROMPT_TIME, + ResourceManager, + LogUtil, +} from '@hmos/common'; +import { FLEX_GROW_FILL_FULL, FormField, FormOption, FormValue, InputType, DIALOG_EMITTER_EVENT } from './types'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import emitter from '@ohos.events.emitter'; + +const TAG = 'formWeekk'; + +interface TypeDaysOfWeek { + daysOfWeekDesc: string; +} + +interface IBuildLabelAndValue { + label: ResourceStr; + value: ResourceStr; +} + +/** + * Form component that can be configured through template + * + * @since 2022-07-16 + */ +@Component +export struct Form { + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + // This parameter is used to transfer form field information. + // public fields: FormField[] = []; + @Link fields: FormField[]; + // This parameter is used to transfer form properties. + // public properties?: TypeDaysOfWeek | AlarmInfoProperty = undefined; + @Link properties?: AlarmInfoProperty; + // This parameter is used to pass the custom input builders. + @StorageProp('repeatType') repeatType: number = 0; + @StorageProp('repeatModeFields') repeatModeFields: string = ''; + @Prop cancelMargin: boolean = false; + @BuilderParam buildDiyInput?: (property?: string) => void; + @State editingField?: FormField = undefined; + @State dialogSelectChanged: boolean = false; + @State hoverInfoList: boolean[] = this.fields.map(f => { + return false; + }); + @State hoverDialogList: boolean[] = []; + @State currentPressItem: ResourceStr = ''; + @State currentPressSelectItem: ResourceStr = ''; + @State ringDuration:string = ''; + @State snoozeDuration:string = ''; + private editingValue: FormValue; + private editingOptions?: FormOption[] = []; + // The controller of the dialog + private customControllerOptions: CustomDialogControllerOptions = CommonUtil.getCommonDialogConfig(); + private dialogController: CustomDialogController = new CustomDialogController({ + builder: CommonDialog({ + title: this.getStringFromResourceStr(this.editingField?.label), + actionType: this.editingField?.inputType === InputType.RADIO + ? ActionType.SINGE_BUTTON_HORIZONTAL : ActionType.TWO_BUTTON_HORIZONTAL, + firstButtonName: (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('cancel'), + firstButtonCallback: () => this.closeInputDialog(false), + secondButtonName: this.editingField?.inputType === InputType.RADIO + ? undefined : (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('ok'), + secondButtonCallback: this.editingField?.inputType === InputType.RADIO + ? undefined // There's no confirm button for Radio + : () => this.confirmInput(), + content: (): void => this.buildDialogContent(), + }), + autoCancel: this.customControllerOptions.autoCancel, + alignment: this.foldAbleScreen === 1 ? DialogAlignment.Center : this.customControllerOptions.alignment, + customStyle: this.customControllerOptions.customStyle, + }); + // The controller of the textInputDialogController + private textInputDialogController: CustomDialogController = new CustomDialogController({ + builder: CommonTextInputDialog({ + title: this.getStringFromResourceStr(this.editingField?.label), + cancelText: (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('cancel'), + cancelCallback: () => this.closeInputDialog(false), + confirmText: this.editingField?.inputType === InputType.RADIO + ? undefined : (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('ok'), + confirmCallback: this.editingField?.inputType === InputType.RADIO + ? undefined + : () => this.confirmInput(), + textContent: (this.properties as Object as Record)[this.editingField!.property], + textInputCallback: this.textInputCallBack + }), + autoCancel: this.customControllerOptions.autoCancel, + alignment: this.foldAbleScreen === 1 ? DialogAlignment.Center : this.customControllerOptions.alignment, + customStyle: this.customControllerOptions.customStyle, + }); + + private getValueFromInputDialog(): FormValue { + if (this.editingField?.inputType === InputType.TEXT) { + return GlobalContext.getContext().getObject('textInput') as FormValue; + } + if (this.editingField?.inputType !== InputType.CHECK_BOX) { + return this.editingValue; + } + if (this.editingOptions) { + return this.editingOptions!.filter(option => option.isSelected).map(option => option.value); + } + return undefined; + } + + private async confirmInput(): Promise { + if (this.editingField?.onConfirmDialog) { + await this.editingField.onConfirmDialog(); + let snoozeDuration = this.properties?.snoozeDuration; + let snoozeTimes = this.properties?.snoozeTimes; + this.snoozeDuration = snoozeDuration + '&' + snoozeTimes; + this.reportEventForDialogConfirm(); + return; + } + this.reportEventForDialogConfirm(); + // Callback based on field input parameters + // Prepare and update the value of the field + const inputValue: FormValue = this.getValueFromInputDialog(); + this.editingField?.onChange && this.editingField.onChange(inputValue); + if (this.editingField && this.properties) { + (this.properties as Object as Record)[this.editingField.property] = inputValue; + } + this.closeInputDialog(true); + } + + private reportEventForDialogConfirm() { + if (this.editingField?.inputType === InputType.CHECK_BOX) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_NEW_CLOCK_REPEAT_CHOICE) + } + if (this.editingField?.inputType === InputType.TEXT) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_SET_ALARM_NAME_CONFIRM) + } + if (this.editingField?.inputType === InputType.RADIO) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_EDIT_ALARM_RING_DURATION, + // 0, this.ringDuration) + } + if (this.editingField?.inputType === InputType.DIY) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_CONFIRM_SNOOZE_DURATION, + // 0, this.snoozeDuration) + } + } + + private async closeInputDialog(isAuto?: boolean): Promise { + // Callback based on field input parameters + this.editingField?.onCloseDialog && await this.editingField.onCloseDialog(); + if (!isAuto) { + this.reportEventForDialogClose(); + } + this.editingOptions = undefined; + this.editingValue = undefined; + GlobalContext.getContext().setObject('textInput', undefined); + GlobalContext.getContext().setObject('dialogController', undefined); + } + + private reportEventForDialogClose() { + if (this.editingField?.inputType === InputType.CHECK_BOX) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_NEW_CLOCK_REPEAT_CANCEL) + } + if (this.editingField?.inputType === InputType.TEXT) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_SET_ALARM_NAME_CANCEL) + } + if (this.editingField?.inputType === InputType.RADIO) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_CANCEL_RING_DURATION) + } + if (this.editingField?.inputType === InputType.DIY) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_CANCEL_SNOOZE_DURATION) + } + } + + private async WEEKopenInputDialog(field: FormField): Promise { + LogUtil.info(TAG, 'openInputDialog'); + if (field.disabled) { + return; + } + // Callback based on field input parameters + field.onOpenDialog && await field.onOpenDialog(); + + // Prepare Data for Editing + this.editingField = field; + this.editingValue = (this.properties as Object as Record)[field.property]; + this.hoverDialogList = [] + this.editingOptions = field.options?.map(option => { + this.hoverDialogList.push(false) + return { label: option.label, + value: option.value, + isSelected: option.isSelected + } as FormOption; + }); + GlobalContext.getContext().setObject('dialogController', this.dialogController); + this.dialogController.open(); + this.reportEventForDialogOpen(); + } + + private async openInputDialog(field: FormField): Promise { + if (field.disabled) { + return; + } + + // Callback based on field input parameters + field.onOpenDialog && await field.onOpenDialog(); + + // Prepare Data for Editing + this.editingField = field; + this.editingValue = (this.properties as Object as Record)[field.property]; + this.hoverDialogList = [] + this.editingOptions = field.options?.map(option => { + this.hoverDialogList.push(false) + return { label: option.label, + value: option.value, + isSelected: option.isSelected + } as FormOption; + }); + + if (this.editingField?.inputType === InputType.TEXT) { + GlobalContext.getContext() + .setObject('textInput', (this.properties as Object as Record)[field.property]); + GlobalContext.getContext().setObject('dialogController', this.textInputDialogController); + this.textInputDialogController.open(); + } else { + GlobalContext.getContext().setObject('dialogController', this.dialogController); + this.dialogController.open(); + } + this.reportEventForDialogOpen(); + } + + private reportEventForDialogOpen() { + if (this.editingField?.inputType === InputType.CHECK_BOX) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_NEW_CLOCK_REPEAT) + } + if (this.editingField?.inputType === InputType.TEXT) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_SET_ALARM_NAME) + } + if (this.editingField?.inputType === InputType.RADIO) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.SET_ALARM_RING_DURATION) + } + if (this.editingField?.inputType === InputType.DIY) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.SET_ALARM_SNOOZE_DURATION) + } + } + + private getStringFromResourceStr(resourceStr?: ResourceStr): string { + if (!resourceStr) { + return ''; + } + if (typeof resourceStr === 'string') { + return resourceStr; + } + return (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringSync(resourceStr.id); + } + + async textInputCallBack(value: string): Promise { + if (value?.length === ALARM_NAME_INPUT_LIMIT) { + GlobalContext.getContext().setObject('textInput', value); + const inputLimitTips = await ResourceManager.getStringByIdAsync($r('app.string.strlenght_full_Toast').id); + promptAction.showToast({ message: inputLimitTips, duration: PROMPT_TIME }); + } else { + GlobalContext.getContext().setObject('textInput', value); + } + } + + @Builder + buildTextInput() { + if (this.editingField?.inputType === InputType.TEXT) { + Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Column }) { + TextInput({ text: this.editingValue as ResourceStr || (this.properties as Object as + Record)[this.editingField.property] }) + .backgroundColor($r('app.color.transparent')) + .height($r('app.float.input_height')) + .maxLength(ALARM_NAME_INPUT_LIMIT) + .onChange(async (value: string) => { + if (value?.length === ALARM_NAME_INPUT_LIMIT) { + const inputLimitTips = await ResourceManager.getStringByIdAsync($r('app.string.strlenght_full_Toast').id); + promptAction.showToast({ message: inputLimitTips, duration: PROMPT_TIME }); + } + this.editingValue = value; + if (value?.length === ALARM_NAME_INPUT_MIN) { + this.editingValue = (this.properties as Object as Record)[this.editingField!.property]; + } + }) + .padding({ + left: 0, + right: 0, + }) + } + .height($r('app.float.list_item_height')) + + Divider().color($r('sys.color.comp_divider')) + } + } + + @Builder + buildSelectBox(option: FormOption) { + if (this.editingField?.inputType === InputType.RADIO) { + Radio({ value: option.label, group: this.editingField.property }) + .height($r('app.float.radio_size')) + .width($r('app.float.radio_size')) + .padding(0) + .checked(option.isSelected as boolean) + .radioStyle({ + checkedBackgroundColor: $r('sys.color.comp_background_emphasize') + }) + .hitTestBehavior(HitTestMode.None) + } else { + Checkbox() + .select(option.isSelected as boolean) + .selectedColor($r('sys.color.comp_background_emphasize')) + .shape(CheckBoxShape.ROUNDED_SQUARE) + .height($r('app.float.button_icon_size')) + .width($r('app.float.button_icon_size')) + .padding(0) + .hitTestBehavior(HitTestMode.None) + } + } + + @Builder + buildSelectInputItem(option: FormOption, index: number | undefined) { + Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { + Text(option.label) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + Row() { + this.buildSelectBox(option) + } + } + .id('id_alarmInfo_edit') + .onClick(() => { + option.isSelected = !option.isSelected; + if (this.editingField?.inputType === InputType.RADIO) { + if (option.isSelected) { + this.editingValue = option.value; + this.ringDuration = String(option.value); + this.confirmInput(); + this.dialogController.close(); + } + } else { + this.dialogSelectChanged = !this.dialogSelectChanged; + } + }) + .onHover((isHover) => { + if (typeof index === 'number') { + if (isHover === true) { + this.hoverDialogList[index] = true; + } else { + this.hoverDialogList[index] = false; + } + } + }) + .backgroundColor(this.currentPressSelectItem === option.label ? $r('sys.color.interactive_click') : + ((typeof index === 'number' && this.hoverDialogList[index]) ? $r('app.color.color_hover') : $r('app.color.color_hover_disabled'))) + .onMouse((event: MouseEvent) => { + if ((event?.action === MouseAction.Press) && (event?.button === MouseButton.Left)) { + this.currentPressSelectItem = option.label; + } else if (event?.action === MouseAction.Release) { + this.currentPressSelectItem = ''; + } + }) + // .backgroundColor((typeof index === 'number' && this.hoverDialogList[index]) ? $r('app.color.color_hover') : + // $r('app.color.color_hover_disabled')) + .borderRadius($r('app.float.default_corner_radius_l')) + .padding({ left: $r('app.float.float_content_padding') }) + .height($r('app.float.list_item_height')) + } + + @Builder + buildSelectInput() { + if (this.editingField?.inputType === InputType.CHECK_BOX || this.editingField?.inputType === InputType.RADIO) { + ForEach(this.editingOptions || [], (option: FormOption, index: number | undefined) => { + this.renderDivider(index) + this.buildSelectInputItem(option, index) + }, (option: FormOption) => JSON.stringify(option) + this.dialogSelectChanged) + } + } + + @Builder + buildDialogContent() { + Column() { + if (this.editingField?.inputType === InputType.DIY && this.buildDiyInput) { + // The user side uses the builder to implement the input. + this.buildDiyInput(this.editingField.property) + } else { + this.buildSelectInput() + this.buildTextInput() + } + } + } + + @Builder + // buildLabelAndValue(label: ResourceStr, value: ResourceStr) { + buildLabelAndValue($$: IBuildLabelAndValue) { + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text($$.label) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + .margin({ right: $r('app.float.card_tag_margin_end') }) + .fontWeight(FontWeight.Medium) + .width('30%') + Column() { + Row() { + Text($$.value) + .fontSize($r('sys.float.Body_M')) + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) + .width('90%') + .textAlign(TextAlign.End) + .padding({ + top: $r('app.float.card_inner_padding_horizontal'), + bottom: $r('app.float.card_inner_padding_horizontal'), + }) + } + } + .alignItems(HorizontalAlign.End) + .flexGrow(FLEX_GROW_FILL_FULL) + } + .width('100%') + .constraintSize({ minHeight: $r('app.float.set_alarm_card_height') }) + } + + @Builder + renderDivider(index: number | undefined) { + if (index) { + // Render divider above items except for the first item with index zero + Divider().color($r('sys.color.comp_divider')) + } + } + + @Builder + buildFieldItem(field: FormField) { + Row() { + this.buildLabelAndValue({ label: field.label, value: (this.properties as Object as + Record)[field.property] }) + } + .onClick(() => this.openInputDialog(field)) + } + + build() { + Row() { + Card({ isMultipleSubItems: true, cancelMargin: true, bgColor: $r('sys.color.comp_background_list_card')} ) { + ForEach(this.fields, (field: FormField, index: number | undefined) => { + Column() { + this.renderDivider(index) + this.buildFieldItem(field) + } + .id('id_alarmInfo_item') + .padding({ + right: $r('app.float.card_inner_padding_horizontal'), + left: $r('app.float.card_inner_padding_horizontal'), + }) + .onHover((isHover) => { + if (typeof index === 'number') { + if (isHover === true) { + this.hoverInfoList[index] = true; + } else { + this.hoverInfoList[index] = false; + } + } + }) + .backgroundColor(this.currentPressItem === field.property ? $r('sys.color.interactive_click') : + ((typeof index === 'number' && this.hoverInfoList[index]) ? $r('app.color.color_hover') : + $r('app.color.color_hover_disabled'))) + .onMouse((event: MouseEvent) => { + if ((event?.action === MouseAction.Press) && (event?.button === MouseButton.Left)) { + this.currentPressItem = field.property; + } else if (event?.action === MouseAction.Release) { + this.currentPressItem = ''; + } + }) + .borderRadius($r('app.float.default_corner_radius_l')) + }) + } + } + } +} diff --git a/feature/alarmclock/src/main/ets/components/Form/types.ets b/feature/alarmclock/src/main/ets/components/Form/types.ets new file mode 100644 index 0000000..f298f1c --- /dev/null +++ b/feature/alarmclock/src/main/ets/components/Form/types.ets @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum InputType { + TEXT, + RADIO, + CHECK_BOX, + DIY, // The user side uses the builder to implement the input. +} + +export interface FormOption { + label: string; + value: number; + isSelected?: boolean; +} + +export interface FormField { + label: ResourceStr; + property: string; + inputType: InputType; + options?: FormOption[]; // Mandatory for radio or check box + onChange?: (value?: FormValue) => void; + onOpenDialog?: () => void; // Callback when the input dialog is opened + onCloseDialog?: () => void; // Callback when the input dialog is closed + onConfirmDialog?: () => void; // Callback when the input dialog is confirmed + disabled?: boolean; +} + +export interface WeekListField { + label: string; + value: number, + selected: boolean; +} + +export interface FormDiyCallbackInfo { + onOpenDialog: () => void; + onCloseDialog: () => void; + onConfirmDialog: () => void; +} + +export const FLEX_GROW_FILL_FULL = 1; + +export type FormValue = ResourceStr | number | number[] | undefined; + +export const DIALOG_EMITTER_EVENT = 99999; \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/components/PCBuilder/index.ets b/feature/alarmclock/src/main/ets/components/PCBuilder/index.ets new file mode 100644 index 0000000..11f8709 --- /dev/null +++ b/feature/alarmclock/src/main/ets/components/PCBuilder/index.ets @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ManageAlarmClock } from '../../pages/ManageAlarmClock'; + + +interface IPCAlarmFormPopupBuilderProps { + isPCView: boolean; + visible: boolean; + onConfirm: () => void; + onCancel: () => void; +} + +/** + * PC 端 新建闹钟 popup + * + * */ +@Builder +function PCAlarmFormPopupBuilder(props: IPCAlarmFormPopupBuilderProps) { + Flex() { + if (props.visible) { + ManageAlarmClock({ + isPCView: props.isPCView, + onConfirmCallback: props.onConfirm, + onCancelCallback: props.onCancel, + }) + } + } + .size({ + width: $r('app.float.pc_city_popup_builder_flex_width'), + }) + .padding({ + top: $r('sys.float.padding_level4') + }) + .backgroundColor($r('app.color.component_ultra_thick_panel')) +} + +export { + PCAlarmFormPopupBuilder +}; \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/components/SlideCloseButton/index.ets b/feature/alarmclock/src/main/ets/components/SlideCloseButton/index.ets new file mode 100644 index 0000000..8cdb916 --- /dev/null +++ b/feature/alarmclock/src/main/ets/components/SlideCloseButton/index.ets @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// import hiSysEvent from '@ohos.hiSysEvent'; +import { LogUtil, AlarmInfo, WantAgentUtil, EventReportUtil, EventName, CommonUtil } from '@hmos/common'; + +const FIRST_FINGER = 0; +const TAG = 'SlideCloseButton'; +const MOVE_SCALE = 1; + +/** + * Button for sliding to turn off + * + * @since 2022-08-18 + */ +@Component +export default struct SlideCloseButton { + @Prop alarmInfo?: AlarmInfo; + private preX: number = 0; + private preY: number = 0; + private curX: number = 0; + private curY: number = 0; + private startX: number = 0; + private startY: number = 0; + private isMoved: boolean = false; + @State positionX: number = 0; + + private updateActionMove(event: TouchEvent): void { + this.curX = event.touches[FIRST_FINGER].windowX; + this.curY = event.touches[FIRST_FINGER].windowY; + LogUtil.info(TAG, 'curX: ' + this.curX, 'curY: ' + this.curY); + + if (Math.abs(this.curX - this.startX) <= Math.abs(this.curY - this.startY)) { + // Move in Y-direction, no response. + LogUtil.info(TAG, 'move Y direction=' + Math.abs(this.curY - this.startY)); + this.isMoved = false; + } else { + // Move in X-direction, make the button move. + LogUtil.info(TAG, 'move X direction=' + Math.abs(this.curX - this.startX)); + if (Math.abs(this.curX - this.startX) >= 100) { + this.isMoved = true; + } + } + this.positionX += (this.curX - this.preX) * MOVE_SCALE; + + // Record the previous coordinate + this.preX = this.curX; + this.preY = this.curY; + } + + private async onSlideButton(event: TouchEvent): Promise { + switch (event.type) { + case TouchType.Down: { + LogUtil.info(TAG, 'touch down'); + this.startX = event.touches[FIRST_FINGER].screenX; + this.startY = event.touches[FIRST_FINGER].screenY; + LogUtil.info(TAG, 'startX: ' + this.startX, 'startY: ' + this.startY); + this.preX = this.startX; + this.preY = this.startY; + break; + } + case TouchType.Up: { + LogUtil.info(TAG, `touch up: ${this.isMoved} ${JSON.stringify(this.alarmInfo)}`); + if (this.isMoved && this.alarmInfo) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.LOCK_SCREEN_SWIPE_CLOSE_ALARM); + await WantAgentUtil.triggerCloseAlarm(this.alarmInfo); + CommonUtil.terminateFullScreenAbility(); + } else { + this.positionX = 0; + } + break; + } + case TouchType.Move: { + LogUtil.info(TAG, 'touch move'); + this.updateActionMove(event); + break; + } + case TouchType.Cancel: { + LogUtil.info(TAG, 'touch cancel'); + break; + } + default: { + LogUtil.info(TAG, 'default'); + } + } + } + + build() { + Column() + .borderWidth($r('app.float.slide_to_turn_off_button_border_width')) + .borderColor(Color.White) + .borderRadius($r('app.float.slide_to_turn_off_button_size')) + .width($r('app.float.slide_to_turn_off_button_size')) + .height($r('app.float.slide_to_turn_off_button_size')) + .offset({ x: this.positionX, y: 0 }) + .onTouch((event?: TouchEvent) => this.onSlideButton(event as TouchEvent)); + } +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/components/index.ets b/feature/alarmclock/src/main/ets/components/index.ets new file mode 100644 index 0000000..5d5ff45 --- /dev/null +++ b/feature/alarmclock/src/main/ets/components/index.ets @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { AlarmCard } from './AlarmCard/index'; + +export { ObservedAlarmInfo, AlarmCardType } from './AlarmCard/types'; + +export { ArraySlider } from './ArraySlider/index'; + +export { Form } from './Form/index'; + +export { InputType } from './Form/types'; + +export { FormValue, FormField, FormOption, FormDiyCallbackInfo, WeekListField } from './Form/types'; \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/manager/AlarmServiceManager.ets b/feature/alarmclock/src/main/ets/manager/AlarmServiceManager.ets new file mode 100644 index 0000000..cd8be95 --- /dev/null +++ b/feature/alarmclock/src/main/ets/manager/AlarmServiceManager.ets @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import power from '@ohos.power'; +// import brightness from '@system.brightness'; +import audio from '@ohos.multimedia.audio'; +import emitter from '@ohos.events.emitter'; +import media from '@ohos.multimedia.media'; +import resmgr from '@ohos.resourceManager'; +// import inputConsumer from '@ohos.multimodalInput.inputConsumer'; +import observer from '@ohos.telephony.observer'; +import call from '@ohos.telephony.call'; +import screenLock from '@ohos.screenLock'; +import hiAppEvent from '@ohos.hiviewdfx.hiAppEvent'; +import worker from '@ohos.worker'; +import Want from '@ohos.app.ability.Want'; +import common from '@ohos.app.ability.common'; +import { + AlarmInfo, + AlarmManager, + AlarmServiceType, + AlarmStateManager, + TimerStateManager, + TIMEOUT_FULL_SCREEN_CLOSE_ID, + CommonUtil, + EVENT_ID_ALARM_RING, + EventName, + EventReportUtil, + EventResult, + GlobalContext, + LogUtil, + TimeUtil, + EVENT_ID_KILL_ALARM_TIMEOUT_MESSAGE, + WantAgentUtil, + FullScreenType +} from '@hmos/common'; +import { NotificationUtil, AlarmCardUtil } from '../utils'; +import { RING_FILE_NAME } from './types'; + +// import hiSysEvent from '@ohos.hiSysEvent'; +import deviceInfo from '@ohos.deviceInfo' +import { BusinessError } from '@ohos.base'; + +interface ReportParams { + PACKAGE_NAME: string; + PROCESS_NAME: string; + RESULT: string; + VALUE?: string; + STATUS?: string +} + +const deviceType: string = deviceInfo.deviceType; + +const TAG = 'AlarmServiceManager'; + +// type ServiceExtensionContext = common.ServiceExtensionContext; + +/** + * Alarm Service Manager + * + * @since 2022-08-25 + */ +class AlarmServiceManager { + public workerInstance?: media.AVPlayer = undefined; + + constructor() { + // Bind snoozeCallback to current context + this.snoozeCallback; + } + + private getEventReportUtil(event_name: string): void { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, event_name,); + } + + private async typeStart(alarmInfo: AlarmInfo, latestAlarmInfo?: AlarmInfo) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_ALARM_IS_ON); + this.getEventReportUtil('ALARM_RINGDOWN_BANNER'); + if (!latestAlarmInfo) { + LogUtil.info(TAG, `alarmInfo is not available: ${JSON.stringify(alarmInfo)}`); + return; + } + await TimeUtil.getFreeday(); // Obtaining statutory holidays data in the database. + await this.startAlarm(alarmInfo); + // After the alarm is generated, you need to update the data in the database. + // 1. The enable attribute of alarms with the same time is set to false. + // 2. Recalculate the alarmTime of the repeated alarms with the same time as the alarmInfo. + // Update the next start time. + await AlarmManager.closeNoRepeatAlarms(alarmInfo); + this.notifyRefreshMainPage(); + } + + private async typeReStart(alarmInfo: AlarmInfo) { + alarmInfo.enabled = true; + alarmInfo.reStart = ''; + await AlarmManager.updateAlarm(alarmInfo); + emitter.emit({ + eventId: EVENT_ID_ALARM_RING, + priority: emitter.EventPriority.IMMEDIATE, + }); + } + + private async typeClose(alarmInfo: AlarmInfo, isSnooze?: boolean, isCloseButton?: boolean) { + this.getEventReportUtil('ALARM_CLOSE_BANNER'); + if (isCloseButton) { + if (isSnooze) { + EventReportUtil.write(EventName.NOTIFICATION_CANCEL_ALARM_SNOOZE, hiAppEvent.EventType.BEHAVIOR, { + 'result': EventResult.EVENT_RESULT_SUCCESS }); + } else { + EventReportUtil.write(EventName.NOTIFICATION_CLOSE_ALARM, hiAppEvent.EventType.BEHAVIOR, { + 'result': EventResult.EVENT_RESULT_SUCCESS }); + } + } + await this.stopAlarm(alarmInfo); + this.notifyRefreshMainPage(); + } + + private async typeDelay(alarmInfo: AlarmInfo) { + this.getEventReportUtil('ALARM_REMINDER_LATER_BANNER'); + EventReportUtil.write(EventName.NOTIFICATION_SNOOZE_ALARM, hiAppEvent.EventType.BEHAVIOR, { + 'result': EventResult.NOTIFICATION_SNOOZE_ALARM }); + await this.delayAlarm(alarmInfo); + } + + /** + * Deal with the alarm processing request based on the service type. + * + * @param alarmInfo Alarm information + * @param serviceType Alarm service type which could be Start, Stop, or Delay. + */ + async dealAlarmWithServiceType(alarmInfo: AlarmInfo, serviceType: AlarmServiceType, + isCloseButton?: boolean, isSnooze?: boolean): Promise { + if (!alarmInfo || !serviceType) { + LogUtil.error(TAG, 'Execute method dealAlarmWithServiceType failed, params is incorrect.'); + return; + } + const latestAlarmInfo = await AlarmManager.getLatestAlarmFromDb(alarmInfo?.id as string); + if (latestAlarmInfo) { + alarmInfo = latestAlarmInfo; + LogUtil.info(TAG, 'Get latest alarm from db success.'); + } else { + LogUtil.info(TAG, 'This alarm has not been found in db.'); + } + switch (serviceType) { + case AlarmServiceType.Start: { + await this.typeStart(alarmInfo, latestAlarmInfo); + break; + } + case AlarmServiceType.ReStart: { + await this.typeReStart(alarmInfo); + break; + } + case AlarmServiceType.Close: { + await this.typeClose(alarmInfo, isSnooze, isCloseButton); + break; + } + case AlarmServiceType.Delay: { + await this.typeDelay(alarmInfo); + break; + } + default: { + LogUtil.error(TAG, 'Service type is not right:', serviceType); + break; + } + } + } + + setScreenOn(): void { + try { + LogUtil.info(TAG, `setScreenOn`); + // brightness.setKeepScreenOn({ keepScreenOn: true, + // success: () => { + // LogUtil.info(TAG, `setScreenOn success`); + // }, + // fail: (data: string, code: number) => { + // LogUtil.error(TAG, `setScreenOn fail ${data} ${code}`); + // }, + // complete: () => { + // LogUtil.info(TAG, `setScreenOn complete`); + // } }); + } catch (error) { + LogUtil.info(TAG, `setScreenOn failed because: ${JSON.stringify(error)}`); + } + } + + setScreenOff(): void { + try { + LogUtil.info(TAG, `setScreenOff`); + // brightness.setKeepScreenOn({ keepScreenOn: false, + // success: () => { + // LogUtil.info(TAG, `setScreenOff success`); + // }, + // fail: (data: string, code: number) => { + // LogUtil.error(TAG, `setScreenOff fail ${data} ${code}`); + // }, + // complete: () => { + // LogUtil.info(TAG, `setScreenOff complete`); + // } }); + } catch (error) { + LogUtil.info(TAG, `setScreenOff failed because: ${JSON.stringify(error)}`); + } + } + + /** + * Start alarm + * + * @param alarmInfo Alarm information + */ + async startAlarm(alarmInfo: AlarmInfo): Promise { + if (!alarmInfo?.id) { + LogUtil.error(TAG, 'Execute method startAlarm failed, params is incorrect.'); + return; + } + const isFiring = await AlarmStateManager.isFiring(); + if (isFiring) { + const firingAlarmId = await AlarmStateManager.getAlarmId(); + LogUtil.info(TAG, `current is firing: firing alarm id is: ${firingAlarmId}`); + const firingAlarm = await AlarmManager.getLatestAlarmFromDb(firingAlarmId); + await this.delayAlarm(firingAlarm, true); + } + const timerIsFiring = await TimerStateManager.isFiring(); + if (timerIsFiring) { + LogUtil.info(TAG, `timerIsFiring=>${timerIsFiring}`); + emitter.emit({ eventId: TIMEOUT_FULL_SCREEN_CLOSE_ID, priority: emitter.EventPriority.IMMEDIATE }); + CommonUtil.terminateFullScreenAbility(); + } + try { + this.listenSnoozeInput(); + if (CommonUtil.isDevicePhone()) { + this.listenCallStateChange(); + } + await AlarmStateManager.setStateFiring(alarmInfo.id); + const isScreenLocked: boolean = await screenLock.isScreenLocked(); + LogUtil.info(TAG, 'isScreenLocked is ' + isScreenLocked); + if (deviceType !== '2in1') { + if (!isScreenLocked) { + // When the screen is unlocked, send notification. + this.setScreenOn(); + if (alarmInfo.alarmTime) { + await NotificationUtil.publishAlarmReminder(alarmInfo.id!, alarmInfo?.alarmTime, alarmInfo.title!); + } + this.startAudioWorker(); + return; + } + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_LOCK_SCREEN_ALARM); + // power.wakeup('alarm'); + // When the screen is locked, the alarm starts in full screen. + await WantAgentUtil.triggerFullScreenAbility(FullScreenType.Alarm); + } else { + if (alarmInfo.alarmTime) { + await NotificationUtil.publishAlarmReminder(alarmInfo.id!, alarmInfo?.alarmTime, alarmInfo.title!); + } + this.startAudioWorker(); + return; + } + + } catch (error) { + LogUtil.error(TAG, `Request alarm failed because: ${JSON.stringify(error)}`); + } + } + + async audioStateHandle(player: media.AVPlayer) { + if (!player) { + return; + } + player.on('stateChange', async (state) => { + LogUtil.info(TAG, 'audioStateHandle stateChange:' + state); + switch (state) { + case 'initialized': { + let audioRendererInfo: audio.AudioRendererInfo = { + content: audio.ContentType.CONTENT_TYPE_UNKNOWN, + usage: audio.StreamUsage.STREAM_USAGE_ALARM, + rendererFlags: 0 + } + player.audioRendererInfo = audioRendererInfo; + player.prepare(); + break; + } + case 'prepared': { + player.setVolume(1.0); + player.loop = true; + player.play(); + break; + } + default: { + } + } + }); + player.on('error', async (error: BusinessError) => { + LogUtil.error(TAG, 'audio player error:' + JSON.stringify(error)); + }); + player.on('audioInterrupt', async (info: audio.InterruptEvent) => { + LogUtil.info(TAG, 'audioInterrupt:' + JSON.stringify(info)); + if (info.hintType === audio.InterruptHint.INTERRUPT_HINT_STOP) { + this.stopAudioWorker(true); + } + }); + } + + public async createAudioWorker(init?: boolean): Promise { + try { + LogUtil.info(TAG, 'create AudioWorker'); + let audioPlayer = await media.createAVPlayer(); + const resourceManager = (GlobalContext.getContext().getObject('resourceManager') as resmgr.ResourceManager); + const fileDescriptor = await resourceManager.getRawFd(RING_FILE_NAME); + let avFileDescriptor: media.AVFileDescriptor = { + fd: fileDescriptor.fd, + offset: fileDescriptor.offset, + length: fileDescriptor.length + }; + audioPlayer.fdSrc = avFileDescriptor; + this.workerInstance = audioPlayer; + if (!init) { + this.audioStateHandle(this.workerInstance); + } + } catch (err) { + let message = (err as BusinessError).message; + let errCode = (err as BusinessError).code; + LogUtil.error(TAG, `createAudioWorker error: code: ${errCode}, message: ${message} `); + } + } + + /** + * start Audio Worker + */ + public async startAudioWorker(): Promise { + try { + if (!this.workerInstance) { + LogUtil.info(TAG, 'start to create AudioWorker'); + await this.createAudioWorker(); + } else { + LogUtil.info(TAG, 'AudioWorker has been create, state:' + this.workerInstance.state); + this.audioStateHandle(this.workerInstance); + if (this.workerInstance.state === 'completed' || this.workerInstance.state === 'prepared') { + LogUtil.info(TAG, 'completed'); + this.workerInstance.setVolume(1.0); + this.workerInstance.loop = true; + this.workerInstance.play(); + } else if (this.workerInstance.state === 'initialized') { + LogUtil.info(TAG, 'initialized'); + let audioRendererInfo: audio.AudioRendererInfo = { + content: audio.ContentType.CONTENT_TYPE_UNKNOWN, + usage: audio.StreamUsage.STREAM_USAGE_ALARM, + rendererFlags: 0 + } + this.workerInstance.audioRendererInfo = audioRendererInfo; + this.workerInstance.prepare(); + } else if (this.workerInstance.state === 'playing') { + LogUtil.info(TAG, 'playing'); + } else { + LogUtil.info(TAG, 'not completed/playing/initialized/prepared'); + await this.stopAudioWorker(); + await this.createAudioWorker(); + } + } + } catch (err) { + let message = (err as BusinessError).message; + let errCode = (err as BusinessError).code; + LogUtil.error(TAG, `startAudioWorker error: code: ${errCode}, message: ${message} `); + } + } + + /** + * stop Audio Worker + */ + public async stopAudioWorker(isForce: boolean = false): Promise { + const isFiring = await AlarmStateManager.isFiring(); + LogUtil.info(TAG, 'stopAudioWorker isFiring: ' + isFiring + ' isForce=' + isForce); + if (isFiring && !isForce) { + return; + } + if (this.workerInstance) { + LogUtil.info(TAG, 'start to stop AudioWorker'); + let player: media.AVPlayer = this.workerInstance; + this.workerInstance = undefined; + try { + await player.stop(); + } catch (err) { + let message = (err as BusinessError).message; + let errCode = (err as BusinessError).code; + LogUtil.error(TAG, `stopAudioWorker error: code: ${errCode}, message: ${message} `); + } + try { + await player.release(); + } catch (err) { + let message = (err as BusinessError).message; + let errCode = (err as BusinessError).code; + LogUtil.error(TAG, `releaseAudioWorker error: code: ${errCode}, message: ${message} `); + } + } + + } + + /** + * Stop alarm + * + * @param alarmInfo Alarm information + */ + async stopAlarm(alarmInfo: AlarmInfo): Promise { + if (!alarmInfo?.id) { + LogUtil.error(TAG, 'Execute method stopAlarm failed, params is incorrect.'); + return; + } + this.cancelSnoozeInput(); + LogUtil.info(TAG, 'alarm stop alarmInfo = ' + JSON.stringify(alarmInfo)) + await AlarmStateManager.setStateStop(alarmInfo.id); + this.stopAudioWorker(); + await AlarmManager.clearSnoozedData(alarmInfo, true); + await AlarmCardUtil.notifyAlarmCardTimeUpdate(); + await AlarmManager.stopAlarm(alarmInfo); + await NotificationUtil.cancel(alarmInfo.id); + } + + /** + * Delay alarm + * + * @param alarmInfo Alarm information + * @param isAuto Indicates whether the automatic delay scenario is used. + */ + async delayFiringAlarm(isTimer?: boolean): Promise { + const isFiring = await AlarmStateManager.isFiring(); + LogUtil.info(TAG, `delayFiringAlarm: isFiring: ${isFiring}`); + if (isFiring) { + const firingAlarmId = await AlarmStateManager.getAlarmId(); + const firingAlarm = await AlarmManager.getLatestAlarmFromDb(firingAlarmId); + LogUtil.info(TAG, `firingAlarm: ${JSON.stringify(firingAlarm)}`); + await this.delayAlarm(firingAlarm); + emitter.emit({ eventId: EVENT_ID_KILL_ALARM_TIMEOUT_MESSAGE, priority: emitter.EventPriority.IMMEDIATE }); + } + } + + /** + * Delay alarm + * + * @param alarmInfo Alarm information + * @param isAuto Indicates whether the automatic delay scenario is used. + */ + async delayAlarm(alarmInfo?: AlarmInfo, isAuto?: boolean): Promise { + if (!alarmInfo?.id) { + LogUtil.error(TAG, 'Execute method delayAlarm failed, params is incorrect.'); + return; + } + if (isAuto && await AlarmManager.hasReachedLimitedSnoozeTimes(alarmInfo)) { + LogUtil.info(TAG, 'stop the alarm as it has reached limited snooze times.'); + let delayedTime = await AlarmManager.getRealAlertTime(alarmInfo); + if (delayedTime > 0) { + LogUtil.info(TAG, `alarmInfo: ${alarmInfo.hour} ${alarmInfo.minute}`); + LogUtil.info(TAG, `delayedTime : ${new Date(delayedTime)} ${delayedTime}`); + let curDate = new Date(delayedTime); + if (curDate.getHours() < alarmInfo.hour) { + curDate.setDate(curDate.getDate() - 1); + } + curDate.setSeconds(0); + curDate.setHours(alarmInfo.hour); + curDate.setMinutes(alarmInfo.minute); + LogUtil.info(TAG, `new delayedTime: ${curDate} ${curDate.getTime()}`); + delayedTime = curDate.getTime(); + } + await this.stopAlarm(alarmInfo); + if (deviceType === '2in1') { + await NotificationUtil.cancel(alarmInfo.id!); + await NotificationUtil.publishAlarmMissedPC(alarmInfo.id!, delayedTime, alarmInfo.title!); + } else { + await NotificationUtil.publishAlarmMissed(alarmInfo.id!, delayedTime, alarmInfo.title!); + } + CommonUtil.terminateFullScreenAbility(); + return; + } + this.cancelSnoozeInput(); + await AlarmStateManager.setStateSnooze(alarmInfo.id); + const nextAlertTime = await AlarmManager.delayAlarm(alarmInfo, isAuto); + await AlarmManager.updateTimer(); + AlarmCardUtil.notifyAlarmCardTimeUpdate(); + if (deviceType === '2in1') { + await NotificationUtil.publishAlarmSnoozePC(alarmInfo.id!, nextAlertTime, alarmInfo.title!); + } else { + await NotificationUtil.publishAlarmSnooze(alarmInfo.id!, nextAlertTime, + alarmInfo.snoozeDuration, alarmInfo.title!); + } + this.notifyRefreshMainPage(); + CommonUtil.terminateFullScreenAbility(); + this.stopAudioWorker(); + } + + /** + * Refresh next alert time when system time changes + * + * @param forceRefreshTimer The timer needs to be reset during startup. + */ + async refreshNextAlertTime(forceRefreshTimer?: boolean, isIntentMode?: boolean): Promise { + LogUtil.info(TAG, 'do refreshNextAlertTime'); + await AlarmManager.recalculateAllAlarmsAlarmTime(isIntentMode); + if (forceRefreshTimer) { + await AlarmManager.updateTimerImmediately(); + } else { + await AlarmManager.updateTimer(); + } + } + + /** + * Initiate the snooze input listening. + */ + listenSnoozeInput(): void { + } + + /** + * Cancel the snooze input listening. + */ + cancelSnoozeInput(): void { + } + + /** + * listen call state Change + */ + listenCallStateChange(): void { + const SLOT_TWO = 1; + const SLOT_ONE = 0; + observer.on('callStateChange', { slotId: SLOT_TWO }, value => { + LogUtil.info(TAG, 'on slot two callStateChange, state:' + value.state + ', number:' + value.number); + this.callRingSnoozeCallBack(value.state); + }); + observer.on('callStateChange', { slotId: SLOT_ONE }, value => { + LogUtil.info(TAG, 'on slot one callStateChange, state:' + value.state + ', number:' + value.number); + this.callRingSnoozeCallBack(value.state); + }); + } + + /** + * callRingSnoozeCallBack + * @param state + */ + private async callRingSnoozeCallBack(state: call.CallState): Promise { + const isFiring = await AlarmStateManager.isFiring(); + if (state === call.CallState.CALL_STATE_RINGING && isFiring) { + LogUtil.info(TAG, 'CALL_STATE_RINGING'); + const alarmInfo: AlarmInfo = ((GlobalContext.getContext() + .getObject('abilityWant') as Want)?.parameters?.alarmInfo) as AlarmInfo; + if (!alarmInfo) { + LogUtil.info(TAG, 'callRingSnoozeCallBack not alarmInfo'); + return; + } + LogUtil.info(TAG, 'callRingSnoozeCallBack alarmInfo:' + JSON.stringify(alarmInfo)); + this.delayAlarm(alarmInfo); + emitter.emit({ eventId: EVENT_ID_KILL_ALARM_TIMEOUT_MESSAGE, priority: emitter.EventPriority.IMMEDIATE }); + } + } + + /** + * cancel call state Change + */ + private cancelCallStateChange(): void { + observer.off('callStateChange', value => { + LogUtil.info(TAG, 'on cancel CallStateChange'); + }); + } + + private snoozeCallback(): void { + try { + const alarmInfo: AlarmInfo = ((GlobalContext.getContext() + .getObject('abilityWant') as Want)?.parameters?.alarmInfo) as AlarmInfo; + LogUtil.info(TAG, 'snoozeCallback alarmInfo: ', JSON.stringify(alarmInfo)); + if (!alarmInfo) { + return; + } + this.delayAlarm(alarmInfo); + } catch (error) { + LogUtil.error(TAG, 'snoozeCallback failed: ', (error as BusinessError).message); + } + } + + private notifyRefreshMainPage(): void { + emitter.emit({ + eventId: EVENT_ID_ALARM_RING, + priority: emitter.EventPriority.IMMEDIATE, + }); + } + + /** + * Delay alarm and return this alarmInfo + * + * @param alarmInfo Alarm information + * @param isAuto Indicates whether the automatic delay scenario is used. + */ + async delayAlarmAndReturn(): Promise { + const isFiring = await AlarmStateManager.isFiring(); + LogUtil.info(TAG, `delayFiringAlarm: isFiring: ${isFiring}`); + if (isFiring) { + const firingAlarmId = await AlarmStateManager.getAlarmId(); + const firingAlarm = await AlarmManager.getLatestAlarmFromDb(firingAlarmId); + LogUtil.info(TAG, `firingAlarm: ${JSON.stringify(firingAlarm)}`); + await this.delayAlarm(firingAlarm); + return firingAlarm + } + return; + } +} + +export default new AlarmServiceManager(); diff --git a/feature/alarmclock/src/main/ets/manager/AudioManager.ets b/feature/alarmclock/src/main/ets/manager/AudioManager.ets new file mode 100644 index 0000000..45ccb85 --- /dev/null +++ b/feature/alarmclock/src/main/ets/manager/AudioManager.ets @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import audio from '@ohos.multimedia.audio'; +import hiAppEvent from '@ohos.hiviewdfx.hiAppEvent'; +import fileio from '@ohos.fileio'; +import { BusinessError } from '@ohos.base'; +import resourceManager from '@ohos.resourceManager'; +import { LogUtil, EventReportUtil, EventName, GlobalContext } from '@hmos/common'; +import { RING_FILE_NAME, READ_INTERVAL, FILE_SEPARATOR, WRITE_MODE } from './types'; + +type RawFileDescriptor = resourceManager.RawFileDescriptor; +type AudioRendererOptions = audio.AudioRendererOptions; +type AudioStreamInfo = audio.AudioStreamInfo; +type AudioRendererInfo = audio.AudioRendererInfo; + +const TAG = 'AudioManager'; + +/** + * Alarm Audio Manager + * + * @since 2022-08-29 + */ +class AudioManager { + private audioStartTime: number = 0; + private audioEndTime: number = 0; + + /** + * Release audio resources + */ + async releaseAudioResource(): Promise { + LogUtil.info(TAG, 'releaseAudioResource'); + const audioRenderer = await this.getAudioRenderer(); + let state = audioRenderer.state; + if (state === audio.AudioState.STATE_RELEASED || state === audio.AudioState.STATE_NEW) { + LogUtil.info(TAG, 'renderer already released'); + return; + } + await audioRenderer.release(); + state = audioRenderer.state; + if (state === audio.AudioState.STATE_RELEASED) { + LogUtil.info(TAG, 'renderer released'); + } else { + LogUtil.info(TAG, 'renderer release failed'); + } + GlobalContext.getContext().setObject('audioRenderer', undefined); + } + + /** + * Start playing audio + */ + async playAudio(ringFilePath?: string): Promise { + try { + this.audioStartTime = new Date().getTime(); + await this.repeatPlay(ringFilePath); + } catch { + LogUtil.error(TAG, 'Failed to playAudio'); + } + } + + /** + * Stop playing audio + */ + async stopAudio(): Promise { + await this.releaseAudioResource(); + this.audioEndTime = new Date().getTime(); + const alarmDuration = this.audioEndTime - this.audioStartTime; + // EventReportUtil.write(EventName.CLOCK_START_TO_END, hiAppEvent.EventType.BEHAVIOR, { + // 'duration': alarmDuration }); + } + + private async getAudioRenderer(): Promise { + if (GlobalContext.getContext().getObject('audioRenderer')) { + LogUtil.info(TAG, 'no need to createAudioRenderer'); + return GlobalContext.getContext().getObject('audioRenderer') as audio.AudioRenderer; + } + LogUtil.info(TAG, 'start createAudioRenderer instance'); + const audioManager = audio.getAudioManager(); + // audioManager.setAudioScene(audio.AudioScene.AUDIO_SCENE_RINGING).then(() => { + // LogUtil.info(TAG, 'set audio scene mode successfully'); + // }).catch((err: BusinessError) => { + // LogUtil.info(TAG, `failed to set the audio scene mode: ${err}`); + // }); + const audioStreamInfo: AudioStreamInfo = { + samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, + channels: audio.AudioChannel.CHANNEL_2, + sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, + encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW, + }; + const audioRendererInfo: AudioRendererInfo = { + content: audio.ContentType.CONTENT_TYPE_MUSIC, + usage: audio.StreamUsage.STREAM_USAGE_ALARM, + rendererFlags: 0, + }; + const audioRendererOptions: AudioRendererOptions = { + streamInfo: audioStreamInfo, + rendererInfo: audioRendererInfo, + }; + GlobalContext.getContext().setObject('audioRenderer', await audio.createAudioRenderer(audioRendererOptions)); + const interruptMode: audio.InterruptMode = audio.InterruptMode.INDEPENDENT_MODE; + await (GlobalContext.getContext() + .getObject('audioRenderer') as audio.AudioRenderer).setInterruptMode(interruptMode); + (GlobalContext.getContext() + .getObject('audioRenderer') as audio.AudioRenderer).on('audioInterrupt', async (interruptEvent) => { + LogUtil.info(TAG, `audioRenderer is interrupted : ${JSON.stringify(interruptEvent)}`); + }); + LogUtil.info(TAG, 'createAudioRenderer successfully'); + return (GlobalContext.getContext().getObject('audioRenderer') as audio.AudioRenderer); + } + + private async repeatPlay(ringFilePath?: string): Promise { + const audioRenderer = await this.getAudioRenderer(); + let state = audioRenderer.state; + if (state !== audio.AudioState.STATE_PREPARED && state !== audio.AudioState.STATE_PAUSED && + state !== audio.AudioState.STATE_STOPPED) { + LogUtil.info(TAG, `renderer is not in a correct state to start, current state is: ${state}`); + return; + } + try { + await audioRenderer.start(); + } catch (error) { + LogUtil.error(TAG, `audioRenderer start failed: ${JSON.stringify(error)}`); + return; + } + state = audioRenderer.state; + if (state === audio.AudioState.STATE_RUNNING) { + LogUtil.info(TAG, 'renderer started'); + } else { + LogUtil.error(TAG, `renderer start failed, state is ${state}`); + } + let readLength = 0; + const bufferSize: number = await audioRenderer.getBufferSize(); + const fileStream = fileio.createStreamSync(ringFilePath!, 'r'); + const totalSize = fileio.statSync(ringFilePath!).size; + const discardHeader = new ArrayBuffer(bufferSize); + fileStream.readSync(discardHeader); + readLength += bufferSize; + while (readLength < totalSize) { + if (audioRenderer.state === audio.AudioState.STATE_RELEASED) { + await this.stopRenderer(fileStream, audioRenderer); + break; + } + if (audioRenderer.state === audio.AudioState.STATE_RUNNING) { + let arrayBuffer = new ArrayBuffer(bufferSize); + readLength += fileStream.readSync(arrayBuffer); + await this.writeBuf(audioRenderer, arrayBuffer); + } + } + if (readLength >= totalSize) { + await this.stopRenderer(fileStream, audioRenderer); + this.repeatPlay(ringFilePath); + } + } + + private async stopRenderer(fileStream: fileio.Stream, audioRenderer: audio.AudioRenderer): Promise { + fileStream.closeSync(); + if (audioRenderer.state !== audio.AudioState.STATE_RELEASED) { + await audioRenderer.stop(); + } + } + + private async writeBuf(audioRenderer: audio.AudioRenderer, arrayBuffer: ArrayBuffer): Promise { + if (audioRenderer.state !== audio.AudioState.STATE_RUNNING) { + LogUtil.error(TAG, `renderer is not running, do not write, state is ${audioRenderer.state}`); + return; + } + const writtenBytes = await audioRenderer.write(arrayBuffer); + if (writtenBytes < 0) { + LogUtil.error(TAG, 'write buffer failed. check the state of renderer'); + } + } + + public async createRingFileIfNotExist(): Promise { + try { + const filePath = (GlobalContext.getContext() + .getObject('clockContext') as Context).filesDir + FILE_SEPARATOR + RING_FILE_NAME; + let isFileExist = false; + await fileio.access(filePath, 0).then(() => { + isFileExist = true; + }).catch(() => { + isFileExist = false; + }); + if (!isFileExist) { + const rawFileDesc: RawFileDescriptor = await (GlobalContext.getContext() + .getObject('clockContext') as Context).resourceManager.getRawFileDescriptor(RING_FILE_NAME); + if (!rawFileDesc?.fd) { + LogUtil.info(TAG, 'start copy file filed, fd is ' + `${JSON.stringify(rawFileDesc)}`); + return; + } + const descLength: number = rawFileDesc.length; + const buffer = new ArrayBuffer(descLength); + fileio.readSync(rawFileDesc.fd, buffer, { + length: rawFileDesc.length, + position: rawFileDesc.offset, + }); + const streamOutput = fileio.createStreamSync(filePath, WRITE_MODE); + streamOutput.writeSync(buffer); + streamOutput.closeSync(); + } + } catch (error) { + LogUtil.error(TAG, `createRingFile failed: ${error}`); + } + } +} + +export default new AudioManager(); \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/manager/AudioPlayer.ts b/feature/alarmclock/src/main/ets/manager/AudioPlayer.ts new file mode 100644 index 0000000..2ee8000 --- /dev/null +++ b/feature/alarmclock/src/main/ets/manager/AudioPlayer.ts @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import audio from '@ohos.multimedia.audio'; +import fileio from '@ohos.fileio'; +import type { BusinessError } from '@ohos.base'; +import type resourceManager from '@ohos.resourceManager'; +import type common from '@ohos.app.ability.common'; +import hilog from '@ohos.hilog'; +import { GlobalContext } from '../../../../../../common/src/main/ets/utils/GlobalContext'; + +type RawFileDescriptor = resourceManager.RawFileDescriptor; +type Context = common.Context; + +interface AudioStreamInfo { + samplingRate: number; + channels: number; + sampleFormat: number; + encodingType: number; +} + +interface AudioRendererInfo { + content: number; + usage: number; + rendererFlags: number; +} + +interface AudioRendererOptions { + streamInfo: AudioStreamInfo; + rendererInfo: AudioRendererInfo; +} + +const TAG = 'AudioManager'; +const DOMAIN = 0x66EE; +const FORMAT = '%{public}s'; +const PREFIX = '[Clock]'; +const SEPARATOR = ' '; +const RING_FILE_NAME = 'ring.wav'; +const FILE_SEPARATOR = '/'; +const WRITE_MODE = 'w+'; + +/** + * Alarm Audio Manager + * + * @since 2022-08-29 + */ +export class AudioPlayer { + private audioStartTime: number = 0; + private audioEndTime: number = 0; + + logInfo(tag, ...args: string[]): void { + hilog.info(DOMAIN, PREFIX, FORMAT, `tag: ${tag} --> ${args.join(SEPARATOR)}`); + } + + logError(tag, ...args: string[]): void { + hilog.error(DOMAIN, PREFIX, FORMAT, `tag: ${tag} --> ${args.join(SEPARATOR)}`); + } + + /** + * Release audio resources + */ + async releaseAudioResource(): Promise { + this.logInfo(TAG, 'releaseAudioResource'); + const audioRenderer = await this.getAudioRenderer(); + let state = audioRenderer.state; + if (state === audio.AudioState.STATE_RELEASED || state === audio.AudioState.STATE_NEW) { + this.logInfo(TAG, 'renderer already released'); + return; + } + await audioRenderer.release(); + state = audioRenderer.state; + if (state === audio.AudioState.STATE_RELEASED) { + this.logInfo(TAG, 'renderer released'); + } else { + this.logInfo(TAG, 'renderer release failed'); + } + GlobalContext.getContext().setObject('audioRenderer', undefined); + } + + /** + * Start playing audio + */ + async playAudio(ringFilePath?: string): Promise { + try { + this.audioStartTime = new Date().getTime(); + await this.repeatPlay(ringFilePath); + } catch { + this.logError(TAG, 'Failed to playAudio'); + } + } + + /** + * Stop playing audio + */ + async stopAudio(): Promise { + await this.releaseAudioResource(); + this.audioEndTime = new Date().getTime(); + const alarmDuration = this.audioEndTime - this.audioStartTime; + } + + private async getAudioRenderer(): Promise { + if (GlobalContext.getContext().getObject('audioRenderer')) { + this.logInfo(TAG, 'no need to createAudioRenderer'); + return GlobalContext.getContext().getObject('audioRenderer') as audio.AudioRenderer; + } + this.logInfo(TAG, 'start createAudioRenderer instance'); + const audioManager = audio.getAudioManager(); + this.logInfo(TAG, 'set audio scene mode successfully'); + // audioManager.setAudioScene(audio.AudioScene.AUDIO_SCENE_RINGING).then(() => { + // this.logInfo(TAG, 'set audio scene mode successfully'); + // }).catch((err: BusinessError) => { + // this.logInfo(TAG, `failed to set the audio scene mode: ${err}`); + // }); + const audioStreamInfo: AudioStreamInfo = { + samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, + channels: audio.AudioChannel.CHANNEL_2, + sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, + encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW, + }; + const audioRendererInfo: AudioRendererInfo = { + content: audio.ContentType.CONTENT_TYPE_MUSIC, + usage: audio.StreamUsage.STREAM_USAGE_ALARM, + rendererFlags: 0, + }; + const audioRendererOptions: AudioRendererOptions = { + streamInfo: audioStreamInfo, + rendererInfo: audioRendererInfo, + }; + GlobalContext.getContext().setObject('audioRenderer', await audio.createAudioRenderer(audioRendererOptions)); + const interruptMode: audio.InterruptMode = audio.InterruptMode.INDEPENDENT_MODE; + await (GlobalContext.getContext() + .getObject('audioRenderer') as audio.AudioRenderer).setInterruptMode(interruptMode); + (GlobalContext.getContext() + .getObject('audioRenderer') as audio.AudioRenderer).on('audioInterrupt', async (interruptEvent) => { + this.logInfo(TAG, `audioRenderer is interrupted : ${JSON.stringify(interruptEvent)}`); + }); + this.logInfo(TAG, 'createAudioRenderer successfully'); + return (GlobalContext.getContext().getObject('audioRenderer') as audio.AudioRenderer); + } + + private async repeatPlay(ringFilePath?: string): Promise { + const audioRenderer = await this.getAudioRenderer(); + let state = audioRenderer.state; + if (state !== audio.AudioState.STATE_PREPARED && state !== audio.AudioState.STATE_PAUSED && + state !== audio.AudioState.STATE_STOPPED) { + this.logInfo(TAG, `renderer is not in a correct state to start, current state is: ${state}`); + return; + } + try { + await audioRenderer.start(); + } catch (error) { + this.logError(TAG, `audioRenderer start failed: ${JSON.stringify(error)}`); + return; + } + state = audioRenderer.state; + if (state === audio.AudioState.STATE_RUNNING) { + this.logInfo(TAG, 'renderer started'); + } else { + this.logError(TAG, `renderer start failed, state is ${state}`); + } + let readLength = 0; + const bufferSize: number = await audioRenderer.getBufferSize(); + const fileStream = fileio.createStreamSync(ringFilePath!, 'r'); + const totalSize = fileio.statSync(ringFilePath!).size; + const discardHeader = new ArrayBuffer(bufferSize); + fileStream.readSync(discardHeader); + readLength += bufferSize; + while (readLength < totalSize) { + if (audioRenderer.state === audio.AudioState.STATE_RELEASED) { + await this.stopRenderer(fileStream, audioRenderer); + break; + } + if (audioRenderer.state === audio.AudioState.STATE_RUNNING) { + let arrayBuffer = new ArrayBuffer(bufferSize); + readLength += fileStream.readSync(arrayBuffer); + await this.writeBuf(audioRenderer, arrayBuffer); + } + } + if (readLength >= totalSize) { + await this.stopRenderer(fileStream, audioRenderer); + this.repeatPlay(ringFilePath); + } + } + + private async stopRenderer(fileStream: fileio.Stream, audioRenderer: audio.AudioRenderer): Promise { + fileStream.closeSync(); + if (audioRenderer.state !== audio.AudioState.STATE_RELEASED) { + await audioRenderer.stop(); + } + } + + private async writeBuf(audioRenderer: audio.AudioRenderer, arrayBuffer: ArrayBuffer): Promise { + if (audioRenderer.state !== audio.AudioState.STATE_RUNNING) { + this.logError(TAG, `renderer is not running, do not write, state is ${audioRenderer.state}`); + return; + } + const writtenBytes = await audioRenderer.write(arrayBuffer); + if (writtenBytes < 0) { + this.logError(TAG, 'write buffer failed. check the state of renderer'); + } + } + + public async createRingFileIfNotExist(): Promise { + try { + const filePath = (GlobalContext.getContext() + .getObject('clockContext') as Context).filesDir + FILE_SEPARATOR + RING_FILE_NAME; + let isFileExist = false; + await fileio.access(filePath, 0).then(() => { + isFileExist = true; + }).catch(() => { + isFileExist = false; + }); + if (!isFileExist) { + const rawFileDesc: RawFileDescriptor = await (GlobalContext.getContext() + .getObject('clockContext') as Context).resourceManager.getRawFileDescriptor(RING_FILE_NAME); + if (!rawFileDesc?.fd) { + this.logInfo(TAG, 'start copy file filed, fd is ' + `${JSON.stringify(rawFileDesc)}`); + return; + } + const descLength: number = rawFileDesc.length; + const buffer = new ArrayBuffer(descLength); + fileio.readSync(rawFileDesc.fd, buffer, { + length: rawFileDesc.length, + position: rawFileDesc.offset, + }); + const streamOutput = fileio.createStreamSync(filePath, WRITE_MODE); + streamOutput.writeSync(buffer); + streamOutput.closeSync(); + } + } catch (error) { + this.logError(TAG, `createRingFile failed: ${error}`); + } + } +} + +export default new AudioPlayer(); \ No newline at end of file diff --git a/product/pc/src/main/ets/Application/MyAbilityStage.ts b/feature/alarmclock/src/main/ets/manager/index.ets similarity index 68% rename from product/pc/src/main/ets/Application/MyAbilityStage.ts rename to feature/alarmclock/src/main/ets/manager/index.ets index 0a9efc1..52a1bf2 100644 --- a/product/pc/src/main/ets/Application/MyAbilityStage.ts +++ b/feature/alarmclock/src/main/ets/manager/index.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,10 +13,7 @@ * limitations under the License. */ -import AbilityStage from "@ohos.application.AbilityStage" +import AlarmServiceManager from './AlarmServiceManager'; +import AudioManager from './AudioManager'; -export default class MyAbilityStage extends AbilityStage { - onCreate() { - console.log("[Demo] MyAbilityStage onCreate") - } -} \ No newline at end of file +export { AlarmServiceManager, AudioManager }; \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/manager/types.ets b/feature/alarmclock/src/main/ets/manager/types.ets new file mode 100644 index 0000000..fd68e16 --- /dev/null +++ b/feature/alarmclock/src/main/ets/manager/types.ets @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// import inputConsumer from '@ohos.multimodalInput.inputConsumer' + +// type KeyOptions = inputConsumer.KeyOptions; + +export const FULL_SCREEN_ALARM_PAGE = 'pages/FullScreenAlarm'; + +export const BANNER_ALARM_PAGE = 'pages/BannerAlarm'; + +export const ALARM_WINDOW_TYPE = 2123; + +export const ALARM_WINDOW_NAME = 'FullScreenAlarmWindow'; + +export const BANNER_ALARM_WINDOW_NAME = 'BannerAlarmWindow'; + +const KEYCODE_VOLUME_UP = 16; + +const KEYCODE_VOLUME_DOWN = 17; + +// export const VOLUME_UP_KEY_OPTIONS: KeyOptions = { +// preKeys: [], +// finalKey: KEYCODE_VOLUME_UP, +// isFinalKeyDown: true, +// finalKeyDownDuration: 0, +// }; + +// export const VOLUME_DOWN_KEY_OPTIONS: KeyOptions = { +// preKeys: [], +// finalKey: KEYCODE_VOLUME_DOWN, +// isFinalKeyDown: true, +// finalKeyDownDuration: 0, +// }; + +export const FD_PREFIX = 'fd://'; + +export const FILE_SEPARATOR = '/'; + +export const WRITE_MODE = 'w+'; + +export const RING_FILE_NAME = 'ring.wav'; + +export const READ_INTERVAL = 16; diff --git a/common/src/main/ets/components/imageComponent.ets b/feature/alarmclock/src/main/ets/pages/BannerAlarm/index.ets similarity index 56% rename from common/src/main/ets/components/imageComponent.ets rename to feature/alarmclock/src/main/ets/pages/BannerAlarm/index.ets index 38f95cb..09ccc18 100644 --- a/common/src/main/ets/components/imageComponent.ets +++ b/feature/alarmclock/src/main/ets/pages/BannerAlarm/index.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,22 +13,29 @@ * limitations under the License. */ +import { LogUtil } from '@hmos/common'; + +const TAG = 'BannerAlarm'; + +/** + * One-pixel page for raising priorities + * + * @since 2022-11-24 + */ @Component -export struct ImageComponent { - private color: ResourceStr = ''; - private imgLength: number = 32; - private times: number = 2; - @State imageSrc: ResourceStr = ''; +export struct BannerAlarm { + async aboutToAppear() { + LogUtil.info(TAG, 'aboutToAppear'); + } + + aboutToDisappear() { + LogUtil.info(TAG, 'aboutToDisappear'); + } build() { - Stack() { - Circle() - .width(this.imgLength * this.times) - .height(this.imgLength * this.times) - .fill(this.color); - Image(this.imageSrc) - .height(this.imgLength) - .width(this.imgLength); - } + Flex() + .height('100%') + .width('100%') + .backgroundColor(Color.Transparent) } } \ No newline at end of file diff --git a/product/pc/src/main/ets/pages/timer/TimerClock.ets b/feature/alarmclock/src/main/ets/pages/ForegroundPage/index.ets similarity index 56% rename from product/pc/src/main/ets/pages/timer/TimerClock.ets rename to feature/alarmclock/src/main/ets/pages/ForegroundPage/index.ets index 5e8f75d..6d063cd 100644 --- a/product/pc/src/main/ets/pages/timer/TimerClock.ets +++ b/feature/alarmclock/src/main/ets/pages/ForegroundPage/index.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,24 +13,29 @@ * limitations under the License. */ -import { TimerView, TimerController } from '@ohos/timer'; +import { LogUtil } from '@hmos/common'; -const CLOCK_SIZE = 232; +const TAG = 'ForegroundPage'; +/** + * Used to increase the process priority during full-screen startup. + * + * @since 2023-1-5 + */ @Component -export struct TimerClock { - @State currentTime: number = 0; - @State timerController: TimerController = new TimerController(); - +export struct ForegroundView { aboutToAppear() { - this.timerController.setClockUpdateListener((currentTimeMs: number) => { - this.currentTime = currentTimeMs; - }) + LogUtil.info(TAG, 'aboutToAppear'); + } + + aboutToDisappear() { + LogUtil.info(TAG, 'aboutToDisappear'); } build() { - Column() { - TimerView({ sSize: CLOCK_SIZE, currentTime: this.currentTime }) - } + Flex() + .height('100%') + .width('100%') + .backgroundColor(Color.Transparent) } } \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/pages/FullScreenAlarm/index.ets b/feature/alarmclock/src/main/ets/pages/FullScreenAlarm/index.ets new file mode 100644 index 0000000..b18da69 --- /dev/null +++ b/feature/alarmclock/src/main/ets/pages/FullScreenAlarm/index.ets @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import CommonEventManager from '@ohos.commonEventManager'; +import emitter from '@ohos.events.emitter'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import Want from '@ohos.app.ability.Want'; +import common from '@ohos.app.ability.common'; +import { + AlarmInfo, + EVENT_ID_FINISH_ABILITY, + EventName, + EventReportUtil, + GlobalContext, + LogUtil, + ResourceManager, + TIME_TAG_ID_LIST_IN_ZH, + TimeUtil, + AlarmStateManager, + EVENT_ID_FULL_SCREEN_ALARM, + EVENT_ID_SEND_ALARM, + AlarmManager, + CommonUtil, + WantAgentUtil +} from '@hmos/common'; +import SlideCloseButton from '../../components/SlideCloseButton'; +import { AlarmServiceManager } from '../../manager'; +import wallpaper from '@ohos.wallpaper'; +import image from '@ohos.multimedia.image'; +import buffer from '@ohos.buffer'; +import audio from '@ohos.multimedia.audio'; + +const TAG = 'FullScreenClock'; +const ALARM_BACKGROUND_BLUR = 30; +const SLIDE_BTN_SPACE = 14; +const FLEX_GROW_DEUCE = 1; +const TIME_SUBSCRIBE_INFO: CommonEventSubscribeInfo = { + events: [ + CommonEventManager.Support.COMMON_EVENT_TIME_TICK, + ], +}; + +// This type is not exported from @ohos.commonEvent, so we get it by exported function. +type CommonEventSubscriber = CommonEventManager.CommonEventSubscriber +// type ServiceExtensionContext = common.ServiceExtensionContext; +type CommonEventSubscribeInfo = CommonEventManager.CommonEventSubscribeInfo + + +/** + * Full Screen Alarm Page + * + * @since 2022-08-18 + */ +@Component +export struct FullScreenAlarm { + @Link isBackImageShown: boolean; + @State currentDateWithWeek: string = ''; + @State private timeText: string = ''; + @State wallpaperImage: image.PixelMap | undefined = undefined; + @State alarmInfo: AlarmInfo | undefined = undefined; + private timeSubscriber?: CommonEventSubscriber; + private minute: number = -1; + // Tag of time , Exp: am pm + private leftTag: string = ''; + private rightTag: string = ''; + private maxLine: number = 2; + + async getAlarmInfo() { + let wantAlarmInfo = (GlobalContext.getContext() + .getObject('abilityWant') as Want)?.parameters?.alarmInfo; + LogUtil.info(TAG, `wantAlarmInfo => ${JSON.stringify(wantAlarmInfo as AlarmInfo)}`) + if (wantAlarmInfo) { + this.alarmInfo = wantAlarmInfo as AlarmInfo; + } else { + const isFiring = await AlarmStateManager.isFiring(); + if (isFiring) { + const firingAlarmId = await AlarmStateManager.getAlarmId(); + const alarmFromList = await AlarmManager.getIDAlarmFrom(firingAlarmId.toString() as string); + if (alarmFromList.length > 0) { + this.alarmInfo = alarmFromList[0]; + } else { + LogUtil.error(TAG, `firingAlarmId:${firingAlarmId}. cannot get alarmInfo.`) + } + } else { + LogUtil.error(TAG, `not firing state.`) + } + } + LogUtil.info(TAG, `alarmInfo => ${JSON.stringify(this.alarmInfo)}`) + } + + async aboutToAppear() { + LogUtil.info(TAG, 'aboutToAppear'); + this.sendAlarmListening('show') + if (GlobalContext.getContext().getObject('wallpaperImage')) { + this.wallpaperImage = GlobalContext.getContext().getObject('wallpaperImage') as image.PixelMap; + } + LogUtil.info(TAG, 'wallpaperImage:' + JSON.stringify(this.wallpaperImage)); + this.listeningSnooze(); + await this.getAlarmInfo(); + await ResourceManager.preloadStringResources(TIME_TAG_ID_LIST_IN_ZH); + this.updateTime(); + this.subscribeTimeChangedEvent(); + AlarmServiceManager.listenSnoozeInput(); + AlarmServiceManager.startAudioWorker(); + audio.getAudioManager().getVolumeManager().on('volumeChange', (volumeEvent: audio.VolumeEvent) => { + LogUtil.info(TAG, `VolumeType of stream =>${volumeEvent.volumeType}`) + this.delayAlarm(); + }); + } + + aboutToDisappear() { + LogUtil.info(TAG, 'aboutToDisappear'); + this.sendAlarmListening('hide') + this.unSubscribeTimeChangedEvent(); + AlarmServiceManager.cancelSnoozeInput(); + this.notifyAbilityDismiss(); + audio.getAudioManager().getVolumeManager().on('volumeChange', (volumeEvent: audio.VolumeEvent) => { + LogUtil.info(TAG, ` aboutToDisappear getVolumeManager=>${volumeEvent.volumeType}`) + return + }); + emitter.off(EVENT_ID_FULL_SCREEN_ALARM); + } + + private sendAlarmListening(toggle: string): void { + emitter.emit({ eventId: EVENT_ID_SEND_ALARM }, { data: { toggle: toggle } }) + } + + private listeningSnooze(): void { + emitter.on({ eventId: EVENT_ID_FULL_SCREEN_ALARM }, (target) => { + if (target) { + this.delayAlarm(); + } else { + LogUtil.error(TAG, `listeningSnooze: + ${target}`); + return; + } + LogUtil.info(TAG, `listeningSnooze target: + ${target}`); + }); + } + + private async updateTime(): Promise { + const currentMinute = new Date().getMinutes(); + if (currentMinute === this.minute) { + return; + } + const nowDate = new Date(); + const timeObj = TimeUtil.getFormattedTimeObj(nowDate.getHours(), nowDate.getMinutes()); + if (timeObj.isTagLeft) { + this.leftTag = timeObj.tag as string; + this.rightTag = ''; + } else { + this.rightTag = timeObj.tag as string; + this.leftTag = ''; + } + this.timeText = timeObj.time!; + this.currentDateWithWeek = TimeUtil.getCurrentFormattedDateWithWeek(); + this.minute = currentMinute; + } + + private async subscribeTimeChangedEvent(): Promise { + // Create a subscriber and subscribe to time-change event. + this.timeSubscriber = await CommonEventManager.createSubscriber(TIME_SUBSCRIBE_INFO); + if (!this.timeSubscriber) { + return; + } + CommonEventManager.subscribe(this.timeSubscriber, () => this.updateTime()); + } + + private unSubscribeTimeChangedEvent() { + if (this.timeSubscriber) { + CommonEventManager.unsubscribe(this.timeSubscriber, error => { + if (error) { + LogUtil.error(TAG, `Unsubscribe to time-change event failed: ${JSON.stringify(error)}`); + return; + } + LogUtil.info(TAG, 'Unsubscribe to time-change event successfully!'); + }); + } + + } + + private notifyAbilityDismiss(): void { + LogUtil.info(TAG, 'notifyAbilityDismiss'); + emitter.emit({ + eventId: EVENT_ID_FINISH_ABILITY, + priority: emitter.EventPriority.IMMEDIATE, + }) + } + + private async delayAlarm(): Promise { + LogUtil.info(TAG, 'Delay Alarm'); + if (!this.alarmInfo) { + LogUtil.info(TAG, `No alarmInfo`) + return; + } + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_LOCK_SCREEN_REMIND_LATER) + const firingAlarmId = await AlarmStateManager.getAlarmId(); + if (JSON.parse(JSON.stringify(this.alarmInfo)).id == firingAlarmId) { + LogUtil.info(TAG, `delayAlarm=>${firingAlarmId}`); + await WantAgentUtil.triggerDelayAlarm(this.alarmInfo); + } else { + LogUtil.info(TAG, `alarmInfo=>${JSON.stringify(this.alarmInfo)} `, `delayAlarm=>${firingAlarmId}`) + return + } + } + + @Builder + buildTimeArea() { + Column() { + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Baseline }) { + Text(this.leftTag) + .fontColor($r('sys.color.ohos_id_color_primary')) + .fontSize($r('app.float.ohos_id_text_size_sub_title1')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + Text(this.timeText) + .fontSize($r('app.float.ohos_id_text_size_headline2')) + .fontColor($r('sys.color.ohos_id_color_text_primary')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + Text(this.rightTag) + .fontColor($r('sys.color.ohos_id_color_primary')) + .fontSize($r('app.float.ohos_id_text_size_sub_title1')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + }.margin({ top: $r('app.float.current_time_margin_top') }) + + Text(this.currentDateWithWeek) + .fontSize($r('app.float.ohos_id_text_size_sub_title1')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + .margin({ + top: $r('app.float.current_date_margin_top'), + left: $r('app.float.ohos_id_max_padding_start'), + right: $r('app.float.ohos_id_max_padding_end'), + }) + } + .flexGrow(FLEX_GROW_DEUCE) + .width('100%') + } + + @Builder + buildLabelAndSnoozeArea() { + Row() { + Column() { + Text(this.alarmInfo?.title ? this.alarmInfo.title : $r('app.string.alarm_clock')) + .fontSize($r('app.float.ohos_id_text_size_headline6')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + .maxLines(this.maxLine) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .margin({ + left: $r('app.float.ohos_id_max_padding_start'), + right: $r('app.float.ohos_id_max_padding_end'), + }) + .alignSelf(ItemAlign.Center) + this.buildSnoozeButton() + } + .width('100%') + } + .width('100%') + .flexGrow(FLEX_GROW_DEUCE) + .alignItems(VerticalAlign.Center) + } + + @Builder + buildSnoozeButton() { + if (typeof this.alarmInfo?.snoozeDuration === 'number') { + Row() { + Button($r( + 'app.plural.alarm_alert_snooze_button_text', + this.alarmInfo?.snoozeDuration || 0, + this.alarmInfo?.snoozeDuration || 0, + ), { + type: ButtonType.Normal + }) + .fontSize($r('sys.float.Body_L')) + .margin({ top: $r('app.float.snooze_btn_margin_top') }) + .backgroundColor($r('app.color.snooze_button_background_color')) + .padding({ + left: $r('app.float.snooze_button_padding_horizontal'), + right: $r('app.float.snooze_button_padding_horizontal'), + top: $r('app.float.snooze_button_padding_vertical'), + bottom: $r('app.float.snooze_button_padding_vertical'), + }) + .border({ + width: $r('app.float.snooze_button_border_width'), + color: $r('app.color.snooze_button_border_color'), + }) + .borderRadius($r('sys.float.corner_radius_level10')) + .fontColor($r('sys.color.warning')) + .onClick(() => this.delayAlarm()) + } + } + } + + @Builder + buildSlideButton() { + Row() { + Column({ space: SLIDE_BTN_SPACE }) { + SlideCloseButton({ alarmInfo: this.alarmInfo }) + Text($r('app.string.slide_to_turn_off')) + .fontSize($r('sys.float.Body_L')) + .align(Alignment.Bottom) + .fontColor(Color.White) + .fontWeight(FontWeight.Regular) + .fontSize($r('app.float.slide_to_turn_off_font_size')) + .margin({ + bottom: $r('app.float.slide_to_turn_off_margin_bottom'), + top: $r('app.float.slide_to_turn_off_margin_top') + }) + } + .width('100%') + .alignItems(HorizontalAlign.Center) + } + .flexGrow(FLEX_GROW_DEUCE) + .width('100%') + .alignItems(VerticalAlign.Bottom) + } + + build() { + Stack({ alignContent: Alignment.Center }) { + if (this.wallpaperImage) { + Image(this.wallpaperImage) + .width('100%') + .height('100%') + .objectRepeat(ImageRepeat.NoRepeat) + .backgroundImageSize(ImageSize.Cover) + .backdropBlur(ALARM_BACKGROUND_BLUR) + .onComplete((event) => { + LogUtil.info(TAG, 'Image onComplete:' + JSON.stringify(event)); + if (event && event.loadingStatus === 1) { + this.isBackImageShown = true; + } + }) + } + Flex({ direction: FlexDirection.Column }) { + this.buildTimeArea(); + this.buildLabelAndSnoozeArea(); + this.buildSlideButton(); + } + .height('100%') + .width('100%') + } + .visibility(this.isBackImageShown ? Visibility.Visible : Visibility.Hidden) + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/pages/ManageAlarmClock/index.ets b/feature/alarmclock/src/main/ets/pages/ManageAlarmClock/index.ets new file mode 100644 index 0000000..d046bd2 --- /dev/null +++ b/feature/alarmclock/src/main/ets/pages/ManageAlarmClock/index.ets @@ -0,0 +1,1343 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import emitter from '@ohos.events.emitter'; +import i18n from '@ohos.i18n'; +import resmgr from '@ohos.resourceManager'; +import deviceInfo from '@ohos.deviceInfo'; +import { BusinessError } from '@ohos.base'; +import { + AlarmInfo, + AlarmInfoProperty, + AlarmManager, + AlarmStateManager, + CommonGrid, + CommonUtil, + ConfirmDialog, + EVENT_ID_CHANGE_ALARM, + EVENT_ID_DELETE_ALARM, + EventName, + EventReportUtil, + EventResult, + FIRST_PARAM, + GlobalContext, + LogUtil, + ResourceManager, + SECOND_PARAM, + TIME_DESC_STRING_ID_LIST, + TimeUtil, + TitleBar, + WantAgentUtil, + Card +} from '@hmos/common'; +import { + ArraySlider, + Form, + FormDiyCallbackInfo, + FormField, + FormValue, + FormOption, + InputType, + ObservedAlarmInfo, + WeekListField +} from '../../components'; +import { NotificationUtil } from '../../utils/NotificationUtil'; +import { + DELETE_BUTTON_WIDTH, + DFT_RING_DURATION, + DFT_SNOOZE_DURATION, + DFT_SNOOZE_INFO, + DFT_SNOOZE_TIMES, + RING_DURATION_OPTIONS, + SNOOZE_DURATION_OPTIONS, + SNOOZE_TIMES_OPTIONS, + SnoozeInfo, + TIME_PICKER_WIDTH, + HTTP_TIMEOUT_TIMER, +} from './types'; +import { FLEX_GROW_FILL_FULL } from '../../components/Form/types'; +import connection from '@ohos.net.connection'; +import promptAction from '@ohos.promptAction' +import { CACHE_HOLIDAYS_LIST, HolidaysType } from '@hmos/common/src/main/ets/utils/types'; +// import hiSysEvent from '@ohos.hiSysEvent'; + +const TAG = 'ManageAlarmClock'; +/** + * 状态值 : + * 4 : 法定工作日 + * 3 : 自定义 + * 0 : 单次 + */ +enum StateType { + WAKE_DAYS = 4, // + USER_DEFINED = 3, // + ONES = 0 +} + + +@Extend(Image) +function appBarIconBtn(isPC: boolean) { + .width($r('app.float.appbar_icon_size')) + .height($r('app.float.appbar_icon_size')) + .responseRegion({ + width: $r('app.float.response_region_size'), + height: $r('app.float.response_region_size'), + }) +} + +function getItemBackgroundColor(item: string, active: IRepeatDropMenuActive) { + if (item !== active.key) { + return Color.Transparent; + } + if (active.status === 'hover') { + return $r('sys.color.ohos_id_color_hover'); + } else if (active.status === 'press') { + return $r('sys.color.ohos_id_color_click_effect'); + } + return Color.Transparent; +} + +type TOnConfirmCallback = () => void; +type TOnCancelCallback = () => void; + +interface IRepeatDropMenuActive { + key: string; + status: string; +} + +/** + * Alarm management page + * + * @since 2022-07-18 + */ +@Component +export struct ManageAlarmClock { + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + @Prop isPCView: boolean = false; + @State alarmInfo?: AlarmInfoProperty = undefined; + @State isMilitaryTime: boolean = false + @State isChanged: boolean = false; + @State alarmInfoParam?: ObservedAlarmInfo = undefined; + @Consume('pageInfos') pageInfos: NavPathStack; + @State repeatType: number = 0; + @State hoverBackgroundInfo: Resource = $r('sys.color.ohos_id_color_titlebar_sub_bg'); + @State confirmButtonBg: ResourceColor = this.isPCView ? $r('sys.color.ohos_id_color_button_normal') : + $r('sys.color.ohos_id_color_button_normal'); + @State daysOfWakeType: number = 0; + @State selectList: string[] = []; + @State selectIndex: number = 0; + @State selectVal: ResourceStr = ''; + @StorageLink('EditBindSheet') isEditShow: boolean = false; + @StorageLink('NewBindSheet') isNewShow: boolean = false; + @StorageLink('ShouldDismiss') @Watch('Dismiss') isDismiss: boolean = false; + @State itemBackgroundColor: ResourceColor = $r('sys.color.ohos_id_color_card_bg'); + @State @Watch('updateWeekMapList') weekListMap: string = '{}'; + @State isPcShowMenu: boolean = false; + @StorageProp('pcAlarmFormPopupVisible') isPcNewAlarmShow: boolean = false; + @State repeatDropMenuActive: IRepeatDropMenuActive = { key: '', status: '' }; + @State isModelEdit: boolean = true; + + @Styles + itemBackgroundStyles() { + .backgroundColor(this.itemBackgroundColor) + .onHover((isHover) => { + if (isHover) { + this.itemBackgroundColor = $r('sys.color.ohos_id_color_hover'); + } else { + this.itemBackgroundColor = $r('sys.color.ohos_id_color_card_bg'); + } + }) + .onMouse((event) => { + if ((event?.action === MouseAction.Press) && (event?.button === MouseButton.Left)) { + this.itemBackgroundColor = $r('sys.color.ohos_id_color_click_effect'); + } else if (event?.action === MouseAction.Release) { + this.itemBackgroundColor = $r('sys.color.ohos_id_color_card_bg'); + } + }) + } + + private isModified: boolean = false; + private isEditMode: boolean = false; + // Selected time of the time picker + @State selectedTime: Date = new Date(); + private editingSnoozeInfo?: SnoozeInfo = DFT_SNOOZE_INFO; + private daysOfWeekLabels: string[] = []; + private ringDurationOptions: FormOption[] = []; + // Form field template for repeat mode + private repeatModeFields: FormField[] = []; + // private weekListMap: string = ''; + // Form field template for alarm details, except for repeat mode + // private alarmDetailFields: FormField[] = []; + @State alarmDetailFields: FormField[] = []; + private cancelConfirmDialog?: CustomDialogController = undefined; + private deleteConfirmDialog?: CustomDialogController = undefined; + private scrollerForScroll: Scroller = new Scroller(); + private httpOutTimeTimer: number = -1; + + private getBackgroundColor() { + return this.isPCView ? Color.Transparent : $r('sys.color.ohos_id_color_panel_bg') + } + + private onConfirmCallback: TOnConfirmCallback = () => { + }; + private onCancelCallback: TOnCancelCallback = () => { + }; + readonly RESTRICT: number = -999; + // private toast: string = (GlobalContext.getContext() + // .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('show_toast_name'); + + /** + * handleButtonHoverEvent + * + * @param isHover + */ + private handleImageHoverEvent(isHover: boolean): Resource { + if (isHover) { + return $r('app.color.color_hover'); + } else { + return $r('app.color.color_hover_disabled'); + } + }; + + private updateWeekMapList() { + LogUtil.info(TAG, `updateWeekMapList = ${this.weekListMap}`) + } + + private Dismiss() { + if (this.isModified) { + this.showCancelConfirmDialog(); + } else { + this.isNewShow = false; + this.isEditShow = false; + AppStorage.setOrCreate('EditBindSheet', false); + AppStorage.setOrCreate('NewBindSheet', false); + } + } + + async aboutToAppear() { + this.selectList = [(GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('once_alarm'), + (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('customize'), + (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('days_of_wake_type') + ]; + + LogUtil.info(TAG, 'aboutToAppear: ManageAlarmClock.'); + let appearList: number[] = [ + $r('app.string.alarm_clock').id, + $r('app.plural.times').id, + $r('app.string.text_with_separate').id]; + for (let i = 0; i < TIME_DESC_STRING_ID_LIST.length; ++i) { + appearList.push(TIME_DESC_STRING_ID_LIST[i]) + } + await ResourceManager.preloadStringResources(appearList); + await this.initRingDurationOptions(); + await this.initAlarmInfoProperty(); + this.daysOfWeekLabels = TimeUtil.getDaysOfWeekLabels(); + this.repeatModeFields = this.getRepeatModeFields(); + this.alarmDetailFields = this.getAlarmDetailFields(); + this.isMilitaryTime = i18n.is24HourClock(); + let weekListArr: WeekListField[] = []; + this.daysOfWeekLabels.map((label: string, index: number) => { + weekListArr.push({ + label: label, + value: index, + selected: this.alarmInfo!.daysOfWeek!.some(day => day === index), + }) + this.weekListMap = JSON.stringify(weekListArr) + }) + } + + async aboutToDisappear(): Promise { + LogUtil.info(TAG, 'aboutToDisappear') + this.repeatType = 0; + this.isPcShowMenu = false; + } + + /** + * Get the current network status. + * + * @return boolean. + */ + private async getHasNet(): Promise { + try { + let isHaveNet = await connection.hasDefaultNet(); + + if (isHaveNet) { + let netHandle: connection.NetHandle = await connection.getDefaultNet(); + return netHandle.netId >= 100; + } else { + return false; + } + } catch (error) { + LogUtil.error(TAG, ' getHasNet failed.'); + return false; + } + } + + /** + * Legal workday click event. + * + * @param Index of legal workday in the menu. + * @return void. + */ + private async onClickWorkDay(index: number) { + let cacheHolidaysList = AppStorage.get(CACHE_HOLIDAYS_LIST); + if (!cacheHolidaysList?.year) { // Not statutory holidays data. + let isToastShowEnd: boolean = false; + TimeUtil.getWorkDaysNetData().then((hasNetData: boolean) => { + clearTimeout(this.httpOutTimeTimer); + if (hasNetData) { + this.selectVal = this.selectList[index]; + this.selectIndex = index; + this.daysOfWakeType = StateType.WAKE_DAYS; + } else { + !isToastShowEnd && promptAction.showToast({ + message: "网络服务不可用", + duration: 2000, + bottom: this.RESTRICT + }); + } + }); + + // If request http not response within 1s, show not network dialog. + clearTimeout(this.httpOutTimeTimer); + this.httpOutTimeTimer = setTimeout(() => { + promptAction.showToast({ + message: "网络服务不可用", + duration: 2000, + bottom: this.RESTRICT + }); + isToastShowEnd = true; + this.httpOutTimeTimer = -1; + }, HTTP_TIMEOUT_TIMER); + } else { + this.selectVal = this.selectList[index]; + this.selectIndex = index; + this.daysOfWakeType = StateType.WAKE_DAYS; + } + } + + private async initRingDurationOptions(): Promise { + this.ringDurationOptions = []; + for (const ringDuration of RING_DURATION_OPTIONS) { + this.ringDurationOptions.push({ + label: await this.getTimeWithUnitMinute(ringDuration), + value: ringDuration, + }); + } + } + + private async initAlarmInfoProperty(): Promise { + // If the parameter contains the alarm to edit, the mode is Edit. + this.isEditMode = !!this.alarmInfoParam; + let alarmInfo: AlarmInfo = {} as AlarmInfo; + if (this.alarmInfoParam) { + alarmInfo = this.alarmInfoParam; + this.daysOfWakeType = this.alarmInfoParam.daysOfWakeType!; + this.selectIndex = this.daysOfWakeType === StateType.WAKE_DAYS ? 2 : + this.daysOfWakeType === StateType.USER_DEFINED ? 1 : 0; + } else { + alarmInfo = this.getDefaultAlarmToAdd(); + } + + if (this.isEditMode) { + this.selectedTime.setHours(alarmInfo.hour!); + this.selectedTime.setMinutes(alarmInfo.minute!); + } + + this.alarmInfo = { + id: alarmInfo.id, + title: alarmInfo.title, + hour: alarmInfo.hour, + minute: alarmInfo.minute, + second: alarmInfo.second, + daysOfWeek: alarmInfo.daysOfWeek, + daysOfWakeType: alarmInfo.daysOfWakeType, + alarmTime: alarmInfo.alarmTime, + enabled: alarmInfo.enabled, + ringDuration: alarmInfo.ringDuration, + snoozeDuration: alarmInfo.snoozeDuration, + snoozeTimes: alarmInfo.snoozeTimes, + snoozeCount: alarmInfo.snoozeCount, + ringDurationDesc: await this.getTimeWithUnitMinute(alarmInfo.ringDuration!), + snoozeDesc: await this.getSnoozeDesc(alarmInfo.snoozeDuration!, alarmInfo.snoozeTimes!), + }; + + let selectVal = ''; + let selectIndex = 0; + let daysOfWakeType = StateType.ONES; + + if (alarmInfo.daysOfWakeType === StateType.WAKE_DAYS) { // 法定工作日 + selectVal = this.selectList[2]; + selectIndex = 2; + daysOfWakeType = StateType.WAKE_DAYS; + } else if (alarmInfo.daysOfWakeType === StateType.ONES) { // 只响一次 + selectVal = this.selectList[0]; + selectIndex = 0; + daysOfWakeType = StateType.ONES; + } else { // 1 2 3 用户自定义 + selectVal = this.selectList[1]; + selectIndex = 1; + daysOfWakeType = StateType.USER_DEFINED; + } + this.selectVal = selectVal; + this.selectIndex = selectIndex; + this.daysOfWakeType = daysOfWakeType; + } + + private getDefaultAlarmToAdd(): AlarmInfo { + return { + title: ResourceManager.getStringById($r('app.string.alarm_clock').id), + hour: this.selectedTime.getHours(), + minute: this.selectedTime.getMinutes(), + second: this.selectedTime.getSeconds(), + ringDuration: DFT_RING_DURATION, + snoozeDuration: DFT_SNOOZE_DURATION, + snoozeTimes: DFT_SNOOZE_TIMES, + daysOfWeek: [], + daysOfWakeType: this.daysOfWakeType, + enabled: true, + }; + } + + private async resetToDefaultData() { + await this.initAlarmInfoProperty(); + this.selectIndex = 0; + this.selectedTime = new Date(); + if (this.selectList.length) { + this.selectVal = this.selectList[this.selectIndex]; + } + this.alarmDetailFields = this.alarmDetailFields.map(item => { + if (item.property === 'ringDurationDesc') { + item.options = this.ringDurationOptions.map(option => { + return { + label: option.label, + value: option.value, + isSelected: this.alarmInfo!.ringDuration === option.value, + } as FormOption; + }) + } + return item + }) + try { + const arr: WeekListField[] = JSON.parse(this.weekListMap); + arr.forEach(item => { + item.selected = false; + }); + this.weekListMap = JSON.stringify(arr); + + } catch (error) { + LogUtil.error(TAG, JSON.stringify(error)) + } + this.repeatModeFields = this.getRepeatModeFields(); + } + + private async onConfirm(): Promise { + LogUtil.info(TAG, 'onConfirm Click In ManageAlarmClock'); + if (!this.alarmInfo) { + return; + } + GlobalContext.getContext().setObject('dialogController', undefined); + if (this.isPCView) { + this.onConfirmCallback && this.onConfirmCallback(); + } else { + this.pageInfos.pop(); + } + const alarmTitle = await AlarmManager.getDefaultAlarmTitle(this.alarmInfo!.id as string, + this.selectedTime.getHours()); + if (alarmTitle !== '') { + this.alarmInfo!.title = alarmTitle; + } + await AlarmManager.saveDefaultAlarmTitle(this.alarmInfo!.id!, this.alarmInfo!.title!); + this.handleDaysOfWakeType() + let alarmInfo: AlarmInfo = this.buildAlarm() + const clearedAlarmIds = await AlarmManager.clearSnoozedData(alarmInfo, this.isEditMode); + if (this.isEditMode) { + alarmInfo.alarmTime = TimeUtil.getAlarmTime(alarmInfo) + alarmInfo.alarmTimeIntent = TimeUtil.getAlarmTime(alarmInfo) + await AlarmManager.updateAlarm(alarmInfo); + } else { + alarmInfo.alarmTimeIntent = TimeUtil.getAlarmTime(alarmInfo) + await AlarmManager.addAlarm(alarmInfo); + } + const alarmRingTime = await TimeUtil.calculateRingTime(TimeUtil.getAlarmTime(alarmInfo)); + LogUtil.info(TAG, `alarmRingTime=${alarmRingTime}, clearedAlarmIds=${JSON.stringify(clearedAlarmIds)}`) + const isFiring = await AlarmStateManager.isFiring(); + const firingAlarmId = await AlarmStateManager.getAlarmId(); + LogUtil.info(TAG, `isFiring=>${JSON.stringify(isFiring)}, firingAlarmId=>${JSON.stringify(firingAlarmId)}`); + clearedAlarmIds.forEach(alarmId => { + if (!isFiring || Number(alarmId) !== firingAlarmId) { //编辑起闹闹钟,不清除横幅提示框和实况胶囊 + NotificationUtil.cancel(alarmId); + } + }); + emitter.emit({ + eventId: EVENT_ID_CHANGE_ALARM, + priority: emitter.EventPriority.IMMEDIATE, + }, { + data: { KEY_ALARM_RING_TIME: alarmRingTime } + }); + this.isPCView && this.resetToDefaultData(); + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_ALARM_SAVE_SETTING) + } + + private handleDaysOfWakeType() { + if (this.daysOfWakeType === StateType.ONES && this.alarmInfo!.daysOfWeek.length > 0) { + this.alarmInfo!.daysOfWeek = []; + } + if (this.daysOfWakeType === StateType.USER_DEFINED && this.alarmInfo!.daysOfWeek.length === 0) { + this.selectVal = this.selectList[0]; + this.selectIndex = 0; + this.daysOfWakeType = StateType.ONES; + } + } + private buildAlarm() { + const alarmInfo: AlarmInfo = { + id: this.alarmInfo!.id, + title: this.alarmInfo!.title, + daysOfWeek: this.alarmInfo!.daysOfWeek, + daysOfWakeType: this.daysOfWakeType, + alarmTime: this.alarmInfo!.alarmTime, + ringDuration: this.alarmInfo!.ringDuration, + snoozeDuration: this.alarmInfo!.snoozeDuration, + snoozeTimes: this.alarmInfo!.snoozeTimes, + snoozeCount: this.alarmInfo!.snoozeCount, + hour: this.selectedTime.getHours(), + minute: this.selectedTime.getMinutes(), + second: this.selectedTime.getSeconds(), + enabled: true, + alarmTimeIntent: this.alarmInfo!.alarmTime, + }; + LogUtil.info(TAG, `ManageAlarmClock=>${JSON.stringify(alarmInfo)}}`); + return alarmInfo + } + + private showDeleteConfirmDialog(): void { + if (!this.deleteConfirmDialog) { + let customControllerOptions: CustomDialogControllerOptions = CommonUtil.getCommonDialogConfig(); + this.deleteConfirmDialog = new CustomDialogController({ + builder: ConfirmDialog({ + confirmMessage: $r('app.string.delete_alarm_confirm'), + cancel: () => { + }, + confirm: () => { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_EDIT_ALARM_DEL_CONFIRM) + this.dealDeleteAction(); + this.isEditShow = false; + AppStorage.setOrCreate('EditBindSheet', false); + }, + cancelText: $r('app.string.cancel'), + confirmText: $r('app.string.confirm_delete'), + hasWarning: true, + }), + autoCancel: customControllerOptions.autoCancel, + alignment: this.foldAbleScreen === 1 ? DialogAlignment.Center : customControllerOptions.alignment, + customStyle: customControllerOptions.customStyle, + }); + } + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_EDIT_ALARM_DEL) + GlobalContext.getContext().setObject('dialogController', this.deleteConfirmDialog); + this.deleteConfirmDialog.open(); + } + + private async dealDeleteAction(): Promise { + // If you want to delete an alarm that is alerting, you need to stop the alarm. + await AlarmManager.removeAlarm(this.alarmInfo!); + NotificationUtil.cancel(this.alarmInfo!.id!); + const isFiring = await AlarmStateManager.isFiring(); + const alarmId = await AlarmStateManager.getAlarmId(); + LogUtil.info(TAG, `delete alarm id: ${alarmId}, isFiring${isFiring}`); + if (isFiring && alarmId.toString() === this.alarmInfo!.id) { + await WantAgentUtil.triggerCloseAlarm(this.alarmInfo!); + } + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_EDIT_ALARM_DEL_CONFIRM) + emitter.emit({ + eventId: EVENT_ID_DELETE_ALARM, + priority: emitter.EventPriority.IMMEDIATE, + }); + this.pageInfos.pop(); + } + + private showCancelConfirmDialog(): void { + if (!this.cancelConfirmDialog) { + let customControllerOptions: CustomDialogControllerOptions = CommonUtil.getCommonDialogConfig(); + this.cancelConfirmDialog = new CustomDialogController({ + builder: ConfirmDialog({ + confirmMessage: $r('app.string.save_changes_confirm'), + cancel: () => { + this.isNewShow = false; + this.isEditShow = false; + AppStorage.setOrCreate('EditBindSheet', false); + AppStorage.setOrCreate('NewBindSheet', false); + GlobalContext.getContext().setObject('dialogController', undefined); + if (this.isPCView) { + this.resetToDefaultData(); + this.onCancelCallback && this.onCancelCallback(); + } else { + this.pageInfos.pop() + } + }, + confirm: () => { + this.onConfirm(); + this.isNewShow = false; + this.isEditShow = false; + AppStorage.setOrCreate('EditBindSheet', false); + AppStorage.setOrCreate('NewBindSheet', false); + }, + cancelText: $r('app.string.quit'), + confirmText: $r('app.string.keep'), + }), + autoCancel: customControllerOptions.autoCancel, + alignment: this.foldAbleScreen === 1 ? DialogAlignment.Center : customControllerOptions.alignment, + customStyle: customControllerOptions.customStyle, + }); + } + GlobalContext.getContext().setObject('dialogController', this.cancelConfirmDialog); + this.cancelConfirmDialog.open(); + } + + private async getTimeWithUnitMinute(minute: number): Promise { + return await ResourceManager.getPluralStringAsync($r('app.plural.minute').id, minute); + } + + private async getSnoozeDesc(duration: number, times: number): Promise { + const durationWithUnit = await this.getTimeWithUnitMinute(duration); + const snoozeTimes = await ResourceManager.getPluralStringAsync($r('app.plural.times').id, times); + return ResourceManager.getStringById($r('app.string.text_with_separate').id) + .replace(FIRST_PARAM, durationWithUnit) + .replace(SECOND_PARAM, snoozeTimes); + } + + private getRepeatModeFields(): FormField[] { + return [ + { + label: $r('app.string.repeat'), + property: 'daysOfWeekDesc', + inputType: InputType.CHECK_BOX, + options: this.daysOfWeekLabels.map((label: string, dayNumber: number): FormOption => { + return { + label, + value: dayNumber, + isSelected: this.alarmInfo!.daysOfWeek!.some(day => day === dayNumber), + } + }), + onChange: (daysOfWeek: FormValue) => { + this.alarmInfo!.daysOfWeek = daysOfWeek as number[]; + const tempAlarmInfo = this.alarmInfo; + this.alarmInfo = undefined; + this.alarmInfo = tempAlarmInfo; + this.repeatModeFields = this.getRepeatModeFields(); + this.isChanged = !this.isChanged; + }, + onOpenDialog: () => this.isModified = true + }, + ]; + } + + private getAlarmDetailFields(): FormField[] { + let formDiyCallbackInfo: FormDiyCallbackInfo = this.getCallbacksForSnoozeField(); + return [ + { + label: $r('app.string.label'), + property: 'title', + inputType: InputType.TEXT, + onChange: async (value: FormValue) => { + this.alarmInfo!.title = value as string; + const tempAlarmInfo = this.alarmInfo; + this.alarmInfo = undefined; + this.alarmInfo = tempAlarmInfo; + await AlarmManager.saveDefaultAlarmTitle(this.alarmInfo!.id!, this.alarmInfo!.title!); + this.isChanged = !this.isChanged; + }, + onOpenDialog: () => this.isModified = true + }, + { + label: $r('app.string.ring_duration'), + property: 'ringDurationDesc', + inputType: InputType.RADIO, + options: this.ringDurationOptions.map(option => { + return { + label: option.label, + value: option.value, + isSelected: this.alarmInfo!.ringDuration === option.value, + } as FormOption; + }), + onChange: async (value: FormValue) => { + this.alarmInfo!.ringDuration = value as number; + this.alarmInfo!.ringDurationDesc = await this.getTimeWithUnitMinute(value as number); + const tempAlarmInfo = this.alarmInfo; + this.alarmInfo = undefined; + this.alarmInfo = tempAlarmInfo; + this.alarmDetailFields = this.getAlarmDetailFields(); + this.isChanged = !this.isChanged; + }, + onOpenDialog: () => this.isModified = true + }, + { + label: $r('app.string.snooze_duration'), + property: 'snoozeDesc', + inputType: InputType.DIY, + onOpenDialog: formDiyCallbackInfo.onOpenDialog, + onCloseDialog: formDiyCallbackInfo.onCloseDialog, + onConfirmDialog: formDiyCallbackInfo.onConfirmDialog, + } + ]; + } + + private getCallbacksForSnoozeField(): FormDiyCallbackInfo { + return { + onOpenDialog: () => { + this.isModified = true; + this.editingSnoozeInfo = { + duration: this.alarmInfo!.snoozeDuration!, + times: this.alarmInfo!.snoozeTimes!, + }; + }, + onCloseDialog: () => { + this.editingSnoozeInfo = undefined; + }, + onConfirmDialog: async () => { + + let duration = this.editingSnoozeInfo!.duration; + let times = this.editingSnoozeInfo!.times; + + if (!this.alarmInfo) { + return; + } + + this.alarmInfo = { + id: this.alarmInfo.id, + title: this.alarmInfo.title, + hour: this.alarmInfo.hour, + minute: this.alarmInfo.minute, + second: this.alarmInfo.second, + daysOfWeek: this.alarmInfo.daysOfWeek, + alarmTime: this.alarmInfo.alarmTime, + enabled: this.alarmInfo.enabled, + ringDuration: this.alarmInfo.ringDuration, + ringDurationDesc: this.alarmInfo.ringDurationDesc, + snoozeCount: this.alarmInfo.snoozeCount, + snoozeDesc: await this.getSnoozeDesc(duration, times), + snoozeDuration: duration, + snoozeTimes: times, + }; + this.isChanged = !this.isChanged; + }, + } + } + + private isPcModelPage(): boolean { + return deviceInfo.deviceType === '2in1'; + } + + // 编辑打点事件 + private changeTime(): void { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.EDIT_ALARM_CLOCK_TIME) + } + + @Builder + renderDivider(index: number | undefined) { + if (index) { + // Render divider above items except for the first item with index zero + Divider().color($r('sys.color.comp_divider')) + } + } + + //设置闹钟的单次或者工作日 + @Builder + MyMenu() { + Column() { + ForEach(this.selectList, (item: string, index: number) => { + this.renderDivider(index) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(item) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + .focusable(true) + .tabIndex(1) + Image(this.selectIndex === index ? $r('app.media.ic_public_ok') : '') + .height($r('app.float.menu_item_icon')) + .width($r('app.float.menu_item_icon')) + .fillColor($r('sys.color.icon_primary')) + } + .height($r('app.float.list_item_height')) + .padding({ + left: $r('app.float.card_margin_start'), + right: $r('app.float.card_margin_start'), + top: $r('app.float.list_item_padding'), + bottom: $r('app.float.list_item_padding') + }) + .margin({ + left: $r('app.float.card_item_margin_horizontal__8'), + right: $r('app.float.card_item_margin_horizontal__8'), + }) + .onClick(() => { + this.isModified = true + if (index === 2) { //法定工作日 + this.alarmInfo!.daysOfWeek = []; + this.onClickWorkDay(index) + } else if (index === 1) { //自定义 + if (this.repeatType !== index) { + this.selectVal = this.selectList[index]; + } + this.selectIndex = index; + this.daysOfWakeType = StateType.USER_DEFINED; + } else { //单次 + this.selectVal = this.selectList[index]; + this.selectIndex = index; + this.daysOfWakeType = StateType.ONES; + } + this.repeatType = index; + }) + .borderRadius($r('sys.float.corner_radius_level10')) + .backgroundColor(getItemBackgroundColor(item, this.repeatDropMenuActive)) + .onHover((isHover: boolean) => { + const repeatDropMenuActive: IRepeatDropMenuActive = { key: item, status: '' }; + if (isHover) { + repeatDropMenuActive.status = 'hover'; + } else { + repeatDropMenuActive.status = ''; + } + this.repeatDropMenuActive = repeatDropMenuActive; + }) + .onMouse((event: MouseEvent) => { + if ((event.action === MouseAction.Press) && (event.button === MouseButton.Left)) { + const repeatDropMenuActive: IRepeatDropMenuActive = { key: item, status: 'press' }; + this.repeatDropMenuActive = repeatDropMenuActive; + } else if (event.action === MouseAction.Release) { + const repeatDropMenuActive: IRepeatDropMenuActive = { key: item, status: 'hover' }; + this.repeatDropMenuActive = repeatDropMenuActive; + } + }) + }) + } + .width(this.isPcModelPage() ? $r('app.float.pc_new_alarm_menu_width') : '44%') + .padding({ left: $r('app.float.card_margin_start'), right: $r('app.float.card_margin_start') }) + } + + // PC create alarm repeating options menu. + @Builder + pcNewAlarmMenu() { + Column() { + ForEach(this.selectList, (item: string, index: number) => { + this.renderDivider(index) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(item) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + .focusable(true) + .tabIndex(1) + Image(this.selectIndex === index ? $r('app.media.ic_public_ok') : '') + .height($r('app.float.menu_item_icon')) + .width($r('app.float.menu_item_icon')) + .fillColor($r('sys.color.icon_primary')) + } + .borderRadius($r('sys.float.corner_radius_level10')) + .height($r('app.float.list_item_height')) + .padding({ + left: $r('app.float.card_margin_start'), + right: $r('app.float.card_margin_start'), + top: $r('app.float.list_item_padding'), + bottom: $r('app.float.list_item_padding') + }) + .margin({ + left: $r('app.float.card_item_margin_horizontal__8'), + right: $r('app.float.card_item_margin_horizontal__8'), + }) + .backgroundColor(getItemBackgroundColor(item, this.repeatDropMenuActive)) + .onHover((isHover: boolean) => { + const repeatDropMenuActive: IRepeatDropMenuActive = { key: item, status: '' }; + if (isHover) { + repeatDropMenuActive.status = 'hover'; + } else { + repeatDropMenuActive.status = ''; + } + this.repeatDropMenuActive = repeatDropMenuActive; + }) + .onMouse((event: MouseEvent) => { + if ((event.action === MouseAction.Press) && (event.button === MouseButton.Left)) { + const repeatDropMenuActive: IRepeatDropMenuActive = { key: item, status: 'press' }; + this.repeatDropMenuActive = repeatDropMenuActive; + } else if (event.action === MouseAction.Release) { + const repeatDropMenuActive: IRepeatDropMenuActive = { key: item, status: 'hover' }; + this.repeatDropMenuActive = repeatDropMenuActive; + } + }) + .onClick(() => { + if (index === 2) { // Statutory working days. + this.alarmInfo!.daysOfWeek = []; + this.onClickWorkDay(index) + } else if (index === 1) { // Customization. + if (this.repeatType !== index) { + this.selectVal = this.selectList[index]; + } + this.selectIndex = index; + this.daysOfWakeType = StateType.USER_DEFINED; + } else { // Single time. + this.alarmInfo!.daysOfWeek = []; + this.selectVal = this.selectList[index]; + this.selectIndex = index; + this.daysOfWakeType = StateType.ONES; + } + this.isPcShowMenu = false; + this.repeatType = index; + }) + }) + } + .width($r('app.float.pc_new_alarm_menu_width')) + .padding({ + top: $r('app.float.card_padding_vertical'), + bottom: $r('app.float.card_padding_vertical'), + left: $r('app.float.card_margin_start'), + right: $r('app.float.card_margin_start'), + }) + .position({ + x: $r('app.float.pc_new_alarm_menu_position_x'), y: $r('app.float.pc_new_alarm_menu_position_y') + }) + .borderRadius($r('sys.float.corner_radius_level10')) + .backgroundColor($r('app.color.component_ultra_thick_dialog')) + .transition( + TransitionEffect.OPACITY.animation({ curve: Curve.Linear, duration: 0.5 }) + .combine(TransitionEffect.translate({ x: 0, y: -56 })).animation({ curve: Curve.Linear, duration: 0.5 }) + ) + .shadow(ShadowStyle.OUTER_DEFAULT_MD) + } + + @Builder + repeatOptionText() { + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text($r('app.string.repeat')) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + .margin({ right: $r('app.float.card_tag_margin_end') }) + .fontWeight(FontWeight.Medium) + Column() { + Row() { + Text(this.selectVal) + .fontSize($r('sys.float.Body_M')) + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) + .textAlign(TextAlign.End) + } + } + .alignItems(HorizontalAlign.End) + .flexGrow(FLEX_GROW_FILL_FULL) + } + .padding({ + left: $r('app.float.card_inner_padding_horizontal'), + right: $r('app.float.card_inner_padding_horizontal'), + }) + .constraintSize({ minHeight: $r('app.float.set_alarm_card_height') }) + .borderRadius($r('sys.float.corner_radius_level10')) + .itemBackgroundStyles() + } + + // Create alarm repeating options. + @Builder + repeatOption($$: boolean) { + if ($$) { + Row() { + this.repeatOptionText(); + if (this.isPcShowMenu) { + this.pcNewAlarmMenu(); + } + } + .onClick(() => this.isPcShowMenu = !this.isPcShowMenu); + } else { + Row() { + this.repeatOptionText(); + } + .bindMenu(this.MyMenu, { + placement: Placement.BottomRight, + }); + } + } + + @Builder + buildAlarmInfoForms() { + Column() { + Column() { + Row() { + this.repeatOption(this.isPcNewAlarmShow); + } + .zIndex(1) + + if (this.selectIndex === 1) { + Column() { + Divider().color($r('sys.color.comp_divider')) + Row({ space: 8.5 }) { + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + ForEach(JSON.parse(this.weekListMap), (item: WeekListField, index: number) => { + Button({ type: ButtonType.Circle, stateEffect: true }) { + Text(item.label.split('')[1]) + .textAlign(TextAlign.Center) + .fontSize(13) + .fontWeight(item.selected ? FontWeight.Medium : FontWeight.Regular) + .fontColor(item.selected ? $r('sys.color.font_on_primary') : $r('sys.color.font_secondary')) + .focusable(true) + } + .backgroundColor(item.selected ? $r('sys.color.ohos_id_icon_color_active') : $r('sys.color.ohos_id_background_secondary')) + .width('40vp') + .height('40vp') + .onClick(() => { + this.isModified = true + let _weekList: WeekListField[] = JSON.parse(this.weekListMap); + _weekList[index].selected = !item.selected + this.weekListMap = JSON.stringify(_weekList) + let daysNumArr: number[] = []; + _weekList.map((item: WeekListField, index: number) => { + if (item.selected) { + daysNumArr.push(item.value) + } + }) + this.alarmInfo!.daysOfWeek = daysNumArr + }) + }) + } + } + .width('100%') + .height(60) + .justifyContent(FlexAlign.Center) + } + .width('100%') + .padding({ + left: $r('app.float.card_inner_padding_horizontal'), + right: $r('app.float.card_inner_padding_horizontal'), + }) + } + } + .zIndex(9) + .padding({ + top: $r('app.float.card_padding_horizontal'), + right: $r('app.float.card_padding_horizontal'), + bottom: $r('app.float.card_padding_horizontal'), + left: $r('app.float.card_padding_horizontal'), + }) + .margin({ + bottom: $r('app.float.card_margin_end') + }) + .borderRadius($r('sys.float.corner_radius_level10')) + .backgroundColor($r('sys.color.comp_background_list_card')) + + Row() { + // Form of alarm details + Form({ + fields: this.alarmDetailFields, + properties: this.alarmInfo, + buildDiyInput: (): void => this.buildSnoozeInput(), + }) + } + } + } + + @Builder + buildDivider() { + Column() { + Divider().color($r('sys.color.comp_divider')) + } + // Make the divider fill the width of the dialog + .margin({ + top: $r('app.float.snooze_setting_margin_vertical_max'), + bottom: $r('app.float.snooze_setting_margin_vertical_max'), + left: $r('app.float.divider_margin_horizontal'), + right: $r('app.float.divider_margin_horizontal'), + }) + .offset({ x: $r('app.float.divider_margin_offset_left') }) + } + + @Builder + buildSnoozeInput() { + if (this.editingSnoozeInfo) { + Column() { + ArraySlider({ + label: $r('app.string.snooze_duration_time'), + value: this.editingSnoozeInfo.duration, + scales: SNOOZE_DURATION_OPTIONS, + onChange: (value: number) => { + try { + if (this.editingSnoozeInfo) { + this.editingSnoozeInfo.duration = value; + } + } catch (error) { + LogUtil.error(TAG, 'set ArraySlider1 failed: ', (error as BusinessError).message); + } + }, + }) + this.buildDivider() + ArraySlider({ + label: $r('app.string.number_of_snoozes'), + value: this.editingSnoozeInfo.times, + scales: SNOOZE_TIMES_OPTIONS, + onChange: (value: number) => { + try { + if (this.editingSnoozeInfo) { + this.editingSnoozeInfo.times = value; + } + } catch (error) { + LogUtil.error(TAG, 'set ArraySlider2 failed: ', (error as BusinessError).message); + } + }, + }) + } + .padding({ + left: $r('app.float.snooze_setting_margin_vertical_min'), + right: $r('app.float.snooze_setting_margin_vertical_min') + }) + } + } + + @Builder + buildPageTitle() { + Flex({ alignItems: ItemAlign.Start }) { + Row() { + TitleBar({ + modelEditFlag: this.isModelEdit, + title: (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync(this.isEditMode ? 'edit_alarm' : 'add_alarm'), + titleFontWeight: this.isPCView ? FontWeight.Bold : FontWeight.Medium, + isPCView: this.isPCView, + onIconClick: () => { + if (this.isEditMode) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_ALARM_SAVE_SETTING) + } else { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLOCK_ALARM_SAVE_SETTING) + } + if (this.isModified) { + this.showCancelConfirmDialog(); + } else if (this.isPCView) { + this.onCancelCallback && this.onCancelCallback(); + } else { + GlobalContext.getContext().setObject('dialogController', undefined); + this.pageInfos.pop(); + this.isNewShow = false; + this.isEditShow = false; + AppStorage.setOrCreate('EditBindSheet', false); + AppStorage.setOrCreate('NewBindSheet', false); + } + }, + iconResource: $r('app.media.ic_cancel'), + isHoverInfo: true, + isFocusAble: true, + isModelBg: true, + operationArea: (): void => this.buildPageOperations(), + }) + } + .backgroundColor(this.getBackgroundColor()) + } + .hitTestBehavior(HitTestMode.Transparent) + .width('100%') + .height('100%') + .padding({ + left: this.isPCView ? $r('sys.float.padding_level8') : 0, + right: this.isPCView ? $r('sys.float.padding_level8') : 0 + }) + } + + @Builder + buildPageOperations() { + Row() { + Image($r('app.media.ic_confirm')) + .fillColor($r('sys.color.icon_primary')) + .draggable(false) + .width($r('app.float.button_icon_size')) + .height($r('app.float.button_icon_size')) + .appBarIconBtn(this.isPCView) + .focusable(true) + .id('id_confirmed_img') + } + .onClick(() => { + this.onConfirm(); + this.isNewShow = false; + this.isEditShow = false; + AppStorage.setOrCreate('EditBindSheet', false); + AppStorage.setOrCreate('NewBindSheet', false); + }) + .justifyContent(FlexAlign.Center) + .width($r('app.float.new_button_size')) + .height($r('app.float.new_button_size')) + .borderRadius($r('app.float.new_button_size')) + .backgroundColor(this.confirmButtonBg) + .onMouse((event) => { + if ((event?.action === MouseAction.Press) && (event?.button === MouseButton.Left)) { + this.confirmButtonBg = $r('sys.color.interactive_click'); + } else if (event?.action === MouseAction.Release) { + this.confirmButtonBg = $r('sys.color.interactive_hover'); + } + }) + } + + @Builder + buildTimePicker() { + Column() { + TimePicker({ selected: this.selectedTime }) + .selectedTextStyle({ + color: $r('sys.color.ohos_id_icon_color_active') + }) + .useMilitaryTime(this.isMilitaryTime) + .onChange((date) => { + this.changeTime(); + this.isModified = true; + this.selectedTime.setHours(date.hour!); + this.selectedTime.setMinutes(date.minute!); + }) + .width(this.isPCView ? '100%' : $r('app.float.timer_picker_width')) + .height($r('app.float.timer_picker_height')) + } + .backgroundColor($r('sys.color.comp_background_list_card')) + .borderRadius($r('sys.float.corner_radius_level10')) + .width(TIME_PICKER_WIDTH) + .padding({ + top: $r('app.float.timer_pick_padding_vertical'), + bottom: $r('app.float.timer_pick_padding_vertical'), + }) + .margin({ + top: $r('app.float.card_margin_top'), + }) + } + + @Builder + buildDeleteButton() { + if (this.isEditMode) { + Flex({ justifyContent: FlexAlign.Center }) { + Button($r('app.string.delete_alarm')) + .backgroundColor($r('sys.color.comp_background_tertiary')) + .fontColor($r('sys.color.warning')) + .width(DELETE_BUTTON_WIDTH) + .onClick(() => this.showDeleteConfirmDialog()) + .id('id_deleteAlarm_button') + } + .hitTestBehavior(HitTestMode.Transparent) + .width('100%') + .padding({ + bottom: $r('app.float.delete_button_margin_bottom'), + top: $r('sys.float.padding_level8') + }) + .backgroundColor(this.getBackgroundColor()) + } + } + + @Builder + pcContentBuilder() { + Column() { + Scroll(this.scrollerForScroll) { + Flex({ + direction: FlexDirection.Column, + alignItems: ItemAlign.Center + }) { + Card({ + cancelMargin: true, + bgColor: $r('sys.color.comp_background_list_card') + }) { + this.buildTimePicker(); + } + + Row() { + this.buildAlarmInfoForms(); + } + } + .padding({ + left: $r('sys.float.padding_level8'), + right: $r('sys.float.padding_level8') + }) + } + .height('100%') + } + .padding({ + top: $r('app.float.header_footer_height'), + bottom: this.isEditMode ? $r('app.float.edit_alarm_content_padding_bottom') : 0 + }) + .height('100%') + .backgroundColor(this.getBackgroundColor()) + } + + @Builder + buildContent() { + Scroll(this.scrollerForScroll) { + Column() { + Row() { + CommonGrid() { + this.buildTimePicker(); + } + } + .margin({ + bottom: $r('app.float.card_margin_end') + }) + + if (this.isChanged) { + CommonGrid() { + this.buildAlarmInfoForms(); + } + } else { + CommonGrid() { + this.buildAlarmInfoForms(); + } + } + } + } + .align(Alignment.Top) + .width('100%') + .height('100%') + .margin({ + top: this.isPcModelPage() ? $r('app.float.header_footer_height') : '', + bottom: this.isPcModelPage() ? $r('app.float.edit_alarm_content_padding_bottom') : '' + }) + .padding({ + left: $r('sys.float.padding_level8'), + right: $r('sys.float.padding_level8'), + top: this.isPcModelPage() ? '' : $r('app.float.header_footer_height'), + bottom: this.isPcModelPage() ? '' : (this.isEditMode ? $r('app.float.edit_alarm_content_padding_bottom') : 0) + }) + .backgroundColor(this.getBackgroundColor()) + } + + build() { + NavDestination() { + if (this.alarmInfo) { + Stack({ alignContent: Alignment.Bottom }) { + if (this.isPCView) { + this.pcContentBuilder(); + } else { + this.buildContent(); + } + this.buildDeleteButton(); + this.buildPageTitle(); + } + .expandSafeArea([SafeAreaType.KEYBOARD]) + .onClick(() => { + if (this.isPCView && this.isPcShowMenu) { + this.isPcShowMenu = false; + } + }) + } + } + .renderGroup(true) + .onBackPressed(() => { + if (this.isModified) { + this.showCancelConfirmDialog(); + return true; + } else { + return false; + } + }) + .hideTitleBar(true) + .backgroundColor(Color.Transparent) + .height((this.isPCView && !this.isEditMode) ? '560vp' : '100%') + .constraintSize({ + maxHeight: (this.isPCView && !this.isEditMode) ? '90%' : '100%' + }) + } +} + diff --git a/feature/alarmclock/src/main/ets/pages/ManageAlarmClock/types.ets b/feature/alarmclock/src/main/ets/pages/ManageAlarmClock/types.ets new file mode 100644 index 0000000..c3d2a2b --- /dev/null +++ b/feature/alarmclock/src/main/ets/pages/ManageAlarmClock/types.ets @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AlarmInfo } from '@hmos/common'; + +export interface SnoozeInfo { + times: number; // Snooze times + duration: number; // Snooze duration +} + +export interface ManageAlarmClockParam { + alarmInfoToEdit?: AlarmInfo; +} + +// Default ringing duration(5 minutes) +export const DFT_RING_DURATION = 5; + +// Default snooze duration(10 minutes) +export const DFT_SNOOZE_DURATION = 10; + +// Default snooze times(3 times) +export const DFT_SNOOZE_TIMES = 3; + +export const DFT_SNOOZE_INFO: SnoozeInfo = { + duration: DFT_RING_DURATION, + times: DFT_SNOOZE_TIMES, +} + +// Snooze times(minutes) +export const SNOOZE_TIMES_OPTIONS = [0, 1, 3, 5, 10]; + +// Snooze duration options(minutes) +export const SNOOZE_DURATION_OPTIONS = [5, 10, 15, 20, 25, 30]; + +// Ringing duration options(minutes) +export const RING_DURATION_OPTIONS = [1, 5, 10, 15, 20, 30]; + +export const TIME_PICKER_HEIGHT = '30%'; + +export const TIME_PICKER_WIDTH = '100%'; + +export const DELETE_BUTTON_WIDTH = '50%'; + +export const HTTP_TIMEOUT_TIMER = 1000; + diff --git a/feature/alarmclock/src/main/ets/pages/index.ets b/feature/alarmclock/src/main/ets/pages/index.ets new file mode 100644 index 0000000..a99f924 --- /dev/null +++ b/feature/alarmclock/src/main/ets/pages/index.ets @@ -0,0 +1,1295 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// import hiSysEvent from '@ohos.hiSysEvent'; +// import dataShare from '@ohos.data.dataShare'; +import settings from '@ohos.settings'; +import resmgr from '@ohos.resourceManager'; +import preferencesUtil from '@ohos.data.preferences'; +import { BusinessError } from '@ohos.base'; +import CommonEventManager from '@ohos.commonEventManager'; +import common from '@ohos.app.ability.common'; +import i18n from '@ohos.i18n'; +import promptAction from '@ohos.promptAction'; +import curves from '@ohos.curves'; +import deviceInfo from '@ohos.deviceInfo'; +import router from '@ohos.router'; +import { + AddButton, + AlarmInfo, + AlarmInfoToShow, + AlarmManager, + AlarmStateManager, + BreakPoint, + ButtonSize, + Clock, + CommonGrid, + EVENT_ID_ALARM_RING, + EventName, + EventReportUtil, + FROM_DATA_STORE, + GlobalContext, + LESS_THAN_ONE_MINUTE_FLAG, + LogUtil, + NO_ALARM_ENABLED, + ONE_MINUTE, + ResourceManager, + TIMER_Toggle_FRONT_AND_BACKEND, + TimerManager, + TimeUtil, + CommonUtil, + FIRST_PARAM, + SECOND_PARAM +} from '@hmos/common'; +import { AlarmCardUtil } from '../utils/AlarmCardUtil'; +import { AlarmCard, AlarmCardType, ObservedAlarmInfo } from '../components/index'; +import { NotificationUtil } from '../utils'; +import { AlarmServiceManager } from '../manager'; +import { PCAlarmFormPopupBuilder } from '../components/PCBuilder'; +import { ManageAlarmClock } from './ManageAlarmClock'; +import emitter from '@ohos.events.emitter'; + + +interface ParamType { + alarmList: ObservedAlarmInfo[], + selectedAlarmId?: string +} + +interface OffsetNum { + yOffset: number +} + +interface visibleType { + isVisible: boolean +} + +const MANAGE_ALARM_CLOCK_PAGE = 'pages/ManageAlarmClock'; + +// Threshold for long press gesture. The current value is 600 ms. +const LONG_PRESS_DURATION = 500; + +const WAKE_DAYS = 4; + +export const REPEAT_TAG_ID_LIST_IN_ZH = [ + $r('app.string.customize').id, + $r('app.string.open_tomorrow').id, + $r('app.string.will_open').id, +]; + +export const FLEX_GROW_FILL_FULL = 1; + +const CLOCK_DIAL_AREA_OFFSET = 340; +const TAG = 'AlarmClock'; +const MAX_HEAD_ANGLE: number = 17; +const BODY_HEAD_CRITICAL_ANGLE: number = 12; +const MAX_BODY_ANGLE: number = 10; +const DELETE_SWIPE_HEAD_WIDTH: number = 56; +const DELETE_SWIPE_BODY_WIDTH: number = 123; +const ANGLE_RADIO: number = 10; +const TIME_SUBSCRIBE_INFO: CommonEventSubscribeInfo = { + events: [ + CommonEventManager.Support.COMMON_EVENT_TIME_CHANGED, + CommonEventManager.Support.COMMON_EVENT_TIMEZONE_CHANGED, + CommonEventManager.Support.COMMON_EVENT_TIME_TICK, + ], +}; +const TRIGGER_TIME_NEVER: number = -1; +const HOUR_OF_DAY: number = 24; +const ONE_DAY: number = 1; +const ZERO_DAY: number = 0; +const ZERO_HOUR: number = 0; +const ALARM_CARD: string = 'ALARM_CARD'; +const LIST_ITEM_HEIGHT: number = 60; +const LIST_ITEM_MARGIN: number = 12; +const FOLD_SCREEN_DIAMETER_RATIO = 0.85; +// This type is not exported from @ohos.commonEventManager, so we get it by exported function. +type CommonEventSubscriber = CommonEventManager.CommonEventSubscriber +// type ServiceExtensionContext = common.ServiceExtensionContext; +type CommonEventSubscribeInfo = CommonEventManager.CommonEventSubscribeInfo; + + +interface ITranslateList { + x: number, + y: number, + z: number +} + +interface ICustomMenuItem extends MenuItemOptions { + key: string; +} + + +/** + * Alarm clock home page + * + * Used to display the time, list of existing alarms, and pause and resume alarms. + * Provides entries for adding, modifying, and deleting alarms. + * + * @since 2022-07-05 + */ +@Component +export struct AlarmClock { + @Link @Watch('updateAlarmClockList') alarmClockListFromEntry: AlarmInfoToShow[]; + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + @StorageProp('setOrientaion') getOrientaion: number = 0; + @StorageProp('ALARM_CLOCK_TAB') ALARM_CLOCK_TAB: Boolean = false; + @Prop @Watch('isBigViewChangeHandle') isBigView: string = '' + @State isDateTypeClock: boolean = i18n.System.is24HourClock(); + @Prop isPortraitOrientation: boolean = true; //纵向 true 横向 false + @State translateList: ITranslateList = { x: 0, y: 0, z: 0 }; + @State alarmClockList: ObservedAlarmInfo[] = []; + @State refreshAlarmClockInfo: boolean = false; + @State deviceType: string = deviceInfo.deviceType; // 设备类型 + @Link leftTimeToRingTime: string; + @State isClockHide: boolean = false; + @State clockDiameter: number = 0; + @State headAngle: number = 0; + @State bodyAngle: number = 0; + @State swipeScale: number = 1; + @State isFiring: boolean = false; + @StorageLink('EditBindSheet') isEditShow: boolean = false; + @StorageLink('NewBindSheet') isNewShow: boolean = false; + @StorageLink('ShouldDismiss') isDismiss: boolean = false; + @StorageLink('TextInputClose') isTextInputClose: boolean = false; + @StorageLink('isRepeat') isRepeat: boolean = false; + @State deleteAlarmId: string = ''; + @State editShowItem: ObservedAlarmInfo | undefined = undefined; + @Prop isAlarmClockVisible: boolean = true; + @State isPCView: boolean = this.computedIsPCView(); + @State listIndexCache: number = 0; + @State itemContextMenuData: Array = [{ key: 'del', content: $r('app.string.alarm_tips_del') }]; + @StorageLink('pcAlarmFormPopupVisible') pcAlarmFormPopupVisible: boolean = false; + @State isAlarm: boolean = true; + + private isPCLg() { + return this.deviceType === '2in1' + } + + private timeSubscriber?: CommonEventSubscriber; + // private dataShareHelper?: dataShare.DataShareHelper; + private scrollerForScroll: Scroller = new Scroller(); + private scrollerForList: Scroller = new Scroller(); + private uri: string = 'datashare:///com.ohos.settingsdata/entry/settingsdata/SETTINGSDATA?Proxy=true&key=' + settings.date.TIME_FORMAT; + private isReady: boolean = false + readonly RESTRICT: number = -999; + private isFoldCross: number = 2224; + private isFoldVertical: number = 2496; + // private toast: string = (GlobalContext.getContext() + // .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('show_toast_name'); + private timeOutId: number = -1; + + private async updateAlarmClockList(): Promise { + LogUtil.info(TAG, 'start to updateAlarmClockList'); + this.alarmClockList = this.alarmClockListFromEntry.map(item => new ObservedAlarmInfo(item)); + if (this.alarmClockList.length === this.alarmClockListFromEntry.length) { + this.refreshAlarmClockInfo = !this.refreshAlarmClockInfo; // refresh only when modify + } + this.isFiring = await AlarmStateManager.isFiring(); + this.AlarmClockVisibleFn(); + } + + private async AlarmClockVisibleFn(): Promise { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, FROM_DATA_STORE); + let alarm_clock_time_id = await TimerManager.getTriggerTimeID(); + LogUtil.info(TAG, 'alarm_active ' + this.ALARM_CLOCK_TAB); + let ringTimeInMs: number = await TimeUtil.getNextAlarmTime(); + if (this.ALARM_CLOCK_TAB && ringTimeInMs != TRIGGER_TIME_NEVER) { + let order = this.getAlarmClockListIndex(String(alarm_clock_time_id)) + // 152为ListiItem高;12为ListItem间距 + let SCROOL_HEIGHT = order * (LIST_ITEM_HEIGHT + LIST_ITEM_MARGIN); + LogUtil.info(TAG, 'alarm_active Order=' + order + '&& SCROOL_HEIGHT=' + (CLOCK_DIAL_AREA_OFFSET + SCROOL_HEIGHT)); + this.timeOutId = setTimeout(() => { + this.scrollerForScroll.scrollTo({ + xOffset: 0, + yOffset: CLOCK_DIAL_AREA_OFFSET + MAX_HEAD_ANGLE + SCROOL_HEIGHT, + animation: { duration: 100, curve: Curve.Linear } + }) + clearTimeout(this.timeOutId); + this.timeOutId = -1; + }, 50) + } + } + + //获取指定id闹钟列表index + private getAlarmClockListIndex(id: string): number { + let listIndex = 0; + this.alarmClockList.map((item, index) => { + if (item.id == id) { + listIndex = index; + } + }) + return Number(listIndex) + } + + // 获取时间戳 + private async calculateRingTime(ringTimeInMs: number): Promise { + const currentDate = new Date(); + const leftTimeInMs = ringTimeInMs - currentDate.getTime(); + const leftTime = new Date(leftTimeInMs); + const leftMinutes = leftTime.getUTCMinutes(); + const leftHours = leftTime.getUTCHours(); + const leftDays = leftTime.getUTCDate() - ONE_DAY; + if (leftDays === 0 && leftHours === 0 && leftMinutes < ONE_MINUTE) { + return LESS_THAN_ONE_MINUTE_FLAG; + } + const leftTimeStrParts: string[] = []; + if (leftDays != 0) { + // If the number of remaining days is not 0, the description of remaining days needs to be assembled. + const leftDayStr = await ResourceManager.getPluralStringAsync($r('app.plural.day').id, leftDays); + leftTimeStrParts.push(leftDayStr); + } + if (leftHours != 0) { + // If the number of remaining hours is not 0, the description of remaining hours needs to be assembled. + const leftHourStr = await ResourceManager.getPluralStringAsync($r('app.plural.hour').id, leftHours); + leftTimeStrParts.push(leftHourStr); + } + if (leftMinutes != 0) { + // If the number of remaining minutes is not 0, the description of the remaining minutes needs to be assembled. + const leftMinuteStr = await ResourceManager.getPluralStringAsync($r('app.plural.minute').id, leftMinutes); + leftTimeStrParts.push(leftMinuteStr); + } + return `${leftTimeStrParts.join(' ')}`; + } + + private async openAlarmShowToast(alarmId: string): Promise { + const alarmClockListFromDatabase: AlarmInfo[] = await AlarmManager.getAllAlarms(); + alarmClockListFromDatabase.forEach(((alarmClockInfo, alarmClockIndex) => { + LogUtil.info(TAG, 'alarmClockInfo:', JSON.stringify(alarmClockInfo)); + if (alarmClockInfo.id === alarmId) { + this.calculateRingTime(Number(alarmClockInfo.alarmTime)).then((data) => { + return promptAction.showToast({ + message: data === LESS_THAN_ONE_MINUTE_FLAG + ? $r('app.string.less_than_one_minute') + : $r('app.string.ring_in', data), // 显示文本 + duration: 2000, // 显示时长 + bottom: this.RESTRICT // 距离底部的距离 + }) + }) + } + })) + } + + /** + * Update the time since the last ring + */ + private async refreshLeftTime(isTimeChange: boolean): Promise { + if (isTimeChange) { + await AlarmServiceManager.refreshNextAlertTime(isTimeChange); + } + this.leftTimeToRingTime = await TimeUtil.getLeftTimeToRingTime(isTimeChange); + const isFiring = await AlarmStateManager.isFiring(); + LogUtil.info(TAG, 'refreshLeftTime isFiring=' + isFiring) + if (!isFiring) { + AlarmCardUtil.notifyAlarmCardTimeUpdate(); + } + if (this.leftTimeToRingTime === NO_ALARM_ENABLED && isTimeChange) { + this.refreshAlarmData(); + } + } + + async aboutToAppear(): Promise { + LogUtil.info(TAG, 'aboutToAppear len1:' + this.alarmClockListFromEntry.length + ' len2:' + this.alarmClockList.length); + if (this.alarmClockList.length !== this.alarmClockListFromEntry.length) { + this.refreshAlarmData(); + } + await this.subscribeEvents(); + await this.subscribeDataBaseShare(); + await TimeUtil.getWorkDaysNetData(); + this.isReady = true; + await TimeUtil.getFreeday(); // Obtaining statutory holidays data in the database. + emitter.on({ eventId: TIMER_Toggle_FRONT_AND_BACKEND }, (eventData) => { + LogUtil.info(TAG, `TIMER_Toggle_FRONT_AND_BACKEND pageShow=${eventData.data?.pageShow}`) + if (eventData.data?.pageShow) { + this.AlarmClockVisibleFn() + } + }); + } + + async aboutToDisappear(): Promise { + this.unSubscribeEvents(); + this.unSubscribeDataBaseShare(); + emitter.off(TIMER_Toggle_FRONT_AND_BACKEND); + } + + private isBigViewChangeHandle() { + this.isPCView = this.computedIsPCView(); + } + + private computedIsPCView(): boolean { + return this.isBigView === BreakPoint.LG; + } + + /** + * Subscribe events, used to refresh the page + */ + private async subscribeEvents(): Promise { + // Create a subscriber and subscribe to time-change event. + LogUtil.info(TAG, 'createSubscriber'); + this.timeSubscriber = await CommonEventManager.createSubscriber(TIME_SUBSCRIBE_INFO); + if (!this.timeSubscriber) { + return; + } + CommonEventManager.subscribe(this.timeSubscriber, (error, data) => { + if (error) { + LogUtil.error(TAG, 'time changed error:' + JSON.stringify(error)); + } else { + if (data && (data.event.indexOf('usual.event.TIME_CHANGED') !== -1 || data.event.indexOf('usual.event.TIMEZONE_CHANGED') !== -1)) { + this.refreshLeftTime(true); + } else { + this.refreshLeftTime(false); + } + setTimeout(async () => { + await this.checkTriggerTime(); + }, 1000); + } + }); + } + + async checkTriggerTime(): Promise { + try { + let ringTimeInMs = await TimerManager.getTriggerTime(); + let ringTimeId = await TimerManager.getTriggerTimeID(); + const currentTime = new Date().getTime(); + if (ringTimeInMs === TRIGGER_TIME_NEVER || currentTime <= ringTimeInMs) { + return; + } + const isFiring = await AlarmStateManager.isFiring(); + const firingAlarmId = await AlarmStateManager.getAlarmId(); + if (isFiring && firingAlarmId === ringTimeId) { + return; + } + LogUtil.info(TAG, `checkTriggerTime begin ${ringTimeId} ${ringTimeInMs} ${currentTime}`); + await AlarmManager.closeNoRepeatAlarmsDB(ringTimeInMs); + emitter.emit({ + eventId: EVENT_ID_ALARM_RING, + priority: emitter.EventPriority.IMMEDIATE, + }); + } catch (err) { + let message = (err as BusinessError).message; + let errCode = (err as BusinessError).code; + LogUtil.error(TAG, `checkTriggerTime error: code: ${errCode}, message: ${message} `); + } + + } + + /** + * Subscribe DataBaseShare, used to refresh the page + */ + private async subscribeDataBaseShare(): Promise { + LogUtil.info(TAG, 'subscribeDataBaseShare'); + // try { + // dataShare.createDataShareHelper((GlobalContext.getContext().getObject('clockContext') as Context), this.uri) + // .then((data: dataShare.DataShareHelper) => { + // LogUtil.info(TAG, 'createDataShareHelper succeed'); + // this.dataShareHelper = data; + // this.dataShareHelper.on('dataChange', this.uri, () => { + // LogUtil.info(TAG, 'refreshAlarmData'); + // this.isDateTypeClock = !this.isDateTypeClock + // this.refreshAlarmData(); + // }); + // }) + // .catch((err: BusinessError) => { + // LogUtil.error(TAG, `createDataShareHelper error: code: ${err.code}, message: ${err.message} `); + // }); + // } catch (err) { + // let message = (err as BusinessError).message; + // let errCode = (err as BusinessError).code; + // LogUtil.error(TAG, `createDataShareHelper error: code: ${errCode}, message: ${message} `); + // } + } + + /** + * Unsubscribe DataBaseShare + */ + private unSubscribeDataBaseShare(): void { + // this.dataShareHelper!.off('dataChange', this.uri, () => { + LogUtil.info(TAG, 'unSubscribeDataBaseShare'); + // }); + } + + /** + * Unsubscribe events + */ + private unSubscribeEvents(): void { + LogUtil.info(TAG, 'unSubscribeEvents'); + if (!this.timeSubscriber) { + return; + } + try { + CommonEventManager.unsubscribe(this.timeSubscriber, error => { + if (error) { + LogUtil.error(TAG, 'Unsubscribe to time-change event failed because: ', JSON.stringify(error)); + } else { + LogUtil.info(TAG, 'Unsubscribe to time-change event successfully!'); + } + }); + } catch (error) { + LogUtil.error(TAG, 'Unsubscribe to time-change event error'); + } + } + + async refreshAlarmData(): Promise { + this.alarmClockListFromEntry = await AlarmManager.getAllAlarmsToShow(); + } + + /** + * Obtains the displayed string resource from the last ringing time. + * + * @return The string resource corresponding to the latest ringing time. + */ + private getLeftTimesToAlarmTips(): Resource { + if (this.leftTimeToRingTime === NO_ALARM_ENABLED) { + return $r('app.string.no_alarms_on'); + } + return this.leftTimeToRingTime === LESS_THAN_ONE_MINUTE_FLAG + ? $r('app.string.less_than_one_minute') + : $r('app.string.ring_in', this.leftTimeToRingTime); + } + + private onScrollParent(yOffset: number): void { + if (yOffset === 0 || yOffset > 0 && this.isClockHide || yOffset < 0 && !this.isClockHide) { + return; + } + const offset = this.scrollerForScroll.currentOffset(); + LogUtil.info(TAG, `yOffset=${yOffset} current=${offset.yOffset}`); + if (yOffset > 0) { + this.isClockHide = true; + } else { + this.isClockHide = false; + } + } + + private onScrollStopParent(): void { + const offset = this.scrollerForScroll.currentOffset(); + LogUtil.info(TAG, `onScrollStop current=${offset.yOffset} ${this.isClockHide}`); + if (this.isClockHide) { + this.scrollerForScroll.scrollEdge(Edge.End); + } else { + this.scrollerForScroll.scrollEdge(Edge.Start); + } + } + + private pcRightControlMenuClickHandle = (menuItem: ICustomMenuItem, alarmItem: ObservedAlarmInfo) => { + if (menuItem.key === 'del') { + this.deleteAlarmId = alarmItem.id as string; + this.dealAlarmDelete(alarmItem); + } + } + + @Builder + pcRightControlMenuBuilder(alarmItem: ObservedAlarmInfo) { + Menu() { + ForEach(this.itemContextMenuData, (item: ICustomMenuItem) => { + MenuItem({ content: item.content }) + .onClick(() => this.pcRightControlMenuClickHandle(item, alarmItem)) + }, (item: MenuItemOptions, index: number) => String(index)) + } + .shadow(ShadowStyle.OUTER_DEFAULT_MD) + } + + @Builder + buildClock(): void { + Row() { + Clock({ + getOrientaion: this.getOrientaion, + isPortraitOrientation: this.isPortraitOrientation, + showBoth: this.isPCView + }) + } + .transition(TransitionEffect.opacity(1)) + .width('100%') + .margin({ + top: this.isPCLg() ? '' : (this.isPortraitOrientation || this.foldAbleScreen === 1 ? $r('app.float.clock_shadow_above_space_32') : '10vp'), + bottom: this.isPCLg() ? '' : (this.isPortraitOrientation || this.foldAbleScreen === 1 ? $r('app.float.clock_margin_vertical') : $r('app.float.clock_landscape_no_height')), + }) + .focusable(false) + } + + @Builder + buildNoAlarms(): void { + Row() { + Flex({ justifyContent: FlexAlign.Center }) { + Text($r('app.string.no_alarms')) + .fontSize($r('sys.float.Body_M')) + .fontColor(this.isPCView ? $r('sys.color.font_secondary') : $r('sys.color.font_tertiary')) + .fontWeight(FontWeight.Regular) + .margin(this.isBigView === BreakPoint.MD ? { top: '99vp' } : {}) + } + } + .renderGroup(true) + .width('100%') + .flexGrow(FLEX_GROW_FILL_FULL) + .margin({ bottom: this.isPCView ? $r('app.float.date_info_margin_bottom') : 0 }) + .focusable(false) + } + + @Builder + buildLeftTimeTips(): void { + Flex({ justifyContent: FlexAlign.Center }) { + Text(this.getLeftTimesToAlarmTips()) + .fontColor(this.isPCView ? $r('sys.color.font_secondary') : $r('sys.color.icon_primary')) + .fontSize(this.isPCView ? $r('sys.float.Subtitle_S') : $r('app.float.clock_text_size')) + .lineHeight(this.isPCView ? 0 : $r('app.float.left_time_line_height')) + .fontWeight(this.isPCView ? FontWeight.Regular : FontWeight.Medium) + .margin({ top: !this.isPortraitOrientation && this.foldAbleScreen === 1 ? '10vp' : 0 }) + } + .renderGroup(true) + .width('100%') + .padding({ + bottom: this.isPCView ? $r('app.float.date_info_margin_bottom') : $r('app.float.left_time_tips_margin_bottom') + }) + } + + @Builder + ManageAlarmClockBuilder(alarmItem: ObservedAlarmInfo | undefined) { + Stack() { + if (alarmItem) { + ManageAlarmClock({ + alarmInfoParam: alarmItem as ObservedAlarmInfo, + }); + } else { + ManageAlarmClock({}); + } + } + .width('100%') + } + + private alarmRestart(item: ObservedAlarmInfo, alarmClockInfo: ObservedAlarmInfo, + alarmClockIndex: number, enabled?: boolean) { + this.alarmClockList[alarmClockIndex].enabled = enabled! + const repeatAlarmShow = TimeUtil.getAlarmTime(alarmClockInfo); + const newDate = new Date(repeatAlarmShow); + const repeatAlarmDate = newDate.setDate(newDate.getDate() + 1); + const repeatAlarmDay = new Date(repeatAlarmDate); + const todayDate = new Date(); + const tomorrowDate = todayDate.setDate(todayDate.getDate() + 1); + const tomorrow = new Date(tomorrowDate); + const tomorrowDay = tomorrow.getDate(); + const tomorrowMonth = tomorrow.getMonth() + 1; + const day = repeatAlarmDay.getDate(); + let month = repeatAlarmDay.getMonth() + 1; + if (month === 13) { + month = 1; + } + if (tomorrowDay === day && tomorrowMonth === month) { + item.reStart = ResourceManager.getStringById($r('app.string.open_tomorrow').id); + } else { + item.reStart = ResourceManager.getStringById($r('app.string.will_open').id) + .replace(FIRST_PARAM, JSON.stringify(month)) + .replace(SECOND_PARAM, JSON.stringify(day)); + } + } + + private async onRightBtn(item: ObservedAlarmInfo, enabled?: boolean) { + await ResourceManager.preloadStringResources(REPEAT_TAG_ID_LIST_IN_ZH); + item.enabled = enabled!; + this.alarmClockList.forEach((alarmClockInfo, alarmClockIndex) => { + if (alarmClockInfo.id === item.id) { + this.alarmClockList[alarmClockIndex].enabled = enabled! + } + }) + !enabled && AlarmManager.clearSnoozedData(item, true); + const alarmTitle = await AlarmManager.getDefaultAlarmTitle(item.id as string, item.hour); + if (alarmTitle !== '') { + item.title = alarmTitle; + } + LogUtil.info(TAG, 'updateAlarm', JSON.stringify(item)) + await AlarmManager.updateAlarm(item); + item.enabled && this.openAlarmShowToast(item.id as string); + this.refreshLeftTime(false); + const isFiring = await AlarmStateManager.isFiring(); + + if (isFiring) { + const firingAlarmId = await AlarmStateManager.getAlarmId(); + if (firingAlarmId.toString() !== item.id) { + !enabled && NotificationUtil.cancel(item.id as string); + } + } else { + !enabled && NotificationUtil.cancel(item.id as string); + } + } + + @Builder + buildAlarmListItem(item: ObservedAlarmInfo): void { + Stack() { + if (this.isPCView) { + // 子容器获焦后,父容器不可再获焦,为列表可获焦,改为兄弟组件 + Column() { + } + .width($r('app.float.list_item_focus_width')) + .height($r('app.float.list_item_focus_height')) + .onClick(() => { + this.alarmItemClick(item) + }) + } + Column() { + AlarmCard({ + alarmInfo: item, + alarmInfoList: this.alarmClockList, + refreshAlarmClockInfo: this.refreshAlarmClockInfo, + cardType: AlarmCardType.MainPageCard, + isPCView: this.isPCView, + onRightBtnChange: async (enabled?: boolean) => { + this.onRightBtn(item, enabled); + }, + cancelDoubleCardMargin: this.foldAbleScreen === 1 && this.alarmClockList.length > 3, + isDateTypeClock: this.isDateTypeClock + }) + } + .bindContextMenu(this.pcRightControlMenuBuilder(item), ResponseType.RightClick) + .onClick(() => { + this.alarmItemClick(item) + }) + .bindSheet(this.isEditShow && this.editShowItem && + this.editShowItem.id === item.id, this.ManageAlarmClockBuilder(this.editShowItem), { + height: '94.3%', + backgroundColor: $r('sys.color.ohos_id_background_secondary'), + maskColor: $r('sys.color.mask_fourth'), + showClose: false, + dragBar: true, + preferType: SheetType.CENTER, + onAppear: () => { + LogUtil.info(TAG, '==== BindSheet onAppear.' + JSON.stringify(this.editShowItem)); + }, + onDisappear: () => { + this.onDisappear(); + }, + shouldDismiss: () => { + this.shouldDismiss(); + } + }) + } + } + + private onDisappear() { + this.isEditShow = false; + AppStorage.setOrCreate('EditBindSheet', false); + if (this.isTextInputClose == false) { + this.isTextInputClose = true; + AppStorage.setOrCreate('TextInputClose', true); + } else if (this.isTextInputClose == true) { + this.isTextInputClose = false; + AppStorage.setOrCreate('TextInputClose', false); + } + LogUtil.info(TAG, '==== BindSheet onDisappear.'); + } + + private shouldDismiss() { + if (this.isDismiss === false) { + this.isDismiss = true; + AppStorage.setOrCreate('ShouldDismiss', true); + } else if (this.isDismiss === true) { + this.isDismiss = false; + AppStorage.setOrCreate('ShouldDismiss', false); + } + } + + private alarmItemClick(item: ObservedAlarmInfo) { + LogUtil.info(TAG, '==== edit alarm:', JSON.stringify(item)); + animateTo({ + curve: curves.interpolatingSpring(0, 1, 328, 36) + }, () => { + this.isEditShow = false; + AppStorage.setOrCreate('EditBindSheet', false); + this.isEditShow = true; + AppStorage.setOrCreate('EditBindSheet', true); + this.editShowItem = item; + }) + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.ENTER_ALARM_EDIT_PAGE) + } + + private async dealAlarmDelete(alarmInfo: ObservedAlarmInfo): Promise { + LogUtil.info(TAG, 'dealAlarmDelete alarmId: ' + alarmInfo.id); + let delIndex = -1; + this.alarmClockList.forEach((item: ObservedAlarmInfo, index: number) => { + if (item.id === alarmInfo.id) { + delIndex = index; + } + }) + LogUtil.info(TAG, 'dealAlarmDelete alarmId index: ' + delIndex); + if (delIndex !== -1) { + this.alarmClockList.splice(delIndex, 1); + } + alarmInfo.enabled = false; + const isFiring = await AlarmStateManager.isFiring(); + const firingAlarmId = await AlarmStateManager.getAlarmId(); + LogUtil.info(TAG, `isFiring: ${isFiring}, firingAlarmId: ${firingAlarmId}, deleteAlarmId: ${alarmInfo.id}`); + if (isFiring && String(firingAlarmId) === alarmInfo.id) { + await AlarmServiceManager.stopAlarm(alarmInfo); + await CommonUtil.terminateAlarmService(); + } else { + await AlarmManager.clearSnoozedData(alarmInfo, true); + NotificationUtil.cancel(alarmInfo.id as string); + } + let deletedAlarmSet: Set = new Set(); + deletedAlarmSet.add(alarmInfo.id as string); + await AlarmManager.removeAlarms(deletedAlarmSet); + this.refreshLeftTime(false); + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.SINGLE_ALARM_SWIPE_LEFT_DEL) + } + + @Styles + buttonStyle() + { + .borderRadius($r('app.float.swipe_image_height_and_width')) + .height($r('app.float.swipe_button_height_and_width')) + .width($r('app.float.swipe_button_height_and_width')) + .margin($r('app.float.swipe_margin_size')) + } + + @Builder + renderSwipeAction(alarmInfo: ObservedAlarmInfo): void { + Row() { + Stack() { + Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Image($r('app.media.ic_delete_grey')) + .width($r('app.float.swipe_delete_head_width')) + .rotate({ + x: 0, + y: 0, + z: 1, + centerX: '100%', + centerY: '100%', + angle: this.headAngle, + }) + } + .width($r('app.float.swipe_button_height_and_width')) + .height($r('app.float.swipe_button_height_and_width')) + } + .buttonStyle() + .scale({ x: this.swipeScale, y: this.swipeScale }) + .backgroundColor($r('app.color.swipe_delete_button_color')) + .onClick(() => { + setTimeout(() => { + this.deleteAlarmId = alarmInfo.id as string; + if (this.alarmClockList.length > 3 && this.foldAbleScreen === 1) { + this.dealAlarmDelete(alarmInfo); + } else { + animateTo({ + duration: 350, + curve: Curve.Friction, + }, () => { + this.dealAlarmDelete(alarmInfo); + }) + } + }, 150) + }) + } + .id('id_deleteAlarm_img') + .padding(this.foldAbleScreen === 1 && this.alarmClockList.length < 4 ? { + left: 0, + top: $r('app.float.swipe_margin_size'), + right: $r('app.float.swipe_padding_size'), + bottom: $r('app.float.swipe_margin_size') + } : $r('app.float.swipe_margin_size')) + .onAreaChange((oldValue: Area, newValue: Area) => { + let wid = newValue.width; + this.headAngle = (Number(wid) - DELETE_SWIPE_HEAD_WIDTH) / DELETE_SWIPE_HEAD_WIDTH * ANGLE_RADIO; + if (this.headAngle <= 0) { + this.headAngle = 0; + } + if (this.headAngle >= MAX_HEAD_ANGLE) { + this.headAngle = MAX_HEAD_ANGLE; + } + if (this.headAngle > BODY_HEAD_CRITICAL_ANGLE) { + this.swipeScale = 1.05; + this.bodyAngle = (Number(wid) - DELETE_SWIPE_BODY_WIDTH) / DELETE_SWIPE_BODY_WIDTH * ANGLE_RADIO; + } else { + this.bodyAngle = 0; + this.swipeScale = 1; + } + if (this.bodyAngle > MAX_BODY_ANGLE) { + this.bodyAngle = MAX_BODY_ANGLE; + this.swipeScale = 1.05; + } + this.bodyAngle = (-1) * this.bodyAngle; + }) + .justifyContent(FlexAlign.SpaceEvenly) + } + + private swipeAction(item: ObservedAlarmInfo) { + setTimeout(() => { + this.deleteAlarmId = item.id as string; + if (this.alarmClockList.length > 3 && this.foldAbleScreen === 1) { + this.dealAlarmDelete(item); + } else { + animateTo({ + duration: 350, + curve: Curve.Friction, + }, () => { + this.dealAlarmDelete(item); + }) + } + }, 350); + } + + private keyEvent(e: KeyEvent, item: ObservedAlarmInfo) { + if (e.keyText === 'KEYCODE_TAB' || e.keyText === 'KEYCODE_DPAD_DOWN' || e.keyText === 'KEYCODE_DPAD_UP') { + const listIndex = this.alarmClockList.findIndex(i => item.id === i.id) + if (listIndex !== this.listIndexCache) { + this.listIndexCache = listIndex + // 38 视口区最后显示一半的列表向上滚动的距离,94.2每条item滚动高度 + this.scrollerForScroll.scrollTo({ + xOffset: 0, + yOffset: listIndex > 3 ? (listIndex - 4) * 94.2 + 38 : 0, + animation: { duration: 100, curve: Curve.Linear } + }); + } + } + } + + @Builder + alarmListItem() { + ForEach(this.alarmClockList, (item: ObservedAlarmInfo) => { + ListItem() { + Row() { + this.buildAlarmListItem(item); + } + } + .transition( + TransitionEffect.asymmetric( + TransitionEffect.IDENTITY, + TransitionEffect.scale({ x: 1, y: 1 }) + .animation({ curve: Curve.Friction, duration: 0 }) + .combine( + TransitionEffect.OPACITY.animation({ curve: Curve.Sharp, duration: 0 }) + ) + ) + ) + .margin(this.alarmClockList.length === 1 || this.isPCView ? 0 : { bottom: $r('app.float.card_list_margin') }) + .clip(this.isPCLg() ? false : true) + .zIndex(this.deleteAlarmId === item.id ? 0 : 1) + .borderRadius($r('sys.float.corner_radius_level10')) + .swipeAction( + { + edgeEffect: SwipeEdgeEffect.None, + end: { + builder: (): void => this.renderSwipeAction(item), + onAction: () => { + this.swipeAction(item); + }, + onEnterActionArea: () => { + LogUtil.info(TAG, 'List Swipe EnterDeleteArea'); + }, + onExitActionArea: () => { + LogUtil.info(TAG, 'List Swipe ExitDeleteArea'); + } + }, + } + ) + .onKeyEvent((e: KeyEvent) => { + this.keyEvent(e, item); + }) + }, (item: ObservedAlarmInfo) => JSON.stringify(item)) + } + + @Builder + alarmListItemPc() { + ForEach(this.alarmClockList, (item: ObservedAlarmInfo) => { + ListItem() { + Row() { + this.buildAlarmListItem(item); + } + } + .transition( + TransitionEffect.asymmetric( + TransitionEffect.IDENTITY, + TransitionEffect.scale({ x: 1, y: 1 }) + .animation({ curve: Curve.Friction, duration: 0 }) + .combine( + TransitionEffect.OPACITY.animation({ curve: Curve.Sharp, duration: 0 }) + ) + ) + ) + .margin(this.alarmClockList.length === 1 || this.isPCView ? 0 : { bottom: $r('app.float.card_list_margin') }) + .clip(this.isPCLg() ? false : true) + .zIndex(this.deleteAlarmId === item.id ? 0 : 1) + .onKeyEvent((e: KeyEvent) => { + this.keyEvent(e, item); + }) + }, (item: ObservedAlarmInfo) => JSON.stringify(item)) + } + + @Builder + listAlarm(): void { + if (this.isPCView) { + this.alarmListItemPc() + } else if (!this.isPCView) { + this.alarmListItem() + } + if (!this.isPCView) { + ListItem() { + } + .transition( + TransitionEffect.asymmetric( + TransitionEffect.IDENTITY, + TransitionEffect.scale({ x: 1, y: 1 }) + .animation({ curve: Curve.Friction, duration: 0 }) + .combine( + TransitionEffect.OPACITY.animation({ curve: Curve.Sharp, duration: 0 }) + ) + ) + ) + .height($r('app.float.empty_alarm_list_item')) + } + if (this.alarmClockList.length > 3 && this.foldAbleScreen !== 1 && !this.isPCView) { + ListItem() { + } + .transition( + TransitionEffect.asymmetric( + TransitionEffect.IDENTITY, + TransitionEffect.scale({ x: 1, y: 1 }) + .animation({ curve: Curve.Friction, duration: 0 }) + .combine( + TransitionEffect.OPACITY.animation({ curve: Curve.Sharp, duration: 0 }) + ) + ) + ) + } + } + + @Builder + buildAlarmList(): void { + List({ scroller: this.scrollerForList }) { + this.listAlarm() + } + .renderGroup(true) + .padding(this.foldAbleScreen === 1 ? { + left: $r('app.float.flod_padding_left'), + right: $r('app.float.flod_padding_right') + } : {}) + .lanes(this.alarmClockList.length > 3 && this.foldAbleScreen === 1 ? 2 : 1, $r('app.float.lans_list_item_10')) + .margin({ + left: $r('app.float.card_margin_start'), + right: $r('app.float.card_margin_start') + }) + .clip(false) + .edgeEffect(EdgeEffect.Spring) + .nestedScroll({ + scrollForward: NestedScrollMode.PARENT_FIRST, + scrollBackward: NestedScrollMode.SELF_FIRST + }) + } + + @Builder + buildAlarmPcList(): void { + List({ space: 12 }) { + this.listAlarm() + } + .height(this.alarmClockList.length >= 5 ? '100%' : '') + .padding(this.isPCView ? { + right: $r('app.float.card_margin_start') + } : (this.isBigView === BreakPoint.MD ? this.isPortraitOrientation ? { + left: $r('app.float.flod_padding_left'), + right: $r('app.float.flod_padding_right') + } : this.foldAbleScreen === 1 ? + this.getOrientaion === this.isFoldCross ? { + left: $r('app.float.flod_padding_left'), + right: $r('app.float.flod_padding_right') + } : + { + left: $r('app.float.fold_cross_padding'), + right: $r('app.float.fold_cross_padding'), + } : {} : {})) + .margin(this.isPCView ? { left: $r('app.float.card_margin_start'), right: 0 } : { + left: $r('app.float.card_margin_start'), + right: $r('app.float.card_margin_start') + }) + .clip(false) + } + + @Builder + buildTipsAndList() { + if (this.alarmClockList.length === 0) { + if (this.leftTimeToRingTime) { + this.buildNoAlarms() + } + } else { + Stack({ alignContent: Alignment.Start }) { + Flex({ direction: FlexDirection.Column }) { + if (this.leftTimeToRingTime) { + CommonGrid() { + this.buildLeftTimeTips() + } + } + } + + Flex({ direction: FlexDirection.Column }) { + CommonGrid() { + this.buildAlarmList() + } + } + .padding({ top: $r('app.float.empty_alarm_list_item') }) + } + .width('100%') + .height('97%') + } + } + + @Builder + buildPortrait() { // 纵向 + Stack({ alignContent: Alignment.Bottom }) { + Scroll(this.scrollerForScroll) { + Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start }) { + Row() { + this.buildClock() + } + + this.buildTipsAndList(); + } + } + .scrollBar(BarState.Off) + .onScroll((_: number, yOffset: number) => this.onScrollParent(yOffset)) + .onScrollStop(() => this.onScrollStopParent()) + .align(Alignment.Top) + .height('100%') + .width('100%') + + AddButton({ + alarmFlag: this.isAlarm, + onButtonClick: () => { + animateTo({ + curve: curves.interpolatingSpring(0, 1, 328, 36) + }, () => { + this.isNewShow = false; + AppStorage.setOrCreate('NewBindSheet', false); + this.isNewShow = true; + AppStorage.setOrCreate('NewBindSheet', true); + }) + } + }) + .bindSheet($$this.isNewShow, this.ManageAlarmClockBuilder(undefined), { + height: '94.3%', + backgroundColor: $r('sys.color.ohos_id_background_secondary'), + maskColor: $r('sys.color.mask_fourth'), + showClose: false, + preferType: SheetType.CENTER, + dragBar: true, + onAppear: () => { + LogUtil.info(TAG, 'BindSheet onAppear.'); + }, + onDisappear: () => { + LogUtil.info(TAG, 'BindSheet onDisappear.'); + }, + shouldDismiss: () => { + if (this.isDismiss == false) { + this.isDismiss = true; + AppStorage.setOrCreate('ShouldDismiss', true); + } else if (this.isDismiss == true) { + this.isDismiss = false; + AppStorage.setOrCreate('ShouldDismiss', false); + } + } + }) + .transition(TransitionEffect.opacity(1)) + .visibility(this.isAlarmClockVisible ? Visibility.Visible : Visibility.Hidden) + } + .height('100%') + .width('100%') + } + + @Builder + buildLandscape() { // 横向 + Row() { + Column() { + this.buildClock() + if (this.alarmClockList.length === 0) { + if (this.leftTimeToRingTime) { + this.buildNoAlarms() + } + } else { + if (this.leftTimeToRingTime) { + CommonGrid() { + this.buildLeftTimeTips() + } + } + } + } + .width('40%') + .padding({ + left: 0, + }) + + Rect() + .fill($r('sys.color.comp_divider')) + .width('1px') + .height($r('app.float.line_height')) + .margin({ left: $r('app.float.rect_margin_left') }) + Stack({ alignContent: Alignment.Bottom }) { + Scroll(this.scrollerForScroll) { + this.buildAlarmList() + } + .height('100%') + .edgeEffect(EdgeEffect.Spring) + .align(this.isPCView ? Alignment.Center : Alignment.Top) + + AddButton({ + alarmFlag: this.isAlarm, + onButtonClick: () => { + animateTo({ + curve: curves.interpolatingSpring(0, 1, 328, 36) + }, () => { + this.isNewShow = false; + AppStorage.setOrCreate('NewBindSheet', false); + this.isNewShow = true; + AppStorage.setOrCreate('NewBindSheet', true); + }) + } + }) + .bindSheet($$this.isNewShow, this.ManageAlarmClockBuilder(undefined), { + height: '94.3%', + backgroundColor: $r('sys.color.ohos_id_background_secondary'), + maskColor: $r('sys.color.mask_fourth'), + showClose: false, + dragBar: true, + preferType: SheetType.CENTER, + onAppear: () => { + LogUtil.info(TAG, 'BindSheet onAppear.'); + }, + onDisappear: () => { + LogUtil.info(TAG, 'BindSheet onDisappear.'); + }, + shouldDismiss: () => { + if (this.isDismiss == false) { + this.isDismiss = true; + AppStorage.setOrCreate('ShouldDismiss', true); + } else if (this.isDismiss == true) { + this.isDismiss = false; + AppStorage.setOrCreate('ShouldDismiss', false); + } + } + }) + .transition(TransitionEffect.opacity(1)) + .visibility(this.isAlarmClockVisible ? Visibility.Visible : Visibility.Hidden) + } + .width(this.deviceType === 'tablet' ? ('calc(60% - 19vp)') : '60%') + } + .width('100%') + + // .justifyContent(this.isPCView && (this.alarmClockList.length === 0) ? FlexAlign.Center: FlexAlign.Start) + } + + @Builder + pcListAndAddBuilder() { + Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row }) { + if (this.alarmClockList.length) { + Scroll(this.scrollerForScroll) { + this.buildAlarmPcList() + } + .height('76%') + .edgeEffect(EdgeEffect.Spring) + .align(this.isPCView ? Alignment.Center : Alignment.Top) + .flexBasis(50) + .flexGrow(10) + } + Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row, justifyContent: FlexAlign.Center }) { + AddButton({ + alarmFlag: this.isAlarm, + buttonSize: ButtonSize.LARGER, + onButtonClick: (): void => { + if (this.isPCView) { + this.pcAlarmFormPopupVisible = true; + } else { + this.isNewShow = true; + AppStorage.setOrCreate('NewBindSheet', true); + } + }, + }) + .transition(TransitionEffect.opacity(1)) + .visibility(this.isAlarmClockVisible ? Visibility.Visible : Visibility.Hidden) + .bindPopup(this.pcAlarmFormPopupVisible, { + builder: PCAlarmFormPopupBuilder({ + isPCView: this.isPCView, + visible: this.pcAlarmFormPopupVisible, + onConfirm: () => { + this.pcAlarmFormPopupVisible = false; + }, + onCancel: () => { + this.pcAlarmFormPopupVisible = false; + }, + }), + placement: Placement.Left, + popupColor: $r('app.color.component_ultra_thick_panel'), + enableArrow: true, + onStateChange: (e) => { + this.getStateChange(e); + } + }) + } + .flexBasis(50) + .flexGrow(1) + .padding({ + left: $r('app.float.pc_padding'), + right: $r('app.float.pc_padding') + }) + } + .renderGroup(true) + .width(this.isPCView && (this.alarmClockList.length === 0) ? '14%' : '60%') + } + + getStateChange(e: visibleType): void { + if (!e.isVisible) { + this.pcAlarmFormPopupVisible = false; + } + if (this.isTextInputClose === false) { + this.isTextInputClose = true; + AppStorage.setOrCreate('TextInputClose', true); + } else if (this.isTextInputClose === true) { + this.isTextInputClose = false; + AppStorage.setOrCreate('TextInputClose', false); + } + } + + @Builder + buildPC() { + Row() { + Column() { + this.buildClock() + if (this.alarmClockList.length === 0) { + if (this.leftTimeToRingTime) { + this.buildNoAlarms() + } + } else { + if (this.leftTimeToRingTime) { + CommonGrid() { + this.buildLeftTimeTips() + } + } + } + } + .width(this.alarmClockList.length === 0 ? '86%' : '40%') + .padding({ + left: this.alarmClockList.length === 0 ? $r('app.float.tool_bar_width_1') : $r('app.float.tool_bar_width'), + }) + + this.pcListAndAddBuilder(); + } + .width('100%') + .height('100%') + } + + build() { + if (this.isPCView) { + this.buildPC() + } else { + if (this.isPortraitOrientation || this.foldAbleScreen === 1) { + this.buildPortrait(); + } else { + this.buildLandscape(); + } + } + } +} diff --git a/feature/alarmclock/src/main/ets/utils/AlarmCardUtil.ets b/feature/alarmclock/src/main/ets/utils/AlarmCardUtil.ets new file mode 100644 index 0000000..287bcb7 --- /dev/null +++ b/feature/alarmclock/src/main/ets/utils/AlarmCardUtil.ets @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import resmgr from '@ohos.resourceManager'; +import { + FormUtil, + GlobalContext, + LogUtil, + ResourceManager, + TIME_TAG_ID_LIST_IN_ZH, + TimeUtil, + AlarmStateManager, + AlarmManager, + AlarmInfo +} from '@hmos/common'; + + +const TAG: string = 'AlarmCardUtil'; +const TRIGGER_TIME_NEVER: number = -1; +const HOUR_OF_DAY: number = 24; +const MINUTES_OF_THIRTY: number = 30; +const MINUTES_OF_TEN: number = 10; +const ONE_DAY: number = 1; +const ZERO_DAY: number = 0; +const ZERO_HOUR: number = 0; +const ZERO_MINUTE: number = 0; +const ZERO_SECOND: number = 0; +const ALARM_CARD: string = 'ALARM_CARD'; + +/** + * Alarm Clock Card Util + * + * @since 2023-06-08 + */ +export class AlarmCardUtil { + /** + * notify AlarmCard show Ring when alarmClock is ringing + * + * @returns + */ + public static async notifyAlarmCardRingUpdate(): Promise { + const ringingTips = await ResourceManager.getStringByIdAsync($r('app.string.fa_ringing').id); + const firingAlarmId = await AlarmStateManager.getAlarmId(); + const firingAlarm = await AlarmManager.getLatestAlarmFromDb(firingAlarmId) as AlarmInfo; + let timeStemp = TimeUtil.getAlarmTime(firingAlarm) + LogUtil.info(TAG, `firingAlarm: ${JSON.stringify(firingAlarm)}, timeStemp=${timeStemp}`); + const formData: Record = { + 'nextAlarmTime': ringingTips, + 'noAlarms': false, + 'isRinging': true, + 'remainingTime': '', + 'ringingTimestamp': timeStemp + }; + FormUtil.notifyClockCardChanged(formData, ALARM_CARD); + } + + /** + * notify AlarmCard Time Update when clock modify + *-大于等于24小时:“n天后响铃” + *-大于等于30分钟,小于24小时:“n小时内响铃” (向上取整,例:4h32min显示5小时内响铃) + *- 大于等于10分钟,小于30分钟:“30分钟内响铃” + *-小于10分钟:“即将响铃” + * @returns + */ + public static async notifyAlarmCardTimeUpdate(): Promise { + let ringTimeInMs: number = await TimeUtil.getNextAlarmTime(); + const isFiring = await AlarmStateManager.isFiring(); + LogUtil.info(TAG, `notifyAlarmCardTimeUpdate ringTimeInMs:${ringTimeInMs}, isFiring=${isFiring}`); + if (isFiring) { + return AlarmCardUtil.notifyAlarmCardRingUpdate(); + } + if (ringTimeInMs === TRIGGER_TIME_NEVER) { + const nextAlarmTime: ResourceStr = $r('app.string.fa_no_alarms'); + let formData: Record = { + 'nextAlarmTime': nextAlarmTime, + 'noAlarms': true, + 'isRinging': false, + 'remainingTime': '', + 'ringingTimestamp': 0 + }; + LogUtil.info(TAG, 'notifyAlarmCardTimeUpdate formData:' + JSON.stringify(formData)); + FormUtil.notifyClockCardChanged(formData, ALARM_CARD); + } else { + await ResourceManager.preloadStringResources(TIME_TAG_ID_LIST_IN_ZH); + const nextAlarmTime = TimeUtil.getFormattedTime(ringTimeInMs); + const currentDate = new Date(); + const leftTimeInMs = ringTimeInMs - currentDate.getTime(); + if (leftTimeInMs < 0) { + return; + } + const leftTime = new Date(leftTimeInMs); + const leftMinutes = leftTime.getUTCMinutes(); + const leftSeconds = leftTime.getUTCSeconds(); + const leftHours = leftTime.getUTCHours(); + const leftDays = leftTime.getUTCDate() - ONE_DAY; + LogUtil.info(TAG, `notifyAlarmCardTimeUpdate leftDays=${leftDays}, leftHours=${leftHours}, leftMinutes=${leftMinutes},ringTimeInMs =${ringTimeInMs}, currentDate=${currentDate.getTime()}, leftTimeInMs=${leftTimeInMs}`); + let remainTime: Object; + if (leftDays !== ZERO_DAY) { + let dayNum = leftDays; + remainTime = dayNum > ONE_DAY ? $r('app.string.ring_within_other_days', dayNum) : $r('app.string.ring_within_one_days', dayNum); + } else { + if (leftHours !== ZERO_HOUR) { + let hoursNum = leftHours; + if (leftMinutes !== ZERO_MINUTE) { + hoursNum++ + } else { + if (leftSeconds !== ZERO_SECOND) { + hoursNum++ + } + } + remainTime = hoursNum > ONE_DAY ? $r('app.string.ring_within_other_hours', hoursNum) : $r('app.string.ring_within_one_hours', hoursNum); + + } else { + if (leftMinutes >= MINUTES_OF_THIRTY) { + remainTime = $r('app.string.ring_within_one_hours', 1); + } else if (leftMinutes < MINUTES_OF_TEN) { + remainTime = $r('app.string.ringing_soon'); + } else { + remainTime = $r('app.string.ring_within_minutes', MINUTES_OF_THIRTY); + } + } + } + let formData: Record = { + 'nextAlarmTime': nextAlarmTime, + 'noAlarms': false, + 'isRinging': false, + 'remainingTime': remainTime, + 'ringingTimestamp': ringTimeInMs + }; + LogUtil.info(TAG, 'notifyAlarmCardTimeUpdate formData:' + JSON.stringify(formData)); + FormUtil.notifyClockCardChanged(formData, ALARM_CARD); + } + } +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/ets/utils/NotificationUtil.ets b/feature/alarmclock/src/main/ets/utils/NotificationUtil.ets new file mode 100644 index 0000000..be97087 --- /dev/null +++ b/feature/alarmclock/src/main/ets/utils/NotificationUtil.ets @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import screenLock from '@ohos.screenLock'; +import Want from '@ohos.app.ability.Want'; +import notificationManager from '@ohos.notificationManager' +import notification from '@ohos.notification' +import { + AlarmInfo, + AlarmManager, + AlarmServiceType, + FIRST_PARAM, + GlobalContext, + LogUtil, + ResourceManager, + SECOND_PARAM, + STR_PLACE_HOLDER, + TIMER_NOTICE_ID, + TimeUtil, + WantAgentUtil, + EventName, + EventReportUtil +} from '@hmos/common'; +import { NotificationContentUtil } from '@hmos/common/src/main/ets/utils/NotificationContentUtil' +import image from '@ohos.multimedia.image'; +import { AlarmServiceManager } from '../manager'; +import deviceInfo from '@ohos.deviceInfo' +// import hiSysEvent from '@ohos.hiSysEvent'; + +const deviceType: string = deviceInfo.deviceType; + +interface BundleInfo { + bundle: string +} + +interface ButtonInfo { + title: string, + wantAgent: object, +} + +const TAG = 'NotificationUtil'; + +LogUtil.info(TAG, `deviceType-----${deviceType}`) +const SEPARATOR = ' '; +const BUNDLE_INFO: BundleInfo = { + bundle: 'ohos.samples.clock', +}; + +type NotificationRequest = notificationManager.NotificationRequest; +type ActionButtons = Array; + +/** + * Notification Tool Class + * + * @since 2022-07-27 + */ +export class NotificationUtil { + /** + * Send a notification after the alarm starts + */ + static async publishAlarmReminder(alarmId: number | string, alarmTime: number, alarmTitle: string): Promise { + if (!alarmTime) { + LogUtil.info(TAG, `alarmTime is diabled value is: ${alarmTime}`); + return; + } + const content = TimeUtil.getFormattedTime(alarmTime); + LogUtil.info(TAG, 'publishAlarmReminder alarmId:' + alarmId) + LogUtil.info(TAG, 'publishAlarmReminder content:' + content) + if (deviceType === '2in1') { + await NotificationUtil.publishNotificationPC(alarmId, alarmTitle, content); + } else { + await NotificationUtil.publishNotification(alarmId, content, alarmTitle); + ; + } + } + + /** + * Send notification after delay + */ + static async publishAlarmSnooze(alarmId: number | string, nextAlertTime: number, snoozeDuration: number, + alarmTitle: string): Promise { + if (!alarmId || !nextAlertTime || !alarmTitle) { + LogUtil.error(TAG, 'Execute method publishAlarmSnooze failed, params is incorrect.'); + return; + } + const alarmLabel = await ResourceManager.getStringByIdAsync($r('app.string.alarm_clock').id); + const title = (await ResourceManager.getStringByIdAsync( + $r('app.string.alarm_notify_snooze_title').id)).replace(STR_PLACE_HOLDER, alarmLabel); + const alarmTimeStr = TimeUtil.getFormattedTime(nextAlertTime); + const snoozeTimeInfo = (await ResourceManager.getStringByIdAsync( + $r('app.string.alarm_notify_snooze_context').id + )).replace(STR_PLACE_HOLDER, alarmTimeStr); + const content = (await ResourceManager.getStringByIdAsync( + $r('app.string.format_notify_text').id + )) + .replace(FIRST_PARAM, snoozeTimeInfo) + .replace(SECOND_PARAM, alarmTitle); + LogUtil.info(TAG, 'publishAlarmSnooze alarmId:' + alarmId) + LogUtil.info(TAG, 'publishAlarmSnooze content:' + content) + await NotificationUtil.publishAlarmManual(alarmId, nextAlertTime, snoozeDuration, content); + } + + /** + * Send a notification that the alarm is missed + * + * @param alarmId Alarm id + * @param alarmAlertTime Time when the alarm clock last started + * @param alarmAlertTime Alarm title + */ + static async publishAlarmManual(alarmId: number | string, alarmAlertTime: number, snoozeDuration: number, + alarmTitle: string): Promise { + if (!alarmId || !alarmAlertTime || !alarmTitle) { + LogUtil.error(TAG, 'Execute method publishAlarmManual failed, params is incorrect.'); + return; + } + const title = alarmAlertTime.toString(); + const content = alarmTitle; + const initialTime = snoozeDuration * 60 * 1000; + try { + const largeIcon: image.PixelMap = await NotificationContentUtil.createPixelMap('alarm') + const iconPixelMap: image.PixelMap = await NotificationContentUtil.createPixelMap('alarm_capsule') + const iconsPixelMap: image.PixelMap[] = [await NotificationContentUtil.createPixelMap('alarm_stop')] + const iconsPixelMapNames: string[] = ['alarm_stop'] + const notificationRequest: NotificationRequest = { + id: typeof alarmId === 'string' ? Number(alarmId) : alarmId, + notificationSlotType: notificationManager.SlotType.LIVE_VIEW, + largeIcon: largeIcon, + smallIcon: largeIcon, + content: { + notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_SYSTEM_LIVE_VIEW, + systemLiveView: { + title: title, + text: content, + typeCode: 5, + button: { + icons: iconsPixelMap, + names: iconsPixelMapNames + }, + capsule: { + title: content, + icon: iconPixelMap, + backgroundColor: '#3778EF', + }, + time: { + initialTime: initialTime, + isCountDown: true, + isInTitle: true, + isPaused: false + } + } + }, + extraInfo: { isMute: true }, + wantAgent: await WantAgentUtil.getMainPageWantAgent() + }; + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, `publishAlarmManual success: ${JSON.stringify(notificationRequest)}`) + } catch (error) { + LogUtil.error(TAG, `publishAlarmManual failed : ${JSON.stringify(error)}`); + } + } + + static async publishAlarmMissed(alarmId: number | string, alarmAlertTime: number, + alarmTitle: string): Promise { + if (!alarmId || !alarmAlertTime || !alarmTitle) { + LogUtil.error(TAG, 'Execute method publishAlarmMissedPC failed, params is incorrect.'); + return; + } + const alarmLabel = await ResourceManager.getStringByIdAsync($r('app.string.alarm_clock').id); + const title = (await ResourceManager.getStringByIdAsync( + $r('app.string.alarm_notify_miss_label').id)).replace(STR_PLACE_HOLDER, alarmLabel); + const dayOfWeek = TimeUtil.getDayOfWeekByDate(new Date(alarmAlertTime)); + const alarmTimeStr = `${dayOfWeek}${SEPARATOR}${TimeUtil.getFormattedTime(alarmAlertTime)}`; + const content = (await ResourceManager.getStringByIdAsync( + $r('app.string.format_notify_text').id + )) + .replace(FIRST_PARAM, alarmTimeStr) + .replace(SECOND_PARAM, alarmTitle); + LogUtil.info(TAG, 'publishAlarmMissed alarmId:' + alarmId) + LogUtil.info(TAG, 'publishAlarmMissed content:' + content) + try { + const largeIcon: image.PixelMap = await NotificationContentUtil.createPixelMap('alarm') + const notificationRequest: NotificationRequest = { + id: typeof alarmId === 'string' ? Number(alarmId) : alarmId, + notificationSlotType: notificationManager.SlotType.LIVE_VIEW, + largeIcon: largeIcon, + smallIcon: largeIcon, + content: { + notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_SYSTEM_LIVE_VIEW, + systemLiveView: { + title: title, + text: content, + typeCode: 5, + } + }, + extraInfo: { isMute: true }, + wantAgent: await WantAgentUtil.getMainPageWantAgent(alarmId, true) + }; + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, `publishAlarmMissed success: ${JSON.stringify(notificationRequest)}`) + } catch (error) { + LogUtil.error(TAG, `publishAlarmMissed failed : ${JSON.stringify(error)}`); + } + } + + private static async publishNotification(alarmId: number | string, title: string, content: string, + isSnooze?: boolean): Promise { + try { + let alarmInfo: AlarmInfo = ((GlobalContext.getContext() + .getObject('abilityWant') as Want)?.parameters?.alarmInfo) as AlarmInfo; + const largeIcon: image.PixelMap = await NotificationContentUtil.createPixelMap('alarm') + const iconPixelMap: image.PixelMap = await NotificationContentUtil.createPixelMap('alarm_capsule') + const iconsPixelMap: image.PixelMap[] = [ + await NotificationContentUtil.createPixelMap('alarm_sleep'), + await NotificationContentUtil.createPixelMap('alarm_stop') + ] + const iconsPixelMapNames: string[] = ['alarm_sleep', 'alarm_stop'] + const notificationRequest: NotificationRequest = { + id: typeof alarmId === 'string' ? Number(alarmId) : alarmId, + notificationSlotType: notificationManager.SlotType.LIVE_VIEW, + largeIcon: largeIcon, + smallIcon: largeIcon, + content: { + notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_SYSTEM_LIVE_VIEW, + systemLiveView: { + title: title, + text: content, + typeCode: 5, + button: { + icons: iconsPixelMap, + names: iconsPixelMapNames + }, + capsule: { + title: content, + icon: iconPixelMap, + backgroundColor: '#3778EF', + } + } + }, + extraInfo: { isMute: true, hw_heads_up_enable: true, hw_keep_headsup_sticky: true }, + wantAgent: isSnooze ? await WantAgentUtil.getMainPageWantAgent() : + await WantAgentUtil.getToggleFullScreenWantAgent(alarmInfo), + tapDismissed: !isSnooze, + isUnremovable: true, + }; + if (isSnooze && isSnooze === true) { + LogUtil.info(TAG, 'isSnooze:' + isSnooze); + notificationRequest.extraInfo!.hw_keep_headsup_sticky = false + } + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, `publishNotification success: ${JSON.stringify(notificationRequest)}`); + } catch (error) { + LogUtil.error(TAG, `publishNotification failed: ${JSON.stringify(error)}`); + } + } + + static async notificationSubscribeSystemSubscriber(): Promise { + LogUtil.info(TAG, `subscriber_id : JSON.stringify(id)`) + LogUtil.info(TAG, `subscriber_optionBtn : JSON.stringify(option.buttonName)`) + // notificationManager.subscribeSystemLiveView({ + // onResponse: async (id: number, option: notificationManager.ButtonOptions) => { + // if (id != Number(TIMER_NOTICE_ID)) { + // LogUtil.info(TAG, `subscriber_id : ${JSON.stringify(id)}`) + // LogUtil.info(TAG, `subscriber_optionBtn : ${JSON.stringify(option.buttonName)}`) + // const alarmFromList = await AlarmManager.getIDAlarmFrom(id.toString() as string); + // if (alarmFromList.length > 0) { + // if (option.buttonName == 'alarm_sleep') { + // // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.NOTIFICATION_BAR_REMIND_LATER) + // WantAgentUtil.triggerDelayAlarm(alarmFromList[0]!) + // } else if (option.buttonName == 'alarm_stop') { + // // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.NOTIFICATION_BANNER_CLOSE_ALARM) + // WantAgentUtil.triggerCloseAlarm(alarmFromList[0]!); + // } + // } else { + // return + // } + // } + // } + // }) + } + + /** + * Cancel notification by alarmId + * + * @param alarmId Alarm id + */ + static async cancel(alarmId: number | string): Promise { + if (!alarmId) { + LogUtil.error(TAG, 'Execute method cancel failed, alarmId is incorrect.'); + } + try { + await notificationManager.cancel(typeof alarmId === 'string' ? Number(alarmId) : alarmId); + LogUtil.info(TAG, 'publish Cancel alarmId:' + alarmId); + } catch (error) { + LogUtil.info(TAG, `cancel failed=>${error}`) + } + } + + private static async publishNotificationPC(alarmId: number | string, title: string, content: string, + isSnooze?: boolean): Promise { + try { + let alarmInfo: AlarmInfo = ((GlobalContext.getContext() + .getObject('abilityWant') as Want)?.parameters?.alarmInfo) as AlarmInfo; + const actionButtons = await NotificationUtil.getActionButtons(isSnooze, alarmId); + const notificationRequest: NotificationRequest = { + id: typeof alarmId === 'string' ? Number(alarmId) : alarmId, + content: { + contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, + normal: { + title: title, + text: content, + }, + }, + slotType: notification.SlotType.SOCIAL_COMMUNICATION, + wantAgent: await WantAgentUtil.getMainPageWantAgent(), + tapDismissed: !isSnooze, + actionButtons, + extraInfo: { + isMute: true, + hw_keep_headsup_sticky: true, + hw_heads_up_enable: true + }, + isUnremovable: true, + }; + if (isSnooze && isSnooze === true) { + LogUtil.info(TAG, 'isSnooze:' + isSnooze); + notificationRequest.extraInfo!.hw_keep_headsup_sticky = false + } + LogUtil.info(TAG, 'publishNotificationPC notification:', `${JSON.stringify(notificationRequest)}`); + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, 'publishNotificationPC notification success'); + } catch (error) { + LogUtil.error(TAG, 'publishNotificationPC notification failed : ', `${JSON.stringify(error)}`); + } + } + + static async publishAlarmSnoozePC(alarmId: number | string, nextAlertTime: number, + alarmTitle: string): Promise { + if (!alarmId || !nextAlertTime || !alarmTitle) { + LogUtil.error(TAG, 'Execute method publishAlarmSnooze failed, params is incorrect.'); + return; + } + const alarmLabel = await ResourceManager.getStringByIdAsync($r('app.string.alarm_clock').id); + const title = (await ResourceManager.getStringByIdAsync( + $r('app.string.alarm_notify_snooze_title').id)).replace(STR_PLACE_HOLDER, alarmLabel); + const alarmTimeStr = TimeUtil.getFormattedTime(nextAlertTime); + const snoozeTimeInfo = (await ResourceManager.getStringByIdAsync( + $r('app.string.alarm_notify_snooze_context').id + )).replace(STR_PLACE_HOLDER, alarmTimeStr); + const content = (await ResourceManager.getStringByIdAsync( + $r('app.string.format_notify_text').id + )) + .replace(FIRST_PARAM, snoozeTimeInfo) + .replace(SECOND_PARAM, alarmTitle); + LogUtil.info(TAG, 'publishAlarmSnoozePC alarmId:' + alarmId) + LogUtil.info(TAG, 'publishAlarmSnoozePC content:' + content) + await NotificationUtil.publishNotificationPC(alarmId, title, content, true); + } + + static async publishAlarmMissedPC(alarmId: number | string, alarmAlertTime: number, + alarmTitle: string): Promise { + if (!alarmId || !alarmAlertTime || !alarmTitle) { + LogUtil.error(TAG, 'Execute method publishAlarmMissedPC failed, params is incorrect.'); + return; + } + const alarmLabel = await ResourceManager.getStringByIdAsync($r('app.string.alarm_clock').id); + const title = (await ResourceManager.getStringByIdAsync( + $r('app.string.alarm_notify_miss_label').id)).replace(STR_PLACE_HOLDER, alarmLabel); + const dayOfWeek = TimeUtil.getDayOfWeekByDate(new Date(alarmAlertTime)); + const alarmTimeStr = `${dayOfWeek}${SEPARATOR}${TimeUtil.getFormattedTime(alarmAlertTime)}`; + const content = (await ResourceManager.getStringByIdAsync( + $r('app.string.format_notify_text').id + )) + .replace(FIRST_PARAM, alarmTimeStr) + .replace(SECOND_PARAM, alarmTitle); + LogUtil.info(TAG, 'publishAlarmMissedPC alarmId:' + alarmId) + LogUtil.info(TAG, 'publishAlarmMissedPC content:' + content) + try { + const notificationRequest: NotificationRequest = { + id: typeof alarmId === 'string' ? Number(alarmId) : alarmId, + notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION, + content: { + notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, + normal: { + title: title, + text: content, + } + }, + extraInfo: { isMute: true }, + wantAgent: await WantAgentUtil.getMainPageWantAgent(alarmId, true) + }; + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, `publishAlarmMissedPC success : ${JSON.stringify(notificationRequest)}`) + } catch (error) { + LogUtil.error(TAG, `publishAlarmMissedPC failed : ${JSON.stringify(error)}`); + } + } + + private static async getActionButtons(isSnooze?: boolean, alarmId?: number | string): Promise { + let alarmInfo: AlarmInfo = ((GlobalContext.getContext() + .getObject('abilityWant') as Want)?.parameters?.alarmInfo) as AlarmInfo; + if (alarmInfo?.id !== alarmId) { + LogUtil.info(TAG, 'abilityWant not contain alarmId') + alarmInfo = await AlarmManager.getLatestAlarmFromDb(alarmId!) as AlarmInfo; + LogUtil.info(TAG, 'firingAlarm:' + JSON.stringify(alarmInfo)) + } else { + LogUtil.info(TAG, 'abilityWant contain alarmId') + } + if (!alarmInfo) { + return []; + } + const buttons: ButtonInfo[] = [ + { + title: await ResourceManager.getStringByIdAsync($r('app.string.close').id), + wantAgent: await WantAgentUtil.getAlarmWantAgent(alarmInfo, AlarmServiceType.Close, true, isSnooze) as object, + } + ]; + if (!isSnooze) { + buttons.push({ + title: await ResourceManager.getStringByIdAsync($r('app.string.sleep').id), + wantAgent: await WantAgentUtil.getAlarmWantAgent(alarmInfo, AlarmServiceType.Delay) as object, + }); + } + return buttons; + } +} diff --git a/feature/alarmclock/src/main/ets/utils/index.ets b/feature/alarmclock/src/main/ets/utils/index.ets new file mode 100644 index 0000000..a0a3549 --- /dev/null +++ b/feature/alarmclock/src/main/ets/utils/index.ets @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { NotificationUtil } from './NotificationUtil'; + +export { AlarmCardUtil } from './AlarmCardUtil'; + +export const MANAGE_NEW_ALARM: string = 'ManageNewAlarm'; + +export const MANAGE_EDIT_ALARM: string = 'ManageEditAlarm'; + +export const DELETE_ALARM_CLOCK: string = 'DeleteAlarmClock'; \ No newline at end of file diff --git a/feature/countdown/src/main/module.json5 b/feature/alarmclock/src/main/module.json5 similarity index 68% rename from feature/countdown/src/main/module.json5 rename to feature/alarmclock/src/main/module.json5 index aeb2939..187ba55 100644 --- a/feature/countdown/src/main/module.json5 +++ b/feature/alarmclock/src/main/module.json5 @@ -1,11 +1,10 @@ { "module": { - "name": "countdown", + "name": "alarmclock", "type": "har", "deviceTypes": [ "default", "tablet" ], - "uiSyntax": "ets" } } diff --git a/feature/alarmclock/src/main/resources/base/element/color.json b/feature/alarmclock/src/main/resources/base/element/color.json new file mode 100644 index 0000000..6b77861 --- /dev/null +++ b/feature/alarmclock/src/main/resources/base/element/color.json @@ -0,0 +1,56 @@ +{ + "color": [ + { + "name": "main_no_alarm_color", + "value": "#66000000" + }, + { + "name": "text_color_primary", + "value": "#E5000000" + }, + { + "name": "text_color_secondary", + "value": "#99000000" + }, + { + "name": "snooze_button_border_color", + "value": "#66FFFFFF" + }, + { + "name": "snooze_button_background_color", + "value": "#33FFFFFF" + }, + { + "name": "snooze_button_text_color", + "value": "#FFFFFF" + }, + { + "name": "slider_selected_color", + "value": "#254FF7" + }, + { + "name": "bind_sheet_color", + "value": "#F1F3F5" + }, + { + "name": "input_underline_color", + "value": "#E5000000" + }, + { + "name": "swipe_delete_button_color", + "value": "#E84026" + }, + { + "name": "card_text_color_title", + "value": "#000000" + }, + { + "name": "card_text_color_Description", + "value": "#000000" + }, + { + "name": "line_color", + "value": "#d3d3d3" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/base/element/float.json b/feature/alarmclock/src/main/resources/base/element/float.json new file mode 100644 index 0000000..5a6172e --- /dev/null +++ b/feature/alarmclock/src/main/resources/base/element/float.json @@ -0,0 +1,320 @@ +{ + "float": [ + { + "name": "title_font_size", + "value": "20vp" + }, + { + "name": "appbar_icon_margin", + "value": "14vp" + }, + { + "name": "appbar_icon_size", + "value": "24vp" + }, + { + "name": "response_region_size", + "value": "48vp" + }, + { + "name": "snooze_setting_margin_vertical_max", + "value": "16vp" + }, + { + "name": "snooze_setting_margin_vertical_min", + "value": "4vp" + }, + { + "name": "slider_margin_horizontal", + "value": "-16.5vp" + }, + { + "name": "slider_margin_offset_left", + "value": "6.75vp" + }, + { + "name": "divider_margin_horizontal", + "value": "-24vp" + }, + { + "name": "divider_margin_offset_left", + "value": "12vp" + }, + { + "name": "slider_label_opacity", + "value": "0.6" + }, + { + "name": "slider_label_size", + "value": "14fp" + }, + { + "name": "slider_step_label_size", + "value": "10fp" + }, + { + "name": "clock_shadow_above_space_32", + "value": "32vp" + }, + { + "name": "clock_margin_vertical", + "value": "16vp" + }, + { + "name": "clock_fold_margin_vertical", + "value": "32vp" + }, + { + "name": "left_time_tips_margin_bottom", + "value": "32vp" + }, + { + "name": "current_time_margin_top", + "value": "68vp" + }, + { + "name": "current_date_margin_top", + "value": "5vp" + }, + { + "name": "snooze_btn_margin_top", + "value": "16vp" + }, + { + "name": "snooze_btn_opacity", + "value": "0.7" + }, + { + "name": "slide_to_turn_off_font_size", + "value": "16fp" + }, + { + "name": "slide_to_turn_off_margin_bottom", + "value": "38.5vp" + }, + { + "name": "slide_to_turn_off_button_size", + "value": "50vp" + }, + { + "name": "slide_to_turn_off_button_border_width", + "value": "2vp" + }, + { + "name": "check_box_width", + "value": "24vp" + }, + { + "name": "switch_width", + "value": "36vp" + }, + { + "name": "switch_height", + "value": "20vp" + }, + { + "name": "slide_to_turn_off_margin_top", + "value": "14vp" + }, + { + "name": "snooze_button_padding_horizontal", + "value": "45vp" + }, + { + "name": "snooze_button_padding_vertical", + "value": "10vp" + }, + { + "name": "snooze_button_border_width", + "value": "1vp" + }, + { + "name": "snooze_button_border_radius", + "value": "46vp" + }, + { + "name": "select_all_text_size", + "value": "10vp" + }, + { + "name": "select_all_text_margin", + "value": "3vp" + }, + { + "name": "list_item_height", + "value": "48vp" + }, + { + "name": "empty_alarm_list_item", + "value": "62vp" + }, + { + "name": "lans_list_item_10", + "value": "10vp" + }, + { + "name": "list_item_focus_width", + "value": "460vp" + }, + { + "name": "list_item_focus_height", + "value": "80vp" + }, + { + "name": "swipe_button_height_and_width", + "value": "40vp" + }, + { + "name": "swipe_image_height_and_width", + "value": "20vp" + }, + { + "name": "swipe_button_padding", + "value": "12vp" + }, + { + "name": "swipe_delete_head_width", + "value": "18vp" + }, + { + "name": "swipe_delete_body_width", + "value": "15vp" + }, + { + "name": "swipe_margin_size", + "value": "4vp" + }, + { + "name": "swipe_padding_size", + "value": "20vp" + }, + { + "name": "card_content_padding_height", + "value": "10vp" + }, + { + "name": "timer_picker_width", + "value": "312vp" + }, + { + "name": "timer_picker_height", + "value": "200vp" + }, + { + "name": "left_time_line_height", + "value": "28vp" + }, + { + "name": "delete_button_margin_bottom", + "value": "28vp" + }, + { + "name": "line_height", + "value": "500vp" + }, + { + "name": "stopwatch_time_title_text_size", + "value": "42sp" + }, + { + "name": "stopwatch_time_title_change_text_size", + "value": "20sp" + }, + { + "name": "clock_landscape_height", + "value": "25vp" + }, + { + "name": "clock_text_size", + "value": "20dp" + }, + { + "name": "clock_landscape_no_height", + "value": "0vp" + }, + { + "name": "flod_padding_left", + "value": "104.5vp" + }, + { + "name": "flod_padding_right", + "value": "104.5vp" + }, + { + "name": "selected_all_line_height", + "value": "13vp" + }, + { + "name": "title_bar_height", + "value": "56vp" + }, + { + "name": "edit_alarm_content_padding_bottom", + "value": "80vp" + }, + { + "name": "selected_all_padding", + "value": "24vp" + }, + { + "name": "fold_cross_padding", + "value": "115vp" + }, + { + "name": "flod_alarm_padding", + "value": "24vp" + }, + { + "name": "card_list_margin", + "value": "12vp" + }, + { + "name": "ohos_id_text_size_sub_title1", + "value": "18fp" + }, + { + "name": "ohos_id_text_size_headline2", + "value": "72vp" + }, + { + "name": "ohos_id_max_padding_start", + "value": "24vp" + }, + { + "name": "ohos_id_max_padding_end", + "value": "24vp" + }, + { + "name": "ohos_id_text_size_headline6", + "value": "30vp" + }, + { + "name": "rect_margin_left", + "value": "30px" + }, + { + "name": "rect_pc_margin_left", + "value": "17px" + }, + { + "name": "pc_new_alarm_menu_width", + "value": "224vp" + }, + { + "name": "repeat_menu_width", + "value": "140vp" + }, + { + "name": "pc_city_popup_builder_flex_width", + "value": "400vp" + }, + { + "name": "pc_new_alarm_menu_position_x", + "value": "128vp" + }, + { + "name": "pc_new_alarm_menu_position_y", + "value": "-261vp" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/base/element/plural.json b/feature/alarmclock/src/main/resources/base/element/plural.json new file mode 100644 index 0000000..3ff5545 --- /dev/null +++ b/feature/alarmclock/src/main/resources/base/element/plural.json @@ -0,0 +1,101 @@ +{ + "plural": [ + { + "name": "list_select_title", + "value": [ + { + "quantity": "one", + "value": "%d item selected" + }, + { + "quantity": "other", + "value": "%d items selected" + } + ] + }, + { + "name": "delete_alarms_msg", + "value": [ + { + "quantity": "one", + "value": "Delete %d alarm?" + }, + { + "quantity": "other", + "value": "Delete %d alarms?" + } + ] + }, + { + "name": "alarm_alert_snooze_button_text", + "value": [ + { + "quantity": "one", + "value": "Snooze (%d minute)" + }, + { + "quantity": "other", + "value": "Snooze (%d minutes)" + } + ] + }, + { + "name":"ring_within_hours", + "value":[ + { + "quantity":"one", + "value":"<%d hour remaining" + }, + { + "quantity":"zero", + "value":"<%d hours remaining" + }, + { + "quantity":"two", + "value":"<%d hours remaining" + }, + { + "quantity":"few", + "value":"<%d hours remaining" + }, + { + "quantity":"many", + "value":"<%d hours remaining" + }, + { + "quantity":"other", + "value":"<%d hours remaining" + } + ] + }, + { + "name": "times", + "value": [ + { + "quantity": "other", + "value": "%dx" + }, + { + "quantity": "one", + "value": "%dx" + }, + { + "quantity": "zero", + "value": "%dx" + }, + { + "quantity": "few", + "value": "%dx" + }, + { + "quantity": "many", + "value": "%dx" + }, + { + "quantity": "two", + "value": "%dx" + } + ] + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/base/element/string.json b/feature/alarmclock/src/main/resources/base/element/string.json new file mode 100644 index 0000000..ab2600a --- /dev/null +++ b/feature/alarmclock/src/main/resources/base/element/string.json @@ -0,0 +1,192 @@ +{ + "string": [ + { + "name": "ring_in", + "value": "Ring in %1$s" + }, + { + "name": "ring_in_new", + "value": "Next alarm: %1$s" + }, + { + "name": "ring_in_new_two", + "value": "Next alarm: %1$s %2$s" + }, + { + "name": "ring_in_new_other", + "value": "Next alarm: %1$s %2$s %3$s" + }, + { + "name": "ring_in_thirety", + "value": "Ring in %1$s" + }, + { + "name": "less_than_one_minute", + "value": "Next alarm: Less than %s min" + }, + { + "name": "no_alarms_on", + "value": "No alarms on" + }, + { + "name": "no_alarms", + "value": "No alarms" + }, + { + "name": "fa_no_alarms", + "value": "No alarms on" + }, + { + "name": "ringing_soon", + "value": "Ringing soon" + }, + { + "name": "fa_ringing", + "value": "Ringing" + }, + { + "name": "repeat", + "value": "Repeat" + }, + { + "name": "sound", + "value": "Sound" + }, + { + "name": "label", + "value": "Label" + }, + { + "name": "ring_duration", + "value": "Ring duration" + }, + { + "name": "snooze_duration", + "value": "Snooze duration" + }, + { + "name": "number_of_snoozes", + "value": "Number of snoozes" + }, + { + "name": "add_alarm", + "value": "Add alarm" + }, + { + "name": "edit_alarm", + "value": "Edit alarm" + }, + { + "name": "delete_alarm", + "value": "Delete" + }, + { + "name": "list_select_title_none", + "value": "None selected" + }, + { + "name": "delete", + "value": "Delete" + }, + { + "name": "confirm_delete", + "value": "Delete" + }, + { + "name": "list_select_all", + "value": "Select all" + }, + { + "name": "list_unselect_all", + "value": "Deselect all" + }, + { + "name": "delete_alarm_confirm", + "value": "Delete this alarm?" + }, + { + "name": "delete_all_alarms_confirm", + "value": "Delete all alarms?" + }, + { + "name": "save_changes_confirm", + "value": "Save your changes?" + }, + { + "name": "quit", + "value": "Discard" + }, + { + "name": "keep", + "value": "Save" + }, + { + "name": "alarmReminder_desc", + "value": "description" + }, + { + "name": "slide_to_turn_off", + "value": "Slide to turn off alarm" + }, + { + "name": "alarm_notify_snooze_title", + "value": "%s (snoozed)" + }, + { + "name": "alarm_notify_snooze_context", + "value": "Snoozing until %s" + }, + { + "name": "format_notify_text", + "value": "%1$s - %2$s" + }, + { + "name": "close", + "value": "Stop" + }, + { + "name": "sleep", + "value": "Snooze" + }, + { + "name": "alarm_notify_miss_label", + "value": "%s (missed)" + }, + { + "name": "strlenght_full_Toast", + "value": "Maximum length reached." + }, + { + "name": "once_alarm", + "value": "Never" + }, + { + "name": "customize", + "value": "Custom" + }, + { + "name": "days_of_wake_type", + "value": "Work days" + }, + { + "name": "show_toast_name", + "value": "Network service is unavailable." + }, + { + "name": "alarm_tips_title", + "value": "编辑列表" + }, + { + "name": "alarm_tips_del", + "value": "Delete" + }, + { + "name": "ring_within_one_hours", + "value": "<%d hour remaining" + }, + { + "name": "ring_within_other_hours", + "value": "<%d hours remaining" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/base/media/ic_delete_grey.svg b/feature/alarmclock/src/main/resources/base/media/ic_delete_grey.svg new file mode 100644 index 0000000..7a177f3 --- /dev/null +++ b/feature/alarmclock/src/main/resources/base/media/ic_delete_grey.svg @@ -0,0 +1,14 @@ + + + Public/ic_delete_grey + + + + + + + + + + + \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/bo_CN/element/plural.json b/feature/alarmclock/src/main/resources/bo_CN/element/plural.json new file mode 100644 index 0000000..b341587 --- /dev/null +++ b/feature/alarmclock/src/main/resources/bo_CN/element/plural.json @@ -0,0 +1,49 @@ +{ + "plural":[ + { + "name":"delete_alarms_msg", + "value":[ + { + "quantity":"other", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་ %d བསུབ་རྒྱུ་ཡིན་ནམ།" + } + ] + }, + { + "name":"times", + "value":[ + { + "quantity":"other", + "value":"ཐེངས་ %d" + } + ] + }, + { + "name":"alarm_alert_snooze_button_text", + "value":[ + { + "quantity":"other", + "value":"སྐར་མ་ %d རྗེས་སུ་དྲན་སྐུལ་བྱ།" + } + ] + }, + { + "name":"list_select_title", + "value":[ + { + "quantity":"other", + "value":"%d བདམ་ཟིན།" + } + ] + }, + { + "name":"ring_within_hours", + "value":[ + { + "quantity":"other", + "value":"ཆུ་ཚོད་ %d ནང་དྲིལ་སྒྲ་གྲགས།" + } + ] + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/bo_CN/element/string.json b/feature/alarmclock/src/main/resources/bo_CN/element/string.json new file mode 100644 index 0000000..ba576d9 --- /dev/null +++ b/feature/alarmclock/src/main/resources/bo_CN/element/string.json @@ -0,0 +1,136 @@ +{ + "string":[ + { + "name":"repeat", + "value":"བསྐྱར་ཟློས།" + }, + { + "name":"fa_no_alarms", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་ཀྱི་ཁ་ཕྱེས་མེད།" + }, + { + "name":"ringing_soon", + "value":"དྲིལ་སྒྲ་གྲགས་ལ་ཉེ།" + }, + { + "name":"snooze_duration", + "value":"དྲིལ་སྒྲ་བསྐྱར་སྒྲོག་གི་དུས་ཡུན།" + }, + { + "name":"slide_to_turn_off", + "value":"འདྲུད་ནས་དྲིལ་བརྡ་བཀག་པ།" + }, + { + "name":"edit_alarm", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་སྒྲིག་པ།" + }, + { + "name":"alarm_notify_miss_label", + "value":"%s (ཤོར་ཟིན།)" + }, + { + "name":"no_alarms", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་མི་འདུག" + }, + { + "name":"delete", + "value":"སུབ་པ།" + }, + { + "name":"add_alarm", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་གསར་འཛུགས་བྱེད།" + }, + { + "name":"list_unselect_all", + "value":"བདམ་པ་ཚ་ཚང་འདོར་བ།" + }, + { + "name":"delete_all_alarms_confirm", + "value":"བདམ་ཟིན་པའི་ཐིར་ལྡན་ཆུ་ཚོད་ཚང་མ་བསུབ་རྒྱུ་ཡིན་ནམ།" + }, + { + "name":"ring_in", + "value":"%1$s རྗེས་ནས་དྲིལ་སྒྲ་གྲགས།" + }, + { + "name":"list_select_title_none", + "value":"བདམ་མེད།" + }, + { + "name":"save_changes_confirm", + "value":"སྒྱུར་བཅོས་ཉར་ཚགས་བྱེད་དམ།" + }, + { + "name":"delete_alarm_confirm", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་འདི་གསུབ་བམ།" + }, + { + "name":"number_of_snoozes", + "value":"དྲིལ་སྒྲ་བསྐྱར་སྒྲོག་གི་ཐེངས་གྲངས།" + }, + { + "name":"format_notify_text", + "value":"%1$sནས་%2$s བར།" + }, + { + "name":"no_alarms_on", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་ཚང་མ་ཁ་བརྒྱབ་ཟིན།" + }, + { + "name":"fa_ringing", + "value":"དྲིལ་བརྡ་གཏོང་བཞིན་པ།" + }, + { + "name":"ring_duration", + "value":"དྲིལ་སྒྲ་སྒྲོག་ཡུན།" + }, + { + "name":"alarm_tips_title", + "value":"རེའུ་མིག་རྩོམ་སྒྲིག" + }, + { + "name":"list_select_all", + "value":"ཆ་ཚང་འདེམ་པ།" + }, + { + "name":"quit", + "value":"འདོར་བ།" + }, + { + "name":"sleep", + "value":"ཐུན་ཐུང་།" + }, + { + "name":"keep", + "value":"གསོག་ཉར།" + }, + { + "name":"alarm_notify_snooze_title", + "value":"%s (ཏོག་ཙམ་ནས་དྲན་སྐུལ་གནང་རོགས།)" + }, + { + "name":"alarm_notify_snooze_context", + "value":"%s བར་དུ་ཉིན་མོའི་གཉིད་ཐུན་ཉལ་བ།" + }, + { + "name":"strlenght_full_Toast", + "value":"ནང་འཇུག་བྱས་པའི་ནང་དོན་ཆེས་མཐོའི་ཚད་དུ་སླེབས།" + }, + { + "name":"label", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་ཀྱི་མིང་།" + }, + { + "name":"sound", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་ཀྱི་དྲིལ་སྒྲ།" + }, + { + "name":"close", + "value":"ཁ་རྒྱག" + }, + { + "name":"delete_alarm", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་སུབ་པ།" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/dark/element/float.json b/feature/alarmclock/src/main/resources/dark/element/float.json new file mode 100644 index 0000000..74b687e --- /dev/null +++ b/feature/alarmclock/src/main/resources/dark/element/float.json @@ -0,0 +1,284 @@ +{ + "float": [ + { + "name": "title_font_size", + "value": "20vp" + }, + { + "name": "appbar_icon_margin", + "value": "14vp" + }, + { + "name": "appbar_icon_size", + "value": "24vp" + }, + { + "name": "response_region_size", + "value": "48vp" + }, + { + "name": "snooze_setting_margin_vertical_max", + "value": "16vp" + }, + { + "name": "snooze_setting_margin_vertical_min", + "value": "4vp" + }, + { + "name": "slider_margin_horizontal", + "value": "-16.5vp" + }, + { + "name": "slider_margin_offset_left", + "value": "6.75vp" + }, + { + "name": "divider_margin_horizontal", + "value": "-24vp" + }, + { + "name": "divider_margin_offset_left", + "value": "12vp" + }, + { + "name": "slider_label_opacity", + "value": "0.6" + }, + { + "name": "slider_label_size", + "value": "14fp" + }, + { + "name": "slider_step_label_size", + "value": "10fp" + }, + { + "name": "clock_shadow_above_space_32", + "value": "32vp" + }, + { + "name": "clock_margin_vertical", + "value": "16vp" + }, + { + "name": "clock_fold_margin_vertical", + "value": "32vp" + }, + { + "name": "left_time_tips_margin_bottom", + "value": "32vp" + }, + { + "name": "current_time_margin_top", + "value": "68vp" + }, + { + "name": "current_date_margin_top", + "value": "5vp" + }, + { + "name": "snooze_btn_margin_top", + "value": "16vp" + }, + { + "name": "snooze_btn_opacity", + "value": "0.7" + }, + { + "name": "slide_to_turn_off_font_size", + "value": "16fp" + }, + { + "name": "slide_to_turn_off_margin_bottom", + "value": "38.5vp" + }, + { + "name": "slide_to_turn_off_button_size", + "value": "50vp" + }, + { + "name": "slide_to_turn_off_button_border_width", + "value": "2vp" + }, + { + "name": "check_box_width", + "value": "24vp" + }, + { + "name": "switch_width", + "value": "36vp" + }, + { + "name": "switch_height", + "value": "20vp" + }, + { + "name": "slide_to_turn_off_margin_top", + "value": "14vp" + }, + { + "name": "snooze_button_padding_horizontal", + "value": "45vp" + }, + { + "name": "snooze_button_padding_vertical", + "value": "10vp" + }, + { + "name": "snooze_button_border_width", + "value": "1vp" + }, + { + "name": "snooze_button_border_radius", + "value": "46vp" + }, + { + "name": "select_all_text_size", + "value": "10vp" + }, + { + "name": "select_all_text_margin", + "value": "3vp" + }, + { + "name": "list_item_height", + "value": "48vp" + }, + { + "name": "empty_alarm_list_item", + "value": "62vp" + }, + { + "name": "lans_list_item_10", + "value": "10vp" + }, + { + "name": "list_item_focus_width", + "value": "460vp" + }, + { + "name": "list_item_focus_height", + "value": "80vp" + }, + { + "name": "swipe_button_height_and_width", + "value": "40vp" + }, + { + "name": "swipe_image_height_and_width", + "value": "20vp" + }, + { + "name": "swipe_button_padding", + "value": "12vp" + }, + { + "name": "swipe_delete_head_width", + "value": "18vp" + }, + { + "name": "swipe_delete_body_width", + "value": "15vp" + }, + { + "name": "swipe_margin_size", + "value": "4vp" + }, + { + "name": "swipe_padding_size", + "value": "20vp" + }, + { + "name": "card_content_padding_height", + "value": "10vp" + }, + { + "name": "timer_picker_width", + "value": "312vp" + }, + { + "name": "timer_picker_height", + "value": "200vp" + }, + { + "name": "left_time_line_height", + "value": "28vp" + }, + { + "name": "delete_button_margin_bottom", + "value": "28vp" + }, + { + "name": "line_height", + "value": "500vp" + }, + { + "name": "stopwatch_time_title_text_size", + "value": "42sp" + }, + { + "name": "stopwatch_time_title_change_text_size", + "value": "20sp" + }, + { + "name": "clock_landscape_height", + "value": "25vp" + }, + { + "name": "clock_text_size", + "value": "20dp" + }, + { + "name": "clock_landscape_no_height", + "value": "0vp" + }, + { + "name": "flod_padding_left", + "value": "104.5vp" + }, + { + "name": "flod_padding_right", + "value": "104.5vp" + }, + { + "name": "selected_all_line_height", + "value": "13vp" + }, + { + "name": "title_bar_height", + "value": "56vp" + }, + { + "name": "edit_alarm_content_padding_bottom", + "value": "80vp" + }, + { + "name": "selected_all_padding", + "value": "24vp" + }, + { + "name": "fold_cross_padding", + "value": "115vp" + }, + { + "name": "flod_alarm_padding", + "value": "24vp" + }, + { + "name": "card_list_margin", + "value": "12vp" + }, + { + "name": "pc_city_popup_builder_flex_width", + "value": "400vp" + }, + { + "name": "pc_new_alarm_menu_position_x", + "value": "128vp" + }, + { + "name": "pc_new_alarm_menu_position_y", + "value": "-261vp" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/dark/element/plural.json b/feature/alarmclock/src/main/resources/dark/element/plural.json new file mode 100644 index 0000000..3ff5545 --- /dev/null +++ b/feature/alarmclock/src/main/resources/dark/element/plural.json @@ -0,0 +1,101 @@ +{ + "plural": [ + { + "name": "list_select_title", + "value": [ + { + "quantity": "one", + "value": "%d item selected" + }, + { + "quantity": "other", + "value": "%d items selected" + } + ] + }, + { + "name": "delete_alarms_msg", + "value": [ + { + "quantity": "one", + "value": "Delete %d alarm?" + }, + { + "quantity": "other", + "value": "Delete %d alarms?" + } + ] + }, + { + "name": "alarm_alert_snooze_button_text", + "value": [ + { + "quantity": "one", + "value": "Snooze (%d minute)" + }, + { + "quantity": "other", + "value": "Snooze (%d minutes)" + } + ] + }, + { + "name":"ring_within_hours", + "value":[ + { + "quantity":"one", + "value":"<%d hour remaining" + }, + { + "quantity":"zero", + "value":"<%d hours remaining" + }, + { + "quantity":"two", + "value":"<%d hours remaining" + }, + { + "quantity":"few", + "value":"<%d hours remaining" + }, + { + "quantity":"many", + "value":"<%d hours remaining" + }, + { + "quantity":"other", + "value":"<%d hours remaining" + } + ] + }, + { + "name": "times", + "value": [ + { + "quantity": "other", + "value": "%dx" + }, + { + "quantity": "one", + "value": "%dx" + }, + { + "quantity": "zero", + "value": "%dx" + }, + { + "quantity": "few", + "value": "%dx" + }, + { + "quantity": "many", + "value": "%dx" + }, + { + "quantity": "two", + "value": "%dx" + } + ] + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/dark/element/string.json b/feature/alarmclock/src/main/resources/dark/element/string.json new file mode 100644 index 0000000..25bc918 --- /dev/null +++ b/feature/alarmclock/src/main/resources/dark/element/string.json @@ -0,0 +1,156 @@ +{ + "string": [ + { + "name": "ring_in", + "value": "Ring in %1$s" + }, + { + "name": "ring_in_thirety", + "value": "Ring in %1$s" + }, + { + "name": "less_than_one_minute", + "value": "Ring in less than 1 minute." + }, + { + "name": "no_alarms_on", + "value": "No alarms on" + }, + { + "name": "no_alarms", + "value": "No alarms" + }, + { + "name": "fa_no_alarms", + "value": "No alarms on" + }, + { + "name": "ringing_soon", + "value": "Ringing soon" + }, + { + "name":"fa_ringing", + "value":"Ringing" + }, + { + "name": "repeat", + "value": "Repeat" + }, + { + "name": "sound", + "value": "Sound" + }, + { + "name": "label", + "value": "Label" + }, + { + "name": "ring_duration", + "value": "Ring duration" + }, + { + "name": "snooze_duration", + "value": "Snooze duration" + }, + { + "name": "number_of_snoozes", + "value": "Number of snoozes" + }, + { + "name": "add_alarm", + "value": "Add alarm" + }, + { + "name": "edit_alarm", + "value": "Edit alarm" + }, + { + "name": "delete_alarm", + "value": "Delete" + }, + { + "name": "list_select_title_none", + "value": "None selected" + }, + { + "name": "delete", + "value": "Delete" + }, + { + "name": "confirm_delete", + "value": "Delete" + }, + { + "name": "list_select_all", + "value": "Select all" + }, + { + "name": "list_unselect_all", + "value": "Deselect all" + }, + { + "name": "delete_alarm_confirm", + "value": "Delete this alarm?" + }, + { + "name": "delete_all_alarms_confirm", + "value": "Delete all alarms?" + }, + { + "name": "save_changes_confirm", + "value": "Save your changes?" + }, + { + "name": "quit", + "value": "Discard" + }, + { + "name": "keep", + "value": "Save" + }, + { + "name": "alarmReminder_desc", + "value": "description" + }, + { + "name": "slide_to_turn_off", + "value": "Slide to turn off alarm" + }, + { + "name": "alarm_notify_snooze_title", + "value": "%s (snoozed)" + }, + { + "name": "alarm_notify_snooze_context", + "value": "Snoozing until %s" + }, + { + "name": "format_notify_text", + "value": "%1$s - %2$s" + }, + { + "name": "close", + "value": "Stop" + }, + { + "name": "sleep", + "value": "Snooze" + }, + { + "name": "alarm_notify_miss_label", + "value": "%s (missed)" + }, + { + "name": "strlenght_full_Toast", + "value": "Maximum length reached." + }, + { + "name": "alarm_tips_title", + "value": "编辑列表" + }, + { + "name": "alarm_tips_del", + "value": "删除" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/en/element/plural.json b/feature/alarmclock/src/main/resources/en/element/plural.json new file mode 100644 index 0000000..ab9a023 --- /dev/null +++ b/feature/alarmclock/src/main/resources/en/element/plural.json @@ -0,0 +1,101 @@ +{ + "plural":[ + { + "name": "list_select_title", + "value": [ + { + "quantity":"one", + "value":"%d item selected" + }, + { + "quantity":"other", + "value":"%d items selected" + } + ] + }, + { + "name": "delete_alarms_msg", + "value": [ + { + "quantity":"one", + "value":"Delete %d alarm?" + }, + { + "quantity":"other", + "value":"Delete %d alarms?" + } + ] + }, + { + "name": "alarm_alert_snooze_button_text", + "value": [ + { + "quantity": "one", + "value": "Snooze (%d minute)" + }, + { + "quantity": "other", + "value": "Snooze (%d minutes)" + } + ] + }, + { + "name":"ring_within_hours", + "value":[ + { + "quantity":"one", + "value":"<%d hour remaining" + }, + { + "quantity":"zero", + "value":"<%d hours remaining" + }, + { + "quantity":"two", + "value":"<%d hours remaining" + }, + { + "quantity":"few", + "value":"<%d hours remaining" + }, + { + "quantity":"many", + "value":"<%d hours remaining" + }, + { + "quantity":"other", + "value":"<%d hours remaining" + } + ] + }, + { + "name": "times", + "value": [ + { + "quantity": "other", + "value": "%dx" + }, + { + "quantity": "one", + "value": "%dx" + }, + { + "quantity": "zero", + "value": "%dx" + }, + { + "quantity": "few", + "value": "%dx" + }, + { + "quantity": "many", + "value": "%dx" + }, + { + "quantity": "two", + "value": "%dx" + } + ] + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/en/element/string.json b/feature/alarmclock/src/main/resources/en/element/string.json new file mode 100644 index 0000000..a2acc51 --- /dev/null +++ b/feature/alarmclock/src/main/resources/en/element/string.json @@ -0,0 +1,192 @@ +{ + "string": [ + { + "name": "ring_in", + "value": "Ring in %1$s" + }, + { + "name": "ring_in_new", + "value": "Ring in %1$s" + }, + { + "name": "ring_in_new_two", + "value":"Next alarm: %1$s %2$s" + }, + { + "name": "ring_in_new_other", + "value": "Next alarm: %1$s %2$s %3$s" + }, + { + "name": "ring_in_thirety", + "value": "Ring in %1$s" + }, + { + "name": "less_than_one_minute", + "value": "Ring in less than 1 minute." + }, + { + "name": "no_alarms_on", + "value": "No alarms on" + }, + { + "name": "no_alarms", + "value": "no alarms" + }, + { + "name":"fa_no_alarms", + "value":"No alarms on" + }, + { + "name":"ringing_soon", + "value":"Ringing soon" + }, + { + "name":"fa_ringing", + "value":"Ringing" + }, + { + "name": "repeat", + "value": "Repeat" + }, + { + "name": "sound", + "value": "Sound" + }, + { + "name": "label", + "value": "Label" + }, + { + "name": "ring_duration", + "value": "Ring duration" + }, + { + "name": "snooze_duration", + "value": "Snooze duration" + }, + { + "name": "snooze_duration_time", + "value": "Snooze duration (min)" + }, + { + "name": "number_of_snoozes", + "value": "Number of snoozes" + }, + { + "name": "add_alarm", + "value": "Add alarm" + }, + { + "name": "edit_alarm", + "value": "Edit alarm" + }, + { + "name": "delete_alarm", + "value": "DELETE" + }, + { + "name": "list_select_title_none", + "value": "None selected" + }, + { + "name": "delete", + "value": "Delete" + }, + { + "name": "confirm_delete", + "value": "DELETE" + }, + { + "name": "list_select_all", + "value": "Select all" + }, + { + "name": "list_unselect_all", + "value": "Deselect all" + }, + { + "name": "delete_alarm_confirm", + "value": "Delete this alarm?" + }, + { + "name": "delete_all_alarms_confirm", + "value": "Delete all alarms?" + }, + { + "name": "save_changes_confirm", + "value": "Save your changes?" + }, + { + "name": "quit", + "value": "DISCARD" + }, + { + "name": "keep", + "value": "SAVE" + }, + { + "name": "slide_to_turn_off", + "value": "Slide to turn off alarm" + }, + { + "name": "alarm_notify_snooze_title", + "value": "%s (snoozed)" + }, + { + "name": "alarm_notify_snooze_context", + "value": "Snoozing until %s" + }, + { + "name": "format_notify_text", + "value": "%1$s - %2$s" + }, + { + "name": "close", + "value": "Stop" + }, + { + "name": "sleep", + "value": "Snooze" + }, + { + "name": "alarm_notify_miss_label", + "value": "%s (missed)" + }, + { + "name": "strlenght_full_Toast", + "value": "Maximum length reached." + }, + { + "name": "once_alarm", + "value": "once" + }, + { + "name": "customize", + "value": "customize" + }, + { + "name": "days_of_wake_type", + "value": "wakeing days" + }, + { + "name": "show_toast_name", + "value": "Network service is unavailable" + }, + { + "name": "ring_within_one_hours", + "value": "<%d hour remaining" + }, + { + "name": "ring_within_other_hours", + "value": "<%d hours remaining" + }, + { + "name": "alarm_tips_title", + "value": "Edit list" + }, + { + "name": "alarm_tips_del", + "value": "Clear" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/ug/element/plural.json b/feature/alarmclock/src/main/resources/ug/element/plural.json new file mode 100644 index 0000000..8562ea8 --- /dev/null +++ b/feature/alarmclock/src/main/resources/ug/element/plural.json @@ -0,0 +1,69 @@ +{ + "plural":[ + { + "name":"delete_alarms_msg", + "value":[ + { + "quantity":"one", + "value":"%d قوڭغۇراقنى ئۆچۈرەمسىز؟" + }, + { + "quantity":"other", + "value":"%d قوڭغۇراقنى ئۆچۈرەمسىز؟" + } + ] + }, + { + "name":"times", + "value":[ + { + "quantity":"one", + "value":"%d قېتىم" + }, + { + "quantity":"other", + "value":"%d قېتىم" + } + ] + }, + { + "name":"alarm_alert_snooze_button_text", + "value":[ + { + "quantity":"other", + "value":"%d مىنۇت كېچىكتۈرۈش" + }, + { + "quantity":"one", + "value":"%d مىنۇت كېچىكتۈرۈش" + } + ] + }, + { + "name":"list_select_title", + "value":[ + { + "quantity":"one", + "value":"%d تۈر تاللاندى" + }, + { + "quantity":"other", + "value":"%d تۈر تاللاندى" + } + ] + }, + { + "name":"ring_within_hours", + "value":[ + { + "quantity":"other", + "value":"%d سائەتتىن كېيىن سايرايدۇ" + }, + { + "quantity":"one", + "value":"%d سائەتتىن كېيىن سايرايدۇ" + } + ] + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/ug/element/string.json b/feature/alarmclock/src/main/resources/ug/element/string.json new file mode 100644 index 0000000..def633c --- /dev/null +++ b/feature/alarmclock/src/main/resources/ug/element/string.json @@ -0,0 +1,136 @@ +{ + "string":[ + { + "name":"repeat", + "value":"قايتىلاش" + }, + { + "name":"fa_no_alarms", + "value":"قوڭغۇراق سائەت ئېتىك" + }, + { + "name":"ringing_soon", + "value":"سايرىماقچى" + }, + { + "name":"snooze_duration", + "value":"قايتا سايراش ئارىلىقى" + }, + { + "name":"slide_to_turn_off", + "value":"قوڭغۇراقنى سىيرىپ ئېتىش" + }, + { + "name":"edit_alarm", + "value":"قوڭغۇراق تەھرىرلەش" + }, + { + "name":"alarm_notify_miss_label", + "value":"%s(ئۆتكۈزۈۋېتىش)" + }, + { + "name":"no_alarms", + "value":"قوڭغۇراق سائەت يوق" + }, + { + "name":"delete", + "value":"ئۆچۈرۈش" + }, + { + "name":"add_alarm", + "value":"قوڭغۇراق سائەت قۇرۇش" + }, + { + "name":"list_unselect_all", + "value":"ھەممىنى تاللاشنى بىكار قىلىش" + }, + { + "name":"delete_all_alarms_confirm", + "value":"بارلىق قوڭغۇراقنى ئۆچۈرەمسىز؟" + }, + { + "name":"ring_in", + "value":"%1$s كېيىن سايرايدۇ" + }, + { + "name":"list_select_title_none", + "value":"تاللانمىدى" + }, + { + "name":"save_changes_confirm", + "value":"ئۆزگەرتىشنى ساقلامسىز؟" + }, + { + "name":"delete_alarm_confirm", + "value":"بۇ قوڭغۇراقنى ئۆچۈرەمدۇق؟" + }, + { + "name":"number_of_snoozes", + "value":"قايتا سايراش قېتىمى" + }, + { + "name":"format_notify_text", + "value":"%1$s (%2$s)" + }, + { + "name":"no_alarms_on", + "value":"بارلىق قوڭغۇراق سائەت ئېتىك" + }, + { + "name":"fa_ringing", + "value":"سايراۋاتىدۇ" + }, + { + "name":"ring_duration", + "value":"سايراش ۋاقتى" + }, + { + "name":"alarm_tips_title", + "value":"تىزىملىك تەھرىرلەش" + }, + { + "name":"list_select_all", + "value":"ﮬﻪﻣﻤﯩﻨﻰ ﺗﺎﻟﻼش" + }, + { + "name":"quit", + "value":"تاشلاش" + }, + { + "name":"sleep", + "value":"سەل تۇرۇپ ئەسكەرتىڭ" + }, + { + "name":"keep", + "value":"ساقلاش" + }, + { + "name":"alarm_notify_snooze_title", + "value":"%s (كېچىكتۈرۈلدى)" + }, + { + "name":"alarm_notify_snooze_context", + "value":"%s دە قايتا سايرايدۇ" + }, + { + "name":"strlenght_full_Toast", + "value":"كىرگۈزگەن خەت سانى يۇقىرى چەككە يەتتى" + }, + { + "name":"label", + "value":"قوڭغۇراق سائەت نامى" + }, + { + "name":"sound", + "value":"قوڭغۇراق سائەت" + }, + { + "name":"close", + "value":"ئېتىش" + }, + { + "name":"delete_alarm", + "value":"ئۆچۈرۈش" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/zh_CN/element/plural.json b/feature/alarmclock/src/main/resources/zh_CN/element/plural.json new file mode 100644 index 0000000..ba4d820 --- /dev/null +++ b/feature/alarmclock/src/main/resources/zh_CN/element/plural.json @@ -0,0 +1,71 @@ +{ + "plural":[ + { + "name":"list_select_title", + "value":[ + { + "quantity":"other", + "value":"已选择 %d 项" + } + ] + }, + { + "name":"delete_alarms_msg", + "value":[ + { + "quantity":"one", + "value":"是否删除此闹钟?" + }, + { + "quantity":"other", + "value":"是否删除 %d 个闹钟?" + } + ] + }, + { + "name":"alarm_alert_snooze_button_text", + "value":[ + { + "quantity":"other", + "value":"%d 分钟后提醒" + } + ] + }, + { + "name":"times", + "value":[ + { + "quantity":"other", + "value":"%d 次" + } + ] + }, + { + "name":"ring_within_hours", + "value":[ + { + "quantity":"other", + "value":"%d 小时内响铃" + } + ] + }, + { + "name":"ring_within_days", + "value":[ + { + "quantity":"other", + "value":"%d 天后响铃" + } + ] + }, + { + "name":"ring_within_minutes", + "value":[ + { + "quantity":"other", + "value":"%d 分钟内响铃" + } + ] + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/zh_CN/element/string.json b/feature/alarmclock/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..450a634 --- /dev/null +++ b/feature/alarmclock/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,220 @@ +{ + "string": [ + { + "name": "ring_in", + "value": "%1$s 后响铃" + }, + { + "name": "ring_in_new", + "value": "%1$s后响铃" + }, + { + "name": "ring_in_new_two", + "value": "%1$s %2$s后响铃" + }, + { + "name": "ring_in_new_other", + "value": "%1$s %2$s %3$s后响铃" + }, + { + "name": "ring_in_thirety", + "value": "%1$s 内响铃" + }, + { + "name": "less_than_one_minute", + "value": "1 分钟内响铃" + }, + { + "name": "no_alarms_on", + "value": "所有闹钟已关闭" + }, + { + "name": "no_alarms", + "value": "没有闹钟" + }, + { + "name": "fa_no_alarms", + "value": "无闹钟开启" + }, + { + "name": "ringing_soon", + "value": "即将响铃" + }, + { + "name": "fa_ringing", + "value": "正在响铃" + }, + { + "name": "repeat", + "value": "重复" + }, + { + "name": "sound", + "value": "闹钟铃声" + }, + { + "name": "label", + "value": "闹钟名" + }, + { + "name": "ring_duration", + "value": "响铃时长" + }, + { + "name": "snooze_duration", + "value": "再响间隔" + }, + { + "name": "snooze_duration_time", + "value": "响铃间隔时间 (分钟)" + }, + { + "name": "number_of_snoozes", + "value": "重复响铃次数" + }, + { + "name": "add_alarm", + "value": "新建闹钟" + }, + { + "name": "edit_alarm", + "value": "编辑闹钟" + }, + { + "name": "delete_alarm", + "value": "删除闹钟" + }, + { + "name": "list_select_title_none", + "value": "未选择" + }, + { + "name": "delete", + "value": "删除" + }, + { + "name": "confirm_delete", + "value": "删除" + }, + { + "name": "list_select_all", + "value": "全选" + }, + { + "name": "list_unselect_all", + "value": "取消全选" + }, + { + "name": "delete_alarm_confirm", + "value": "是否删除此闹钟?" + }, + { + "name": "delete_all_alarms_confirm", + "value": "是否删除全部闹钟?" + }, + { + "name": "save_changes_confirm", + "value": "是否保存更改?" + }, + { + "name": "quit", + "value": "放弃" + }, + { + "name": "keep", + "value": "保存" + }, + { + "name": "slide_to_turn_off", + "value": "滑动关闭闹铃" + }, + { + "name": "alarm_notify_snooze_title", + "value": "%s (稍后提醒)" + }, + { + "name": "alarm_notify_snooze_context", + "value": "%s 再响" + }, + { + "name": "format_notify_text", + "value": "%1$s - %2$s" + }, + { + "name": "close", + "value": "关闭" + }, + { + "name": "sleep", + "value": "稍后提醒" + }, + { + "name": "alarm_notify_miss_label", + "value": "%s (已错过)" + }, + { + "name": "strlenght_full_Toast", + "value": "输入字数已达上限" + }, + { + "name": "once_alarm", + "value": "单次" + }, + { + "name": "customize", + "value": "自定义" + }, + { + "name": "days_of_wake_type", + "value": "法定工作日" + }, + { + "name": "show_toast_name", + "value": "网络服务不可用" + }, + { + "name": "ring_within_one_days", + "value": "%d 天后响铃" + }, + { + "name": "ring_within_other_days", + "value": "%d 天后响铃" + }, + { + "name": "ring_within_minutes", + "value": "%d 分钟内响铃" + }, + { + "name": "alarm_tips_title", + "value": "编辑列表" + }, + { + "name": "alarm_tips_del", + "value": "删除" + }, + { + "name": "ring_within_one_hours", + "value": "%d 小时内响铃" + }, + { + "name": "ring_within_other_hours", + "value": "%d 小时内响铃" + }, + { + "name": "close_only_once", + "value": "仅关闭一次" + }, + { + "name": "close_repeating_alarm", + "value": "关闭该重复闹钟" + }, + { + "name": "open_tomorrow", + "value": " | 将在明天开启" + }, + { + "name": "will_open", + "value": " | 将在%1$s月%2$s日开启" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/zh_HK/element/plural.json b/feature/alarmclock/src/main/resources/zh_HK/element/plural.json new file mode 100644 index 0000000..bdae577 --- /dev/null +++ b/feature/alarmclock/src/main/resources/zh_HK/element/plural.json @@ -0,0 +1,49 @@ +{ + "plural":[ + { + "name":"delete_alarms_msg", + "value":[ + { + "quantity":"other", + "value":"刪除 %d 個鬧鐘?" + } + ] + }, + { + "name":"times", + "value":[ + { + "quantity":"other", + "value":"%d 次" + } + ] + }, + { + "name":"alarm_alert_snooze_button_text", + "value":[ + { + "quantity":"other", + "value":"%d 分鐘後提醒" + } + ] + }, + { + "name":"list_select_title", + "value":[ + { + "quantity":"other", + "value":"已選擇 %d 個" + } + ] + }, + { + "name":"ring_within_hours", + "value":[ + { + "quantity":"other", + "value":"%d 小時內響鬧" + } + ] + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/zh_HK/element/string.json b/feature/alarmclock/src/main/resources/zh_HK/element/string.json new file mode 100644 index 0000000..971a4c1 --- /dev/null +++ b/feature/alarmclock/src/main/resources/zh_HK/element/string.json @@ -0,0 +1,140 @@ +{ + "string":[ + { + "name":"repeat", + "value":"重複" + }, + { + "name":"fa_no_alarms", + "value":"無已啟用的鬧鐘" + }, + { + "name":"ringing_soon", + "value":"即將響鬧" + }, + { + "name":"snooze_duration", + "value":"延遲時間設定" + }, + { + "name":"slide_to_turn_off", + "value":"滑動以關閉鬧鐘" + }, + { + "name":"edit_alarm", + "value":"編輯鬧鐘" + }, + { + "name":"alarm_notify_miss_label", + "value":"%s(已錯過)" + }, + { + "name":"no_alarms", + "value":"無鬧鐘" + }, + { + "name":"delete", + "value":"刪除" + }, + { + "name":"add_alarm", + "value":"新增鬧鐘" + }, + { + "name":"list_unselect_all", + "value":"取消全選" + }, + { + "name":"delete_all_alarms_confirm", + "value":"刪除所有鬧鐘?" + }, + { + "name":"confirm_delete", + "value":"刪除" + }, + { + "name":"ring_in", + "value":"%1$s後響鬧" + }, + { + "name":"list_select_title_none", + "value":"未選擇" + }, + { + "name":"save_changes_confirm", + "value":"儲存變更?" + }, + { + "name":"delete_alarm_confirm", + "value":"刪除此鬧鐘?" + }, + { + "name":"number_of_snoozes", + "value":"重複響鬧次數" + }, + { + "name":"format_notify_text", + "value":"%1$s - %2$s" + }, + { + "name":"no_alarms_on", + "value":"所有鬧鐘已關閉" + }, + { + "name":"fa_ringing", + "value":"響鬧中" + }, + { + "name":"ring_duration", + "value":"鈴聲持續時間" + }, + { + "name":"alarm_tips_title", + "value":"編輯清單" + }, + { + "name":"list_select_all", + "value":"全選" + }, + { + "name":"quit", + "value":"捨棄" + }, + { + "name":"sleep", + "value":"重響" + }, + { + "name":"keep", + "value":"儲存" + }, + { + "name":"alarm_notify_snooze_title", + "value":"%s(稍後提醒)" + }, + { + "name":"alarm_notify_snooze_context", + "value":"%s 再響" + }, + { + "name":"strlenght_full_Toast", + "value":"已達長度上限。" + }, + { + "name":"label", + "value":"鬧鐘名稱" + }, + { + "name":"sound", + "value":"鬧鐘鈴聲" + }, + { + "name":"close", + "value":"關閉" + }, + { + "name":"delete_alarm", + "value":"刪除鬧鐘" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/zh_TW/element/plural.json b/feature/alarmclock/src/main/resources/zh_TW/element/plural.json new file mode 100644 index 0000000..fb81842 --- /dev/null +++ b/feature/alarmclock/src/main/resources/zh_TW/element/plural.json @@ -0,0 +1,49 @@ +{ + "plural":[ + { + "name":"delete_alarms_msg", + "value":[ + { + "quantity":"other", + "value":"是否刪除 %d 個鬧鐘?" + } + ] + }, + { + "name":"times", + "value":[ + { + "quantity":"other", + "value":"%d 次" + } + ] + }, + { + "name":"alarm_alert_snooze_button_text", + "value":[ + { + "quantity":"other", + "value":"%d 分鐘後提醒" + } + ] + }, + { + "name":"list_select_title", + "value":[ + { + "quantity":"other", + "value":"已選取 %d 項" + } + ] + }, + { + "name":"ring_within_hours", + "value":[ + { + "quantity":"other", + "value":"%d 小時內響鈴" + } + ] + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/zh_TW/element/string.json b/feature/alarmclock/src/main/resources/zh_TW/element/string.json new file mode 100644 index 0000000..b092328 --- /dev/null +++ b/feature/alarmclock/src/main/resources/zh_TW/element/string.json @@ -0,0 +1,140 @@ +{ + "string":[ + { + "name":"repeat", + "value":"重複" + }, + { + "name":"fa_no_alarms", + "value":"無鬧鐘啟用" + }, + { + "name":"ringing_soon", + "value":"即將響鈴" + }, + { + "name":"snooze_duration", + "value":"貪睡時間設定" + }, + { + "name":"slide_to_turn_off", + "value":"滑動以關閉鬧鐘" + }, + { + "name":"edit_alarm", + "value":"編輯鬧鐘" + }, + { + "name":"alarm_notify_miss_label", + "value":"%s(已錯過)" + }, + { + "name":"no_alarms", + "value":"無鬧鐘" + }, + { + "name":"delete", + "value":"刪除" + }, + { + "name":"add_alarm", + "value":"新增鬧鐘" + }, + { + "name":"list_unselect_all", + "value":"取消全選" + }, + { + "name":"delete_all_alarms_confirm", + "value":"是否刪除全部鬧鐘?" + }, + { + "name":"confirm_delete", + "value":"刪除" + }, + { + "name":"ring_in", + "value":"%1$s後響鈴" + }, + { + "name":"list_select_title_none", + "value":"未選取" + }, + { + "name":"save_changes_confirm", + "value":"要儲存變更?" + }, + { + "name":"delete_alarm_confirm", + "value":"您要刪除這個鬧鐘嗎?" + }, + { + "name":"number_of_snoozes", + "value":"重複響鈴次數" + }, + { + "name":"format_notify_text", + "value":"%1$s - %2$s" + }, + { + "name":"no_alarms_on", + "value":"所有鬧鐘已關閉" + }, + { + "name":"fa_ringing", + "value":"正在響鈴" + }, + { + "name":"ring_duration", + "value":"鈴聲持續時間" + }, + { + "name":"alarm_tips_title", + "value":"編輯清單" + }, + { + "name":"list_select_all", + "value":"全選" + }, + { + "name":"quit", + "value":"放棄" + }, + { + "name":"sleep", + "value":"延遲" + }, + { + "name":"keep", + "value":"儲存" + }, + { + "name":"alarm_notify_snooze_title", + "value":"%s(稍後提醒)" + }, + { + "name":"alarm_notify_snooze_context", + "value":"延到 %s" + }, + { + "name":"strlenght_full_Toast", + "value":"已達最大長度。" + }, + { + "name":"label", + "value":"標籤" + }, + { + "name":"sound", + "value":"鬧鐘鈴聲" + }, + { + "name":"close", + "value":"關閉" + }, + { + "name":"delete_alarm", + "value":"刪除鬧鐘" + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/zz_ZX/element/plural.json b/feature/alarmclock/src/main/resources/zz_ZX/element/plural.json new file mode 100644 index 0000000..d1b1605 --- /dev/null +++ b/feature/alarmclock/src/main/resources/zz_ZX/element/plural.json @@ -0,0 +1,149 @@ +{ + "plural":[ + { + "name":"alarm_alert_snooze_button_text", + "value":[ + { + "quantity":"other", + "value":"[TS_810231]_Snooze (%d minutes)" + }, + { + "quantity":"one", + "value":"[TS_810221]_Snooze (%d minute)" + }, + { + "quantity":"zero", + "value":"[TS_810229]_Snooze (%d minutes)" + }, + { + "quantity":"few", + "value":"[TS_810227]_Snooze (%d minutes)" + }, + { + "quantity":"many", + "value":"[TS_810238]_Snooze (%d minutes)" + }, + { + "quantity":"two", + "value":"[TS_810224]_Snooze (%d minutes)" + } + ] + }, + { + "name":"list_select_title", + "value":[ + { + "quantity":"one", + "value":"[TS_794232]_%d item selected" + }, + { + "quantity":"other", + "value":"[TS_794219]_%d items selected" + }, + { + "quantity":"zero", + "value":"[TS_794246]_%d items selected" + }, + { + "quantity":"few", + "value":"[TS_794256]_%d items selected" + }, + { + "quantity":"many", + "value":"[TS_794236]_%d items selected" + }, + { + "quantity":"two", + "value":"[TS_794228]_%d items selected" + } + ] + }, + { + "name":"delete_alarms_msg", + "value":[ + { + "quantity":"one", + "value":"[TS_794259]_Delete %d alarm?" + }, + { + "quantity":"other", + "value":"[TS_794255]_Delete %d alarms?" + }, + { + "quantity":"zero", + "value":"[TS_794263]_Delete %d alarms?" + }, + { + "quantity":"few", + "value":"[TS_794226]_Delete %d alarms?" + }, + { + "quantity":"many", + "value":"[TS_794234]_Delete %d alarms?" + }, + { + "quantity":"two", + "value":"[TS_794266]_Delete %d alarms?" + } + ] + }, + { + "name":"times", + "value":[ + { + "quantity":"one", + "value":"[TS_816880]_%dx" + }, + { + "quantity":"many", + "value":"[TS_816883]_%dx" + }, + { + "quantity":"two", + "value":"[TS_816879]_%dx" + }, + { + "quantity":"other", + "value":"[TS_816884]_%dx" + }, + { + "quantity":"zero", + "value":"[TS_816881]_%dx" + }, + { + "quantity":"few", + "value":"[TS_816882]_%dx" + } + ] + }, + { + "name":"ring_within_hours", + "value":[ + { + "quantity":"other", + "value":"[TS_843076]_<%d hours remaining" + }, + { + "quantity":"many", + "value":"[TS_843079]_<%d hours remaining" + }, + { + "quantity":"one", + "value":"[TS_843088]_<%d hour remaining" + }, + { + "quantity":"two", + "value":"[TS_843086]_<%d hours remaining" + }, + { + "quantity":"zero", + "value":"[TS_843083]_<%d hours remaining" + }, + { + "quantity":"few", + "value":"[TS_843075]_<%d hours remaining" + } + ] + } + ] +} \ No newline at end of file diff --git a/feature/alarmclock/src/main/resources/zz_ZX/element/string.json b/feature/alarmclock/src/main/resources/zz_ZX/element/string.json new file mode 100644 index 0000000..00ce178 --- /dev/null +++ b/feature/alarmclock/src/main/resources/zz_ZX/element/string.json @@ -0,0 +1,192 @@ +{ + "string": [ + { + "name": "no_alarms", + "value": "[TS_794218]_No alarms" + }, + { + "name": "ring_in", + "value": "[TS_794245]_Ring in %1$s" + }, + { + "name": "ring_in_new", + "value": "[TS_872839]_Next alarm: %1$s" + }, + { + "name": "ring_in_new_two", + "value": "[TS_881710]_Next alarm: %1$s %2$s" + }, + { + "name": "ring_in_new_other", + "value": "[TS_881709]_Next alarm: %1$s %2$s %3$s" + }, + { + "name": "ring_in_thirety", + "value": "[TS_870996]_Ring in %1$s" + }, + { + "name": "format_notify_text", + "value": "[TS_810230]_%1$s - %2$s" + }, + { + "name": "quit", + "value": "[TS_810219]_Discard" + }, + { + "name": "keep", + "value": "[TS_810228]_Save" + }, + { + "name": "slide_to_turn_off", + "value": "[TS_810218]_Slide to turn off alarm" + }, + { + "name": "alarm_notify_miss_label", + "value": "[TS_810217]_%s (missed)" + }, + { + "name": "list_unselect_all", + "value": "[TS_794213]_Deselect all" + }, + { + "name": "delete_all_alarms_confirm", + "value": "[TS_794221]_Delete all alarms?" + }, + { + "name": "list_select_title_none", + "value": "[TS_794223]_None selected" + }, + { + "name": "save_changes_confirm", + "value": "[TS_810233]_Save your changes?" + }, + { + "name": "delete_alarm_confirm", + "value": "[TS_794222]_Delete this alarm?" + }, + { + "name": "number_of_snoozes", + "value": "[TS_794262]_Number of snoozes" + }, + { + "name": "no_alarms_on", + "value": "[TS_794261]_No alarms on" + }, + { + "name": "ring_duration", + "value": "[TS_794242]_Ring duration" + }, + { + "name": "list_select_all", + "value": "[TS_794248]_Select all" + }, + { + "name": "sleep", + "value": "[TS_810241]_Snooze" + }, + { + "name": "alarm_notify_snooze_title", + "value": "[TS_810242]_%s (snoozed)" + }, + { + "name": "alarm_notify_snooze_context", + "value": "[TS_810234]_Snoozing until %s" + }, + { + "name": "label", + "value": "[TS_794225]_Label" + }, + { + "name": "sound", + "value": "[TS_794241]_Sound" + }, + { + "name": "close", + "value": "[TS_810222]_Stop" + }, + { + "name": "repeat", + "value": "[TS_794227]_Repeat" + }, + { + "name": "snooze_duration", + "value": "[TS_794265]_Snooze duration" + }, + { + "name": "edit_alarm", + "value": "[TS_794224]_Edit alarm" + }, + { + "name": "delete", + "value": "[TS_794239]_Delete" + }, + { + "name": "add_alarm", + "value": "[TS_794229]_Add alarm" + }, + { + "name": "confirm_delete", + "value": "[TS_794251]_Delete" + }, + { + "name": "delete_alarm", + "value": "[TS_794233]_Delete" + }, + { + "name": "fa_no_alarms", + "value": "[TS_843073]_No alarms on" + }, + { + "name": "ringing_soon", + "value": "[TS_843078]_Ringing soon" + }, + { + "name": "alarmReminder_desc", + "value": "[TS_810223]_description" + }, + { + "name": "less_than_one_minute", + "value": "[TS_794230]_Next alarm: Less than %s min" + }, + { + "name": "fa_ringing", + "value": "[TS_843085]_Ringing" + }, + { + "name": "strlenght_full_Toast", + "value": "[TS_816885]_Maximum length reached." + }, + { + "name": "ring_within_other_hours", + "value": "[TS_876306]_<%d hours remaining" + }, + { + "name": "customize", + "value": "[TS_876304]_Custom" + }, + { + "name": "once_alarm", + "value": "[TS_875365]_Never" + }, + { + "name": "ring_within_one_hours", + "value": "[TS_876303]_<%d hour remaining" + }, + { + "name": "alarm_tips_del", + "value": "[TS_878046]_Delete" + }, + { + "name": "show_toast_name", + "value": "[TS_876302]_Holiday info will be updated over WLAN or mobile data." + }, + { + "name": "alarm_tips_title", + "value": "[TS_878045]_编辑列表" + }, + { + "name": "days_of_wake_type", + "value": "[TS_875366]_Work days" + } + ] +} \ No newline at end of file diff --git a/feature/countdown/package-lock.json b/feature/countdown/package-lock.json deleted file mode 100644 index 2de3a00..0000000 --- a/feature/countdown/package-lock.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@ohos/countdown", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@ohos/common": { - "version": "file:../../common" - } - } -} diff --git a/feature/countdown/package.json b/feature/countdown/package.json deleted file mode 100644 index ad31594..0000000 --- a/feature/countdown/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "license": "ISC", - "types": "", - "devDependencies": {}, - "name": "@ohos/countdown", - "description": "a npm package which contains arkUI2.0 page", - "ohos": { - "org": "" - }, - "main": "src/main/ets/components/MainPage/MainPage.ets", - "repository": {}, - "version": "1.0.0", - "dependencies": { - "@ohos/common": "file:../../common" - } -} diff --git a/feature/countdown/src/main/ets/components/CountdownView.ets b/feature/countdown/src/main/ets/components/CountdownView.ets deleted file mode 100644 index 7f51b62..0000000 --- a/feature/countdown/src/main/ets/components/CountdownView.ets +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CountdownState } from '../controller/CountdownController'; -import { ConfigData } from '@ohos/common'; - -@Component -export struct CountdownView { - private viewSize: number = 40; - private context: CanvasRenderingContext2D = new CanvasRenderingContext2D({ antialias: true }); - private normalColor: string = '#007DFF'; - private originRadius: number = 8; - private shadowOffset: number = 2.5; - private shadowColor: string = '#1400001E'; - private scaleWidth: number = 2.5; - private scaleHeight: number = 16; - private scalePadding: number = 10.5; - private pointerOffset: number = 13.5; - private scaleBackgroundColor: string = '#66182431'; - private times: number = 2; - @Prop totalTime: number; - @Prop currentTime: number; - @Prop state: CountdownState; - - /** - * Draw CountdownView. - */ - private draw(): void { - this.context.clearRect(0, 0, this.viewSize, this.viewSize); - - this.drawDials(); - this.drawPointer(this.totalTime, this.currentTime); - } - - /** - * Draw origin circle and tick marks. - */ - private drawDials(): void { - this.context.save(); - this.context.beginPath(); - this.context.fillStyle = this.normalColor; - this.context.arc(this.viewSize / this.times, this.viewSize / this.times, this.originRadius, 0, Math.PI * this.times); - this.context.fill(); - this.context.shadowBlur = this.originRadius * this.times + this.shadowOffset; - this.context.shadowColor = this.shadowColor; - this.context.restore(); - - for (let n = 0; n < ConfigData.SCALE_NUM; n++) { - var theta = n * (Math.PI * this.times) / ConfigData.SCALE_NUM; - this.context.save(); - this.context.lineWidth = this.scaleWidth; - this.context.beginPath(); - var x_move = (this.viewSize / this.times - this.scalePadding) * Math.sin(theta) + this.viewSize / this.times; - var y_move = -(this.viewSize / this.times - this.scalePadding) * Math.cos(theta) + this.viewSize / this.times; - var x_to = (this.viewSize / this.times - (this.scalePadding + this.scaleHeight)) * Math.sin(theta) + this.viewSize / this.times; - var y_to = -(this.viewSize / this.times - (this.scalePadding + this.scaleHeight)) * Math.cos(theta) + this.viewSize / this.times; - this.context.strokeStyle = - (this.state == CountdownState.RUNNING || this.state == CountdownState.PAUSED) && - (this.currentTime / this.totalTime > n / ConfigData.SCALE_NUM) && n ? - 'rgb(' + Math.ceil(ConfigData.COLOR_R - (ConfigData.COLOR_R / ConfigData.SCALE_NUM * n)) + ',' - + Math.ceil(ConfigData.COLOR_G_START - (ConfigData.COLOR_G_START - ConfigData.COLOR_G_END) / ConfigData.SCALE_NUM * n) + ',' + ConfigData.COLOR_B + ')' : - this.scaleBackgroundColor; - this.context.moveTo(x_move, y_move); - this.context.lineTo(x_to, y_to); - this.context.stroke(); - this.context.restore(); - } - } - - /** - * Draw pointer. - */ - private drawPointer(totalTime: number, currentTime: number): void { - this.context.save(); - this.context.strokeStyle = this.normalColor; - this.context.shadowBlur = this.scaleWidth + this.shadowOffset; - this.context.shadowColor = this.shadowColor; - var theta = totalTime ? currentTime / totalTime * Math.PI * this.times : 0; - this.context.beginPath(); - this.context.lineWidth = this.scaleWidth; - var x_move = (this.viewSize / this.times - this.scalePadding) * Math.sin(theta) + this.viewSize / this.times; - var y_move = -(this.viewSize / this.times - this.scalePadding) * Math.cos(theta) + this.viewSize / this.times; - var x_to = -(this.originRadius + this.pointerOffset) * Math.sin(theta) + this.viewSize / this.times; - var y_to = (this.originRadius + this.pointerOffset) * Math.cos(theta) + this.viewSize / this.times; - this.context.moveTo(x_move, y_move); - this.context.lineTo(x_to, y_to); - this.context.stroke(); - this.context.restore(); - } - - build() { - Canvas(this.context) - .width(this.viewSize) - .height(this.viewSize) - .backgroundColor($r('app.color.background_color_white')) - .border({ radius: this.viewSize }) - .shadow({ radius: $r('app.float.radius_value_26_5'), color: this.shadowColor }) - .onReady(() => { - this.draw(); - }); - } -} \ No newline at end of file diff --git a/feature/countdown/src/main/ets/components/TimeSelectView.ets b/feature/countdown/src/main/ets/components/TimeSelectView.ets deleted file mode 100644 index 5162ad5..0000000 --- a/feature/countdown/src/main/ets/components/TimeSelectView.ets +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ConfigData, LogUtil } from '@ohos/common'; - -const TAG = 'TimeSelectView : '; - -@Component -export struct TimeSelectView { - private marginRight: number | Resource = $r('app.float.distance_30'); - private dividerMargin: number | Resource = $r('app.float.distance_75'); - private hourRange: number[] = Array.from(new Array(ConfigData.HOUR_SELECT).keys()); - private minuteRange: number[] = Array.from(new Array(ConfigData.MINUTE_SELECT).keys()); - private secondRange: number[] = Array.from(new Array(ConfigData.SECOND_SELECT).keys()); - private callBack?: () => void; - private sca: number = 1; - @Link hour: string; - @Link minute: string; - @Link second: string; - - build() { - Flex({ justifyContent: FlexAlign.Center, alignContent: FlexAlign.Start }) { - Stack() { - Row() { - TimeSelectItem({ - fruits: this.convert(this.hourRange), - text: $r('app.string.hour'), - isVisibility: true, - marginRight: this.marginRight, - value: $hour, - onChangCallBack: this.callBack - }); - - TimeSelectItem({ - fruits: this.convert(this.minuteRange), - text: $r('app.string.minute'), - isVisibility: true, - marginRight: this.marginRight, - value: $minute, - onChangCallBack: this.callBack - }); - - TimeSelectItem({ - fruits: this.convert(this.secondRange), - text: $r('app.string.second'), - isVisibility: false, - marginRight: this.marginRight, - value: $second, - onChangCallBack: this.callBack - }); - } - - Divider() - .strokeWidth(1) - .color($r('app.color.divider_color')) - .margin({ - left: this.dividerMargin, - right: this.dividerMargin, top: $r('app.float.distance_35') - }); - - Divider() - .strokeWidth(1) - .color($r('app.color.divider_color')) - .margin({ - left: this.dividerMargin, - right: this.dividerMargin, bottom: $r('app.float.distance_78') - }); - } - } - .scale({ x: this.sca, y: this.sca }) - } - - /** - * Make up 0 if less than 10. - */ - private convert(char: Array) { - let str = []; - for (let i = 0; i < char.length; i++) { - str.push((char[i] > 9 ? '' : '0') + char[i]); - } - return str; - } -} - -@Component -struct TimeSelectItem { - private fruits: string[] = []; - private text: ResourceStr = ''; - private isVisibility: boolean = true; - private marginRight: number | Resource = $r('app.float.distance_30'); - private componentWidth: number | Resource = $r('app.float.wh_value_40'); - private componentHeight: number | Resource = $r('app.float.wh_value_160'); - private onChangCallBack?: () => void; - @Link value: string; - - build() { - Row() { - Column() { - TextPicker({ range: this.fruits, selected: Number(this.value) }) - .backgroundColor($r('app.color.background_color')) - .width(this.componentWidth) - .height(this.componentHeight) - .onChange((value: string, index: number) => { - this.value = value; - this.onChangCallBack(); - LogUtil.info(`${TAG} Picker item changed, value: ${value}, index: ${index} `); - }); - Text(this.text).fontSize($r('app.float.font_24')).fontWeight(FontWeight.Regular).height($r('app.float.wh_value_21_5')) - }.margin({ right: this.isVisibility ? this.marginRight : $r('app.float.distance_0') }); - - Column() { - Text(':').fontSize($r('app.float.font_40')).fontWeight(FontWeight.Regular).fontColor($r('app.color.font_color')); - Text('').fontSize($r('app.float.font_24')).fontWeight(FontWeight.Regular).height($r('app.float.wh_value_21_5')); - }.margin({ right: this.marginRight }) - .visibility(this.isVisibility ? Visibility.Visible : Visibility.None); - } - } -} \ No newline at end of file diff --git a/feature/countdown/src/main/ets/controller/CountdownController.ets b/feature/countdown/src/main/ets/controller/CountdownController.ets deleted file mode 100644 index d54767c..0000000 --- a/feature/countdown/src/main/ets/controller/CountdownController.ets +++ /dev/null @@ -1,277 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import dataStorage from '@ohos.data.preferences'; -import { ConfigData, LogUtil } from '@ohos/common'; - -const TAG = 'CountdownController : '; - -export enum CountdownState { - /** - * The total time in the initial state is not set. The default is 0, but can be triggered by the reset() method or PREPARED to set it to 0 - */ - IDLE, - - /** - * Set the legal time (positive number) at this time to cancel the ash can click Start - */ - PREPARED, - - /** - * Continuously update the time starting from click on PREPARE or PAUSED - */ - RUNNING, - - /** - * Click Pause from the RUNNING state - */ - PAUSED, - - /** - * RUNNING status Automatically transitions to STOPPED when the countdown ends. This state is not triggered externally - */ - STOPPED -} - -export class CountdownController { - private totalTimeMs: number = 0; - private currentTimeMs: number = 0; - private notifierId: number = 0; - private startTimestamp: number = 0; - private startAnchorTimeMs: number = 0; - private state: CountdownState = CountdownState.IDLE; - private timeUpdateListener: (currentTimeMs: number, totalTimeMs: number) => void = undefined; - private stateUpdateListener: (CountdownState) => void = undefined; - private preferences = null; - private totalTimeMsStr: string = 'totalTimeMs'; - private countdownStr: string = 'Countdown'; - private countdownStateStr: string = 'CountdownState'; - private startAnchorTimeMsStr: string = 'startAnchorTimeMs'; - private startTimestampStr: string = 'startTimestamp'; - - /** - * Set time listener. - */ - public setTimeUpdateListener(listener: (currentTimeMs: number, totalTimeMs: number) => void) { - this.timeUpdateListener = listener; - } - - /** - * Set countdownState listener. - */ - public setStateUpdateListener(listener: (CountdownState) => void) { - this.stateUpdateListener = listener; - } - - /** - * Set countdown time. - */ - public setTime(totalTimeMs: number) { - LogUtil.info(`${TAG} set countdown time. `); - if (this.state != CountdownState.IDLE && this.state != CountdownState.PREPARED) { - throw ('only support set time before start!'); - } - if (totalTimeMs < 0) { - throw ('illegal totalTimeMs!'); - } - this.totalTimeMs = totalTimeMs; - LogUtil.info(`${TAG} countdown totalTime: ${this.totalTimeMs}ms. `); - this.startAnchorTimeMs = this.totalTimeMs; - this.updateData(this.totalTimeMsStr, this.totalTimeMs); - this.updateData(this.startAnchorTimeMsStr, this.startAnchorTimeMs); - this.currentTimeMs = totalTimeMs; - this.notifyTimeChanges(); - if (this.state == CountdownState.IDLE && totalTimeMs > 0) { - this.state = CountdownState.PREPARED; - this.updateData(this.countdownStateStr, this.state); - this.notifyStateChanges(); - } else if (this.state == CountdownState.PREPARED && totalTimeMs == 0) { - this.state = CountdownState.IDLE; - this.updateData(this.countdownStateStr, this.state); - this.notifyStateChanges(); - } - } - - private notifyTimeChanges() { - if (this.timeUpdateListener) { - this.timeUpdateListener(this.currentTimeMs, this.totalTimeMs); - } - } - - private notifyStateChanges() { - if (this.stateUpdateListener) { - this.stateUpdateListener(this.state); - } - } - - /** - * Countdown start. - */ - public start() { - LogUtil.info(`${TAG} countdown start. `); - if (this.state != CountdownState.PREPARED && this.state != CountdownState.PAUSED) { - return; - } - LogUtil.info(`${TAG} countdown start in. `); - if (this.totalTimeMs > 0 && this.currentTimeMs > 0) { - if (this.currentTimeMs >= ConfigData.NOTIFY_INTERVAL_MS) { - this.startTimestamp = new Date().getTime(); - this.updateData(this.startTimestampStr, this.startTimestamp); - this.countdown(); - } else { - this.lastRefresh(); - } - } - this.state = CountdownState.RUNNING; - this.updateData(this.countdownStateStr, this.state); - this.notifyStateChanges(); - } - - /** - * Countdown start-up. - */ - public countdown() { - LogUtil.info(`${TAG} countdown start up. `); - this.notifierId = setInterval(() => { - let timestamp = new Date().getTime(); - this.currentTimeMs = this.startAnchorTimeMs - (timestamp - this.startTimestamp); - if (this.currentTimeMs <= 0) { - this.currentTimeMs = 0; - clearInterval(this.notifierId); - this.notifierId = 0; - } else if (this.currentTimeMs < ConfigData.NOTIFY_INTERVAL_MS) { - clearInterval(this.notifierId); - this.notifierId = 0; - this.lastRefresh(); - } - this.notifyTimeChanges(); - if (this.currentTimeMs == 0) { - this.state = CountdownState.STOPPED; - this.updateData(this.countdownStateStr, this.state); - this.notifyStateChanges(); - } - }, ConfigData.NOTIFY_INTERVAL_MS); - } - - /** - * Countdown reStart. - */ - public reStart() { - LogUtil.info(`${TAG} countdown reStart. `) - if (this.state == CountdownState.STOPPED) { - this.state = CountdownState.PREPARED; - this.updateData(this.countdownStateStr, this.state); - this.notifyStateChanges(); - } - } - - /** - * Countdown lastRefresh. - */ - public lastRefresh() { - LogUtil.info(`${TAG} countdown lastRefresh. `); - setTimeout(() => { - this.currentTimeMs = 0; - this.notifyTimeChanges(); - this.state = CountdownState.STOPPED; - this.updateData(this.countdownStateStr, this.state); - this.notifyStateChanges(); - }, this.currentTimeMs); - } - - /** - * Countdown pause. - */ - public pause() { - LogUtil.info(`${TAG} countdown pause. `); - if (this.state != CountdownState.RUNNING) { - return; - } - LogUtil.info(`${TAG} countdown pause in. `); - let timestamp = new Date().getTime(); - clearInterval(this.notifierId); - this.startAnchorTimeMs = this.startAnchorTimeMs - (timestamp - this.startTimestamp); - this.updateData(this.startAnchorTimeMsStr, this.startAnchorTimeMs); - this.notifierId = 0; - this.state = CountdownState.PAUSED; - this.updateData(this.countdownStateStr, this.state); - this.notifyStateChanges(); - } - - /** - * Countdown reset. - */ - public reset() { - LogUtil.info(`${TAG} countdown reset. `); - if (this.state == CountdownState.IDLE) { - return; - } - LogUtil.info(`${TAG} countdown reset in. `); - if (this.notifierId != 0) { - clearInterval(this.notifierId); - this.notifierId = 0; - } - this.totalTimeMs = 0; - this.updateData(this.totalTimeMsStr, this.totalTimeMs); - this.currentTimeMs = 0; - this.startTimestamp = 0; - this.updateData(this.startTimestampStr, this.startTimestamp); - this.startAnchorTimeMs = 0; - this.notifyTimeChanges(); - this.state = CountdownState.IDLE; - this.updateData(this.countdownStateStr, this.state); - this.notifyStateChanges(); - } - - /** - * Get preferences data. - */ - public async getPreferences() { - if (!this.preferences) { - this.preferences = await dataStorage.getPreferences(globalThis.abilityContext, this.countdownStr); - } - } - - /** - * Instance matching a specified preferences file name. - */ - public async getData() { - await this.getPreferences(); - this.state = await this.preferences.get(this.countdownStateStr, CountdownState.IDLE); - this.startTimestamp = await this.preferences.get(this.startTimestampStr, 0); - this.totalTimeMs = await this.preferences.get(this.totalTimeMsStr, 0); - this.startAnchorTimeMs = await this.preferences.get(this.startAnchorTimeMsStr, 0); - - if (this.state == CountdownState.PREPARED) { - this.currentTimeMs = this.totalTimeMs; - } else if (this.state == CountdownState.RUNNING) { - this.countdown(); - } else if (this.state == CountdownState.PAUSED) { - this.currentTimeMs = this.startAnchorTimeMs; - } - this.notifyStateChanges(); - this.notifyTimeChanges(); - } - - /** - * Put and flush preferences data. - */ - public async updateData(key: string, defValue: string | number) { - if (this.preferences) { - await this.preferences.put(key, defValue); - await this.preferences.flush(); - } - } -} \ No newline at end of file diff --git a/feature/countdown/src/main/resources/base/element/color.json b/feature/countdown/src/main/resources/base/element/color.json deleted file mode 100644 index 240a3f9..0000000 --- a/feature/countdown/src/main/resources/base/element/color.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "color": [ - { - "name": "background_color_white", - "value": "#FFFFFF" - }, - { - "name": "background_color", - "value": "#f1f3f5" - }, - { - "name": "divider_color", - "value": "#000000" - }, - { - "name": "font_color", - "value": "#2B78FB" - } - ] -} \ No newline at end of file diff --git a/feature/countdown/src/main/resources/base/element/float.json b/feature/countdown/src/main/resources/base/element/float.json deleted file mode 100644 index abe9f37..0000000 --- a/feature/countdown/src/main/resources/base/element/float.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "float": [ - { - "name": "wh_value_21_5", - "value": "21.5vp" - }, - { - "name": "wh_value_40", - "value": "40vp" - }, - { - "name": "wh_value_160", - "value": "160vp" - }, - { - "name": "radius_value_26_5", - "value": "26.5vp" - }, - { - "name": "font_24", - "value": "24px" - }, - { - "name": "font_40", - "value": "40px" - }, - { - "name": "distance_0", - "value": "0vp" - }, - { - "name": "distance_30", - "value": "30vp" - }, - { - "name": "distance_35", - "value": "35vp" - }, - { - "name": "distance_75", - "value": "75vp" - }, - { - "name": "distance_78", - "value": "78vp" - } - ] -} \ No newline at end of file diff --git a/feature/countdown/src/main/resources/zh_CN/element/string.json b/feature/countdown/src/main/resources/zh_CN/element/string.json deleted file mode 100644 index c2af9ff..0000000 --- a/feature/countdown/src/main/resources/zh_CN/element/string.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "string": [ - { - "name": "hour", - "value": "时" - }, - { - "name": "minute", - "value": "分" - }, - { - "name": "second", - "value": "秒" - } - ] -} diff --git a/feature/stopwatch/BuildProfile.ets b/feature/stopwatch/BuildProfile.ets new file mode 100644 index 0000000..3a501e5 --- /dev/null +++ b/feature/stopwatch/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/feature/countdown/build-profile.json5 b/feature/stopwatch/build-profile.json5 similarity index 100% rename from feature/countdown/build-profile.json5 rename to feature/stopwatch/build-profile.json5 diff --git a/product/pc/hvigorfile.js b/feature/stopwatch/hvigorfile.ts similarity index 62% rename from product/pc/hvigorfile.js rename to feature/stopwatch/hvigorfile.ts index d7720ee..9d84ef8 100644 --- a/product/pc/hvigorfile.js +++ b/feature/stopwatch/hvigorfile.ts @@ -1,2 +1,2 @@ // Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. -module.exports = require('@ohos/hvigor-ohos-plugin').hapTasks +module.exports = require('@ohos/hvigor-ohos-plugin').harTasks; diff --git a/product/pc/src/ohosTest/ets/Application/TestAbilityStage.ts b/feature/stopwatch/index.ets similarity index 68% rename from product/pc/src/ohosTest/ets/Application/TestAbilityStage.ts rename to feature/stopwatch/index.ets index 23d6aa9..3bcac74 100644 --- a/product/pc/src/ohosTest/ets/Application/TestAbilityStage.ts +++ b/feature/stopwatch/index.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -12,11 +12,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import AbilityStage from "@ohos.application.AbilityStage" -export default class TestAbilityStage extends AbilityStage { - onCreate() { - console.log("[Demo] TestAbilityStage onCreate") - } -} \ No newline at end of file +export { Stopwatch } from './src/main/ets/pages/index'; \ No newline at end of file diff --git a/feature/stopwatch/oh-package-lock.json5 b/feature/stopwatch/oh-package-lock.json5 new file mode 100644 index 0000000..688e757 --- /dev/null +++ b/feature/stopwatch/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@hmos/common@../../common": "@hmos/common@../../common", + "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har": "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har" + }, + "packages": { + "@hmos/common@../../common": { + "name": "@ohos/common", + "version": "1.0.0", + "resolved": "../../common", + "registryType": "local", + "dependencies": { + "@ohos/lottie": "file:./src/main/ets/libs/lottieArkTS.har" + } + }, + "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har": { + "name": "@ohos/lottie", + "version": "2.0.5", + "resolved": "../../common/src/main/ets/libs/lottieArkTS.har", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/feature/stopwatch/oh-package.json5 b/feature/stopwatch/oh-package.json5 new file mode 100644 index 0000000..c9aa55c --- /dev/null +++ b/feature/stopwatch/oh-package.json5 @@ -0,0 +1,14 @@ +{ + "license": "ISC", + "types": "", + "devDependencies": { + "@hmos/common": "file:../../common" + }, + "name": "@hmos/stopwatch", + "description": "a npm package which contains pages of stopwatch", + "main": "index.ets", + "repository": {}, + "version": "1.0.0", + "dynamicDependencies": {}, + "dependencies": {} +} diff --git a/feature/stopwatch/src/main/ets/SoundManager/SoundPoolManager.ets b/feature/stopwatch/src/main/ets/SoundManager/SoundPoolManager.ets new file mode 100644 index 0000000..068178b --- /dev/null +++ b/feature/stopwatch/src/main/ets/SoundManager/SoundPoolManager.ets @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import media from '@ohos.multimedia.media'; +import audio from '@ohos.multimedia.audio'; +import fs from '@ohos.file.fs'; +import { LogUtil, GlobalContext } from '@hmos/common'; +import resourceManager from '@ohos.resourceManager'; + +const TAG: string = 'SoundPoolManager'; + +interface ISoundPoolManager { + create: (options: ICreateOptions) => Promise; + openFile: (filePath: string, openMode: number) => Promise; + openResourceFile: (resourceName: string, context: Context) => Promise; + loadFile: (rawFileDescriptor: resourceManager.RawFileDescriptor, soundPool: media.SoundPool) => Promise; + play: (soundId: number, mediaOptions: media.PlayParameters, soundPool: media.SoundPool) => Promise +} + +interface ICreateOptions { + maxStreams: number; // soundPool实例的最大播放的流数 + audioRenderInfo: audio.AudioRendererInfo; // 音频播放参数信息 + +} + +function getContext(): Context { + return GlobalContext.getContext().getObject('clockContext') as Context; +} + +const soundPoolManager: ISoundPoolManager = { + async create(options) { + let soundPool = {} as media.SoundPool; + try { + soundPool = await media.createSoundPool(options.maxStreams, options.audioRenderInfo); + LogUtil.info(TAG, `createSoundPool success.`); + return soundPool; + } catch (error) { + LogUtil.error(TAG, `create fail. code: ${error.code}, message: ${error.message}`); + } + return soundPool; + }, + async openResourceFile(resourceName, context) { + let rawFileDescriptor = {} as resourceManager.RawFileDescriptor; + try { + const resourceManagerInstance = context.resourceManager; + rawFileDescriptor = await resourceManagerInstance.getRawFd(resourceName); + LogUtil.info(TAG, `openResourceFile success.`, String(rawFileDescriptor.fd), String(rawFileDescriptor.offset), String(rawFileDescriptor.length)); + return rawFileDescriptor; + } catch (error) { + LogUtil.error(TAG, `openResourceFile fail. code: ${error.code}, message: ${error.message}`); + } + return rawFileDescriptor; + }, + async openFile(filePath, openMode) { + let file = {} as fs.File; + // 获取文件 + try { + file = await fs.open(filePath, openMode); + LogUtil.info(TAG, `open file success. filePath: ${filePath}`); + return file; + } catch (error) { + LogUtil.error(TAG, `open file fail. code: ${error.code}, message: ${error.message}, filePath: ${filePath}`); + } + return file; + }, + async loadFile(rawFileDescriptor, soundPool) { + let soundId: number = -1; + try { + const soundId: number = await soundPool.load(rawFileDescriptor.fd, + rawFileDescriptor.offset, rawFileDescriptor.length); + LogUtil.info(TAG, `loadFile success.`, String(soundId)); + return soundId; + } catch (error) { + LogUtil.error(TAG, `loadFile fail. code: ${error.code}, message: ${error.message}`); + } + return soundId; + }, + async play(soundId, mediaOptions, soundPool) { + + try { + const streamId = await soundPool.play(soundId, mediaOptions); + LogUtil.info(TAG, `play success.`); + } catch (error) { + LogUtil.error(TAG, `play fail. code: ${error.code}, message: ${error.message}`); + } + } +} + +const MAX_STREAMS = 5; +const createOptions: ICreateOptions = { + maxStreams: MAX_STREAMS, + audioRenderInfo: { + usage: audio.StreamUsage.STREAM_USAGE_MEDIA, + rendererFlags: 1 + } +}; +const mediaPlayOptions: media.PlayParameters = { + loop: 0, // 循环0次 + rate: audio.AudioRendererRate.RENDER_RATE_NORMAL, // 正常倍速 + leftVolume: 0.5, + rightVolume: 0.5, + priority: 0, // 最低优先级 + parallelPlayFlag: false // 不和其它正在播放的音频并行播放 +}; +let soundPool: media.SoundPool; +let soundIdArr: number[] = []; +const resourceName = 'stopwatch_lap.wav'; +let count = 0; + +export async function soundPoolInit() { + const context = getContext(); + if (!soundPool) { + soundPool = await soundPoolManager.create(createOptions); + + soundPool.on('loadComplete', (soundId: number) => { + LogUtil.info(TAG, 'loadComplete success,soundId:' + soundId); + }) + soundPool.on('playFinished', () => { + LogUtil.info(TAG, 'playFinished success'); + }) + } + const rawFileDescriptor = await soundPoolManager.openResourceFile(resourceName, context); + const s1 = await soundPoolManager.loadFile(rawFileDescriptor, soundPool); + const s2 = await soundPoolManager.loadFile(rawFileDescriptor, soundPool); + const s3 = await soundPoolManager.loadFile(rawFileDescriptor, soundPool); + const s4 = await soundPoolManager.loadFile(rawFileDescriptor, soundPool); + const s5 = await soundPoolManager.loadFile(rawFileDescriptor, soundPool); + soundIdArr.push(s1); + soundIdArr.push(s2); + soundIdArr.push(s3); + soundIdArr.push(s4); + soundIdArr.push(s5); +} + +export async function stopWatchTickHandle() { + if (soundPool) { + const soundId = soundIdArr[count]; + LogUtil.info(TAG, 'play', String(count), String(soundId)); + await soundPoolManager.play(soundId, mediaPlayOptions, soundPool); + count++; + if (count >= MAX_STREAMS) { + count = 0; + } + } +} + +export default soundPoolManager; \ No newline at end of file diff --git a/feature/stopwatch/src/main/ets/components/StopwatchDial/StopwatchDialScale.ets b/feature/stopwatch/src/main/ets/components/StopwatchDial/StopwatchDialScale.ets new file mode 100644 index 0000000..f2e0fe2 --- /dev/null +++ b/feature/stopwatch/src/main/ets/components/StopwatchDial/StopwatchDialScale.ets @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MINUTES_ANGLE_STEP, SECONDS_ANGLE_STEP, CONST_STOPWATCH_POINTER } from './types'; +import { FULL_CIRCLE_ANGLE, HALF_CIRCLE_ANGLE } from '@hmos/common/src/main/ets/components/Clock/types'; +import { DIAL_DIAMETER_RATIO, LogUtil } from '@hmos/common'; + +const MINUTEHAND_SCALE_RATIO = 0.32; +const MINUTEHAND_OFFSET_RATIO = 0.18; + +/** + * Stopwatch Dial, scale and hands + * + * @since 2023-09-08 + */ +@Component +export struct StopwatchDialScale { + @Prop isPortraitOrientation: boolean = true; + @Prop private diameter: number = 0; + @Link @Watch('updateAngle') passedTime: number; + @State secondsAngle: number = 0; + @State minutesAngle: number = 0; + + private updateAngle(): void { + if (this.passedTime == 0) { + if (this.secondsAngle > 0) { + animateTo({ + duration: 350, + curve: Curve.Friction, + }, () => { + this.secondsAngle = this.secondsAngle > HALF_CIRCLE_ANGLE ? FULL_CIRCLE_ANGLE : 0; + this.minutesAngle = this.minutesAngle > HALF_CIRCLE_ANGLE ? FULL_CIRCLE_ANGLE : 0; + }) + } + } else { + this.secondsAngle = (this.passedTime * SECONDS_ANGLE_STEP) % FULL_CIRCLE_ANGLE; + this.minutesAngle = (this.passedTime * MINUTES_ANGLE_STEP) % FULL_CIRCLE_ANGLE; + } + } + + @Builder + buildMinutesDial() { + Stack() { + Image($r('app.media.img_stopwatch_minutes_dial_scale')) + .height(this.diameter * MINUTEHAND_SCALE_RATIO) + .width(this.diameter * MINUTEHAND_SCALE_RATIO) + .objectFit(ImageFit.Contain) + .draggable(false) + .interpolation(ImageInterpolation.Low) + Image($r('app.media.img_stopwatch_minutehand')) + .height(this.diameter * MINUTEHAND_SCALE_RATIO) + .objectFit(ImageFit.Contain) + .draggable(false) + .interpolation(ImageInterpolation.Low) + .rotate({ angle: this.minutesAngle }) + Image($r('app.media.img_stopwatch_minutehand_shadow')) + .height(this.diameter * MINUTEHAND_SCALE_RATIO) + .objectFit(ImageFit.Contain) + .draggable(false) + .interpolation(ImageInterpolation.Low) + .rotate({ angle: this.minutesAngle }) + } + .offset({ y: -this.diameter * MINUTEHAND_OFFSET_RATIO }) + .width('100%') + } + + build() { + Stack() { + Image($r('app.media.img_stopwatch_dial_scale')) + .height(this.diameter * DIAL_DIAMETER_RATIO) + .width(this.diameter * DIAL_DIAMETER_RATIO) + .objectFit(ImageFit.Contain) + .draggable(false) + .interpolation(ImageInterpolation.Low) + this.buildMinutesDial() + } + } +} \ No newline at end of file diff --git a/feature/stopwatch/src/main/ets/components/StopwatchDial/index.ets b/feature/stopwatch/src/main/ets/components/StopwatchDial/index.ets new file mode 100644 index 0000000..54d1610 --- /dev/null +++ b/feature/stopwatch/src/main/ets/components/StopwatchDial/index.ets @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BreakPoint, + CLOCK_RATIO_FOR_LG, + CLOCK_RATIO_FOR_NORMAL, + CLOCK_SCALE_VALUE, + HALF, + LogUtil, + SCALE_DIAMETER_RATIO, + SizeInfo, + UPDATE_WIDTH_DELAY, + GlobalContext, + CLOCK_RATIO_FOR_NORMAL_CUSTOM, + CLOCK_RATIO_FOR_LG_CUSTOM +} from '@hmos/common'; + +import window from '@ohos.window'; +import { StopwatchDialScale } from './StopwatchDialScale'; + +const TAG = 'Stopwatch(Component)'; + +/** + * An entrance to a stopwatch assembly + * + * @since 2023-09-08 + */ +@Component +export struct StopwatchDial { + @Link passedTime: number; + @StorageLink('mainWindow') mainWindow: window.Window | undefined = undefined; + @StorageProp('windowStandardHeight') windowStandardHeight: number = 0; + @StorageProp('currentBreakpoint') @Watch('updateClockRatio') currentBreakpoint: string = ''; + @State diameter: number = 0; // Dial diameter (in vp) + @State clockRatio: number = CLOCK_RATIO_FOR_NORMAL_CUSTOM; + @Prop scaleDiameterRatio: number = SCALE_DIAMETER_RATIO; + @Prop isPortraitOrientation: Boolean = true; + + async aboutToAppear(): Promise { + const windowSize: SizeInfo | window.Rect = (this.mainWindow?.getWindowProperties())?.windowRect || + { width: 0, height: 0 }; + this.updateStopwatchDiameter(windowSize as ESObject); + this.mainWindow?.on('windowSizeChange', (sizeInfo: SizeInfo) => this.updateStopwatchDiameter(sizeInfo)); + } + + aboutToDisappear(): void { + try { + if (this.mainWindow && this.mainWindow.isWindowShowing()) { + this.mainWindow.off('windowSizeChange'); + } + } catch (error) { + LogUtil.info(TAG, `${JSON.stringify(error)}`) + } + } + + @Builder + buildStopwatchDial() { + Column() { + Stack() { + StopwatchDialScale({ + isPortraitOrientation: false, + diameter: this.diameter * this.scaleDiameterRatio, + passedTime: $passedTime, + }); + } + .id('id_stopwatch') + .width('100%') + .height('100%') + .transition({ type: TransitionType.All, opacity: 0, scale: { x: CLOCK_SCALE_VALUE, y: CLOCK_SCALE_VALUE } }) + } + .justifyContent(FlexAlign.Center) + } + + build() { + if (this.diameter) { + Row() { + this.buildStopwatchDial(); + } + .width('100%') + .height(this.diameter) + .justifyContent(FlexAlign.Center) + } + } + + private updateClockRatio(): void { + this.clockRatio = this.currentBreakpoint === BreakPoint.LG ? CLOCK_RATIO_FOR_LG_CUSTOM : + CLOCK_RATIO_FOR_NORMAL_CUSTOM; + } + + private async onWindowSizeChange(sizeInfo: SizeInfo): Promise { + if (GlobalContext.getContext().getObject('updateDiameterId')) { + clearTimeout(GlobalContext.getContext().getObject('updateDiameterId') as number); + } + GlobalContext.getContext() + .setObject('updateDiameterId', setTimeout(() => this.updateStopwatchDiameter(sizeInfo), UPDATE_WIDTH_DELAY)); + } + + private updateStopwatchDiameter(sizeInfo: SizeInfo): void { + LogUtil.info(TAG, 'updateDiameter, currentBreakpoint:', this.currentBreakpoint); + if (!sizeInfo || !sizeInfo.height || !sizeInfo.width) { + return; + } + const height: number = sizeInfo.height || this.windowStandardHeight; + const longEdge = Math.max(height, sizeInfo.width); + const shortEdge = Math.min(height, sizeInfo.width); + this.diameter = px2vp(Math.min(longEdge * HALF, shortEdge) * this.clockRatio); + LogUtil.info(TAG, 'updateDiameter, diameter:' + this.diameter); + } +} \ No newline at end of file diff --git a/feature/stopwatch/src/main/ets/components/StopwatchDial/types.ets b/feature/stopwatch/src/main/ets/components/StopwatchDial/types.ets new file mode 100644 index 0000000..913c0be --- /dev/null +++ b/feature/stopwatch/src/main/ets/components/StopwatchDial/types.ets @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const SECONDS_ANGLE_STEP = 0.00599; + +export const MINUTES_ANGLE_STEP = 0.0002; + +export const CONST_STOPWATCH_POINTER = 24; + diff --git a/feature/stopwatch/src/main/ets/components/index.ets b/feature/stopwatch/src/main/ets/components/index.ets new file mode 100644 index 0000000..73c45e1 --- /dev/null +++ b/feature/stopwatch/src/main/ets/components/index.ets @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { StopwatchDial } from './StopwatchDial/index'; \ No newline at end of file diff --git a/feature/stopwatch/src/main/ets/pages/index.ets b/feature/stopwatch/src/main/ets/pages/index.ets new file mode 100644 index 0000000..95a1147 --- /dev/null +++ b/feature/stopwatch/src/main/ets/pages/index.ets @@ -0,0 +1,1188 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Card, + CommonGrid, + FloatingActionButton, + FloatingActionButtonColor, + LogUtil, + SPECAIL_CLOCK_RATIO_FOR_NORMAL_CUSTOM, + BreakPoint, + EventReportUtil, + EventName, + GlobalContext, + FROM_DATA_STORE, + STOP_WATCH_DATA_STORE, + SoundPool, + CommonUtil, + EVENT_ID_SEND_ALARM +} from '@hmos/common'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import { StopwatchDial } from '../components'; +import preferencesUtil from '@ohos.data.preferences'; +import device, { DeviceResponse } from '@system.device' +import window from '@ohos.window'; +import { BusinessError } from '@ohos.base'; +import { + VERTICAL_HEIGHT, + CROSS_HEIGHT, + PC_HEIGHT, + COMMEN_X, + VERTICAL_Y, + COMMEN_Z, + COMMEN_Y, + COMMEN_HEIGHT, + ADD_HEIGHT, + INIT_VERHEIGHT, + ADD_VERHEIGHT, + FRIST_H, + SECOND_H, + THIRD_H, + FOURTH_H, + FIVE_H, + SIX_H, + RESTRICT, + STOPTIME, + RESTRICT_MAXTIME, + BREAKLG, + RESTRICT_HEIGHT, + BREAKLG_HEIGHT, + FLOD_ALL_HEIGHT, + VERTICAL_OHOS_HEIGHT, + FLOD_CROSS_HEIGHT, + FLOD_VERTICAL_HEIGHT_SECOND, + PRIVATE_FIRST_HEIGHT, + PRIVATE_SECOND_HEIGHT, + DEFAULT_HEIGHT +} from './types' +import promptAction from '@ohos.promptAction' +import curves from '@ohos.curves'; +import lottie, { AnimationItem } from '../../../../../../common/oh_modules/@ohos/lottie'; +import deviceInfo from '@ohos.deviceInfo' +import emitter from '@ohos.events.emitter'; + +/** + * Main page for stopwatch + * + * @since 2023-08-15 + */ +const TAG: string = 'Stopwatch---'; +const CLOCK_DIAL_AREA_OFFSET = 340; +const TICK_FILE_NAME = 'stopwatch_tick.wav'; +const LAP_FILE_NAME = 'stopwatch_lap.wav'; +const SCALE_DIAMETER_RATIO = 0.94; +const FOLD_SCREEN_DIAMETER_RATIO = 0.85; + +interface LapItem { + index: number; + fromPrevLapTime: number; + totalTime: number; +} + +interface ReportParams { + PACKAGE_NAME: string; + PROCESS_NAME: string; + RESULT: string; + VALUE?: string; + STATUS?: string +} + +interface OffsetNum { + yOffset: number +} + +interface ITranslateList { + x: number, + y: number, + z: number +} + + +@Component +export struct Stopwatch { + @StorageProp('currentAbleScreen') @Watch('listenFoldScreen') foldAbleScreen: number = 0; + @Prop @Watch('listenProationChangeg') isPortraitOrientation: boolean = true; + @Prop @Watch('pageVisibilityChanged') isStopwatchVisible: boolean = true; + @StorageProp('setOrientaion') @Watch('listenOrientaion') getOrientaion: number = 0; + @Prop currentIndex: number = 0 + @Prop @Watch('onPageToggle') isPageHide: boolean = true; + @Prop isBigView: string = '' + @State translateList: ITranslateList = { x: 0, y: 0, z: 0 }; + @Link isClockHide: boolean; + @State iHeight: number = 0; + @State playTimer: number = 0; + @State @Watch('listenStopwatchRunning') private isStopwatchRunning: boolean = false; + @State @Watch('stopTimesShow') private passedTime: number = 0; + @State private passedFromLastLapTime: number = 0; + @State private lastLapTime: number = 0; + @State private pauseTime: number = 0; + @State private interValFq: number = 90; //计时器频率ms + @State private interValCount: number = 0; //计时器计次数 + @State private laps: LapItem[] = []; + @State private maxNum: number = 0; + @State private minNum: number = 0; + @State private listItemTranslateY: string = '-95vp'; + @State deviceType: string = deviceInfo.deviceType; // 设备类型 + @State recorDing: boolean = false + @State selfFocus: boolean = false; + private startTime: number = 0; + private dateToFormat: Date = new Date(); + private scrollerForScroll: Scroller = new Scroller(); + private textFoldVerticalPadding: number = 54; + private textFoldCrossPadding: number = 62.5; + private titletoppadding: number = 60.5; + private initStopTime: string = ''; + private settings: RenderingContextSettings = new RenderingContextSettings(true) + private controller: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); + // 动画名称 + private canvasAnimateName: string = 'stopWatchPlayAnimate'; + private canvasPauseAnimateName: string = 'stopWatchPauseAnimate'; + private currentAnimateName: string = 'pause'; + // 动画json + private canvasAnimatePath: string = 'resources/rawfile/playToPause.json'; + private canvasPauseAnimatePath: string = 'resources/rawfile/pauseToPlay.json'; + private animateItem: AnimationItem | null = null; + private isDeviceModels: string = 'NOH-AN00' + private isFoldCross: number = 2224; + private isFoldVertical: number = 2496; + private setCode: boolean = false; + private isFoldHeight: number = 485; + @State windowClass: window.Window | undefined = AppStorage.Get('mainWindow') + private timerTimeout: number = -1; + + listenStopwatchRunning() { + if (this.isStopwatchRunning) { + this.initPauseAnimation(); + } else { + this.initAnimation(); + } + } + + aboutToAppear() { + this.listeningAlarm() + // 获取设备类型 + this.deviceType = deviceInfo.deviceType + } + + private isPCLg() { + return this.deviceType === '2in1' + } + + aboutToDisappear() { + LogUtil.info(TAG, 'aboutToDisPage'); + } + + private listeningAlarm() { + emitter.on({ eventId: EVENT_ID_SEND_ALARM }, async (target) => { + if (this.isStopwatchRunning) { + if (target.data?.toggle === 'show') { + await SoundPool.stopForPageHide('stopwatch') + } + if (target.data?.toggle === 'hide' && this.currentIndex === 2 && !this.isPageHide) { + await SoundPool.playSoundPool('stopwatch') + } + } + }); + } + + async onPageToggle(): Promise { + if (this.isStopwatchRunning) { + if (this.isPageHide) { + await SoundPool.stopForPageHide('stopwatch') + this.setWindowKeepScreenOn(false) + } else { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, FROM_DATA_STORE); + const ALARM_ACTIVE = await preferences.get('ALARM_CLOCK_TAB', ''); + LogUtil.info(TAG, `on page show ALARM_ACTIVE=${ALARM_ACTIVE}`); + if (ALARM_ACTIVE) { + await SoundPool.stopForPageHide('stopwatch') + } else { + if (this.currentIndex == 2) { + SoundPool.playStopwatchForDelay() + } + } + this.setWindowKeepScreenOn(true) + } + } else { + this.setWindowKeepScreenOn(false) + } + } + + // CALC LIST HEIGHT + public changeIheight(count: number, code?: boolean) { + switch (count) { + case 1: { + this.iHeight = FRIST_H; + break; + } + case 2: { + this.iHeight = SECOND_H; + break; + } + case 3: { + this.iHeight = THIRD_H; + break; + } + case 4: { + this.iHeight = FOURTH_H; + break; + } + case 5: { + this.iHeight = FIVE_H; + break; + } + case 6: { + this.iHeight = SIX_H; + break; + } + default: { + if (this.foldAbleScreen === 1) { + this.getFoldHeight(code); + } else { + if (this.isDeviceModels === 'ALT-AL10') { + this.iHeight = this.isFoldHeight; + } else { + this.iHeight = this.isDeviceModels !== 'NOH-AN00' ? VERTICAL_OHOS_HEIGHT : VERTICAL_HEIGHT; + } + } + break; + } + } + } + + /** + * Do not modify the folding method + */ + private getFoldHeight(code?: boolean) { + if (code) { + this.setCode = true + } + if (this.getOrientaion === this.isFoldCross) { + this.iHeight = (code ? code : this.setCode) ? FLOD_VERTICAL_HEIGHT_SECOND : FLOD_VERTICAL_HEIGHT_SECOND + + } else { + this.iHeight = (code ? code : this.setCode) ? FLOD_CROSS_HEIGHT : PRIVATE_SECOND_HEIGHT + } + } + + private onScroll(yOffset: number | string): void { + + if (this.deviceType === 'tablet' && this.isPortraitOrientation) { + } + if (yOffset > 0 && this.isClockHide || yOffset < 0 && !this.isClockHide) { + return; + } + const offset = this.scrollerForScroll.currentOffset(); + if (this.deviceType !== 'tablet') { + if (this.laps.length >= 1) { + if (!this.isPortraitOrientation) { + this.scrollerForScroll.scrollTo({ xOffset: 0, yOffset: 0 }) + } else { + this.scrollerForScroll.scrollTo({ xOffset: 0, yOffset: 0 }) + } + } + } + + if (offset.yOffset > CLOCK_DIAL_AREA_OFFSET) { + this.isClockHide = true; + } else { + this.isClockHide = false; + } + } + + private stopTimesShow() { + if (this.passedTime >= STOPTIME) { + this.initStopTime = RESTRICT_MAXTIME + this.onStopwatchToggled(false) + } + } + + private listenProationChangeg() { + if (this.foldAbleScreen === 1) { + return; + } + if (!this.isPortraitOrientation) { + this.listenUpdateCrossScreen() + } else { + this.listenUpdateVertaivalScreen() + } + } + + // listen foldScreen + private listenFoldScreen() { + if (this.foldAbleScreen === 1) { + this.listenUpdateVertaivalScreen(true) + } else { + this.listenUpdateVertaivalScreen() + } + } + + private listenOrientaion() { + if (this.foldAbleScreen === 1) { + this.listenUpdateVertaivalScreen() + } + } + + private listenUpdateCrossScreen() { + if (this.laps.length === 1) { + this.iHeight = FRIST_H + } else if (this.laps.length === 2) { + this.iHeight = SECOND_H; + } else if (this.laps.length === 3) { + this.iHeight = THIRD_H; + } else { + this.iHeight = this.laps.length === 0 ? COMMEN_HEIGHT : CROSS_HEIGHT; + } + this.scrollerForScroll.scrollTo({ xOffset: 0, yOffset: 0 }) + } + + private listenUpdateVertaivalScreen(code?: boolean) { + if (this.laps.length === 0) { + this.titletoppadding = 60.5 + if (this.foldAbleScreen === 1) { + this.textFoldCrossPadding = 62.5 + } else if (this.foldAbleScreen === 2) { + this.textFoldVerticalPadding = 54 + } + } + if (this.laps.length >= 1) { + this.titletoppadding = 0 + this.textFoldVerticalPadding = 0 + this.textFoldCrossPadding = 0 + this.changeIheight(this.laps.length, code) + } else { + this.iHeight = this.foldAbleScreen === 1 ? COMMEN_HEIGHT : COMMEN_HEIGHT; + } + + this.scrollerForScroll.scrollTo({ xOffset: 0, yOffset: 0 }) + } + + //页面进入获取缓存秒表数据 + private async getStopWatchData() { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, STOP_WATCH_DATA_STORE); + let timeToLeave = await preferences.get('timeToLeave', ''); + let nowDate = new Date().getTime(); + let timeDiff = nowDate - Number(timeToLeave); + this.interValCount = this.interValCount + (timeDiff / this.interValFq); + } + + //页面离开缓存秒表数据 + private async cacheStopWatchData() { + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, STOP_WATCH_DATA_STORE); + await preferences.put('timeToLeave', new Date().getTime()); + await preferences.flush() + } + + private async pageVisibilityChanged() { + LogUtil.info(TAG, 'soundPool---isStopwatchVisible=' + this.isStopwatchVisible + ',this.isStopwatchVisible=' + `Stopwatch page visibility changed to ${this.isStopwatchVisible}`); + this.isStopwatchRunning ? this.setWindowKeepScreenOn(true) : this.setWindowKeepScreenOn(false) + this.setWindowKeepScreenOn(true) + if (this.isStopwatchVisible && this.isStopwatchRunning) { + SoundPool.playSoundPool('stopwatch'); + this.getStopWatchData(); + this.refreshTime(); + } else if (!this.isStopwatchVisible) { + LogUtil.info(TAG, 'soundPool---!isStopwatchVisible=' + !this.isStopwatchVisible + ',this.isStopwatchVisible=' + this.isStopwatchVisible) + SoundPool.stopForPageHide('stopwatch'); + this.cacheStopWatchData(); + + } + + } + + private setWindowKeepScreenOn(flag: boolean) { + try { + if (this.windowClass) { + this.windowClass.setWindowKeepScreenOn(flag, (err: BusinessError) => { + const errCode: number = err.code; + if (errCode) { + LogUtil.error(TAG, 'Failed to set the screen to be always on. Cause: ' + JSON.stringify(err)); + return; + } + LogUtil.info(TAG, 'Succeeded in setting the screen to be always on.'); + }); + } + } catch (exception) { + LogUtil.error(TAG, 'Failed to set the screen to be always on. Cause: ' + JSON.stringify(exception)); + } + } + + private async onStopwatchToggled(running: boolean) { + device.getInfo({ + success: (ret: DeviceResponse) => { + const modelProduct = ret.model + this.isDeviceModels = modelProduct + } + }) + if (this.isStopwatchRunning == running) { + return; + } + if (this.initStopTime === RESTRICT_MAXTIME) { + this.isStopwatchRunning = false + } else { + this.isStopwatchRunning = running; + } + + if (this.isStopwatchRunning) { + this.playAnimation(); + SoundPool.playSoundPool('stopwatch') + this.setWindowKeepScreenOn(true) + if (this.startTime == 0) { + this.startTime = Date.now(); + } else { + this.startTime = this.startTime; + } + this.refreshTime(); + this.getEventReportUtil(EventName.CLICK_START_STOPWATCH); + } else { + SoundPool.stopForPageHide('stopwatch'); + this.setWindowKeepScreenOn(false) + this.pauseTime = this.startTime; + this.getEventReportUtil(EventName.CLICK_STOPWATCH_PAUSE); + this.pauseAnimation(); + } + } + + private timer(localTime: number): void { + clearTimeout(this.timerTimeout); + this.timerTimeout = -1; + const now = new Date().getTime(); // 获取当前本地时间 + const timeGap = now - localTime; // 计算时间差 + //下一次计时器应该触发的时间 + const nextTickTime = this.interValFq - (timeGap % this.interValFq); + if (this.isStopwatchRunning && this.isStopwatchVisible) { + this.timerTimeout = setTimeout(() => { + this.interValCount++; + this.passedTime = this.interValFq * this.interValCount; + this.passedFromLastLapTime = this.passedTime - this.lastLapTime; + LogUtil.info(TAG, `passedTime=${this.passedTime}`) + this.timer(localTime); // 递归调用,实现循环 + }, nextTickTime); + } + } + + private refreshTime(): void { + let localTime = new Date().getTime(); //记录本地时间 + this.timer(localTime); // 启动计时器 + } + + // 秒表重置 + private resetStopwatch() { + this.recorDing = false + LogUtil.info(TAG, `Stopwatch reset`); + this.onStopwatchToggled(false); + this.listItemTranslateY = '0vp'; + this.initStopTime = ''; + this.startTime = 0; + this.passedTime = 0; + this.interValCount = 0; + this.lastLapTime = 0; + this.pauseTime = 0; + this.laps = []; + if (this.isPortraitOrientation || this.foldAbleScreen === 1) { + this.titletoppadding = 60.5; + this.textFoldVerticalPadding = 54; + this.textFoldCrossPadding = 62.5; + } else { + this.titletoppadding = 0 + this.textFoldVerticalPadding = 0 + this.textFoldCrossPadding = 0 + } + // 重置表盘下移动画 + animateTo({ + curve: curves.interpolatingSpring(0, 1, 131, 18), + delay: 300, + onFinish: () => { + this.passedFromLastLapTime = 0; + } + }, () => { + this.listItemTranslateY = '-95vp'; + + if (!this.isPortraitOrientation) { + + this.iHeight = this.foldAbleScreen === 1 ? COMMEN_HEIGHT : COMMEN_HEIGHT; + } else { + // Iheight no update + this.iHeight = this.foldAbleScreen === 1 ? COMMEN_HEIGHT : COMMEN_HEIGHT; + } + + }) + + this.getEventReportUtil(EventName.CLICK_STOPWATCH_RESET) + } + + private getEventReportUtil(eventName: string): void { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, eventName); + } + + private formatTimeString(time: number): string { + this.dateToFormat.setTime(time); + let hours = (this.dateToFormat.getUTCDate() - 1) * 24 + this.dateToFormat.getUTCHours(); + if (this.dateToFormat.toISOString().slice(14, 22) === RESTRICT_MAXTIME) { + this.initStopTime = RESTRICT_MAXTIME + this.onStopwatchToggled(false) + } + return this.passedTime / 1000 / 60 / 60 > 1 ? this.dateToFormat.toISOString() + .slice(11, 22) : this.dateToFormat.toISOString().slice(14, 22) + } + + testPlayAudio() { + + } + + private addLap() { + // 计次声音 + SoundPool.playForRepeat() + this.recorDing = true + // 秒表计次 + if (this.laps.length >= 99) { + return promptAction.showToast({ + message: $r('app.string.tips_title'), // 显示文本 + duration: 2000, // 显示时长 + bottom: null // 距离底部的距离 + }) + } + LogUtil.info(TAG, `Stopwatch lap added at ${this.passedTime}`); + if (this.laps == null) { + this.laps = []; + } + let littleTime = Math.max((new Date().getTime() - this.startTime) % this.interValFq, 0); + let time = this.passedTime + Math.floor(littleTime / 10) * 10; + // 计次卡片出现动画 + animateTo({ + delay: 150, + curve: curves.interpolatingSpring(0, 1, 131, 23), + onFinish: () => { + } + }, () => { + this.laps.unshift({ + index: this.laps.length + 1, + fromPrevLapTime: time - this.lastLapTime, + totalTime: time + }) + }) + if (this.laps.length > 1) { + const list: LapItem[] = JSON.parse(JSON.stringify(this.laps)) + const small: LapItem[] = JSON.parse(JSON.stringify(this.laps)) + list.sort((a, b) => { + return a.fromPrevLapTime - b.fromPrevLapTime + }) + small.sort((a, b) => { + return b.fromPrevLapTime - a.fromPrevLapTime + }) + this.minNum = list[0].fromPrevLapTime; + this.maxNum = small[0].fromPrevLapTime; + } + // 卡片透明度动画 + animateTo( + { + duration: 150, + curve: curves.interpolatingSpring(0, 1, 131, 23), + playMode: PlayMode.Normal, + onFinish: () => { + } + }, () => { + if (!this.isPortraitOrientation) { + if (this.foldAbleScreen === 1) { + this.titletoppadding = 0 + } + if (this.foldAbleScreen === 1) { + this.calcDefaultScreenVetacial() + } else { + this.calcDefaultScreenCross() + } + } else if (this.deviceType === 'tablet') { + this.calcDefaultTabletScreenVetacial() + } else { + this.calcDefaultScreenVetacial() + } + }) + this.lastLapTime = time; + + this.getEventReportUtil(EventName.CLICK_STOPWATCH_COUNT) + } + private calcDefaultTabletScreenVetacial() { + this.titletoppadding = 0 + this.textFoldVerticalPadding = 0 + this.textFoldCrossPadding = 0 + this.iHeight += ADD_VERHEIGHT + if (this.foldAbleScreen === 1 && !this.isPortraitOrientation && this.laps.length >= 6) { + this.iHeight = PRIVATE_SECOND_HEIGHT + } else { + if (this.iHeight > 480) { + this.commenCalcHeight() + } + } + } + + private calcDefaultScreenCross() { + if (this.isBigView === BreakPoint.LG) { + if (this.laps.length < 6) { + if (this.laps.length == 1) { + this.iHeight = INIT_VERHEIGHT + return + } + this.iHeight += BREAKLG + if (this.iHeight > RESTRICT_HEIGHT) { + this.iHeight = BREAKLG_HEIGHT; + } + } else { + this.iHeight = BREAKLG_HEIGHT; + } + } else { + this.iHeight += ADD_HEIGHT + if (this.iHeight > CROSS_HEIGHT) { + this.iHeight = CROSS_HEIGHT; + } + } + } + + private calcDefaultScreenVetacial() { + this.titletoppadding = 0 + this.textFoldVerticalPadding = 0 + this.textFoldCrossPadding = 0 + this.iHeight += ADD_VERHEIGHT + if (this.foldAbleScreen === 1 && !this.isPortraitOrientation && this.laps.length >= 6) { + this.iHeight = PRIVATE_SECOND_HEIGHT + } else { + if (this.iHeight > DEFAULT_HEIGHT) { + this.commenCalcHeight() + } + } + } + + private commenCalcHeight() { + if (this.deviceType === 'tablet' && this.isPortraitOrientation) { + } else if (this.foldAbleScreen === 1) { + /** + * Do not modify the folding method + */ + if (this.getOrientaion === this.isFoldCross) { + this.iHeight = this.setCode ? FLOD_VERTICAL_HEIGHT_SECOND : FLOD_VERTICAL_HEIGHT_SECOND + } else { + this.iHeight = this.setCode ? PRIVATE_FIRST_HEIGHT : PRIVATE_SECOND_HEIGHT + } + } else { + if (this.isDeviceModels === 'ALT-AL10') { + this.iHeight = this.isFoldHeight + } else { + this.iHeight = this.isBigView === BreakPoint.MD ? FLOD_ALL_HEIGHT : this.isDeviceModels !== 'NOH-AN00' ? VERTICAL_OHOS_HEIGHT : VERTICAL_HEIGHT; + } + } + } + + // 初始动画 + private initAnimation() { + if (this.animateItem != null) { + this.animateItem.destroy() + this.animateItem = null + } + this.currentAnimateName = 'pause'; + this.animateItem = lottie.loadAnimation({ + container: this.controller, + renderer: 'canvas', + loop: false, + autoplay: false, + name: this.canvasAnimateName, + path: this.canvasAnimatePath + }) + } + + // 初始动画 + private initPauseAnimation() { + if (this.animateItem != null) { + this.animateItem.destroy() + this.animateItem = null + } + this.currentAnimateName = 'play'; + this.animateItem = lottie.loadAnimation({ + container: this.controller, + renderer: 'canvas', + loop: false, + autoplay: false, + name: this.canvasPauseAnimateName, + path: this.canvasPauseAnimatePath + }) + } + + // 开始动画 + private playAnimation() { + lottie.setDirection(this.currentAnimateName == 'pause' ? 1 : -1, this.getCurrentCanvasName()); + lottie.play(this.getCurrentCanvasName()); + } + + // 暂停动画 + private pauseAnimation() { + lottie.setDirection(this.currentAnimateName == 'pause' ? -1 : 1, this.getCurrentCanvasName()); + lottie.play(this.getCurrentCanvasName()); + } + + getCurrentCanvasName(): string { + return this.currentAnimateName == 'pause' ? this.canvasAnimateName : this.canvasPauseAnimateName + } + + getDeviceRatio(): number { + if (this.isPCLg()) { + return SPECAIL_CLOCK_RATIO_FOR_NORMAL_CUSTOM + } + if (this.foldAbleScreen === 1) { + return this.getOrientaion === this.isFoldCross ? SCALE_DIAMETER_RATIO : FOLD_SCREEN_DIAMETER_RATIO + } else { + return this.isPortraitOrientation ? SCALE_DIAMETER_RATIO : SPECAIL_CLOCK_RATIO_FOR_NORMAL_CUSTOM + } + } + + private isVisibility(): Visibility { + return this.isStopwatchVisible ? Visibility.Visible : Visibility.Hidden + } + + private getTimerPadding(): Padding { + if (this.isPCLg()) { + return {} + } + if (this.foldAbleScreen === 1) { + return this.getOrientaion === this.isFoldCross ? { top: $r('app.float.ban_padding_20') } : { + top: $r('app.float.ban_padding_20') + } + } else if (this.foldAbleScreen === 2) { + return { + top: $r('app.float.ban_padding_17') + } + } else { + return this.isPortraitOrientation ? { top: $r('app.float.ban_padding_19') } : { + right: $r('app.float.ban_padding') + } + } + } + + @Builder + buildStopwatchDial(): void { + Row() { + StopwatchDial({ + isPortraitOrientation: false, + passedTime: $passedTime, + scaleDiameterRatio: this.getDeviceRatio() + }) + } + .translate(this.isPCLg() ? {} : this.isPortraitOrientation || this.foldAbleScreen === 1 ? this.translateList : {}) + .transition(TransitionEffect.opacity(1)) + .visibility(this.isStopwatchVisible ? Visibility.Visible : Visibility.Hidden) + .padding(this.getTimerPadding()) + .margin(this.isPCLg() ? {} : (this.foldAbleScreen === 1 ? + { + top: $r('app.float.dial_margin_32'), bottom: '' + } : + { + top: $r('app.float.dial_margin_32'), + bottom: this.foldAbleScreen === 2 ? $r('app.float.dial_margin_46') : $r('app.float.dial_margin_24') + } + )) + + } + + @Builder + buildTimePassed() { + Column() { + Column() { + Text(this.formatTimeString(this.passedTime)) + .textAlign(TextAlign.Center) + .lineHeight( + this.isPCLg() ? $r('app.float.stopwatch_PC_text_line_height') : + (this.isPortraitOrientation || this.foldAbleScreen === 1 + ? $r('app.float.stopwatch_time_title_line_height') + : $r('app.float.stopwatch_time_title_line_change_height'))) + .fontColor($r('sys.color.font_primary')) + .fontSize( + this.isPCLg() ? $r('app.float.stopwatch_PC_text_size') : + (this.isPortraitOrientation || this.foldAbleScreen === 1 + ? $r('app.float.stopwatch_time_title_text_size') + : $r('app.float.stopwatch_time_title_change_text_size'))) + .fontWeight(FontWeight.Medium) + .margin({ + bottom: this.isPCLg() ? $r('app.float.stopwatch_pc_bottom') : (this.isPortraitOrientation || this.foldAbleScreen === 1 ? $r('app.float.stopwatch_time_title_margin_bottom') : $r('app.float.stopwatch_time_title_margin_change_bottom')) + }) + .height( + this.isPCLg() ? $r('app.float.stopwatch_PC_text_height') : + (this.isPortraitOrientation || this.foldAbleScreen === 1 + ? $r('app.float.stopwatch_time_height') + : $r('app.float.stopwatch_time_title_line_change_height'))) + Text(this.formatTimeString(this.passedFromLastLapTime)) + .fontColor($r('sys.color.ohos_fa_text_secondary')) + .fontSize($r('app.float.stopwatch_time_subtitle_text_size')) + .lineHeight(this.isPCLg() ? $r('app.float.stopwatch_time_subtitle_height') : $r('app.float.stopwatch_time_subtitle_line_height')) + .height($r('app.float.stopwatch_time_subtitle_height')) + .fontWeight(FontWeight.Regular) + .margin(this.isPCLg() ? { bottom: '0' } : (this.foldAbleScreen === 1 ? { + bottom: $r('app.float.stopwatch_laps_list_fold_top') + } : { + bottom: $r('app.float.stopwatch_laps_list_margin_top') + })) + .visibility(this.laps && this.laps.length > 0 ? Visibility.Visible : Visibility.Hidden) + .transition( + TransitionEffect.OPACITY.animation({ delay: 300, duration: 150, curve: Curve.Sharp })) + } + .visibility(this.isVisibility()) + } + .translate(this.isPCLg() ? {} : this.isPortraitOrientation || this.foldAbleScreen === 1 ? this.translateList : {}) + .width('100%') + .padding(this.getPassedPadding()) + .margin({ + top: this.isPCLg() ? '' : this.foldAbleScreen === 1 ? $r('app.float.passed_margin_44') : $r('app.float.passed_margin_23'), + right: this.isPortraitOrientation || this.foldAbleScreen === 1 ? 0 : this.isPCLg() ? 0 : $r('app.float.passed_margin_48') + }) + .visibility(this.isVisibility()) + } + + private getPassedPadding(): Padding { + if (this.isPCLg()) { + return {} + } + if (this.foldAbleScreen === 1) { + return { + top: this.textFoldCrossPadding, + bottom: this.textFoldCrossPadding - 24 + } + } else if (this.foldAbleScreen === 2) { + return { + top: this.textFoldVerticalPadding, + bottom: this.textFoldVerticalPadding - 24 + } + } else { + return { + top: this.titletoppadding, + bottom: this.titletoppadding - 24 + } + } + } + + @Builder + buildLapItem(lap: LapItem) { + Card({ cancelDoubleCardMargin: true, openHoverStatus: true, restrictAlarmCardMargin: true }) { + Row() { + Row() { + Text(lap.index <= 9 ? 0 + lap.index.toString() : lap.index.toString()) + .width($r('app.float.text_width')) + .height($r('app.float.text_height')) + .borderRadius(50) + .fontSize(14) + .backgroundColor(lap.fromPrevLapTime == this.maxNum ? $r('sys.color.warning') : lap.fromPrevLapTime == this.minNum ? $r('sys.color.confirm') : $r('sys.color.interactive_hover')) + .textAlign(TextAlign.Center) + .fontColor(lap.fromPrevLapTime == this.maxNum ? $r('sys.color.font_on_primary') : lap.fromPrevLapTime == this.minNum ? $r('sys.color.font_on_primary') : $r('sys.color.font_primary')) + .animation({ + duration: 150, + curve: Curve.Linear, + }) + Text(`+${this.formatTimeString(lap.fromPrevLapTime)}`) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Medium) + .fontColor(lap.fromPrevLapTime == this.maxNum ? $r('sys.color.warning') : lap.fromPrevLapTime == this.minNum ? $r('app.color.stopwatch_fon_size_min') : $r('app.color.stopwatch_fon_size_default')) + } + .width('109vp') + .height($r('app.float.stopwatch_lap_item_height_no_padding')) + .justifyContent(FlexAlign.SpaceBetween) + .alignItems(VerticalAlign.Center) + + Text(this.formatTimeString(lap.totalTime)) + .fontSize(24) + .fontWeight(FontWeight.Medium) + } + .justifyContent(FlexAlign.SpaceBetween) + .alignItems(VerticalAlign.Center) + .width('100%') + .height($r('app.float.stopwatch_lap_item_height_no_padding')) + .clip(true) + .borderRadius($r('sys.float.corner_radius_level10')) + } + } + + @Builder + buildLapsList() { + List({ space: 0 }) { + ForEach(this.laps, (item: LapItem) => { + ListItem() { + this.buildLapItem(item); + } + .transition( + TransitionEffect.translate({ y: this.listItemTranslateY }) + .combine(TransitionEffect.OPACITY.animation( + { duration: 0, curve: Curve.Sharp }), + ) + ) + .margin(this.isPCLg() ? { + bottom: item.index === 1 ? 0 : $r('app.float.cancel_bottom_margin') + } : (item.index == 1 ? { + bottom: this.isPortraitOrientation || this.foldAbleScreen === 1 ? + this.laps.length <= (this.foldAbleScreen === 1 && !this.isPortraitOrientation ? 6 : 7) ? + $r('app.float.cross_bottom') : + this.isBigView === BreakPoint.MD ? this.foldAbleScreen === 1 && this.getOrientaion === this.isFoldCross ? + $r('app.float.fold_margin_bottom_second') : $r('app.float.fold_margin_bottom') : + $r('app.float.cross_over') + : this.laps.length === 1 || this.laps.length === 2 || this.laps.length === 3 ? $r('app.float.vertical_bottom') : + $r('app.float.special_bottom') + } : { bottom: $r('app.float.cancel_bottom_margin') })) + }, (item: LapItem) => JSON.stringify(item)) + } + .scrollBar(this.laps && this.laps.length > 1 ? BarState.On : BarState.Off) + .edgeEffect(EdgeEffect.Spring) // 边缘效果设置为Spring + .translate(this.isPCLg() ? {} : this.translateList) + .alignListItem(ListItemAlign.Start) + .visibility(this.isStopwatchVisible ? Visibility.Visible : Visibility.None) + .width('100%') + .height(this.isPCLg() ? PC_HEIGHT : (this.isPortraitOrientation ? this.iHeight : DEFAULT_HEIGHT)) + .margin(this.deviceType === 'tablet' && !this.isPortraitOrientation ? { + top: $r('app.float.normal_tablet_margin') + } : (this.isPCLg() ? '' : (this.isPortraitOrientation || this.foldAbleScreen === 1 ? { + } : this.isBigView !== BreakPoint.LG ? { + top: $r('app.float.normal_margin') } : { top: $r('app.float.lg_margin') }))) + .padding(this.isPCLg() ? {} : (this.isBigView === BreakPoint.MD ? this.isPortraitOrientation ? { + left: $r('app.float.fold_left'), + right: $r('app.float.fold_right') + } : this.foldAbleScreen === 1 ? { + left: $r('app.float.fold_cross_padding'), + right: $r('app.float.fold_cross_padding'), + } : this.isPortraitOrientation ? { + left: $r('app.float.time_list_padding'), + right: $r('app.float.time_list_padding') + } : { + right: $r('app.float.ban_padding') + } : { + left: $r('app.float.time_list_padding'), + right: $r('app.float.time_list_padding') + })) + .border({ width: 1, color: Color.Transparent }) + } + + @Styles + pressedButtonStyle() { + .backgroundColor($r('sys.color.interactive_click')) + } + + @Builder + buildButtons() { + Flex({ + direction: this.isPCLg() ? FlexDirection.Column : FlexDirection.Row, + justifyContent: FlexAlign.SpaceBetween, + alignItems: ItemAlign.Center }) { + FloatingActionButton({ + src: $r('app.media.ic_reset'), + colorSchemeName: FloatingActionButtonColor.WHITE, + isEnabled: !this.isStopwatchRunning && Math.abs(this.passedTime) > 0, + playButton: false, + onButtonClick: (): void => this.resetStopwatch() + }) + .transition(TransitionEffect.opacity(1)) + Stack() { + Button({ type: ButtonType.Circle, stateEffect: true }) { + Canvas(this.controller) + .draggable(false) + .borderRadius($r('app.float.canvas_border_radius')) + .width($r('app.float.canvas_size')) + .height($r('app.float.canvas_size')) + .onReady(() => { + LogUtil.info(TAG, `${this.canvasAnimatePath} dasdasd}`); + if (this.isStopwatchRunning) { + this.initPauseAnimation() + } else { + this.initAnimation() + } + }) + .onDisAppear(() => { + LogUtil.info(TAG, `${TAG} canvas onDisAppear...`); + }) + .onClick(() => { + LogUtil.info(TAG, '点击成功'); + this.onStopwatchToggled(!this.isStopwatchRunning) + }) + } + .id('id_add_button') + .backgroundColor($r('sys.color.ohos_id_container_color_active')) + .width($r('app.float.pc_add_button_size')) + .height($r('app.float.pc_add_button_size')) + .stateStyles({ + pressed: this.pressedButtonStyle, + }) + .shadow({ + radius: $r('app.float.button_shadow_radius'), + color: CommonUtil.changeShadowColor($r('sys.color.ohos_id_container_color_active')), + offsetX: 0, + offsetY: $r('app.float.button_shadow_y2'), + }) + + if (this.isPCLg()) { + Button({ type: ButtonType.Circle, stateEffect: true }) + .opacity(0) + .width($r('app.float.pc_add_button_size')) + .height($r('app.float.pc_add_button_size')) + .onClick(() => { + this.onStopwatchToggled(!this.isStopwatchRunning) + }) + .onFocus(() => { + this.selfFocus = true + }) + .onBlur(() => { + this.selfFocus = false + }) + } + } + .padding('2vp') + .border(this.isPCLg() ? { + width: '2vp', + color: this.selfFocus ? $r('sys.color.ohos_id_icon_color_active') : Color.Transparent, + radius: 32 + } : {}) + .width('64vp') + .offset({ + x: 0 + }) + + FloatingActionButton({ + src: $r('app.media.ic_stopwatch_lap'), + colorSchemeName: FloatingActionButtonColor.WHITE, + isEnabled: this.isStopwatchRunning, + playButton: false, + onButtonClick: (): void => this.addLap() + }) + .transition(TransitionEffect.opacity(1)) + } + .borderRadius($r('app.float.stopwatch_bottom_borderRadius')) + .backgroundColor($r('sys.color.comp_background_tertiary')) + .padding(this.isPCLg() ? { + top: $r('app.float.stopwatch_bottom_padding'), + bottom: $r('app.float.stopwatch_bottom_padding') + } : { left: $r('app.float.stopwatch_bottom_padding'), right: $r('app.float.stopwatch_bottom_padding') }) + .width(this.isPCLg() ? $r('app.float.stopwatch_bottom_padding_height') : $r('app.float.stopwatch_bottom_width')) + .height(this.isPCLg() ? $r('app.float.stopwatch_bottom_width') : $r('app.float.stopwatch_bottom_padding_height')) + .margin({ + bottom: this.isPCLg() ? 0 : $r('app.float.stopwatch_bottom_margin_bottom') + }) + } + + // 竖屏 + @Builder + buildPortrait() { + Stack({ alignContent: Alignment.Bottom }) { + Scroll(this.scrollerForScroll) { + Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + // this.buildStopwatchDial() + this.buildTimePassed() + CommonGrid() { + this.buildLapsList() + } + } + } + .edgeEffect(EdgeEffect.None) + .scrollBar(BarState.Off) + .onScroll((_: number, yOffset: number) => this.onScroll(yOffset)) + .align(Alignment.Top) + .height('100%') + .width('100%') + + if (this.isStopwatchVisible) { + this.buildButtons() + } + } + } + + //横屏 + @Builder + buildLandscape() { + Row() { + Column() { + // this.buildStopwatchDial(); + this.buildTimePassed(); + } + .height('100%') + .width('calc(40% - 19vp)') + + Rect() + .fill($r('sys.color.comp_divider')) + .width('1px') + .height($r('app.float.line_height')) + .margin({ left: $r('app.float.margin_left_1') }) + Stack({ alignContent: Alignment.Bottom }) { + Scroll(this.scrollerForScroll) { + this.buildLapsList(); + } + .height('100%') + .edgeEffect(EdgeEffect.Spring) + .scrollBar(BarState.Off) + .onScroll((_: number, yOffset: number) => this.onScroll(yOffset)) + .align(Alignment.Top) + + if (this.isStopwatchVisible) { + this.buildButtons() + } + } + .padding({ + left: $r('app.float.ban_padding') + }) + .height('100%') + .width('calc(60% + 19vp)') + } + .height('100%') + .width('100%') + .padding({ + left: $r('app.float.ban_padding'), + }) + } + + @Builder + buildPC() { + Row() { + // 钟表 + Column() { + // this.buildStopwatchDial() + this.buildTimePassed() + } + .width(this.laps.length > 0 ? `calc(100% - 40% - 268vp)` : `calc(100% - 232vp)`) + .margin({ + left: this.laps.length > 0 ? $r('app.float.stopwatch_pc_left') : $r('app.float.stopwatch_pc_left_max') + }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + + // 计时按钮 + if (this.isStopwatchVisible) { + Column() { + this.buildButtons() + } + .height('100%') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .margin({ + right: $r('app.float.stopwatch_PC_button_margin'), + }) + } + } + .height('100%') + } + + build() { + + if (this.isPCLg()) { + this.buildPC() + } else { + if (this.isPortraitOrientation || this.foldAbleScreen === 1) { + this.buildPortrait(); + } else { + this.buildLandscape() + } + } + } +} \ No newline at end of file diff --git a/feature/stopwatch/src/main/ets/pages/types.ets b/feature/stopwatch/src/main/ets/pages/types.ets new file mode 100644 index 0000000..b464285 --- /dev/null +++ b/feature/stopwatch/src/main/ets/pages/types.ets @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const VERTICAL_HEIGHT = 475; + +export const DEFAULT_HEIGHT = 532; + +export const PC_HEIGHT = 392; + +export const FLOD_VERTICAL_HEIGHT = 500; + +export const PRIVATE_FIRST_HEIGHT = 419; + +export const PRIVATE_SECOND_HEIGHT = 439; + +export const FLOD_VERTICAL_HEIGHT_SECOND = 469; + +export const FLOD_CROSS_HEIGHT = 419; + +export const VERTICAL_OHOS_HEIGHT = 513; + +export const CROSS_HEIGHT = 235; + +export const COMMEN_HEIGHT = 0; + +export const COMMEN_X = 0; + +export const VERTICAL_Y = -288; + +export const CROSS_Y = -20; + +export const COMMEN_Y = 0; + +export const COMMEN_Z = 0; + +export const INIT_HEIGHT = 159; + +export const ADD_HEIGHT = 76; + +export const INIT_VERHEIGHT = 143; + +export const ADD_VERHEIGHT = 76; + +export const FRIST_H = 76; + +export const SECOND_H = 152; + +export const THIRD_H = 228; + +export const FOURTH_H = 304; + +export const FIVE_H = 380; + +export const SIX_H = 456; + +export const RESTRICT = -999; + +// '99:59:59.99' +export const STOPTIME = 359999000; + +export const BUTTON_MOVE_VERTACIL = -37; + +export const BUTTON_MOVE_CROSS = -43; + +export const BREAKLG = 70; + +export const RESTRICT_HEIGHT = 447; + +export const BREAKLG_HEIGHT = 456; + +export const FLOD_ALL_HEIGHT = 510; + +export const FLOD_BUTTON_MOVE = -60; + +export const BUTTON_MOVE_CROSS_OHOS = -85; + +export const BUTTON_MOVE_VERTACIL_OHOS = -41; + +export const RESTRICT_MAXTIME = '99:59:59.99'; \ No newline at end of file diff --git a/feature/stopwatch/src/main/module.json5 b/feature/stopwatch/src/main/module.json5 new file mode 100644 index 0000000..5f23071 --- /dev/null +++ b/feature/stopwatch/src/main/module.json5 @@ -0,0 +1,10 @@ +{ + "module": { + "name": "stopwatch", + "type": "har", + "deviceTypes": [ + "default", + "tablet" + ], + } +} diff --git a/feature/stopwatch/src/main/resources/base/element/color.json b/feature/stopwatch/src/main/resources/base/element/color.json new file mode 100644 index 0000000..40959ff --- /dev/null +++ b/feature/stopwatch/src/main/resources/base/element/color.json @@ -0,0 +1,56 @@ +{ + "color": [ + { + "name": "stopwatch_shadow_background_color", + "value": "#F1F3F5" + }, + { + "name": "stopwatch_shadow_background_transparent_color", + "value": "#00F1F3F5" + }, + { + "name": "stopwatch_max_color", + "value": "#e44025" + }, + { + "name": "stopwatch_min_color", + "value": "#61bb58" + }, + { + "name": "stopwatch_default_color", + "value": "#f2f2f2" + }, + { + "name": "stopwatch_max_color_num", + "value": "#ffffff" + }, + { + "name": "stopwatch_min_color_num", + "value": "#000000" + }, + { + "name": "stopwatch_fon_size_max", + "value": "#e44025" + }, + { + "name": "stopwatch_fon_size_min", + "value": "#61bb58" + }, + { + "name": "stopwatch_fon_size_default", + "value": "#6c6c6c" + }, + { + "name": "line_color", + "value": "#d3d3d3" + }, + { + "name": "stopwatch_bottom_back", + "value": "#0c000000" + }, + { + "name": "stopwatch_button_back", + "value": "#00000000" + } + ] +} \ No newline at end of file diff --git a/feature/stopwatch/src/main/resources/base/element/float.json b/feature/stopwatch/src/main/resources/base/element/float.json new file mode 100644 index 0000000..1013791 --- /dev/null +++ b/feature/stopwatch/src/main/resources/base/element/float.json @@ -0,0 +1,360 @@ +{ + "float": [ + { + "name": "stopwatch_time_title_margin_bottom", + "value": "8vp" + }, + { + "name": "stopwatch_time_title_margin_change_bottom", + "value": "2vp" + }, + { + "name": "dial_margin_24", + "value": "24vp" + }, + { + "name": "dial_margin_46", + "value": "46vp" + }, + { + "name": "passed_margin_44", + "value": "44vp" + }, + { + "name": "passed_margin_23", + "value": "23vp" + }, + { + "name": "passed_margin_48", + "value": "48vp" + }, + { + "name": "dial_margin_29", + "value": "29vp" + }, + { + "name": "dial_margin_19", + "value": "19vp" + }, + { + "name": "dial_margin_32", + "value": "32vp" + }, + { + "name": "dial_margin_50", + "value": "50vp" + }, + { + "name": "stopwatch_time_title_change_text_size", + "value": "20sp" + }, + { + "name": "stopwatch_time_title_line_change_height", + "value": "26vp" + }, + { + "name": "stopwatch_time_subtitle_text_size", + "value": "14sp" + }, + { + "name": "stopwatch_time_subtitle_line_height", + "value": "16vp" + }, + { + "name": "stopwatch_lap_item_height", + "value": "56vp" + }, + { + "name": "stopwatch_lap_item_height_no_padding", + "value": "64vp" + }, + { + "name": "stopwatch_laps_list_margin_top", + "value": "24vp" + }, + { + "name": "stopwatch_laps_list_fold_top", + "value": "32vp" + }, + { + "name": "stopwatch_bottom_buttons_bar_padding_horizontal", + "value": "16vp" + }, + { + "name": "stopwatch_bottom_buttons_bar_height", + "value": "116vp" + }, + { + "name": "clock_margin_vertical", + "value": "16vp" + }, + { + "name": "stopwatch_bottom_borderRadius", + "value": "36vp" + }, + { + "name": "stopwatch_bottom_padding", + "value": "6vp" + }, + { + "name": "stopwatch_bottom_padding_height", + "value": "60vp" + }, + { + "name": "stopwatch_bottom_width", + "value": "250vp" + }, + { + "name": "stopwatch_bottom_margin_bottom", + "value": "24vp" + }, + { + "name": "clock_shadow_above_space", + "value": "35vp" + }, + { + "name": "stopwatch_time_subtitle_height", + "value": "19vp" + }, + { + "name": "stopwatch_time_height", + "value": "56vp" + }, + { + "name": "stopwatch_time_change_height", + "value": "26vp" + }, + { + "name": "translate_iHeight_cross", + "value": "224vp" + }, + { + "name": "translate_iHeight_vertical", + "value": "500vp" + }, + { + "name": "cross_margin_left", + "value": "-85vp" + }, + { + "name": "vertical_margin_left", + "value": "-85vp" + }, + { + "name": "isPortraitOrientation_width", + "value": "458vp" + }, + { + "name": "stopwatch_time_title_margin_top", + "value": "-1vp" + }, + { + "name": "text_width", + "value": "32vp" + }, + { + "name": "text_height", + "value": "32vp" + }, + { + "name": "padding_top", + "value": "16vp" + }, + { + "name": "padding_bottom", + "value": "16vp" + }, + { + "name": "margin_left_1", + "value": "-1vp" + }, + { + "name": "line_height", + "value": "500vp" + }, + { + "name": "ban_padding", + "value": "48vp" + }, + { + "name": "ban_padding_20", + "value": "20vp" + }, + { + "name": "ban_padding_17", + "value": "17vp" + }, + { + "name": "ban_padding_19", + "value": "19vp" + }, + { + "name": "cross_bottom", + "value": "12vp" + }, + { + "name": "time_title_bottom", + "value": "28vp" + }, + { + "name": "vertical_bottom", + "value": "28vp" + }, + { + "name": "special_bottom", + "value": "88vp" + }, + { + "name": "line_transtion", + "value": "-1vp" + }, + { + "name": "cross_over", + "value": "94vp" + }, + { + "name": "timer_hint_row_height", + "value": "10vp" + }, + { + "name": "timer_hint_row_change_height", + "value": "0vp" + }, + { + "name": "stopwatch_time_title_line_height", + "value": "47vp" + }, + { + "name": "stopwatch_time_title_cricle_line_height", + "value": "35vp" + }, + { + "name": "timer_picker_date_circle_text_size", + "value": "18vp" + }, + { + "name": "clock_shadow_space", + "value": "32vp" + }, + { + "name": "time_list_padding", + "value": "16vp" + }, + { + "name": "time_list_padding_fold", + "value": "24vp" + }, + { + "name": "time_title_margin_dail_bottom", + "value": "34vp" + }, + { + "name": "time_title_margin_right", + "value": "48vp" + }, + { + "name": "tab_bar_margin_right", + "value": "-10vp" + }, + { + "name": "normal_margin", + "value": "15vp" + }, + { + "name": "normal_tablet_margin", + "value": "65vp" + }, + { + "name": "lg_margin", + "value": "86vp" + }, + { + "name": "stopwatch_time_title_text_size", + "value": "42sp" + }, + { + "name": "stopwatch_time_title_text_width", + "value": "48vp" + }, + { + "name": "stopwatch_time_button_width", + "value": "40vp" + }, + { + "name": "stopwatch_time_button_height", + "value": "40vp" + }, + { + "name": "fold_left", + "value": "104.5vp" + }, + { + "name": "fold_right", + "value": "104.5vp" + }, + { + "name": "fold_margin_bottom", + "value": "97vp" + }, + { + "name": "fold_margin_bottom_second", + "value": "98vp" + }, + { + "name": "stopwatch_time_title_text_size_PC", + "value": "56vp" + }, + { + "name": "stopwatch_PC_button_margin", + "value": "56vp" + }, + { + "name": "stopwatch_PC_main_margin", + "value": "268vp" + }, + { + "name": "stopwatch_PC_text_line_height", + "value": "74vp" + }, + { + "name": "stopwatch_PC_text_size", + "value": "56sp" + }, + { + "name": "stopwatch_PC_text_height", + "value": "74vp" + }, + { + "name": "stopwatch_time_img_width", + "value": "65vp" + }, + { + "name": "stopwatch_time_img_height", + "value": "65vp" + }, + { + "name": "fold_cross_padding", + "value": "115vp" + }, + { + "name": "stopwatch_pc_left", + "value": "96vp" + }, + { + "name": "stopwatch_pc_left_max", + "value": "116vp" + }, + { + "name": "stopwatch_pc_bottom", + "value": "4vp" + }, + { + "name": "rect_pad_margin_left", + "value": "-1px" + }, + { + "name": "reset_stopwatch_list_item_translate_y", + "value": "-95vp" + } + ] +} \ No newline at end of file diff --git a/feature/stopwatch/src/main/resources/base/element/string.json b/feature/stopwatch/src/main/resources/base/element/string.json new file mode 100644 index 0000000..97def8b --- /dev/null +++ b/feature/stopwatch/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "stopwatch_title", + "value": "Stopwatch" + }, + { + "name": "stopwatch", + "value": "秒表" + }, + { + "name": "tips_title", + "value": "列表已满" + } + ] +} \ No newline at end of file diff --git a/feature/stopwatch/src/main/resources/base/media/ic_stopwatch_button.svg b/feature/stopwatch/src/main/resources/base/media/ic_stopwatch_button.svg new file mode 100644 index 0000000..15cf18a --- /dev/null +++ b/feature/stopwatch/src/main/resources/base/media/ic_stopwatch_button.svg @@ -0,0 +1,9 @@ + + + ic_worldclock_filled_activated_1 + + + + + + \ No newline at end of file diff --git a/feature/stopwatch/src/main/resources/base/media/img_stopwatch_dial_scale.png b/feature/stopwatch/src/main/resources/base/media/img_stopwatch_dial_scale.png new file mode 100644 index 0000000000000000000000000000000000000000..15aa2ce47e5972b17b7d746dd09dd738ac69ad74 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}K#X;^)4C~IxyaaMs(j9#r85lP9 zbN@+X1@buyJR*x382FBWFymBhK53vJucwP+h(vhukN^Mw*E4YbX8z0S?0O0)!QkoY K=d#Wzp$Pz>Z5;yu literal 0 HcmV?d00001 diff --git a/feature/stopwatch/src/main/resources/base/media/img_stopwatch_minutehand.png b/feature/stopwatch/src/main/resources/base/media/img_stopwatch_minutehand.png new file mode 100644 index 0000000000000000000000000000000000000000..15aa2ce47e5972b17b7d746dd09dd738ac69ad74 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}K#X;^)4C~IxyaaMs(j9#r85lP9 zbN@+X1@buyJR*x382FBWFymBhK53vJucwP+h(vhukN^Mw*E4YbX8z0S?0O0)!QkoY K=d#Wzp$Pz>Z5;yu literal 0 HcmV?d00001 diff --git a/feature/stopwatch/src/main/resources/base/media/img_stopwatch_minutehand_shadow.png b/feature/stopwatch/src/main/resources/base/media/img_stopwatch_minutehand_shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..15aa2ce47e5972b17b7d746dd09dd738ac69ad74 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}K#X;^)4C~IxyaaMs(j9#r85lP9 zbN@+X1@buyJR*x382FBWFymBhK53vJucwP+h(vhukN^Mw*E4YbX8z0S?0O0)!QkoY K=d#Wzp$Pz>Z5;yu literal 0 HcmV?d00001 diff --git a/feature/stopwatch/src/main/resources/base/media/img_stopwatch_minutes_dial_scale.png b/feature/stopwatch/src/main/resources/base/media/img_stopwatch_minutes_dial_scale.png new file mode 100644 index 0000000000000000000000000000000000000000..15aa2ce47e5972b17b7d746dd09dd738ac69ad74 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}K#X;^)4C~IxyaaMs(j9#r85lP9 zbN@+X1@buyJR*x382FBWFymBhK53vJucwP+h(vhukN^Mw*E4YbX8z0S?0O0)!QkoY K=d#Wzp$Pz>Z5;yu literal 0 HcmV?d00001 diff --git a/product/pc/src/main/resources/base/profile/main_pages.json b/feature/stopwatch/src/main/resources/base/profile/main_pages.json similarity index 100% rename from product/pc/src/main/resources/base/profile/main_pages.json rename to feature/stopwatch/src/main/resources/base/profile/main_pages.json diff --git a/product/pc/src/ohosTest/resources/base/element/color.json b/feature/stopwatch/src/main/resources/dark/element/color.json similarity index 32% rename from product/pc/src/ohosTest/resources/base/element/color.json rename to feature/stopwatch/src/main/resources/dark/element/color.json index 1bbc9aa..c1e3f80 100644 --- a/product/pc/src/ohosTest/resources/base/element/color.json +++ b/feature/stopwatch/src/main/resources/dark/element/color.json @@ -1,8 +1,8 @@ { "color": [ { - "name": "white", - "value": "#FFFFFF" + "name": "stopwatch_bottom_back", + "value": "#13FFFFFF" } ] } \ No newline at end of file diff --git a/feature/stopwatch/src/main/resources/dark/element/float.json b/feature/stopwatch/src/main/resources/dark/element/float.json new file mode 100644 index 0000000..7bcbabb --- /dev/null +++ b/feature/stopwatch/src/main/resources/dark/element/float.json @@ -0,0 +1,344 @@ +{ + "float": [ + { + "name": "stopwatch_time_title_margin_bottom", + "value": "8vp" + }, + { + "name": "stopwatch_time_title_margin_change_bottom", + "value": "2vp" + }, + { + "name": "stopwatch_time_title_change_text_size", + "value": "20sp" + }, + { + "name": "stopwatch_time_title_line_change_height", + "value": "26vp" + }, + { + "name": "dial_margin_19", + "value": "19vp" + }, + { + "name": "dial_margin_29", + "value": "29vp" + }, + { + "name": "stopwatch_time_subtitle_text_size", + "value": "14sp" + }, + { + "name": "stopwatch_time_subtitle_line_height", + "value": "16vp" + }, + { + "name": "stopwatch_lap_item_height", + "value": "56vp" + }, + { + "name": "stopwatch_laps_list_margin_top", + "value": "24vp" + }, + { + "name": "stopwatch_laps_list_fold_top", + "value": "32vp" + }, + { + "name": "stopwatch_bottom_buttons_bar_padding_horizontal", + "value": "16vp" + }, + { + "name": "stopwatch_bottom_buttons_bar_height", + "value": "116vp" + }, + { + "name": "clock_margin_vertical", + "value": "16vp" + }, + { + "name": "stopwatch_bottom_borderRadius", + "value": "36vp" + }, + { + "name": "stopwatch_bottom_padding", + "value": "6vp" + }, + { + "name": "stopwatch_bottom_padding_height", + "value": "60vp" + }, + { + "name": "stopwatch_bottom_width", + "value": "250vp" + }, + { + "name": "stopwatch_bottom_margin_bottom", + "value": "24vp" + }, + { + "name": "clock_shadow_above_space", + "value": "35vp" + }, + { + "name": "stopwatch_time_subtitle_height", + "value": "19vp" + }, + { + "name": "stopwatch_time_height", + "value": "56vp" + }, + { + "name": "stopwatch_time_change_height", + "value": "26vp" + }, + { + "name": "translate_iHeight_cross", + "value": "224vp" + }, + { + "name": "translate_iHeight_vertical", + "value": "500vp" + }, + { + "name": "cross_margin_left", + "value": "-85vp" + }, + { + "name": "vertical_margin_left", + "value": "-85vp" + }, + { + "name": "isPortraitOrientation_width", + "value": "458vp" + }, + { + "name": "stopwatch_time_title_margin_top", + "value": "-1vp" + }, + { + "name": "text_width", + "value": "32vp" + }, + { + "name": "text_height", + "value": "32vp" + }, + { + "name": "padding_top", + "value": "16vp" + }, + { + "name": "padding_bottom", + "value": "16vp" + }, + { + "name": "line_height", + "value": "500vp" + }, + { + "name": "margin_left_1", + "value": "-1vp" + }, + { + "name": "ban_padding", + "value": "48vp" + }, + { + "name": "ban_padding_20", + "value": "20vp" + }, + { + "name": "dial_margin_32", + "value": "32vp" + }, + { + "name": "ban_padding_17", + "value": "17vp" + }, + { + "name": "dial_margin_24", + "value": "24vp" + }, + { + "name": "ban_padding_19", + "value": "19vp" + }, + { + "name": "cross_bottom", + "value": "12vp" + }, + { + "name": "time_title_bottom", + "value": "28vp" + }, + { + "name": "vertical_bottom", + "value": "28vp" + }, + { + "name": "special_bottom", + "value": "88vp" + }, + { + "name": "line_transtion", + "value": "-1vp" + }, + { + "name": "cross_over", + "value": "94vp" + }, + { + "name": "timer_hint_row_height", + "value": "10vp" + }, + { + "name": "timer_hint_row_change_height", + "value": "0vp" + }, + { + "name": "stopwatch_time_title_line_height", + "value": "47vp" + }, + { + "name": "stopwatch_time_title_cricle_line_height", + "value": "35vp" + }, + { + "name": "timer_picker_date_circle_text_size", + "value": "18vp" + }, + { + "name": "clock_shadow_space", + "value": "32vp" + }, + { + "name": "time_list_padding", + "value": "16vp" + }, + { + "name": "time_list_padding_fold", + "value": "24vp" + }, + { + "name": "time_title_margin_dail_bottom", + "value": "34vp" + }, + { + "name": "time_title_margin_right", + "value": "48vp" + }, + { + "name": "tab_bar_margin_right", + "value": "-10vp" + }, + { + "name": "normal_margin", + "value": "15vp" + }, + { + "name": "lg_margin", + "value": "86vp" + }, + { + "name": "stopwatch_time_title_text_size", + "value": "42sp" + }, + { + "name": "stopwatch_time_title_text_width", + "value": "48vp" + }, + { + "name": "stopwatch_time_button_width", + "value": "40vp" + }, + { + "name": "stopwatch_time_button_height", + "value": "40vp" + }, + { + "name": "fold_left", + "value": "104.5vp" + }, + { + "name": "fold_right", + "value": "104.5vp" + }, + { + "name": "fold_margin_bottom", + "value": "97vp" + }, + { + "name": "fold_margin_bottom_second", + "value": "98vp" + }, + { + "name": "dial_margin_46", + "value": "46vp" + }, + { + "name": "passed_margin_44", + "value": "44vp" + }, + { + "name": "passed_margin_23", + "value": "23vp" + }, + { + "name": "passed_margin_48", + "value": "48vp" + }, + { + "name": "stopwatch_time_title_text_size_PC", + "value": "56vp" + }, + { + "name": "stopwatch_PC_button_margin", + "value": "56vp" + }, + { + "name": "stopwatch_PC_main_margin", + "value": "268vp" + }, + { + "name": "stopwatch_PC_text_line_height", + "value": "74vp" + }, + { + "name": "stopwatch_PC_text_size", + "value": "56sp" + }, + { + "name": "stopwatch_PC_text_height", + "value": "74vp" + }, + { + "name": "stopwatch_time_img_width", + "value": "65vp" + }, + { + "name": "stopwatch_time_img_height", + "value": "65vp" + }, + { + "name": "fold_cross_padding", + "value": "115vp" + }, + { + "name": "stopwatch_pc_left", + "value": "96vp" + }, + { + "name": "stopwatch_pc_left_max", + "value": "116vp" + }, + { + "name": "stopwatch_pc_bottom", + "value": "4vp" + }, + { + "name": "reset_stopwatch_list_item_translate_y", + "value": "-95vp" + } + ] +} \ No newline at end of file diff --git a/feature/stopwatch/src/main/resources/dark/element/string.json b/feature/stopwatch/src/main/resources/dark/element/string.json new file mode 100644 index 0000000..97def8b --- /dev/null +++ b/feature/stopwatch/src/main/resources/dark/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "stopwatch_title", + "value": "Stopwatch" + }, + { + "name": "stopwatch", + "value": "秒表" + }, + { + "name": "tips_title", + "value": "列表已满" + } + ] +} \ No newline at end of file diff --git a/product/pc/src/ohosTest/resources/base/profile/test_pages.json b/feature/stopwatch/src/main/resources/dark/profile/main_pages.json similarity index 38% rename from product/pc/src/ohosTest/resources/base/profile/test_pages.json rename to feature/stopwatch/src/main/resources/dark/profile/main_pages.json index fcef82b..feec276 100644 --- a/product/pc/src/ohosTest/resources/base/profile/test_pages.json +++ b/feature/stopwatch/src/main/resources/dark/profile/main_pages.json @@ -1,5 +1,5 @@ { "src": [ - "TestAbility/pages/index" + "pages/index" ] } diff --git a/feature/stopwatch/src/main/resources/en_US/element/string.json b/feature/stopwatch/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000..787b361 --- /dev/null +++ b/feature/stopwatch/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "stopwatch_title", + "value": "Stopwatch" + }, + { + "name": "stopwatch", + "value": "Stopwatch" + }, + { + "name": "tips_title", + "value": "The list is full." + } + ] +} \ No newline at end of file diff --git a/feature/stopwatch/src/main/resources/rawfile/pauseToPlay.json b/feature/stopwatch/src/main/resources/rawfile/pauseToPlay.json new file mode 100644 index 0000000..63a9f4f --- /dev/null +++ b/feature/stopwatch/src/main/resources/rawfile/pauseToPlay.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":53.6990661621094,"ip":0,"op":10.0000123209202,"w":300,"h":300,"nm":"开始启动按钮","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":3,"ty":4,"nm":"按钮切换","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[144.73,145.578,0],"ix":2,"l":2},"a":{"a":0,"k":[0.114,992.753,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0.881,"s":[{"i":[[0,-4.706],[0,0],[4.706,0],[0,4.706],[0,0],[-4.706,0]],"o":[[0,0],[0,4.706],[-4.706,0],[0,0],[0,-4.706],[4.706,0]],"v":[[8.52,-36.48],[8.52,36.48],[0,45],[-8.52,36.48],[-8.52,-36.48],[0,-45]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":2,"s":[{"i":[[-0.615,-3.98],[0.695,-1.379],[4.517,-1.289],[2.978,0.608],[0,0],[-5.451,-1.627]],"o":[[0.575,1.419],[-0.74,3.931],[-4.617,1.352],[0,0],[1.549,-1.598],[4.433,1.072]],"v":[[14.381,-23.85],[13.762,21.826],[4.884,34.716],[-11.143,38.466],[-11.143,-41.001],[4.408,-37.728]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":3,"s":[{"i":[[-0.85,-3.704],[0.961,-1.904],[3.733,-3.362],[4.114,-0.954],[0,0],[-4.641,-4.328]],"o":[[0.795,1.96],[-1.022,3.635],[-3.678,3.312],[0,0],[2.139,-0.413],[4.641,4.328]],"v":[[14.002,-18.213],[13.511,15.489],[4.497,27.699],[-11.714,36.128],[-10.964,-39.262],[4.589,-32.491]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":5,"s":[{"i":[[-1.153,-3.346],[1.304,-2.585],[4.52,-3.271],[5.585,-2.978],[0,0],[-2.914,-2.408]],"o":[[1.079,2.661],[-1.388,3.252],[-4.213,3.049],[0,0],[2.904,1.123],[2.914,2.408]],"v":[[16.975,-11.115],[16.4,5.517],[7.209,14.358],[-14.419,26.848],[-14.169,-32.418],[6.066,-23.572]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[-1.167,-3.329],[2.758,-4.118],[4.499,-3.213],[7.071,-2.043],[0,0],[-3.551,-2.322]],"o":[[1.694,2.591],[-1.337,1.995],[-4.244,3.027],[0,0],[3.955,1.653],[2.887,1.887]],"v":[[17.169,-9.829],[16.355,5.629],[6.97,14.688],[-16.341,27.13],[-15.841,-30.892],[5.164,-21.167]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[-1.192,-3.3],[1.348,-2.672],[4.461,-3.112],[5.772,-3.235],[0,0],[-3.856,-2.69]],"o":[[1.115,2.75],[-1.434,3.204],[-4.299,2.989],[0,0],[3.001,1.317],[3.256,2.364]],"v":[[17.51,-7.572],[16.276,5.827],[5.673,14.828],[-17.211,28.806],[-17.211,-31.212],[4.96,-17.634]],"c":true}]},{"t":7.99982652317694,"s":[{"i":[[-1.354,-3.084],[3.087,-2.565],[4.461,-3.112],[5.772,-3.235],[0,0],[-3.856,-2.69]],"o":[[1.354,3.084],[-1.434,3.204],[-4.299,2.989],[0,0],[2.824,1.848],[3.256,2.364]],"v":[[17.51,-7.572],[16.276,5.827],[5.673,14.828],[-16.711,29.181],[-17.211,-31.212],[4.96,-17.634]],"c":true}]}],"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[24.02,993.163],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0.881,"s":[{"i":[[0,-4.694],[0,0],[4.694,0],[0,4.694],[0,0],[-4.694,0]],"o":[[0,0],[0,4.694],[-4.694,0],[0,0],[0,-4.694],[4.694,0]],"v":[[8.5,-36.5],[8.5,36.5],[0,45],[-8.5,36.5],[-8.5,-36.5],[0,-45]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":2,"s":[{"i":[[-0.292,-4.556],[0,0],[4.884,0.043],[0.054,4.84],[0,0],[-4.644,-0.006]],"o":[[0,0],[-0.391,4.61],[-4.759,-0.033],[0,0],[0.07,-4.869],[4.837,0.011]],"v":[[10.626,-35.701],[10.626,35.915],[-0.28,45.198],[-11.728,36.5],[-11.744,-36.401],[-0.198,-45.049]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":3,"s":[{"i":[[-1.123,-4.162],[0,0],[5.421,0.167],[0.206,5.255],[0,0],[-4.503,-0.023]],"o":[[0,0],[-1.503,4.369],[-4.941,-0.125],[0,0],[0.269,-5.365],[5.244,0.041]],"v":[[14.666,-33.054],[14.416,33.877],[-1.076,45.76],[-12.534,36.5],[-12.598,-36.12],[-0.76,-45.19]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":5,"s":[{"i":[[-0.615,-7.689],[0.636,-9.342],[6.491,0.412],[0.509,6.081],[0,0],[-4.22,-0.058]],"o":[[0.615,7.689],[-0.636,9.342],[-5.304,-0.309],[0,0],[0.666,-6.353],[6.053,0.1]],"v":[[19.698,-30.031],[18.949,29.816],[-2.662,46.879],[-14.141,36.5],[-14.298,-37.811],[-1.879,-47.72]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[-3.404,-4.288],[0.222,-5.923],[6.818,0.487],[0.602,6.333],[0,0],[-4.134,-0.068]],"o":[[0.427,5.344],[-3.275,6.171],[-5.415,-0.365],[0,0],[0.787,-6.654],[6.3,0.119]],"v":[[23.967,-33.053],[23.841,31.081],[-3.146,47.221],[-14.632,36.5],[-14.817,-36.953],[-2.221,-47.119]],"c":true}]},{"t":7.99982652317694,"s":[{"i":[[-5.657,-3.046],[0.295,-4.336],[7.066,0.544],[0.672,6.524],[0,0],[-4.068,-0.076]],"o":[[0.285,3.569],[-5.264,3.321],[-5.499,-0.408],[0,0],[0.879,-6.883],[6.487,0.133]],"v":[[35.104,-27.745],[37.211,26.262],[-3.513,47.48],[-15.003,36.5],[-15.21,-36.304],[-2.48,-46.664]],"c":true}]}],"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-23.813,992.342],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 1","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":10.0000123209202,"st":-46.553240691139,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"形状图层 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[146.11,145.58,0],"ix":2,"l":2},"a":{"a":0,"k":[-7.278,-2.768,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"sy":[{"c":{"a":0,"k":[0.039215687662,0.349019616842,0.96862745285,1],"ix":2},"o":{"a":0,"k":20,"ix":3},"a":{"a":0,"k":90,"ix":5},"s":{"a":0,"k":23,"ix":8},"d":{"a":0,"k":13,"ix":6},"ch":{"a":0,"k":0,"ix":7},"bm":{"a":0,"k":1,"ix":1},"no":{"a":0,"k":0,"ix":9},"lc":{"a":0,"k":1,"ix":10},"ty":1,"nm":"投影"}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[260,260],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.349019607843,0.96862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.278,-2.768],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":54.0000665329691,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/feature/stopwatch/src/main/resources/rawfile/playToPause.json b/feature/stopwatch/src/main/resources/rawfile/playToPause.json new file mode 100644 index 0000000..8dc9b14 --- /dev/null +++ b/feature/stopwatch/src/main/resources/rawfile/playToPause.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":53.6990661621094,"ip":0,"op":10.0000123209202,"w":300,"h":300,"nm":"开始启动按钮","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"按钮切换 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[144.73,145.578,0],"ix":2,"l":2},"a":{"a":0,"k":[0.114,992.753,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":1,"s":[{"i":[[-1.354,-3.084],[3.087,-2.565],[4.461,-3.112],[5.772,-3.235],[0,0],[-3.856,-2.69]],"o":[[1.354,3.084],[-1.434,3.204],[-4.299,2.989],[0,0],[2.824,1.848],[3.256,2.364]],"v":[[17.51,-7.572],[16.276,5.827],[5.673,14.828],[-16.711,29.181],[-17.211,-31.212],[4.96,-17.634]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[{"i":[[-1.192,-3.3],[1.348,-2.672],[4.461,-3.112],[5.772,-3.235],[0,0],[-3.856,-2.69]],"o":[[1.115,2.75],[-1.434,3.204],[-4.299,2.989],[0,0],[3.001,1.317],[3.256,2.364]],"v":[[17.51,-7.572],[16.276,5.827],[5.673,14.828],[-17.211,28.806],[-17.211,-31.212],[4.96,-17.634]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[-1.167,-3.329],[2.758,-4.118],[4.499,-3.213],[7.071,-2.043],[0,0],[-3.551,-2.322]],"o":[[1.694,2.591],[-1.337,1.995],[-4.244,3.027],[0,0],[3.955,1.654],[2.887,1.887]],"v":[[17.169,-9.829],[16.355,5.629],[6.97,14.688],[-16.341,27.13],[-15.841,-30.892],[5.164,-21.167]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[{"i":[[-1.153,-3.346],[1.304,-2.585],[4.52,-3.271],[5.585,-2.978],[0,0],[-2.914,-2.408]],"o":[[1.079,2.661],[-1.388,3.252],[-4.213,3.049],[0,0],[2.904,1.123],[2.914,2.408]],"v":[[16.975,-11.115],[16.4,5.517],[7.209,14.358],[-14.419,26.849],[-14.169,-32.418],[6.066,-23.572]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":6,"s":[{"i":[[-0.85,-3.704],[0.961,-1.904],[3.733,-3.362],[4.114,-0.954],[0,0],[-4.641,-4.328]],"o":[[0.795,1.96],[-1.022,3.635],[-3.678,3.312],[0,0],[2.139,-0.413],[4.641,4.328]],"v":[[14.002,-18.213],[13.511,15.489],[4.497,27.699],[-11.714,36.128],[-10.964,-39.262],[4.589,-32.491]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":7,"s":[{"i":[[-0.615,-3.98],[0.695,-1.379],[4.517,-1.289],[2.978,0.608],[0,0],[-5.451,-1.627]],"o":[[0.575,1.419],[-0.74,3.931],[-4.617,1.352],[0,0],[1.549,-1.598],[4.433,1.072]],"v":[[14.381,-23.85],[13.762,21.826],[4.884,34.716],[-11.143,38.466],[-11.143,-41.001],[4.408,-37.728]],"c":true}]},{"t":7.99982652317694,"s":[{"i":[[0,-4.706],[0,0],[4.706,0],[0,4.706],[0,0],[-4.706,0]],"o":[[0,0],[0,4.706],[-4.706,0],[0,0],[0,-4.706],[4.706,0]],"v":[[8.52,-36.48],[8.52,36.48],[0,45],[-8.52,36.48],[-8.52,-36.48],[0,-45]],"c":true}]}],"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[24.02,993.163],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":1,"s":[{"i":[[-5.657,-3.046],[0.295,-4.336],[7.066,0.544],[0.672,6.524],[0,0],[-4.068,-0.076]],"o":[[0.285,3.569],[-5.264,3.321],[-5.499,-0.408],[0,0],[0.879,-6.883],[6.487,0.133]],"v":[[35.104,-27.745],[37.211,26.262],[-3.513,47.48],[-15.003,36.5],[-15.21,-36.304],[-2.48,-46.664]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":2,"s":[{"i":[[-3.404,-4.288],[0.222,-5.923],[6.818,0.487],[0.602,6.333],[0,0],[-4.134,-0.068]],"o":[[0.427,5.344],[-3.275,6.171],[-5.415,-0.365],[0,0],[0.787,-6.654],[6.3,0.119]],"v":[[23.967,-33.054],[23.841,31.081],[-3.146,47.221],[-14.632,36.5],[-14.817,-36.953],[-2.221,-47.119]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[{"i":[[-0.615,-7.689],[0.636,-9.342],[6.491,0.412],[0.509,6.081],[0,0],[-4.22,-0.058]],"o":[[0.615,7.689],[-0.636,9.341],[-5.304,-0.309],[0,0],[0.666,-6.353],[6.053,0.1]],"v":[[19.698,-30.031],[18.949,29.817],[-2.662,46.879],[-14.141,36.5],[-14.298,-37.81],[-1.879,-47.72]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":6,"s":[{"i":[[-1.123,-4.162],[0,0],[5.421,0.167],[0.206,5.255],[0,0],[-4.503,-0.023]],"o":[[0,0],[-1.503,4.369],[-4.941,-0.125],[0,0],[0.269,-5.365],[5.244,0.041]],"v":[[14.666,-33.054],[14.416,33.877],[-1.076,45.76],[-12.534,36.5],[-12.598,-36.12],[-0.76,-45.19]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":7,"s":[{"i":[[-0.292,-4.556],[0,0],[4.884,0.043],[0.054,4.84],[0,0],[-4.644,-0.006]],"o":[[0,0],[-0.391,4.61],[-4.759,-0.033],[0,0],[0.07,-4.869],[4.837,0.011]],"v":[[10.626,-35.701],[10.626,35.915],[-0.28,45.198],[-11.728,36.5],[-11.744,-36.401],[-0.198,-45.049]],"c":true}]},{"t":7.99982652317694,"s":[{"i":[[0,-4.694],[0,0],[4.694,0],[0,4.694],[0,0],[-4.694,0]],"o":[[0,0],[0,4.694],[-4.694,0],[0,0],[0,-4.694],[4.694,0]],"v":[[8.5,-36.5],[8.5,36.5],[0,45],[-8.5,36.5],[-8.5,-36.5],[0,-45]],"c":true}]}],"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-23.813,992.342],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 1","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":10.0000123209202,"st":-46.553240691139,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"形状图层 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[146.11,145.58,0],"ix":2,"l":2},"a":{"a":0,"k":[-7.278,-2.768,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"sy":[{"c":{"a":0,"k":[0.039215687662,0.349019616842,0.96862745285,1],"ix":2},"o":{"a":0,"k":20,"ix":3},"a":{"a":0,"k":90,"ix":5},"s":{"a":0,"k":23,"ix":8},"d":{"a":0,"k":13,"ix":6},"ch":{"a":0,"k":0,"ix":7},"bm":{"a":0,"k":1,"ix":1},"no":{"a":0,"k":0,"ix":9},"lc":{"a":0,"k":1,"ix":10},"ty":1,"nm":"投影"}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[260,260],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.349019607843,0.96862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.278,-2.768],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":54.0000665329691,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/feature/stopwatch/src/main/resources/zh_CN/element/string.json b/feature/stopwatch/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..4763c70 --- /dev/null +++ b/feature/stopwatch/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,12 @@ +{ + "string": [ + { + "name": "stopwatch_title", + "value": "秒表" + }, + { + "name": "tips_title", + "value": "列表已满" + } + ] +} \ No newline at end of file diff --git a/feature/timer/BuildProfile.ets b/feature/timer/BuildProfile.ets new file mode 100644 index 0000000..3a501e5 --- /dev/null +++ b/feature/timer/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/feature/timer/hvigorfile.ts b/feature/timer/hvigorfile.ts new file mode 100644 index 0000000..9d84ef8 --- /dev/null +++ b/feature/timer/hvigorfile.ts @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +module.exports = require('@ohos/hvigor-ohos-plugin').harTasks; diff --git a/feature/timer/index.ets b/feature/timer/index.ets index e628e7f..af8f403 100644 --- a/feature/timer/index.ets +++ b/feature/timer/index.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,6 +13,8 @@ * limitations under the License. */ -export { TimerView } from './src/main/ets/components/TimerView'; +export { Timer } from './src/main/ets/pages/index'; -export { TimerController, TimerState } from './src/main/ets/controller/TimerController'; +export { FullScreenTimer } from './src/main/ets/pages/FullScreenTimer'; + +export { TimerNotificationUtil } from './src/main/ets/utils' \ No newline at end of file diff --git a/feature/timer/oh-package-lock.json5 b/feature/timer/oh-package-lock.json5 new file mode 100644 index 0000000..688e757 --- /dev/null +++ b/feature/timer/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@hmos/common@../../common": "@hmos/common@../../common", + "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har": "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har" + }, + "packages": { + "@hmos/common@../../common": { + "name": "@ohos/common", + "version": "1.0.0", + "resolved": "../../common", + "registryType": "local", + "dependencies": { + "@ohos/lottie": "file:./src/main/ets/libs/lottieArkTS.har" + } + }, + "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har": { + "name": "@ohos/lottie", + "version": "2.0.5", + "resolved": "../../common/src/main/ets/libs/lottieArkTS.har", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/feature/timer/oh-package.json5 b/feature/timer/oh-package.json5 new file mode 100644 index 0000000..92262a9 --- /dev/null +++ b/feature/timer/oh-package.json5 @@ -0,0 +1,14 @@ +{ + "license": "ISC", + "types": "", + "devDependencies": { + "@hmos/common": "file:../../common" + }, + "name": "@hmos/timer", + "description": "a npm package which contains pages of timer", + "main": "index.ets", + "repository": {}, + "version": "1.0.0", + "dynamicDependencies": {}, + "dependencies": {} +} diff --git a/feature/timer/package-lock.json b/feature/timer/package-lock.json deleted file mode 100644 index e693da1..0000000 --- a/feature/timer/package-lock.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@ohos/timer", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@ohos/common": { - "version": "file:../../common" - } - } -} diff --git a/feature/timer/package.json b/feature/timer/package.json deleted file mode 100644 index 811bb31..0000000 --- a/feature/timer/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "license": "ISC", - "types": "", - "devDependencies": {}, - "name": "@ohos/timer", - "description": "a npm package which contains arkUI2.0 page", - "ohos": { - "org": "" - }, - "main": "src/main/ets/components/MainPage/MainPage.ets", - "repository": {}, - "version": "1.0.0", - "dependencies": { - "@ohos/common": "file:../../common" - } -} diff --git a/feature/timer/src/main/ets/common/Utils.ets b/feature/timer/src/main/ets/common/Utils.ets new file mode 100644 index 0000000..4d231b7 --- /dev/null +++ b/feature/timer/src/main/ets/common/Utils.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogUtil } from '@hmos/common'; +import { DateObject } from '../model/Index' + +export { DateObject } + +const TAG: string = 'Timer_Utils'; + +/** + * 0 到 9 数字变双位数:00 09 + * + */ +export function prevInsertZero(num: number = 0): string { + return String(num).padStart(2, '0'); +} + +/** + * 毫秒转 时分秒 + * + */ +export function formatMillisecondToObject(allMillisecond: number = 0): DateObject { + let ms = Math.floor(allMillisecond % 1000); + let s = Math.floor(allMillisecond / 1000); + let m = Math.floor(s / 60); + let h = Math.floor(m / 60); + + s %= 60; + m %= 60; + + let hours = prevInsertZero(h); + let minute = prevInsertZero(m); + let seconds = prevInsertZero(s); + let millisecond = prevInsertZero(ms); + LogUtil.info(TAG, `allMillisecond=>${allMillisecond},${hours}:${minute}:${seconds}:${millisecond}`) + return new DateObject(hours, minute, seconds, millisecond) +} \ No newline at end of file diff --git a/feature/timer/src/main/ets/common/preferencesUtil.ets b/feature/timer/src/main/ets/common/preferencesUtil.ets new file mode 100644 index 0000000..208ebee --- /dev/null +++ b/feature/timer/src/main/ets/common/preferencesUtil.ets @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dataPreferences from '@ohos.data.preferences'; +import { BusinessError } from '@ohos.base' +import { LogUtil, GlobalContext, TIMER_PREFERENCES_CLOCK } from '@hmos/common'; + +const TAG: string = 'PreferencesManager'; + +interface IPreferencesManagerOptions { + context: Context; + name: string; +} + +interface IPreferencesManager { + create: (options: IPreferencesManagerOptions) => Promise; + getKey: (key: string, defVal: dataPreferences.ValueType) => Promise; + setKey: (key: string, value: dataPreferences.ValueType, flush?: boolean) => Promise; + deleteKey: (key: string, flush?: boolean) => Promise; +} + +class PreferencesManager implements IPreferencesManager { + constructor(options: IPreferencesManagerOptions) { + this.options = options + } + + private preferencesInstance = {} as dataPreferences.Preferences; + private options = {} as IPreferencesManagerOptions; + + async create() { + try { + const options = this.options; + let preferencesInstance = await dataPreferences.getPreferences(options.context, options.name); + this.preferencesInstance = preferencesInstance; + LogUtil.info(TAG, `create preferences success.`); + } catch (error) { + LogUtil.info(TAG, `create preferences fail. ${JSON.stringify(error)}`) + } + return this.preferencesInstance; + } + + async getKey(key: string, defVal: dataPreferences.ValueType) { + let value = defVal; + try { + value = await this.preferencesInstance.get(key, defVal); + LogUtil.info(TAG, `getKey success. ${key}, ${value}`); + + } catch (error) { + LogUtil.info(TAG, `getKey fail. ${JSON.stringify(error)}`) + } + return value; + } + + async setKey(key: string, value: dataPreferences.ValueType, flush?: boolean) { + let success = false; + try { + success = await this.preferencesInstance.put(key, value).then(() => true); + LogUtil.info(TAG, `setKey success. ${key}, ${value}`); + if (flush) { + await this.preferencesInstance.flush(); + LogUtil.info(TAG, `setKey flush success. ${key}, ${value}`); + } + } catch (error) { + LogUtil.info(TAG, `setKey fail. ${JSON.stringify(error)}`) + } + return success; + } + + async deleteKey(key: string, flush?: boolean) { + let success = false; + try { + success = await this.preferencesInstance.delete(key).then(() => true); + LogUtil.info(TAG, `deleteKey success. ${key}`); + if (flush) { + await this.preferencesInstance.flush(); + LogUtil.info(TAG, `deleteKey flush success. ${key}`); + } + } catch (error) { + LogUtil.info(TAG, `deleteKey fail. ${JSON.stringify(error)}`) + } + return success; + } +} + +// TIMER_PREFERENCES_CLOCK +interface ITimerPreferencesManagerUtil { + manager?: PreferencesManager; + create: () => Promise; + save: (key: string, value: dataPreferences.ValueType) => Promise; + del: (key: string) => Promise; + getKey: (key: string) => Promise; +} + +function getContext(): Context { + return GlobalContext.getContext().getObject('clockContext') as Context; +} + +export const timerPreferencesManagerUtil: ITimerPreferencesManagerUtil = { + async create() { + if (this.manager) { + return this.manager; + } + const options: IPreferencesManagerOptions = { + context: getContext(), + name: TIMER_PREFERENCES_CLOCK + } + const preferencesManager = new PreferencesManager(options); + await preferencesManager.create(); + this.manager = preferencesManager; + return this.manager; + }, + async save(key, value) { + const manager: PreferencesManager = await this.create(); + const success = await manager.setKey(key, value, true); + return success; + }, + async del(key) { + const manager: PreferencesManager = await this.create(); + const success = await manager.deleteKey(key, true); + return success; + }, + async getKey(key) { + const manager: PreferencesManager = await this.create(); + const value = await manager.getKey(key, 0); + return value; + } +} + +export default PreferencesManager; \ No newline at end of file diff --git a/feature/timer/src/main/ets/components/AnalogTimer.ets b/feature/timer/src/main/ets/components/AnalogTimer.ets new file mode 100644 index 0000000..4c1f0bd --- /dev/null +++ b/feature/timer/src/main/ets/components/AnalogTimer.ets @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CLOCK_SCALE_VALUE, HALF, LogUtil, SCALE_DIAMETER_RATIO, SECOND_IN_MINUTE } from '@hmos/common'; +import { + FULL_CIRCLE_ANGLE, + SCALE_UNIT_HEIGHT, + SCALE_UNIT_WIDTH, + SECOND_ANGLE_STEP, +} from '@hmos/common/src/main/ets/components/Clock/types'; +import { DialData } from '../types' +import deviceInfo from '@ohos.deviceInfo' + +const TAG: string = 'AnalogTimer'; +const FOLD_SCREEN_WIDTH = 2496; +const COLREMAIN_HEIGHT = 33; +const FOLD_REMAIN_HEIGHT = 22; +const ROW_REMAIN_HEIGHT = 18; +const DIAL_DIAMETER_RATIO = 1.66; //表盘直径比率 +const LINE_HEIGHT = 11; + +enum GradientColor { + BLUE = '6239ff', + CYAN = '03b6ee' +} + +/** + * Used to display the analog timer component. + * + * @since 2023-09-10 + */ +@Component +export struct AnalogTimer { + @State remainHeight: number = COLREMAIN_HEIGHT; + @Prop @Watch('setRemainHeight') isPortraitOrientation: boolean; + @Prop scaleDiameterRatio: number = SCALE_DIAMETER_RATIO; + @Prop getOrientaion: number; + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + @Prop private diameter: number = 0; + @Prop @Watch('updateStep') totalTime: number = 0; + @Prop timeLeft: number; + @Link @Watch('resetAngle') reset: boolean; + @State timerId: number = -1; + @State @Watch('updateCurrentAngle') currentAngle: number = 360; + @Prop @Watch('watchPassedTime') passedTime: number = 0; + private angleStep: number = 0; + private range: number[] = []; + private angleList: DialData[] = []; + @State colorList: string[] = []; + @State deviceType: string = deviceInfo.deviceType; // 设备类型 + private isTablePad: string = 'tablet' + + aboutToAppear() { + this.getAngle(); + LogUtil.info(TAG, 'range:' + JSON.stringify(this.range)); + this.setGradientColor(GradientColor.BLUE, GradientColor.CYAN, this.range.length); + this.getStrokeColorList(); + } + + getAngle() { + for (let n: number = FULL_CIRCLE_ANGLE; this.range.length < (SECOND_IN_MINUTE * SCALE_UNIT_WIDTH); + n -= SECOND_ANGLE_STEP * HALF) { + this.range.push(n); + } + } + + getStrokeColorList() { + for (let i = 0; i < this.range.length; i++) { + this.angleList.push({ + num: this.range[i], + color: this.colorList[i - 1] + }); + } + LogUtil.info(TAG, 'angleList:' + JSON.stringify(this.angleList)); + } + + setGradientColor(startColor: string, endColor: string, step: number) { + let startN = this.rgbToArr(startColor); + let bR: number = startN[0]; + let bG: number = startN[1]; + let bB: number = startN[2]; + let endN = this.rgbToArr(endColor); + let eR: number = endN[0]; + let eG: number = endN[1]; + let eB: number = endN[2]; + const rR: number = (eR - bR) / step; + const rG: number = (eG - bG) / step; + const rB: number = (eB - bB) / step; + for (let i = 0; i < step; i++) { + this.colorList.push(`rgb(${Number.parseInt(String(rR * i + bR))},${Number.parseInt(String(rG * i + bG))},${Number.parseInt(String(rB * i + bB))})`); + } + LogUtil.info(TAG, `colorList: ${this.colorList}`); + this.colorList.pop(); + return this.colorList; + } + + rgbToArr(rgb: string) { + let newRgb = this.colorRgb(rgb); + let rgbArr = newRgb.replace('rgb(', '').replace(')', '').split(','); + LogUtil.info(TAG, `rgbArr: ${rgbArr}`); + let rgbNewArr = rgbArr.map(item => { + return Number.parseInt(item); + }) + return rgbNewArr; + } + + colorRgb(sColor: string) { + sColor = sColor.replace('#', ''); + const RR = Number.parseInt(sColor.slice(0, 2), 16); + const BB = Number.parseInt(sColor.slice(2, 4), 16); + const AA = Number.parseInt(sColor.slice(4), 16); + LogUtil.info(TAG, `rgb(${RR},${BB},${AA})`); + return `rgb(${RR},${BB},${AA})`; + } + + updateCurrentAngle() { + } + + aboutToDisappear() { + this.timerId !== -1 && clearInterval(this.timerId); + } + + watchPassedTime() { + let angle = 0; + const passedTime = this.passedTime; + let totalTime = this.totalTime; + let step = 360 / totalTime; + angle = passedTime * step * -1; + if (Math.abs(angle) >= 360) { + angle = 0; + } + this.currentAngle = angle; + } + + private updateStep() { + this.angleStep = (FULL_CIRCLE_ANGLE / this.totalTime); + LogUtil.info(TAG, `totalTime=>${this.totalTime}`); + LogUtil.info(TAG, `angleStep=>${this.angleStep}`); + } + + private resetAngle() { + if (this.reset) { + LogUtil.info(TAG, 'Resetting angle with animation'); + animateTo({ + duration: 350, + curve: Curve.Friction, + }, () => { + }) + } + } + + setRemainHeight() { + if (this.foldAbleScreen === 1) { + this.remainHeight = this.getOrientaion === FOLD_SCREEN_WIDTH ? FOLD_REMAIN_HEIGHT : COLREMAIN_HEIGHT; + } + this.remainHeight = this.isPortraitOrientation ? COLREMAIN_HEIGHT : ROW_REMAIN_HEIGHT; + } + + @Builder + buildTimerDial() { + Column() { + Stack() { + Image($r('app.media.img_clock_timer_bg')) + .height(this.diameter * this.scaleDiameterRatio * DIAL_DIAMETER_RATIO) + .width(this.diameter * this.scaleDiameterRatio * DIAL_DIAMETER_RATIO) + .objectFit(ImageFit.Contain) + .draggable(false) + .interpolation(ImageInterpolation.Medium) + + ForEach(this.angleList, (item: DialData) => { + Line() + .width(SCALE_UNIT_WIDTH) + .height(this.diameter * this.scaleDiameterRatio + LINE_HEIGHT) + .startPoint([0, 0]) + .endPoint([0, SCALE_UNIT_HEIGHT]) + .stroke($r('app.color.timer_stroke')) + .strokeWidth(SCALE_UNIT_WIDTH) + .strokeLineCap(LineCapStyle.Round) + .rotate({ z: 1, angle: item.num }) + .visibility(this.diameter > 0 ? Visibility.Visible : Visibility.Hidden) + + if ((this.currentAngle !== 0) && (item.num <= (FULL_CIRCLE_ANGLE - Math.abs(this.currentAngle)))) { + Line() + .width(SCALE_UNIT_WIDTH) + .height(this.diameter * this.scaleDiameterRatio + LINE_HEIGHT) + .startPoint([0, 0]) + .endPoint([0, SCALE_UNIT_HEIGHT]) + .stroke(item.color) + .strokeWidth(SCALE_UNIT_WIDTH) + .strokeLineCap(LineCapStyle.Round) + .rotate({ z: 1, angle: item.num }) + .opacity(item.num / FULL_CIRCLE_ANGLE + 0.2) + } + }) + + Image($r('app.media.img_clock_timer_dial_secondhand')) + .height(this.diameter * this.scaleDiameterRatio + this.remainHeight) + .objectFit(ImageFit.Contain) + .draggable(false) + .translate({ x: -0.52 }) + .rotate({ z: 1, angle: this.currentAngle }) + .interpolation(ImageInterpolation.Medium) + } + .id('id_timer') + .width('100%') + .height('100%') + .transition({ type: TransitionType.All, opacity: 0, scale: { x: CLOCK_SCALE_VALUE, y: CLOCK_SCALE_VALUE } }) + } + .justifyContent(FlexAlign.Center) + } + + build() { + if (this.diameter) { + Row() { + this.buildTimerDial(); + } + .width('100%') + .height(this.diameter) + .justifyContent(FlexAlign.Center) + } + } +} diff --git a/feature/timer/src/main/ets/components/SlideCloseButton/index.ets b/feature/timer/src/main/ets/components/SlideCloseButton/index.ets new file mode 100644 index 0000000..d98a5d5 --- /dev/null +++ b/feature/timer/src/main/ets/components/SlideCloseButton/index.ets @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + LogUtil, + AlarmInfo, + WantAgentUtil, + EventReportUtil, + EventName, + EventResult, + TIMEOUT_FULL_SCREEN_CLOSE_ID, + CommonUtil +} from '@hmos/common'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import emitter from '@ohos.events.emitter'; + +const FIRST_FINGER = 0; +const TAG = 'SlideCloseButton'; +const MOVE_SCALE = 1; + +/** + * Button for sliding to turn off + * + * @since 2022-08-18 + */ +@Component +export default struct SlideCloseButton { + private alarmInfo?: AlarmInfo; + private preX: number = 0; + private preY: number = 0; + private curX: number = 0; + private curY: number = 0; + private startX: number = 0; + private startY: number = 0; + private isMoved: boolean = false; + @State positionX: number = 0; + + private updateActionMove(event: TouchEvent): void { + this.curX = event.touches[FIRST_FINGER].windowX; + this.curY = event.touches[FIRST_FINGER].windowY; + LogUtil.info(TAG, 'curX: ' + this.curX, 'curY: ' + this.curY); + + if (Math.abs(this.curX - this.startX) <= Math.abs(this.curY - this.startY)) { + // Move in Y-direction, no response. + LogUtil.info(TAG, 'move Y direction=' + Math.abs(this.curY - this.startY)); + this.isMoved = false; + } else { + // Move in X-direction, make the button move. + LogUtil.info(TAG, 'move X direction=' + Math.abs(this.curX - this.startX)); + if (Math.abs(this.curX - this.startX) >= 100) { + this.isMoved = true; + } + } + this.positionX += (this.curX - this.preX) * MOVE_SCALE; + + // Record the previous coordinate + this.preX = this.curX; + this.preY = this.curY; + } + + private async onSlideButton(event: TouchEvent): Promise { + switch (event.type) { + case TouchType.Down: { + LogUtil.info(TAG, 'touch down'); + this.startX = event.touches[FIRST_FINGER].screenX; + this.startY = event.touches[FIRST_FINGER].screenY; + LogUtil.info(TAG, 'startX: ' + this.startX, 'startY: ' + this.startY); + this.preX = this.startX; + this.preY = this.startY; + break; + } + case TouchType.Up: { + LogUtil.info(TAG, 'touch up'); + if (this.isMoved) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.LOCK_SCREEN_SWIPE_CLOSE_ALARM); + emitter.emit({ eventId: TIMEOUT_FULL_SCREEN_CLOSE_ID, priority: emitter.EventPriority.IMMEDIATE }); + CommonUtil.terminateFullScreenAbility(); + } else { + this.positionX = 0; + } + break; + } + case TouchType.Move: { + LogUtil.info(TAG, 'touch move'); + this.updateActionMove(event); + break; + } + case TouchType.Cancel: { + LogUtil.info(TAG, 'touch cancel'); + break; + } + default: { + LogUtil.info(TAG, 'default'); + break; + } + } + } + + build() { + Column() + .borderWidth($r('app.float.slide_to_turn_off_button_border_width')) + .borderColor(Color.White) + .borderRadius($r('app.float.slide_to_turn_off_button_size')) + .width($r('app.float.slide_to_turn_off_button_size')) + .height($r('app.float.slide_to_turn_off_button_size')) + .offset({ x: this.positionX, y: 0 }) + .onTouch((event?: TouchEvent) => this.onSlideButton(event as TouchEvent)); + } +} \ No newline at end of file diff --git a/feature/timer/src/main/ets/components/TimerPicker.ets b/feature/timer/src/main/ets/components/TimerPicker.ets new file mode 100644 index 0000000..d470d2a --- /dev/null +++ b/feature/timer/src/main/ets/components/TimerPicker.ets @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + EventName, + EventReportUtil, + MILLIS_IN_SECOND, + MINUTE_IN_HOUR, + SECOND_IN_MINUTE, + EVENT_ID_SWITCH_PAGE, + LogUtil, + SoundPool +} from '@hmos/common'; +import { prevInsertZero } from '../common/Utils'; +import { ITextPickerConfig } from '../types' +// import hiSysEvent from '@ohos.hiSysEvent'; +import emitter from '@ohos.events.emitter'; +import common from '@ohos.app.ability.common'; +import audio from '@ohos.multimedia.audio'; +import vibrator from '@ohos.vibrator'; + +const SECOND_IN_HOUR = SECOND_IN_MINUTE * MINUTE_IN_HOUR; + +// 横屏默认数据 +const hTextPickerConfig: ITextPickerConfig = { + pickerItemFontSize: $r('app.float.timer_picker_text_size_h'), + pickerSelectedFontSize: $r('app.float.timer_picker_selected_text_size_h'), + pickerItemHeight: '52vp', + pickerHeight: '180vp' +} +// 竖屏默认数据 +const defaultTextPickerConfig: ITextPickerConfig = { + pickerItemFontSize: $r('app.float.timer_picker_text_size'), + pickerSelectedFontSize: $r('app.float.timer_picker_selected_text_size'), + pickerItemHeight: '70vp', + pickerHeight: '210vp' +} + + +interface ReportParams { + PACKAGE_NAME: string; + PROCESS_NAME: string; + RESULT: string; + VALUE?: string; + STATUS?: string +} + +/** + * Timer Picker component + * + * Since 2023-09-22 + */ +@Component +export struct TimerPicker { + @Link @Watch('computedPickerConfigHandle') isPortraitOrientation: boolean; //纵向 true 横向 false + @Link @Watch('updateSelectedValues') time: number; + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + @Link planHours: string; + @Link planMinutes: string; + @Link planSeconds: string; + @Prop isMdView: boolean = false; + @Prop isTabIndexChange: boolean; + private hoursRange: string[] = []; + private minutesRange: string[] = []; + private secondsRange: string[] = []; + private uri: string = ''; + private soundID: number = 0; + private audioRendererInfo: audio.AudioRendererInfo = { + usage: audio.StreamUsage.STREAM_USAGE_MUSIC, + rendererFlags: 1 + }; + @State context: Context = getContext(this) as common.UIAbilityContext; + @State hours: string | number = this.planHours || '00'; + @State minutes: string | number = this.planMinutes || '00'; + @State seconds: string | number = this.planSeconds || '00'; + @State textPickerConfig: ITextPickerConfig = defaultTextPickerConfig; + @State isPageShow: boolean = true; + + aboutToAppear() { + for (let h: number = 0; h < 100; ++h) { + let formatNumber: string = prevInsertZero(h); + this.hoursRange.push(formatNumber); + if (h < MINUTE_IN_HOUR) { + this.minutesRange.push(formatNumber); + this.secondsRange.push(formatNumber); + } + } + // 监听应用切换后台,刷新timePicker + emitter.on({ + eventId: EVENT_ID_SWITCH_PAGE + }, (target) => { + this.isPageShow = target.data?.isPageShow; + }); + } + + // 拨盘声振 + private textPickerChanged(index: number | number[]) { + try { + vibrator.startVibration( + { + type: 'preset', + effectId: 'haptic.clock.timer', + count: 1, + }, + { + id: 0, + usage: 'simulateReality' + }, (error) => { + if (error) { + LogUtil.error('vibrate fail, error.code: ' + error.code + 'error.message: ' + error.message); + return; + } + LogUtil.info('Callback returned to indicate a successful vibration.'); + }); + } catch (err) { + LogUtil.error('errCode: ' + err.code + ' ,msg: ' + err.message); + } + SoundPool.playSoundPool('timePick') + + } + + private updateSelectedValues(): void { + let date = new Date(this.time); + this.hours = prevInsertZero(Number(date.getUTCDate() - 1) * 24 + date.getUTCHours()); + this.minutes = prevInsertZero(date.getUTCMinutes()); + this.seconds = prevInsertZero(date.getUTCSeconds()); + this.planHours = this.hours; + this.planMinutes = this.minutes; + this.planSeconds = this.seconds; + } + + private updateSelectedTime() { + this.time = (Number(this.hours) * SECOND_IN_HOUR + Number(this.minutes) * SECOND_IN_MINUTE + + Number(this.seconds)) * MILLIS_IN_SECOND; + } + + private computedPickerConfigHandle(): void { + if (this.isPortraitOrientation) { + this.textPickerConfig = defaultTextPickerConfig; + } else { + this.textPickerConfig = hTextPickerConfig; + } + } + + private getFoldAbleScreen(): boolean { + return this.foldAbleScreen === 1 || this.isPortraitOrientation ? true : false + } + + private getFoldAbleScreenTextStyle(): Resource { + return this.getFoldAbleScreen() ? $r('app.float.size36') : $r('app.float.size28') + } + + private getFoldAbleScreenSelectedTextStyle(): Resource { + return this.getFoldAbleScreen() ? $r('app.float.size42') : $r('app.float.size38') + } + + build() { + Row() { + if (this.isPageShow && this.isTabIndexChange) { + TextPicker({ range: this.hoursRange, value: this.hours.toString() }) + .canLoop(true) + .width('52vp') + .defaultPickerItemHeight(this.getFoldAbleScreen() ? '56vp' : '52vp') + .backgroundColor(Color.Transparent) + .textStyle({ + color: $r('app.color.text_picker_color'), + font: { + size: this.getFoldAbleScreenTextStyle(), + weight: FontWeight.Medium + } + }) + .selectedTextStyle({ + color: $r('sys.color.font_primary'), + font: { + size: this.getFoldAbleScreenSelectedTextStyle(), + weight: FontWeight.Medium + } + }) + .onChange((value: string | string[], index: number | number[]) => { + this.planHours = prevInsertZero(Number(value)); + if (prevInsertZero(Number(value)) === this.hours) { + return; + } + this.hours = prevInsertZero(Number(value)); + this.textPickerChanged(index); + this.updateSelectedTime(); + }) + .height($r('app.float.timer_picker_height')) + Text(':') + .width($r('app.float.timer_price_width')) + .textAlign(TextAlign.Center) + .fontColor($r('sys.color.font_primary')) + .fontSize(this.textPickerConfig.pickerSelectedFontSize) + .fontWeight(FontWeight.Medium) + .lineHeight(this.getFoldAbleScreen() ? $r('app.float.line_height_36') : 0) + .margin({ + left: $r('app.float.timer_hint_row_height'), + right: $r('app.float.timer_hint_row_height'), + bottom: this.getFoldAbleScreen() ? 0 : $r('app.float.timer_hint_row_height') + }) + + TextPicker({ range: this.minutesRange, value: this.minutes.toString() }) + .canLoop(true) + .width('52vp') + .defaultPickerItemHeight(this.getFoldAbleScreen() ? '56vp' : '52vp') + .backgroundColor(Color.Transparent) + .textStyle({ + color: $r('app.color.text_picker_color'), + font: { + size: this.getFoldAbleScreenTextStyle(), + weight: FontWeight.Medium + } + }) + .selectedTextStyle({ + color: $r('sys.color.font_primary'), + font: { + size: this.getFoldAbleScreenSelectedTextStyle(), + weight: FontWeight.Medium + } + }) + .onChange((value: string | string[], index: number | number[]) => { + this.planMinutes = prevInsertZero(Number(value)); + if (prevInsertZero(Number(value)) === this.minutes) { + return; + } + this.minutes = prevInsertZero(Number(value)); + this.textPickerChanged(index); + this.updateSelectedTime(); + }) + .height($r('app.float.timer_picker_height')) + Text(':') + .width($r('app.float.timer_price_width')) + .fontColor($r('sys.color.font_primary')) + .textAlign(TextAlign.Center) + .lineHeight(this.getFoldAbleScreen() ? $r('app.float.line_height_36') : '') + .fontSize(this.textPickerConfig.pickerSelectedFontSize) + .fontWeight(FontWeight.Medium) + .margin({ + left: $r('app.float.timer_hint_row_height'), + right: $r('app.float.timer_hint_row_height'), + bottom: this.getFoldAbleScreen() ? 0 : $r('app.float.timer_hint_row_height') + }) + + TextPicker({ range: this.secondsRange, value: this.seconds.toString() }) + .canLoop(true) + .width('52vp') + .defaultPickerItemHeight(this.getFoldAbleScreen() ? '56vp' : '52vp') + .backgroundColor(Color.Transparent) + .textStyle({ + color: $r('app.color.text_picker_color'), + font: { + size: this.getFoldAbleScreenTextStyle(), + weight: FontWeight.Medium + } + }) + .selectedTextStyle({ + color: $r('sys.color.font_primary'), + font: { + size: this.getFoldAbleScreenSelectedTextStyle(), + weight: FontWeight.Medium + } + }) + .onChange((value: string | string[], index: number | number[]) => { + this.planSeconds = prevInsertZero(Number(value)); + if (prevInsertZero(Number(value)) === this.seconds) { + return; + } + this.seconds = prevInsertZero(Number(value)); + this.textPickerChanged(index); + this.updateSelectedTime(); + }) + .height($r('app.float.timer_picker_height')) + } + } + .offset({ x: -6 }) + .justifyContent(FlexAlign.SpaceBetween) + .alignItems(VerticalAlign.Center) + .width(this.isMdView ? '252vp' : $r('app.float.timer_picker_width_picker')) + .height(this.isPortraitOrientation ? '210vp' : '') + } +} diff --git a/feature/timer/src/main/ets/components/TimerPickerForPC.ets b/feature/timer/src/main/ets/components/TimerPickerForPC.ets new file mode 100644 index 0000000..5ea0935 --- /dev/null +++ b/feature/timer/src/main/ets/components/TimerPickerForPC.ets @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + HALF, + SECOND_IN_MINUTE, + SCALE_DIAMETER_RATIO, + MINUTE_IN_HOUR, + MILLIS_IN_SECOND, + LogUtil, + EVENT_ID_TIMEPICK_FORPC +} from '@hmos/common'; +import { + FULL_CIRCLE_ANGLE, + SCALE_UNIT_WIDTH, + SECOND_ANGLE_STEP, + SCALE_UNIT_HEIGHT, +} from '@hmos/common/src/main/ets/components/Clock/types'; +import { prevInsertZero } from '../common/Utils'; +import emitter from '@ohos.events.emitter'; + +import resmgr from '@ohos.resourceManager'; +import { GlobalContext } from '@hmos/common'; + +const SECOND_IN_HOUR = SECOND_IN_MINUTE * MINUTE_IN_HOUR; + +const TAG = 'TimerPC'; + +interface b { + one: string, +} + +@Component +struct InputText { + @Prop text: Resource = $r('app.string.timer_picker_hours') + @Prop @Watch('resetChange') reset: boolean; + @Prop indexFlag: String; + @State textValue: string = ''; + @State isHover: boolean = false; + @State isFocus: Boolean = false; + @State isCaret: number = 0; + onCallBack?: (val: string) => void; + controller: TextInputController = new TextInputController() + + resetChange() { + if (this.reset) { + this.textValue = '00'; + } + + } + + build() { + Row() { + Flex({ direction: FlexDirection.Column }) { + Text(this.text) + .fontSize($r('app.float.timer_PC_label_size')) + .lineHeight($r('app.float.line_height_27')) + .textAlign(TextAlign.Center) + .fontColor($r('sys.color.font_secondary')) + .width($r('app.float.timer_PC_label_width')) + .margin({ bottom: $r('app.float.timer_PC_label_margin') }) + TextInput({ + text: !this.isFocus ? prevInsertZero(Number(this.textValue)) : this.textValue + }) + .fontSize($r('app.float.timer_PC_size')) + .fontColor(this.isFocus ? $r('sys.color.font_on_primary') : $r('sys.color.font_primary')) + .fontWeight(FontWeight.Bold) + .maxLength(2) + .copyOption(CopyOptions.None) + .width($r('app.float.timer_PC_label_width')) + .height($r('app.float.timer_PC_input_height')) + .textAlign(TextAlign.Center) + .border({ width: 0 }) + .caretColor(this.isCaret > 1 ? $r('app.color.dialog_background_color') : $r('app.color.dialog_background_caret_color') ) + .backgroundColor( + this.isFocus + ? $r('sys.color.brand') + : $r('sys.color.comp_background_tertiary') + ) + .type(InputType.Number) + .borderRadius($r('sys.float.corner_radius_level4')) + .onChange((val: string) => { + this.textValue = val === '00' ? '00' : val; + if (this.indexFlag === 'minutes' && Number(val) > 59 || this.indexFlag === 'seconds' && Number(val) > 59) { + this.textValue = '00'; + } + this.onCallBack && this.onCallBack(this.textValue); + }) + .onClick(() => { + this.isCaret++; + this.controller.caretPosition(2) + }) + .onFocus(() => { + this.isFocus = true; + }) + .onBlur(() => { + this.isFocus = false; + this.isCaret = 0; + }) + } + } + } +} + + +@Component +export struct TimerPickerForPC { + @Prop scaleDiameterRatio: number = SCALE_DIAMETER_RATIO + @Prop private diameter: number = 0; + @Link @Watch('updateSelectedValues') time: number; + @State hours: string | number = '00'; + @State minutes: string | number = '00'; + @State seconds: string | number = '00'; + @Link @Watch('resetChange') totalTime: Number; + @State isCleared: boolean = false + private range: number[] = []; + private minList: number[] = [1, 5, 10, 15, 20, 30] + + private updateSelectedTime() { + this.time = (Number(this.hours) * SECOND_IN_HOUR + Number(this.minutes) * SECOND_IN_MINUTE + + Number(this.seconds)) * MILLIS_IN_SECOND; + } + + private updateSelectedValues(): void { + let date = new Date(this.time); + this.hours = Number(date.getUTCDate() - 1) * 24 + date.getUTCHours(); + this.minutes = date.getUTCMinutes(); + this.seconds = date.getUTCSeconds(); + emitter.emit({ + eventId: EVENT_ID_TIMEPICK_FORPC, + priority: emitter.EventPriority.IMMEDIATE, + }, { + data: { + hours: prevInsertZero(this.hours), + minutes: prevInsertZero(this.minutes), + seconds: prevInsertZero(this.seconds) + } + }) + LogUtil.info(TAG, 'onCallBack this.hours-' + this.hours + ',this.minutes' + this.minutes + ',this.seconds' + this.seconds) + } + + resetChange() { + LogUtil.info(TAG, 'resetChange' + this.totalTime); + if (this.totalTime === 0) { + this.isCleared = true; + } else { + this.isCleared = false; + } + } + + aboutToAppear() { + // 拿到每个刻度的角度 + for (let n: number = FULL_CIRCLE_ANGLE; this.range.length < (SECOND_IN_MINUTE * SCALE_UNIT_WIDTH); + n -= SECOND_ANGLE_STEP * HALF) { + this.range.push(n); + } + LogUtil.info(TAG, 'Line', this.range.length + '',) + } + + // 快捷时间按钮 + @Builder + timeList() { + Row() { + Flex({ + wrap: FlexWrap.Wrap, + justifyContent: FlexAlign.SpaceBetween, + alignItems: ItemAlign.Center + }) { + ForEach(this.minList, (item: number) => { + Row() { + Stack() { + Row() { + } + .height($r('app.float.timer_PC_button_width')) + .width($r('app.float.timer_PC_button_width')) + .backgroundColor($r('app.color.start_window_background')) + .borderRadius($r('app.float.timer_PC_button_radius')) + + ForEach(this.range, (item: number) => { + Line() + .width(SCALE_UNIT_WIDTH) + .height($r('app.float.timer_PC_button_round_width')) + .startPoint([0, 0]) + .endPoint([0, SCALE_UNIT_HEIGHT]) + .stroke($r('app.color.timer_stroke')) + .strokeWidth(SCALE_UNIT_WIDTH) + .strokeLineCap(LineCapStyle.Round) + .rotate({ z: 1, angle: item }) + .visibility(Visibility.Visible) + }) + Column() { + Text(item + '') + .fontSize($r('app.float.timer_PC_button_size1')) + .fontColor($r('app.color.timer_background_color')) + Text($r('app.string.timer_picker_minutes')) + .fontSize($r('app.float.timer_PC_button_size2')) + .fontColor($r('app.color.timer_PC_label_font_color')) + } + } + } + .width('33.33%') + .margin({ bottom: $r('app.float.timer_PC_button_margin') }) + .justifyContent(FlexAlign.Center) + .onClick((e) => { + this.minutes = item + }) + }) + } + } + .width('100%') + } + + build() { + Column() { + Row() { + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + InputText({ + text: $r('app.string.timer_picker_hours'), + reset: this.isCleared, + indexFlag: 'hours', + onCallBack: ((val: string) => { + LogUtil.info(TAG, 'onCallBack--hours', val) + this.hours = val + this.updateSelectedTime() + }) + }) + Text(':') + .fontSize($r('app.float.timer_PC_placeholder_size')) + .margin({ top: $r('app.float.timer_PC_label_margin_top') }) + .fontColor($r('app.color.timer_PC_input_font_color')) + InputText({ + text: $r('app.string.timer_picker_minutes'), + reset: this.isCleared, + indexFlag: 'minutes', + onCallBack: ((val: string) => { + LogUtil.info(TAG, 'onCallBack--minutes', val) + this.minutes = val + this.updateSelectedTime() + }) + }) + Text(':') + .fontSize($r('app.float.timer_PC_placeholder_size')) + .margin({ top: $r('app.float.timer_PC_label_margin_top') }) + .fontColor($r('app.color.timer_PC_input_font_color')) + InputText({ + text: $r('app.string.timer_picker_seconds'), + reset: this.isCleared, + indexFlag: 'seconds', + onCallBack: ((val: string) => { + LogUtil.info(TAG, 'onCallBack--seconds', val) + this.seconds = val + this.updateSelectedTime() + + }) + }) + } + .height($r('app.float.timer_PC_input_main_height')) + } + .justifyContent(FlexAlign.Center) + .alignItems(VerticalAlign.Center) + } + .height('100%') + .justifyContent(FlexAlign.Center) + } +} \ No newline at end of file diff --git a/feature/timer/src/main/ets/components/index.ets b/feature/timer/src/main/ets/components/index.ets new file mode 100644 index 0000000..26197fc --- /dev/null +++ b/feature/timer/src/main/ets/components/index.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AnalogTimer } from './AnalogTimer'; +import { TimerPicker } from './TimerPicker'; +import { TimerPickerForPC } from './TimerPickerForPC'; + +export { AnalogTimer, TimerPicker, TimerPickerForPC } \ No newline at end of file diff --git a/feature/timer/src/main/ets/manager/timerAudioPlayer.ets b/feature/timer/src/main/ets/manager/timerAudioPlayer.ets new file mode 100644 index 0000000..dd30270 --- /dev/null +++ b/feature/timer/src/main/ets/manager/timerAudioPlayer.ets @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ResourceAudioManager, LogUtil, } from '@hmos/common' + +const TAG = 'timerAudioPlayer'; + +export class TimerAudioPlayer { + async playTick(tickFileName: string): Promise { + try { + ResourceAudioManager.playLoop(tickFileName); + } catch { + LogUtil.error(TAG, 'Failed to playAudio'); + } + } + + async stopTick(tickFileName: string): Promise { + try { + ResourceAudioManager.stopPlayerFor(tickFileName); + } catch { + LogUtil.error(TAG, 'Failed to playAudio'); + } + } +} + +export default new TimerAudioPlayer(); \ No newline at end of file diff --git a/feature/timer/src/main/ets/manager/timerManager.ets b/feature/timer/src/main/ets/manager/timerManager.ets new file mode 100644 index 0000000..7025fe9 --- /dev/null +++ b/feature/timer/src/main/ets/manager/timerManager.ets @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import display from '@ohos.display'; +import windowManager from '@ohos.window'; +import common from '@ohos.app.ability.common'; +import notificationManager from '@ohos.notificationManager' +import { LogUtil } from '@hmos/common'; +import emitter from '@ohos.events.emitter'; +import image from '@ohos.multimedia.image'; +import { NotificationContentUtil } from '@hmos/common/src/main/ets/utils/NotificationContentUtil' +import { WantAgentUtil, TIMEOUT_FULL_SCREEN_CLOSE_ID, } from '@hmos/common'; + +const TAG = 'TimerManager'; + +type NotificationRequest = notificationManager.NotificationRequest; + +/** + * Window-related management class + * + * @since 2022-08-20 + */ +class TimerManager { + async cancel(alarmId: number | string): Promise { + LogUtil.info(TAG, 'publish Cancel alarmId:' + alarmId); + notificationManager.cancel(typeof alarmId === 'string' ? Number(alarmId) : alarmId); + } + + async timeoutPublishAlarmMissed(alarmId: number | string, alarmAlertTime: number, alarmTitle: string): Promise { + const title = alarmAlertTime.toString(); + const content = alarmTitle; + const initialTime = alarmAlertTime; + try { + const largeIcon: image.PixelMap = await NotificationContentUtil.createPixelMap('timer') + const iconPixelMap: image.PixelMap = await NotificationContentUtil.createPixelMap('timer_capsule') + const iconsPixelMap: image.PixelMap[] = [await NotificationContentUtil.createPixelMap('timer_stop')] + const iconsPixelMapNames: string[] = ['timer_stop'] + const notificationRequest: NotificationRequest = { + id: typeof alarmId === 'string' ? Number(alarmId) : alarmId, + notificationSlotType: notificationManager.SlotType.LIVE_VIEW, + largeIcon: largeIcon, + smallIcon: largeIcon, + content: { + notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_SYSTEM_LIVE_VIEW, + systemLiveView: { + title: title, + text: content, + typeCode: 6, + button: { + icons: iconsPixelMap, + names: iconsPixelMapNames + }, + capsule: { + title: title, + icon: iconPixelMap, + backgroundColor: '#3778EF', + }, + time: { + initialTime: initialTime, + isCountDown: false, + isInTitle: true, + isPaused: false + } + } + }, + extraInfo: { isMute: true, hw_heads_up_enable: false, hw_keep_headsup_sticky: true }, + wantAgent: await WantAgentUtil.getTimerFullScreenWantAgent() + }; + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, `timeoutPublishAlarmMissed success: ${JSON.stringify(notificationRequest)}`) + } catch (error) { + LogUtil.error(TAG, `timeoutPublishAlarmMissed faild: ${JSON.stringify(error)}`); + } + } +} + +export default new TimerManager(); \ No newline at end of file diff --git a/feature/timer/src/main/ets/manager/types.ets b/feature/timer/src/main/ets/manager/types.ets new file mode 100644 index 0000000..affb939 --- /dev/null +++ b/feature/timer/src/main/ets/manager/types.ets @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import inputConsumer from '@ohos.multimodalInput.inputConsumer' + +type KeyOptions = inputConsumer.KeyOptions; + +export const FULL_SCREEN_ALARM_PAGE = 'pages/FullScreenTimer'; + +export const BANNER_ALARM_PAGE = 'pages/BannerAlarm'; + +export const ALARM_WINDOW_TYPE = 2123; + +export const ALARM_WINDOW_NAME = 'FullScreenTimerWindow'; + +export const BANNER_ALARM_WINDOW_NAME = 'BannerTimerWindow'; + +const KEYCODE_VOLUME_UP = 16; + +const KEYCODE_VOLUME_DOWN = 17; + +export const VOLUME_UP_KEY_OPTIONS: KeyOptions = { + preKeys: [], + finalKey: KEYCODE_VOLUME_UP, + isFinalKeyDown: true, + finalKeyDownDuration: 0, +}; + +// export const VOLUME_DOWN_KEY_OPTIONS: KeyOptions = { +// preKeys: [], +// finalKey: KEYCODE_VOLUME_DOWN, +// isFinalKeyDown: true, +// finalKeyDownDuration: 0, +// }; + +export const FD_PREFIX = 'fd://'; + +export const FILE_SEPARATOR = '/'; + +export const WRITE_MODE = 'w+'; + +export const RING_FILE_NAME = 'ring.wav'; + +export const READ_INTERVAL = 16; diff --git a/feature/timer/src/main/ets/model/DateObject.ets b/feature/timer/src/main/ets/model/DateObject.ets new file mode 100644 index 0000000..0c36a54 --- /dev/null +++ b/feature/timer/src/main/ets/model/DateObject.ets @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 日志时分秒数据对象 + */ + +export default class DateObject { + public hours: string; + public minute: string; + public seconds: string; + public millisecond: string; + + constructor(hours: string, minute: string, seconds: string, millisecond: string) { + this.hours = hours; + this.minute = minute; + this.seconds = seconds; + this.millisecond = millisecond; + } +} \ No newline at end of file diff --git a/feature/timer/src/main/ets/model/Index.ets b/feature/timer/src/main/ets/model/Index.ets new file mode 100644 index 0000000..8ae4f1c --- /dev/null +++ b/feature/timer/src/main/ets/model/Index.ets @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { default as DateObject } from './DateObject'; \ No newline at end of file diff --git a/feature/timer/src/main/ets/pages/FullScreenTimer/index.ets b/feature/timer/src/main/ets/pages/FullScreenTimer/index.ets new file mode 100644 index 0000000..367c096 --- /dev/null +++ b/feature/timer/src/main/ets/pages/FullScreenTimer/index.ets @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import CommonEventManager from '@ohos.commonEventManager'; +import emitter from '@ohos.events.emitter'; +import Want from '@ohos.app.ability.Want'; +import image from '@ohos.multimedia.image'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import deviceInfo from '@ohos.deviceInfo' +import { + AlarmInfo, + EventName, + EventReportUtil, + GlobalContext, + LogUtil, + ResourceManager, + TIME_TAG_ID_LIST_IN_ZH, + TimeUtil, + TIMEOUT_FULL_SCREEN_ID, + TIMEOUT_FULL_SCREEN_REPATE_ID, + EVENT_ID_TIMER_FULL_SCREEN_CLOSE, + CommonUtil, + TIMER_NOTICE_ID, + FULLSCREEN_SLIPUP_ID, +} from '@hmos/common'; +import SlideCloseButton from '../../components/SlideCloseButton'; +import { TimerNotificationUtil } from '../../utils'; + +const TAG = 'FullScreenTimerClock'; +const ALARM_BACKGROUND_BLUR = 30; +const SLIDE_BTN_SPACE = 14; +const FLEX_GROW_DEUCE = 1; + +interface Time { + hours: string, + minute: string, + second: string +} + +/** + * Full Screen Alarm Page + * + * @since 2022-08-18 + */ +@Component +export struct FullScreenTimer { + @Link isBackImageShown: boolean; + @State currentDateWithWeek: string = ''; + @State private timeText: string = ''; + @State wallpaperImage: image.PixelMap | undefined = undefined; + private alarmInfo = {} as AlarmInfo; + private minute: number = -1; + // Tag of time , Exp: am pm + private leftTag: string = ''; + private rightTag: string = ''; + private maxLine: number = 2; + @Link timeoutDuration: number; + @State timeoutDurationText: string = '00:00:00' + @State deviceType: string = deviceInfo.deviceType; // 设备类型 + + async aboutToAppear() { + LogUtil.info(TAG, 'aboutToAppear'); + if (GlobalContext.getContext().getObject('wallpaperImage')) { + this.wallpaperImage = GlobalContext.getContext().getObject('wallpaperImage') as image.PixelMap; + } + LogUtil.info(TAG, 'wallpaperImage:' + JSON.stringify(this.wallpaperImage)); + this.alarmInfo = ((GlobalContext.getContext() + .getObject('abilityWant') as Want)?.parameters?.alarmInfo) as AlarmInfo; + await ResourceManager.preloadStringResources(TIME_TAG_ID_LIST_IN_ZH); + this.updateTime(); + emitter.on({ eventId: TIMEOUT_FULL_SCREEN_ID }, (eventData) => { + this.timeoutDuration = eventData.data?.timeoutDuration + LogUtil.info(TAG, `${this.timeoutDuration}`); + this.timeoutDurationText = this.changeShowTime() + }); + emitter.on({ eventId: EVENT_ID_TIMER_FULL_SCREEN_CLOSE }, async (eventData) => { + LogUtil.info(TAG, `EVENT_ID_TIMER_FULL_SCREEN_CLOSE`) + CommonUtil.terminateFullScreenAbility(); + await TimerNotificationUtil.timeoutSlipUpPublish(TIMER_NOTICE_ID, this.timeoutDuration); + emitter.emit({ eventId: FULLSCREEN_SLIPUP_ID, priority: emitter.EventPriority.IMMEDIATE }); + }); + + } + + aboutToDisappear() { + LogUtil.info(TAG, 'aboutToDisappear'); + emitter.off(TIMEOUT_FULL_SCREEN_ID); + emitter.off(EVENT_ID_TIMER_FULL_SCREEN_CLOSE); + } + + private async updateTime(): Promise { + const currentMinute = new Date().getMinutes(); + if (currentMinute === this.minute) { + return; + } + const nowDate = new Date(); + const timeObj = TimeUtil.getFormattedTimeObj(nowDate.getHours(), nowDate.getMinutes()); + if (timeObj.isTagLeft) { + this.leftTag = timeObj.tag as string; + this.rightTag = ''; + } else { + this.rightTag = timeObj.tag as string; + this.leftTag = ''; + } + this.timeText = timeObj.time!; + this.currentDateWithWeek = TimeUtil.getCurrentFormattedDateWithWeek(); + this.minute = currentMinute; + } + + private changeShowTime() { + let fillHours = parseInt(String(this.timeoutDuration / 3600000)); + let fillMinute = parseInt(String((this.timeoutDuration % 3600000) / 60000)); + let fillSecond = parseInt(String((this.timeoutDuration % 60000) / 1000)); + let timeoutDisplay: Time = { + hours: (fillHours >= 10) ? fillHours.toString() : ('0' + fillHours.toString()), + minute: (fillMinute >= 10) ? fillMinute.toString() : ('0' + fillMinute.toString()), + second: (fillSecond >= 10) ? fillSecond.toString() : ('0' + fillSecond.toString()) + } + LogUtil.info(TAG, `changeShowTime: ${timeoutDisplay.hours + ':' + timeoutDisplay.minute + ':' + timeoutDisplay.second}`) + return timeoutDisplay.hours + ':' + timeoutDisplay.minute + ':' + timeoutDisplay.second + } + + private async timerRepeat() { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_TIMER_AGAIN); + emitter.emit({ eventId: TIMEOUT_FULL_SCREEN_REPATE_ID, priority: emitter.EventPriority.IMMEDIATE, }, { + data: { + isFullScreen: true + } + }); + } + + @Builder + buildTimeArea() { + Column() { + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Baseline }) { + Text(this.leftTag) + .fontColor($r('sys.color.ohos_id_color_primary')) + .fontSize($r('app.float.ohos_id_text_size_sub_title1')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + Text(this.timeText) + .fontSize($r('app.float.ohos_id_text_size_headline2')) + .fontColor($r('sys.color.ohos_id_color_text_primary')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + Text(this.rightTag) + .fontColor($r('sys.color.ohos_id_color_primary')) + .fontSize($r('app.float.ohos_id_text_size_sub_title1')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + }.margin({ top: $r('app.float.current_time_margin_top') }) + + Text(this.currentDateWithWeek) + .fontSize($r('app.float.ohos_id_text_size_sub_title1')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + .margin({ + top: $r('app.float.current_date_margin_top'), + left: $r('app.float.ohos_id_max_padding_start'), + right: $r('app.float.ohos_id_max_padding_end'), + }) + } + .flexGrow(FLEX_GROW_DEUCE) + .width('100%') + } + + @Builder + async buildLabelAndSnoozeArea() { + Row() { + Column() { + Text($r('app.string.time_to')) + .fontSize($r('sys.float.Title_L')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + .maxLines(this.maxLine) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .margin({ + left: $r('app.float.ohos_id_max_padding_start'), + right: $r('app.float.ohos_id_max_padding_end'), + }) + .alignSelf(ItemAlign.Center) + Text(this.timeoutDurationText) + .fontSize($r('app.float.ohos_id_text_size_headline6')) + .fontWeight(FontWeight.Regular) + .fontColor(Color.White) + .maxLines(this.maxLine) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .margin({ + left: $r('app.float.ohos_id_max_padding_start'), + right: $r('app.float.ohos_id_max_padding_end'), + }) + .alignSelf(ItemAlign.Center) + Button($r('app.string.repeat_button_text'), { type: ButtonType.Normal }) + .margin({ top: $r('app.float.snooze_btn_margin_top') }) + .backgroundColor($r('app.color.snooze_button_background_color')) + .padding({ + left: $r('app.float.snooze_button_padding_horizontal'), + right: $r('app.float.snooze_button_padding_horizontal'), + top: $r('app.float.snooze_button_padding_vertical'), + bottom: $r('app.float.snooze_button_padding_vertical'), + }) + .border({ + width: $r('app.float.snooze_button_border_width'), + color: $r('app.color.snooze_button_border_color'), + }) + .borderRadius($r('sys.float.corner_radius_level10')) + .fontColor($r('sys.color.warning')) + .onClick(() => { + this.timerRepeat() + }) + } + .width('100%') + } + .width('100%') + .flexGrow(FLEX_GROW_DEUCE) + .alignItems(VerticalAlign.Center) + } + + @Builder + buildSlideButton() { + Row() { + Column({ space: SLIDE_BTN_SPACE }) { + SlideCloseButton({ alarmInfo: this.alarmInfo }) + Text($r('app.string.slide_to_off_timer')) + .align(Alignment.Bottom) + .fontColor(Color.White) + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Body_L')) + .margin({ + bottom: $r('app.float.slide_to_turn_off_margin_bottom'), + top: $r('app.float.slide_to_turn_off_margin_top') + }) + } + .width('100%') + .alignItems(HorizontalAlign.Center) + } + .flexGrow(FLEX_GROW_DEUCE) + .width('100%') + .alignItems(VerticalAlign.Bottom) + } + + build() { + Stack({ alignContent: Alignment.Center }) { + if (this.wallpaperImage) { + Image(this.wallpaperImage) + .width('100%') + .height('100%') + .objectRepeat(ImageRepeat.NoRepeat) + .backgroundImageSize(ImageSize.Cover) + .backdropBlur(ALARM_BACKGROUND_BLUR) + .onComplete((event) => { + LogUtil.info(TAG, 'Image onComplete:' + JSON.stringify(event)); + if (event && event.loadingStatus === 1) { + this.isBackImageShown = true; + } + }) + } + Flex({ direction: FlexDirection.Column }) { + this.buildTimeArea() + this.buildLabelAndSnoozeArea() + this.buildSlideButton() + } + .height('100%') + .width('100%') + } + .visibility(this.isBackImageShown ? Visibility.Visible : Visibility.Hidden) + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/feature/timer/src/main/ets/pages/index.ets b/feature/timer/src/main/ets/pages/index.ets new file mode 100644 index 0000000..ede08af --- /dev/null +++ b/feature/timer/src/main/ets/pages/index.ets @@ -0,0 +1,1260 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AlarmInfo, + AlarmManager, + CLOCK_RATIO_FOR_NORMAL, + CLOCK_RATIO_FOR_NORMAL_CUSTOM, + FloatingActionButton, + FloatingActionButtonColor, + HALF, + LogUtil, + MILLIS_IN_SECOND, + ResourceAudioManager, + ResourceManager, + RINGTONE_SELECTION, + BreakPoint, + EventReportUtil, + EventName, + WantAgentUtil, + GlobalContext, + FROM_DATA_STORE, + SoundPool, + EVENT_ID_TIMEPICK_FORPC, + MINUTE_IN_HOUR, + SECOND_IN_MINUTE, + TIME_PAUSE_OR_CONTINUE_ID, + TIMEOUT_FULL_SCREEN_ID, + TIMEOUT_FULL_SCREEN_CLOSE_ID, + TIMEOUT_FULL_SCREEN_REPATE_ID, + AlarmStateManager, + TIMER_NOTICE_ID, + TimerStateManager, + CommonUtil, + EVENT_ID_SEND_ALARM, + TimerSystemTimer, + START_DATE_TIMER_PREFERENCES_CLOCK, + LEFT_TIME_TIMER_PREFERENCES_CLOCK, + TOTAL_TIME_TIMER_PREFERENCES_CLOCK, + PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK, + TIMEOUT_FLAG_ID, + FULLSCREEN_SLIPUP_ID, + TIMER_Toggle_FRONT_AND_BACKEND, + TIMER_DATA_STORE +} from '@hmos/common'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import preferencesUtil from '@ohos.data.preferences'; +import emitter from '@ohos.events.emitter'; +import hiAppEvent from '@ohos.hiviewdfx.hiAppEvent'; +import { AnalogTimer, TimerPicker, TimerPickerForPC } from '../components'; +import window from '@ohos.window'; +import { RingtoneSelectionUtil } from '@hmos/common/src/main/ets/utils/RingtoneSelectionUtil'; +import { DateObject, formatMillisecondToObject } from '../common/Utils'; +import lottie, { AnimationItem } from '../../../../../../common/oh_modules/@ohos/lottie'; +import deviceInfo from '@ohos.deviceInfo' +import notificationManager from '@ohos.notificationManager' +import worker from '@ohos.worker'; +import { NotificationContentUtil } from '@hmos/common/src/main/ets/utils/NotificationContentUtil' +import image from '@ohos.multimedia.image'; +import resmgr from '@ohos.resourceManager'; +import TimerAudioPlayer from '../manager/timerAudioPlayer'; +import screenLock from '@ohos.screenLock'; +import { timerPreferencesManagerUtil } from '../common/preferencesUtil'; +import { TimerNotificationUtil } from '../utils'; +import CommonEventManager from '@ohos.commonEventManager'; + +type NotificationRequest = notificationManager.NotificationRequest; + +const SECOND_IN_HOUR = SECOND_IN_MINUTE * MINUTE_IN_HOUR; +const TAG = 'Timer_index'; +const TICK_FILE_NAME: string = 'timer_tick.ogg'; +const TIMER_BEEP: string = 'Timer_Beep.ogg'; +const UPDATE_INTERVAL: number = 150; + +const SCALE_DIAMETER_RATIO = 0.94; +const FOLDSCREEN_CLOCK_TIMER_NORMAL_CUSTOM = 0.85; +const SPECAIL_CLOCK_TIMER_NORMAL_CUSTOM = 0.7; + +let GLOBAL_PREV_START_DATE = 0; +const noticeUpdateCycle = 1000 * 60 * 5; //5min更新一次实况数据 +const limitTimeLeft = 300; + + +interface PageInfos { + isTimerRingtone: boolean, + onRingtoneSelected: (path: string) => void +} + +interface ReportParams { + PACKAGE_NAME: string; + PROCESS_NAME: string; + RESULT: string; + VALUE?: string; + STATUS?: string +} + +interface Time { + hours: string, + minutes: string, + seconds: string +} + +const SHUTDOWN_SUBSCRIBE_INFO: CommonEventSubscribeInfo = { + events: [ + CommonEventManager.Support.COMMON_EVENT_SHUTDOWN, + ], +}; + +// This type is not exported from @ohos.commonEvent, so we get it by exported function. +type CommonEventSubscriber = CommonEventManager.CommonEventSubscriber +type CommonEventSubscribeInfo = CommonEventManager.CommonEventSubscribeInfo + +/** + * Timer home page + * + * Used to display the timer. + * + * @since 2023-09-05 + */ +@Component +export struct Timer { + @Prop @Watch('updateAnalogTimer') isPortraitOrientation: boolean = true; //纵向 true 横向 false + @Prop isFocus: Boolean = false; + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + @StorageProp('setOrientaion') getOrientaion: number = 0; + @Prop isBigView: string = ''; + @Prop @Watch('pageVisibilityChanged') isTimerVisible: boolean = true; + @StorageProp('windowStandardHeight') windowStandardHeight: number = 0; + @StorageLink('mainWindow') mainWindow: window.Window | undefined = undefined; + @Prop @Watch('onPageToggle') isPageHide: boolean = true; + @Prop @Watch('currentIndexChange') currentIndex: number = 0; + @Prop isScreenEntry: boolean = false; + @Consume('pageInfos') pageInfos: NavPathStack; + @State @Watch('listenTime') totalTime: number = 0; + @State @Watch('timeLeftChanged') timeLeft: number = 0; + @State @Watch('initAnimation') canvasOpacity: number = 0.4; + @State timeOutFlag: boolean = false; + @State started: boolean = false; + @State paused: boolean = false; + @State reset: boolean = false; + @State @Watch('diameterChanged') diameter: number = 0; + @State timeToLeave: number = 0; + @State hours: string = '00'; + @State minutes: string = '00'; + @State seconds: string = '00'; + @State resetZero: string = '00'; + @State screenFull: boolean = false + @State isRepeatTiming: boolean = false; + @State showTimerData: DateObject = new DateObject('00', '00', '00', '00'); + @State @Watch('isTickPlayingChanged') isTickPlaying: boolean = false; + @State isShowPage: boolean = true; + @State timeoutDuration: number = 0; + @State deviceType: string = deviceInfo.deviceType; // 设备类型 + @State @Watch('listenTimerRunning') private isTimerRunning: boolean = false; + @State selfFocus: boolean = false; + @State passedTime: number = 0; + @State timerTimeout: number = -1; + private dateToFormat: Date = new Date(); + private alarm: AlarmInfo = { + id: TIMER_NOTICE_ID, + title: '', + hour: new Date().getHours(), + minute: new Date().getMinutes(), + second: new Date().getSeconds(), + ringDuration: 1, + snoozeDuration: 0, + snoozeTimes: 0, + daysOfWeek: [], + enabled: false, + }; + private settings: RenderingContextSettings = new RenderingContextSettings(true) + private controller: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); + private currentAnimateName: string = 'pauseTimer'; + private isFoldCross: number = 2224; + private isFoldVertical: number = 2496; + private shutDownSubscriber?: CommonEventSubscriber; + + private isPCLg() { + return this.deviceType === '2in1' + } + + // 动画名称 + private timerCanvasAnimateName: string = 'playAnimate'; + private canvasTimerPauseAnimateName: string = 'timerPauseAnimate'; + private canvasTimerPauseAnimatePath: string = 'resources/rawfile/pauseToPlay.json'; + // 动画json + private canvasAnimatePath: string = 'resources/rawfile/playToPause.json'; + private animateItem: AnimationItem | null = null; + + private listenTime() { + this.canvasOpacity = this.totalTime > 0 ? 1 : 0.4; + if (this.totalTime === 0) { + this.timerPreferencesClear(); + } + } + + async currentIndexChange() { + } + + async updateAnalogTimer() { + const windowSize = this.mainWindow?.getWindowProperties()?.windowRect || { width: 0, height: 0 }; + this.updateDiameter(windowSize); + } + + async onPageToggle(): Promise { + if (this.isTickPlaying) { + if (this.isPageHide) { + await SoundPool.stopForPageHide('time') + } else { // 入口页面 + const preferences = await preferencesUtil.getPreferences(GlobalContext.getContext() + .getObject('clockContext') as Context, FROM_DATA_STORE); + const alarm_active = await preferences.get('ALARM_CLOCK_TAB', ''); + LogUtil.info(TAG, `on page show alarm_active=${alarm_active}`); + if (alarm_active) { + await SoundPool.stopForPageHide('time'); + } else { + if (this.currentIndex == 3) { + if (this.isScreenEntry) { + SoundPool.playTimeForDelay(); + } else { + await SoundPool.playSoundPool('time'); + } + } + } + } + } + } + + async timerPreferencesInit() { + GLOBAL_PREV_START_DATE = Number(await timerPreferencesManagerUtil.getKey(START_DATE_TIMER_PREFERENCES_CLOCK)); + const _totalTime = Number(await timerPreferencesManagerUtil.getKey(TOTAL_TIME_TIMER_PREFERENCES_CLOCK)); + const _now = new Date().getTime(); + this.totalTime = _totalTime; + + // GLOBAL_PREV_START_DATE == 0: 暂停状态 + // GLOBAL_PREV_START_DATE != 0: 计时状态 + LogUtil.info(TAG, `timerPreferencesInit=>${GLOBAL_PREV_START_DATE},timerPreferencesInit=>${new Date().getTime()}`) + + if (GLOBAL_PREV_START_DATE !== 0 && this.isTimerVisible) { + let _prevPassedTime = Number(await timerPreferencesManagerUtil.getKey(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK)); + let _passedTime = _now - GLOBAL_PREV_START_DATE >= _totalTime ? _totalTime : + _now - GLOBAL_PREV_START_DATE + _prevPassedTime; + const _timeLeft = _totalTime - _passedTime; + const _showTimerData = formatMillisecondToObject(_totalTime); + this.passedTime = _passedTime; + this.timeLeft = _timeLeft; + this.hours = _showTimerData.hours; + this.minutes = _showTimerData.minute; + this.seconds = _showTimerData.seconds; + } else { + return + } + } + + listenTimerRunning() { + if (this.isTimerRunning) { + this.initPauseAnimation(); + } else { + this.initAnimation(); + } + } + + timerPreferencesClear() { + timerPreferencesManagerUtil.del(TOTAL_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(LEFT_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(START_DATE_TIMER_PREFERENCES_CLOCK); + } + + async aboutToAppear(): Promise { + LogUtil.info(TAG, `aboutToAppear=>`); + clearTimeout(this.timerTimeout); + this.deviceType = deviceInfo.deviceType; + + this.listeningAlarm(); + emitter.on({ + eventId: EVENT_ID_TIMEPICK_FORPC, + }, (eventData) => { + this.showTimerData.hours = eventData.data?.hours; + this.showTimerData.minute = eventData.data?.minutes; + this.showTimerData.seconds = eventData.data?.seconds; + }); + const windowSize = this.mainWindow?.getWindowProperties()?.windowRect || { width: 0, height: 0 }; + this.updateDiameter(windowSize); + this.mainWindow?.on('windowSizeChange', sizeInfo => this.updateDiameter(sizeInfo)); + this.emitterFunc(); + this.subscribeShutDownEvent(); + } + + async aboutToDisappear(): Promise { + LogUtil.info(TAG, `aboutToDisappear=>`); + clearTimeout(this.timerTimeout); + this.IsTimerRunFunc('destroy'); + emitter.off(TIME_PAUSE_OR_CONTINUE_ID); + emitter.off(TIMEOUT_FULL_SCREEN_CLOSE_ID); + emitter.off(TIMEOUT_FULL_SCREEN_REPATE_ID); + emitter.off(TIMEOUT_FLAG_ID); + emitter.off(FULLSCREEN_SLIPUP_ID); + emitter.off(TIMER_Toggle_FRONT_AND_BACKEND); + this.timerPreferencesClear(); + this.unSubscribeTimeChangedEvent(); + } + + private async systemShutDown(): Promise { + LogUtil.info(TAG, `systemShutDown====>`); + this.stopOutTime(); + } + + private async subscribeShutDownEvent(): Promise { + // Create a subscriber and subscribe to shutDown event.订阅手机关机事件 + this.shutDownSubscriber = await CommonEventManager.createSubscriber(SHUTDOWN_SUBSCRIBE_INFO); + LogUtil.info(TAG, `shutDownSubscriber=${this.shutDownSubscriber}`); + if (!this.shutDownSubscriber) { + return; + } + CommonEventManager.subscribe(this.shutDownSubscriber, () => this.systemShutDown()); + } + + private unSubscribeTimeChangedEvent() { + if (this.shutDownSubscriber) { + CommonEventManager.unsubscribe(this.shutDownSubscriber, error => { + if (error) { + LogUtil.error(TAG, `Unsubscribe to shut-down event failed: ${JSON.stringify(error)}`); + return; + } + LogUtil.info(TAG, 'Unsubscribe to shut-down event successfully!'); + }); + } + + } + + private async timerReapte() { + LogUtil.info(TAG, `timerReapte=>${this.totalTime}&&${this.started}&&${this.paused}`); + this.passedTime = 0; + this.started = false; + this.paused = false; + this.totalTime = Number(await timerPreferencesManagerUtil.getKey(TOTAL_TIME_TIMER_PREFERENCES_CLOCK)) + this.isRepeatTiming = false; + this.timeoutDuration = 0; + this.timeOutFlag = false; + TimerAudioPlayer.stopTick(TIMER_BEEP); + TimerNotificationUtil.cancel(TIMER_NOTICE_ID); + GLOBAL_PREV_START_DATE = 0; + timerPreferencesManagerUtil.del(LEFT_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(START_DATE_TIMER_PREFERENCES_CLOCK); + this.cacheStopWatchData(); + await this.onTimerToggled(); + await WantAgentUtil.triggerTimerMainPage('TIMER_TAB'); + CommonUtil.terminateFullScreenAbility(); + } + + private emitterFunc() { + emitter.on({ eventId: TIME_PAUSE_OR_CONTINUE_ID }, (eventData) => { + this.onTimerToggled('fullScreen'); + this.canvasOpacity = 1; + LogUtil.info(TAG, `pause_or_coutinue=> ${eventData}`); + }); + emitter.on({ eventId: TIMEOUT_FULL_SCREEN_CLOSE_ID }, (eventData) => { + if (this.isPCLg()) { + this.stopOutTimePC(); + } else { + this.stopOutTime(); + } + this.canvasOpacity = 1; + LogUtil.info(TAG, `closeTimer=> ${eventData}`); + }); + emitter.on({ eventId: TIMEOUT_FULL_SCREEN_REPATE_ID }, async (eventData) => { + this.screenFull = eventData.data?.isFullScreen; + this.timerReapte(); + this.canvasOpacity = 1; + LogUtil.info(TAG, `repateTimer=> ${eventData}`); + }); + emitter.on({ eventId: TIMEOUT_FLAG_ID }, (eventData) => { + this.canvasOpacity = 0.4; + LogUtil.info(TAG, `TIMEOUT_FLAG=> ${eventData}`); + }); + emitter.on({ eventId: FULLSCREEN_SLIPUP_ID }, async (eventData) => { + this.timeOutFlag = true; + this.timeLeft = 0; + this.passedTime = Number(await timerPreferencesManagerUtil.getKey(TOTAL_TIME_TIMER_PREFERENCES_CLOCK)) + }); + emitter.on({ eventId: TIMER_Toggle_FRONT_AND_BACKEND }, (eventData) => { + if (eventData.data?.pageShow) { + LogUtil.info(TAG, `Timer_Index_FRONT_AND_BACKEND=>pageShow`) + this.timerPreferencesInit(); + } else { + return; + } + }); + } + + private listeningAlarm() { + emitter.on({ eventId: EVENT_ID_SEND_ALARM }, async (target) => { + if (this.isTickPlaying) { + if (target.data?.toggle === 'show') { + await SoundPool.stopForPageHide('time'); + } + if (target.data?.toggle === 'hide' && this.currentIndex === 3 && !this.isPageHide) { + await SoundPool.playSoundPool('time'); + } + } + }); + } + + private changeShowTime() { + let showTime: Time = { + hours: this.timeOutFlag ? this.resetZero : (this.started ? this.showTimerData.hours : + (this.showTimerData.hours !== '00' ? this.showTimerData.hours : this.hours)), + minutes: this.timeOutFlag ? this.resetZero : (this.started ? this.showTimerData.minute : + (this.showTimerData.minute !== '00' ? this.showTimerData.minute : this.minutes)), + seconds: this.timeOutFlag ? this.resetZero : (this.started ? this.showTimerData.seconds : + (this.showTimerData.seconds !== '00' ? this.showTimerData.seconds : this.seconds)) + } + return showTime + } + + private async IsTimerRunFunc(param: string) { + switch (param) { + case 'storedValue_and_create': { + LogUtil.info(TAG, `IsTimerRunFunc=>storedValue_and_create`); + timerPreferencesManagerUtil.save(TOTAL_TIME_TIMER_PREFERENCES_CLOCK, this.totalTime); + if (GLOBAL_PREV_START_DATE === 0) { // 只在 从暂停状态开始的流程存储数据,杀死进程再进入计时器不做存储 + GLOBAL_PREV_START_DATE = new Date().getTime(); + timerPreferencesManagerUtil.save(START_DATE_TIMER_PREFERENCES_CLOCK, GLOBAL_PREV_START_DATE); + const _delay = this.totalTime === this.timeLeft ? this.totalTime : this.timeLeft; + await TimerSystemTimer.create(_delay); + } + break; + } + case 'storedValue_and_destory': { + LogUtil.info(TAG, `IsTimerRunFunc=>storedValue_and_destory`); + timerPreferencesManagerUtil.save(TOTAL_TIME_TIMER_PREFERENCES_CLOCK, this.totalTime); + GLOBAL_PREV_START_DATE = 0; + timerPreferencesManagerUtil.save(START_DATE_TIMER_PREFERENCES_CLOCK, GLOBAL_PREV_START_DATE); + timerPreferencesManagerUtil.save(LEFT_TIME_TIMER_PREFERENCES_CLOCK, this.timeLeft); + timerPreferencesManagerUtil.save(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK, this.passedTime); + await TimerSystemTimer.destory(); + break; + } + case 'stopOutTime_and_destory': { + LogUtil.info(TAG, `IsTimerRunFunc=>stopOutTime_and_destory`); + timerPreferencesManagerUtil.save(TOTAL_TIME_TIMER_PREFERENCES_CLOCK, this.totalTime); + GLOBAL_PREV_START_DATE = 0; + timerPreferencesManagerUtil.save(LEFT_TIME_TIMER_PREFERENCES_CLOCK, 0); + timerPreferencesManagerUtil.save(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK, 0); + timerPreferencesManagerUtil.save(START_DATE_TIMER_PREFERENCES_CLOCK, GLOBAL_PREV_START_DATE); + await TimerSystemTimer.destory(); + break; + } + case 'destroy': { + LogUtil.info(TAG, `IsTimerRunFunc=>destroy`); + GLOBAL_PREV_START_DATE = 0; + timerPreferencesManagerUtil.save(LEFT_TIME_TIMER_PREFERENCES_CLOCK, 0); + timerPreferencesManagerUtil.save(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK, 0); + timerPreferencesManagerUtil.save(START_DATE_TIMER_PREFERENCES_CLOCK, GLOBAL_PREV_START_DATE); + await TimerSystemTimer.destory(); + break; + } + default: { + break; + } + } + } + + private timeLeftChanged(...args: Array) { + let tlr = Math.ceil(this.timeLeft / 1000) * 1000; + let d = this.started ? Math.min(tlr, this.totalTime) : this.totalTime; + this.showTimerData = formatMillisecondToObject(d); + if (this.timeLeft <= 0) { + this.pauseAnimation(); + } + } + + private playTick() { + if (!this.isTickPlaying && this.isTimerVisible) { + SoundPool.playSoundPool('time'); + this.isTickPlaying = true; + } + } + + private async stopTick() { + SoundPool.stopForPageHide('time'); + this.isTickPlaying = false; + } + + //页面进入获取缓存秒表数据 + private async getStopWatchData() { + let passedTime: number = this.passedTime; + let nowDate = new Date().getTime(); + let timeDiff = nowDate - this.timeToLeave; + passedTime += timeDiff; + this.passedTime = passedTime; + LogUtil.info(TAG, `nowDate=${nowDate},timeToLeave=${this.timeToLeave},this.passedTime=${this.passedTime}`); + } + + //页面离开缓存秒表数据 + private async cacheStopWatchData() { + this.timeToLeave = new Date().getTime(); + LogUtil.info(TAG, `cacheStopWatchData timeToLeave=${this.timeToLeave},this.passedTime=${this.passedTime}`); + } + + private pageVisibilityChanged() { + if (this.started && !this.paused) { + if (this.isTimerVisible) { + this.getStopWatchData(); + this.refreshTime(); + SoundPool.playSoundPool('time'); + this.isTickPlaying = true; + } else { + SoundPool.stopForPageHide('time'); + this.cacheStopWatchData(); + } + } + } + + private stopTimer() { + this.started = false; + this.paused = false; + this.timeLeft = 0; + this.stopTick(); + this.timeoutDuration = 0; + this.timeOutFlag = false; + } + + private CloseTimer() { + TimerAudioPlayer.stopTick(TIMER_BEEP); + WantAgentUtil.triggerCloseTimer(TIMER_NOTICE_ID); + } + + private diameterChanged() { + if (this.isPortraitOrientation) { + this.updateAnalogTimer(); + } + } + + private async stopOutTime() { + LogUtil.info(TAG, `stopOutTime`); + this.getEventReportUtil(EventName.CLICK_TIMER_CLOSE); + this.passedTime = 0; + this.timeLeft = 0; + this.timeoutDuration = 0; + GLOBAL_PREV_START_DATE = 0; + this.timeOutFlag = false; + this.started = false; + this.paused = false; + this.reset = true; + this.isTimerRunning = false; + this.isRepeatTiming = false; + this.timerTimeout = -1; + clearTimeout(this.timerTimeout); + this.stopTick(); + this.CloseTimer(); + this.IsTimerRunFunc('stopOutTime_and_destory') + } + + private async stopOutTimePC() { + LogUtil.info(TAG, `stopOutTimepc`); + this.getEventReportUtil(EventName.CLICK_TIMER_CLOSE); + this.passedTime = 0; + this.timeLeft = 0; + this.timeoutDuration = 0; + GLOBAL_PREV_START_DATE = 0; + this.timeOutFlag = false; + this.started = false; + this.paused = false; + this.reset = true; + this.isTimerRunning = false; + this.isRepeatTiming = false; + this.timerTimeout = -1; + clearTimeout(this.timerTimeout); + this.IsTimerRunFunc('stopOutTime_and_destory') + } + + private timer(localTime: number): void { + clearTimeout(this.timerTimeout); + const now = new Date().getTime(); // 获取当前本地时间 + const timeGap = now - localTime; // 计算时间差 + //下一次计时器应该触发的时间 + const nextTickTime = UPDATE_INTERVAL - (timeGap % UPDATE_INTERVAL); + if (this.isTimerRunning && !this.timeOutFlag && this.isTimerVisible) { + this.timerTimeout = setTimeout(() => { + let passedTime = this.passedTime; + passedTime += UPDATE_INTERVAL; + this.passedTime = passedTime; + this.timeLeft = this.totalTime - passedTime; + if (passedTime % noticeUpdateCycle == 0) { + LogUtil.info(TAG, `noticeUpdateCycle`); + WantAgentUtil.triggerStartTimer( + TIMER_NOTICE_ID, + this.timeLeft + ); + } + if (this.timeLeft <= 0) { + this.passedTime = 0; + this.stopTimer(); + (this.deviceType != '2in1') && (this.timeOutFlag = true); + (this.deviceType === '2in1') && + (this.totalTime != 0) && (this.timeLeft = this.totalTime); + } + this.timer(localTime); // 递归调用,实现循环 + }, nextTickTime); + } + if (this.reset) { + this.passedTime = 0; + this.stopTimer(); + } + } + + private refreshTime(): void { + let localTime = new Date().getTime(); //记录本地时间 + this.timer(localTime); // 启动计时器 + } + + private updateDiameter(sizeInfo: window.Size): void { + if (!sizeInfo || !sizeInfo.height || !sizeInfo.width) { + return; + } + const height = sizeInfo.height || this.windowStandardHeight; + const longEdge = Math.max(height, sizeInfo.width); + const shortEdge = Math.min(height, sizeInfo.width); + this.diameter = px2vp(Math.min(longEdge * HALF, shortEdge) * CLOCK_RATIO_FOR_NORMAL); + } + + private formatTimeString(time: number): string { + this.dateToFormat.setTime(time); + let hours = (this.dateToFormat.getUTCDate() - 1) * 24 + this.dateToFormat.getUTCHours(); + return (hours > 10 ? hours : '0' + hours) + this.dateToFormat.toISOString().slice(13, 19); + } + + private resetDialTime() { + this.hours = '00'; + this.minutes = '00'; + this.seconds = '00'; + this.showTimerData.hours = '00'; + this.showTimerData.minute = '00'; + this.showTimerData.seconds = '00'; + } + + private resetTimer() { + if (this.timeLeft < limitTimeLeft && GLOBAL_PREV_START_DATE !== 0) { + LogUtil.info(TAG, 'timeLeft:' + this.timeLeft); + return; + } + this.isTimerRunning = false; + this.isRepeatTiming = false; + this.reset = true; + this.passedTime = 0; + LogUtil.info(TAG, 'totalTimeChanged1:' + this.totalTime); + if (!this.started && this.reset) { + this.totalTime = 0; + } else { + LogUtil.info(TAG, 'timeLeftChanged1:' + this.timeLeft); + if (this.timeLeft > 0) { + LogUtil.info(TAG, 'totalTimeChanged2:' + this.totalTime); + this.reset = true; + if (this.deviceType === '2in1') { + this.timeLeft = this.totalTime; + this.pauseAnimation(); + } else { + LogUtil.info(TAG, 'totalTimeChanged3:' + this.totalTime); + this.timeLeft = 0; + } + timerPreferencesManagerUtil.del(LEFT_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(START_DATE_TIMER_PREFERENCES_CLOCK); + this.stopTimer(); + this.CloseTimer(); + } else { + this.resetDialTime(); + this.totalTime = 0; + this.timeLeft = 0; + timerPreferencesManagerUtil.del(TOTAL_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(LEFT_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(START_DATE_TIMER_PREFERENCES_CLOCK); + LogUtil.info(TAG, 'totalTimeChanged4:' + this.totalTime); + } + } + this.timeOutFlag = false; + this.getEventReportUtil('TIMER_RESET_BANNER'); + this.getEventReportUtil(EventName.CLICK_TIMER_RESET); + this.timerTimeout = -1; + clearTimeout(this.timerTimeout); + WantAgentUtil.triggerCloseTimer(TIMER_NOTICE_ID); + this.IsTimerRunFunc('destroy') + } + + private async onTimerToggled(entry?: string) { + this.reset = false; + this.isTimerRunning = false; + this.pauseAnimation(); + if (this.started) { + + if (this.paused) { + this.isTimerRunning = true; + this.IsTimerRunFunc('storedValue_and_create') + this.paused = false; + this.playAnimation(); + this.refreshTime() + if (entry) { + !this.isPageHide && this.playTick(); + this.isPageHide && (this.isTickPlaying = true) + } else { + this.playTick(); + } + LogUtil.info(TAG, 'continue==>'); + this.getEventReportUtil('TIMER_COUNTDOWN_BANNER'); + if (this.deviceType !== '2in1') { + WantAgentUtil.triggerStartTimer( + TIMER_NOTICE_ID, + Number(await timerPreferencesManagerUtil.getKey(TOTAL_TIME_TIMER_PREFERENCES_CLOCK)) - + Number(await timerPreferencesManagerUtil.getKey(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK)) + ); + } + this.getEventReportUtil(EventName.CLICK_START_TIMER, this.totalTime); + } else { + this.isTimerRunning = false; + this.IsTimerRunFunc('storedValue_and_destory') + this.paused = true; + this.pauseAnimation() + if (this.alarm) { + AlarmManager.removeAlarm(this.alarm); + } + if (entry) { + !this.isPageHide && this.stopTick(); + this.isPageHide && (this.isTickPlaying = false) + } else { + this.stopTick(); + } + LogUtil.info(TAG, 'paused==>') + this.getEventReportUtil('TIMER_PAUSE_BANNER'); + this.timeOutFlag = false; + if (this.deviceType !== '2in1') { + WantAgentUtil.triggerPauseTimer( + TIMER_NOTICE_ID, + Number(await timerPreferencesManagerUtil.getKey(TOTAL_TIME_TIMER_PREFERENCES_CLOCK)) - + Number(await timerPreferencesManagerUtil.getKey(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK)) + ); + } + this.getEventReportUtil(EventName.CLICK_TIMER_PAUSE); + } + } else if (this.totalTime > 0) { + const isScreenLocked: boolean = await screenLock.isScreenLocked(); + LogUtil.info('isScreenLocked_play,isPageHide=' + this.isPageHide); + this.started = true; + this.paused = false; + this.timeLeft = this.totalTime; + LogUtil.info(TAG, `${this.timeLeft}"aaaaaaaa==>"this.passedtime=${this.passedTime}`); + !isScreenLocked && !this.isPageHide && this.playTick(); + this.screenFull && (this.isTickPlaying = true) && (this.screenFull = false); + this.isTimerRunning = true; + this.IsTimerRunFunc('storedValue_and_create'); + this.playAnimation(); + LogUtil.info(TAG, 'started==>init'); + this.getEventReportUtil('TIMER_COUNTDOWN_BANNER'); + this.getEventReportUtil(EventName.CLICK_START_TIMER, this.totalTime); + this.refreshTime(); + if (this.deviceType !== '2in1') { + WantAgentUtil.triggerStartTimer(TIMER_NOTICE_ID, this.timeLeft); + } + } + } + + private getEventReportUtil(EVENT_NAME: string, timer?: number): void { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EVENT_NAME, timer); + } + + private isTickPlayingChanged() { + this.isTimerRunning = this.isTickPlaying + } + + private onTinkleChange() { + this.getEventReportUtil(EventName.CLICK_TIMER_MUSIC); + return + // TODO + this.pageInfos.pushPath(new NavPathInfo(RINGTONE_SELECTION, { + isTimerRingtone: true, + onRingtoneSelected: (path: string) => { + LogUtil.info(TAG, 'New timer ringtone = ' + path); + } + } as PageInfos)) + } + + // 初始化动画 + private initAnimation() { + if (!this.started) { + this.pauseAnimation(); + } else if (!this.paused) { + this.playAnimation(); + } else { + this.pauseAnimation(); + } + if (this.animateItem != null) { + this.animateItem.destroy() + this.animateItem = null + } + this.currentAnimateName = 'pauseTimer'; + this.animateItem = lottie.loadAnimation({ + container: this.controller, + renderer: 'canvas', + loop: false, + autoplay: false, + name: this.timerCanvasAnimateName, + path: this.canvasAnimatePath + }) + } + + // 初始动画 + private initPauseAnimation() { + if (this.animateItem != null) { + this.animateItem.destroy() + this.animateItem = null + } + this.currentAnimateName = 'playTimer'; + this.animateItem = lottie.loadAnimation({ + container: this.controller, + renderer: 'canvas', + loop: false, + autoplay: false, + name: this.canvasTimerPauseAnimateName, + path: this.canvasTimerPauseAnimatePath + }) + } + + // 暂停到开始动画 + private playAnimation() { + lottie.setDirection(this.currentAnimateName == 'pauseTimer' ? 1 : -1, this.getCurrentCanvasName()); + lottie.play(this.getCurrentCanvasName()); + } + + // 开始到暂停动画 + private pauseAnimation() { + lottie.setDirection(this.currentAnimateName == 'pauseTimer' ? -1 : 1, this.getCurrentCanvasName()); + lottie.play(this.getCurrentCanvasName()); + } + + getCurrentCanvasName(): string { + return this.currentAnimateName == 'pauseTimer' ? this.timerCanvasAnimateName : this.canvasTimerPauseAnimateName + } + + getDeviceRatio(): number { + if (this.isPCLg()) { + return SPECAIL_CLOCK_TIMER_NORMAL_CUSTOM + } + if (this.foldAbleScreen === 1) { + return this.getOrientaion === this.isFoldCross ? SCALE_DIAMETER_RATIO : FOLDSCREEN_CLOCK_TIMER_NORMAL_CUSTOM + } else { + return this.isPortraitOrientation ? SCALE_DIAMETER_RATIO : SPECAIL_CLOCK_TIMER_NORMAL_CUSTOM + } + } + + getTimerPadding(): Padding { + if (this.isPCLg()) { + return {} + } + if (this.foldAbleScreen === 1) { + return { + top: $r('app.float.timer_clock_padding_19') + } + } else if (this.foldAbleScreen === 2) { + return { + top: $r('app.float.timer_clock_padding_17') + } + } else { + return this.isPortraitOrientation ? { top: $r('app.float.timer_picker_portrait_top_margin_18') } : { + right: $r('app.float.timer_picker_portrait_top_margin_48') + } + } + } + + @Builder + buildAnalogTimer(): void { + Row() { + AnalogTimer({ + getOrientaion: this.getOrientaion, + isPortraitOrientation: this.isPortraitOrientation, + scaleDiameterRatio: this.getDeviceRatio(), + diameter: this.diameter, + timeLeft: this.timeLeft, + totalTime: this.totalTime, + reset: this.reset, + passedTime: this.passedTime + }) + } + .transition(TransitionEffect.opacity(1)) + .justifyContent(FlexAlign.Center) + .visibility(this.isTimerVisible ? Visibility.Visible : Visibility.Hidden) + .width('100%') + .padding(this.getTimerPadding()) + .margin(this.isPCLg() ? {} : (this.foldAbleScreen === 1 ? + { + top: $r('app.float.stack_timer_picker_padding_fold_32'), + bottom: $r('app.float.stack_timer_picker_padding_fold_18') + } : + { + top: this.isPortraitOrientation ? $r('app.float.stack_timer_picker_padding_fold_32') : + $r('app.float.stack_timer_picker_padding_top_25'), + bottom: this.isPortraitOrientation ? $r('app.float.stack_timer_picker_padding_bottom_3') : 0 + } + )) + } + + @Styles + pressedButtonStyle() { + .backgroundColor($r('sys.color.interactive_click')) + } + + @Builder + buildButtons() { + Flex({ + direction: this.isPCLg() ? FlexDirection.Column : FlexDirection.Row, + justifyContent: FlexAlign.SpaceBetween, + alignItems: ItemAlign.Center + }) { + FloatingActionButton({ + src: $r('app.media.ic_reset'), + colorSchemeName: FloatingActionButtonColor.WHITE, + playButton: false, + onButtonClick: (): void => this.resetTimer(), + isEnabled: this.totalTime > 0 && !this.timeOutFlag + }) + .transition(TransitionEffect.opacity(1)) + .visibility(this.isTimerVisible ? Visibility.Visible : Visibility.Hidden) + + Stack() { + Button({ type: ButtonType.Circle, stateEffect: true }) { + Canvas(this.controller) + .draggable(false) + .borderRadius($r('app.float.canvas_border_radius')) + .width($r('app.float.canvas_size')) + .height($r('app.float.canvas_size')) + .onReady(() => { + if (this.isTimerRunning) { + this.initPauseAnimation(); + } else { + this.initAnimation(); + } + }) + .onDisAppear(() => { + LogUtil.info(TAG, `canvas onDisAppear...`); + }) + .onClick(() => { + LogUtil.info(TAG, 'canvas click.') + if (this.totalTime > 0 && !this.timeOutFlag) { + this.onTimerToggled(); + } + }) + } + .id('id_add_button') + .backgroundColor($r('sys.color.ohos_id_container_color_active')) + .width($r('app.float.pc_add_button_size')) + .height($r('app.float.pc_add_button_size')) + .opacity(this.canvasOpacity) + .stateStyles({ + pressed: this.pressedButtonStyle, + }) + .shadow(this.canvasOpacity === 1 ? { + radius: $r('app.float.button_shadow_radius'), + color: CommonUtil.changeShadowColor($r('sys.color.ohos_id_container_color_active')), + offsetX: 0, + offsetY: $r('app.float.button_shadow_y2'), + } : { radius: 0 }) + + if (this.isPCLg() && this.canvasOpacity === 1) { + Button({ type: ButtonType.Circle, stateEffect: true }) + .opacity(0) + .width($r('app.float.pc_add_button_size')) + .height($r('app.float.pc_add_button_size')) + .onClick(() => { + if (this.totalTime > 0 && !this.timeOutFlag) { + this.onTimerToggled(); + } + }) + .onFocus(() => { + this.selfFocus = true + }) + .onBlur(() => { + this.selfFocus = false + }) + } + } + .padding('2vp') + .border(this.isPCLg() ? { + width: '2vp', + color: this.selfFocus ? $r('sys.color.ohos_id_icon_color_active') : Color.Transparent, + radius: 32 + } : {}) + .width('64vp') + .offset({ + x: 0 + }) + + // FloatingActionButton({ + // src: $r('app.media.ic_ringtone'), + // colorSchemeName: FloatingActionButtonColor.WHITE, + // playButton: false, + // isEnabled: false, + // onButtonClick: (): void => this.onTinkleChange() + // }) + // .transition(TransitionEffect.opacity(1)) + // .visibility(this.isTimerVisible ? Visibility.Visible : Visibility.Hidden) + } + .borderRadius($r('app.float.timer_bottom_borderRadius')) + .backgroundColor(Color.Transparent) + .padding({ + left: this.isPCLg() ? '' : $r('app.float.timer_bottom_padding'), + right: this.isPCLg() ? '' : $r('app.float.timer_bottom_padding'), + top: this.isPCLg() ? $r('app.float.timer_bottom_padding') : 0, + bottom: this.isPCLg() ? $r('app.float.timer_bottom_padding') : 0 + }) + .width(this.isPCLg() ? $r('app.float.timer_button_width') : $r('app.float.timer_bottom_width')) + .height(this.isPCLg() ? $r('app.float.timer_bottom_width') : $r('app.float.timer_button_width')) + .margin({ + bottom: this.isPCLg() ? '' : $r('app.float.timer_bottom_margin_bottom') + }) + } + + @Builder + buildTimerText() { + Column() { + Text(`${this.changeShowTime().hours}:${this.changeShowTime().minutes}:${this.changeShowTime().seconds}`) + .lineHeight( + this.isPCLg() ? $r('app.float.timer_PC_text_line_height') + : (this.isPortraitOrientation || this.foldAbleScreen === 1 + ? $r('app.float.line_height_56') + : $r('app.float.line_height_26'))) + .fontColor($r('sys.color.font_primary')) + .fontSize( + this.isPCLg() ? $r('app.float.timer_PC_text_size') : + (this.isPortraitOrientation || this.foldAbleScreen === 1 + ? $r('app.float.size42') + : $r('app.float.size20') + )) + .fontWeight(FontWeight.Medium) + .height(this.isPCLg() || this.isPortraitOrientation ? $r('app.float.timer_PC_text_line_height') : '') + } + .padding({ + bottom: this.isPortraitOrientation || this.foldAbleScreen === 1 && !this.isPCLg() ? + $r('app.float.padding_bottom_4') : '', + }) + .width('100%') + .justifyContent(this.isPCLg() ? FlexAlign.Start : FlexAlign.Center) + .height( + this.isPCLg() ? $r('app.float.timer_Hints_High') : (this.isPortraitOrientation || this.foldAbleScreen === 1 + ? $r('app.float.build_text_height_108') : '')) + .margin({ + top: this.isPCLg() ? '' : (this.isPortraitOrientation || this.foldAbleScreen === 1 ? '' : '-1vp'), + right: this.isPortraitOrientation || this.foldAbleScreen === 1 || this.isPCLg() ? '0' : + $r('app.float.timer_picker_portrait_top_margin_48'), + }) + .visibility(this.isPCLg() ? Visibility.Visible : this.foldAbleScreen === 1 ? + (((this.started || !this.isPortraitOrientation) || this.timeOutFlag) && this.isTimerVisible) ? + this.foldAbleScreen === 1 && this.started || this.timeOutFlag ? + Visibility.Visible : Visibility.None : Visibility.None : (((this.started || + !this.isPortraitOrientation) || this.timeOutFlag) && this.isTimerVisible) ? + Visibility.Visible : Visibility.None + ) + } + + @Builder + buildPicker() { + Row() { + TimerPicker({ + planHours: $hours, + planMinutes: $minutes, + planSeconds: $seconds, + time: $totalTime, + isPortraitOrientation: $isPortraitOrientation, + isMdView: this.isBigView === BreakPoint.MD, + isTabIndexChange: this.isTimerVisible + }) + .visibility(this.isTimerVisible ? Visibility.Visible : Visibility.Hidden) + } + .alignItems(this.isPortraitOrientation || this.foldAbleScreen === 1 ? VerticalAlign.Center : VerticalAlign.Top) + .visibility((this.started || this.timeOutFlag) || this.isRepeatTiming ? Visibility.None : Visibility.Visible) + .margin({ top: this.foldAbleScreen === 1 && this.getOrientaion === this.isFoldVertical ? '-16vp' : '0', }) + } + + @Builder + buildTimerStack() { + Stack({ + alignContent: Alignment.Center + }) { + if (this.isPortraitOrientation || this.foldAbleScreen === 1) { + this.buildTimerText(); + } + if (!this.started && !this.timeOutFlag) { + this.buildPicker(); + } + } + .height(this.isPortraitOrientation || this.foldAbleScreen === 1 ? '210vp' : '') + } + + @Builder + buildPC() { + Row() { + // 时钟 + Column() { + // this.buildAnalogTimer(); + this.buildTimerText(); + } + .width(this.started ? '100%' : `calc(100% - 40% - 268vp)`) + .margin({ left: this.started ? 0 : $r('app.float.timer_PC_main_padding_left') }) + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + + Column() { + TimerPickerForPC({ + time: $totalTime, + scaleDiameterRatio: this.isPortraitOrientation ? SCALE_DIAMETER_RATIO : CLOCK_RATIO_FOR_NORMAL_CUSTOM, + diameter: this.diameter, + totalTime: this.totalTime + }) + .visibility(this.started ? Visibility.None : Visibility.Visible) + } + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .margin({ right: $r('app.float.timer_PC_main_padding_right') }) + .width(this.started ? $r('app.float.timer_PC_time_picker_defalut_min_width') : '40%') + .height('100%') + + // 按钮 + if (this.isTimerVisible) { + Column() { + this.buildButtons() + } + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .margin({ + right: $r('app.float.timer_PC_main_padding_right'), + left: this.started ? $r('app.float.timer_PC_main_margin_left') : 0 + }) + } + } + .height('100%') + } + + @Builder + buildPortrait() { // 纵向 + Flex({ + direction: this.isPortraitOrientation || this.foldAbleScreen === 1 ? FlexDirection.Column : FlexDirection.Row, + alignItems: ItemAlign.Center + }) { + // 计时器表盘 + Column() { + // this.buildAnalogTimer(); + if (!this.isPortraitOrientation && this.foldAbleScreen !== 1) { + this.buildTimerText(); + } + } + .flexShrink(0) + .width(this.isPortraitOrientation || this.foldAbleScreen === 1 ? '' : '40%') + + Rect() + .fill($r('sys.color.ohos_id_color_list_separator')) + .width('1px') + .height($r('app.float.line_height')) + .margin({ left: '-1vp' }) + .visibility(this.isPortraitOrientation || this.foldAbleScreen === 1 ? Visibility.None : Visibility.Visible) + + // 时间拨盘和按钮 + Flex({ + direction: FlexDirection.Column, + justifyContent: FlexAlign.SpaceBetween, + alignItems: ItemAlign.Center + }) { + Row() { + this.buildTimerStack(); + } + .margin(this.getTimerStack()) + + Row() { + if (this.isTimerVisible) { + this.buildButtons(); + } + } + } + .width(this.isPortraitOrientation || this.foldAbleScreen === 1 ? '100%' : '60%') + .padding(this.isPortraitOrientation || this.foldAbleScreen === 1 ? {} : { + left: $r('app.float.stack_timer_picker_padding_top_48') + }) + .height(this.isPortraitOrientation || this.foldAbleScreen === 1 ? '350vp' : '100%') + .flexGrow(this.isPortraitOrientation || this.foldAbleScreen === 1 ? 1 : 2) + } + .width('100%') + .padding(this.foldAbleScreen === 1 ? { + left: $r('app.float.timer_fold_padding_vertical'), + right: $r('app.float.timer_fold_padding_vertical') + } : { + left: this.isPortraitOrientation ? $r('app.float.timer_picker_padding_vertical') : $r('app.float.timer_picker_portrait_top_margin_48'), + right: this.isPortraitOrientation ? $r('app.float.timer_picker_padding_vertical') : $r('app.float.timer_picker_portrait_top_margin_48') + }) + } + + getTimerStack(): Margin { + if (this.deviceType === 'tablet' && !this.isPortraitOrientation) { + return { + top: '25%' + } + } else if (this.isPCLg()) { + return {} + } + if (this.foldAbleScreen === 1) { + return { top: $r('app.float.timer_stack_margin_11') } + + } else if (this.foldAbleScreen === 2) { + return { top: $r('app.float.timer_stack_margin_42') } + } else { + return this.isPortraitOrientation ? { top: $r('app.float.timer_stack_margin_28') } : { top: '' } + } + } + + @Builder + buildLandscape() { // 横向 + Row() { + Column() { + // this.buildAnalogTimer(); + this.buildTimerText(); + } + .width('40%') + + Rect() + .fill($r('sys.color.comp_divider')) + .width('1px') + .height($r('app.float.line_height')) + .margin({ left: $r('app.float.rect_margin_left') }) + + Stack({ alignContent: Alignment.Bottom }) { + this.buildPicker(); + this.buildButtons(); + } + .width('60%') + .height('100%') + } + .width('100%') + } + + build() { + if (this.isPCLg()) { + // PC + this.buildPC() + } else { + // 移动 + this.buildPortrait(); + } + } +} \ No newline at end of file diff --git a/feature/timer/src/main/ets/types/index.ets b/feature/timer/src/main/ets/types/index.ets new file mode 100644 index 0000000..b33309c --- /dev/null +++ b/feature/timer/src/main/ets/types/index.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * TextPicker 配置属性 + */ +export interface ITextPickerConfig { + pickerItemFontSize: string | number | Resource; + pickerSelectedFontSize: string | number | Resource; + pickerItemHeight: string | number; + pickerHeight: string | number; +} + +export interface DialData { + num: number, + color: string +} + + diff --git a/feature/timer/src/main/ets/utils/index.ets b/feature/timer/src/main/ets/utils/index.ets new file mode 100644 index 0000000..2291eb6 --- /dev/null +++ b/feature/timer/src/main/ets/utils/index.ets @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { TimerNotificationUtil } from './timerNotificationUtil'; diff --git a/feature/timer/src/main/ets/utils/timerNotificationUtil.ets b/feature/timer/src/main/ets/utils/timerNotificationUtil.ets new file mode 100644 index 0000000..685cf31 --- /dev/null +++ b/feature/timer/src/main/ets/utils/timerNotificationUtil.ets @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import screenLock from '@ohos.screenLock'; +import Want from '@ohos.app.ability.Want'; +import notificationManager from '@ohos.notificationManager' +import notification from '@ohos.notification' +import { + GlobalContext, + LEFT_TIME_TIMER_PREFERENCES_CLOCK, + LogUtil, + ResourceManager, + TimerServiceType, + TIMEOUT_FULL_SCREEN_CLOSE_ID, + TimerStateManager, + TIMER_NOTICE_ID, + TIME_PAUSE_OR_CONTINUE_ID, + WantAgentUtil +} from '@hmos/common'; +import { NotificationContentUtil } from '@hmos/common/src/main/ets/utils/NotificationContentUtil' +import image from '@ohos.multimedia.image'; +import deviceInfo from '@ohos.deviceInfo' +import resmgr from '@ohos.resourceManager'; +import emitter from '@ohos.events.emitter'; + +const TAG = 'TimerNotificationUtil'; +const deviceType: string = deviceInfo.deviceType; +const UPDATE_INTERVAL: number = 150; + +type NotificationRequest = notificationManager.NotificationRequest; +type ActionButtons = Array; + +interface ButtonInfo { + title: string, + wantAgent: object, +} + +LogUtil.info(TAG, `deviceType-----${deviceType}`) + +export class TimerNotificationUtil { + static async notificationSubscribeSystemSubscriber(): Promise { + LogUtil.info(TAG, `subscriber_id : JSON.stringify(id)`) + LogUtil.info(TAG, `subscriber_optionBtn : JSON.stringify(option.buttonName)`) + // notificationManager.subscribeSystemLiveView({ + // onResponse: async (id: number, option: notificationManager.ButtonOptions) => { + // if (id == Number(TIMER_NOTICE_ID)) { + // LogUtil.info(TAG, `subscriber_id : ${JSON.stringify(id)}`) + // LogUtil.info(TAG, `subscriber_optionBtn : ${JSON.stringify(option.buttonName)}`) + // if (option.buttonName == 'timer_begin') { + // emitter.emit({ eventId: TIME_PAUSE_OR_CONTINUE_ID, priority: emitter.EventPriority.IMMEDIATE }) + // } else if (option.buttonName == 'timer_pause') { + // emitter.emit({ eventId: TIME_PAUSE_OR_CONTINUE_ID, priority: emitter.EventPriority.IMMEDIATE }) + // } else if (option.buttonName == 'timer_stop') { + // TimerNotificationUtil.cancel(TIMER_NOTICE_ID) + // emitter.emit({ eventId: TIMEOUT_FULL_SCREEN_CLOSE_ID, priority: emitter.EventPriority.IMMEDIATE }) + // } + // } + // } + // }) + } + + static async cancel(timerId: number | string): Promise { + LogUtil.info(TAG, 'publish Cancel timerId:' + timerId); + notificationManager.cancel(typeof timerId === 'string' ? Number(timerId) : timerId); + } + + + static async startPublishAlarmMissed(timerId: number | string, alarmAlertTime: number): Promise { + const title = alarmAlertTime.toString(); + const content = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('is_counting_down'); + const initialTime = Math.ceil((alarmAlertTime - UPDATE_INTERVAL) / 1000) * 1000; + try { + const largeIcon: image.PixelMap = await NotificationContentUtil.createPixelMap('timer') + const iconPixelMap: image.PixelMap = await NotificationContentUtil.createPixelMap('timer_capsule') + const iconsPixelMap: image.PixelMap[] = [ + await NotificationContentUtil.createPixelMap('timer_pause'), + await NotificationContentUtil.createPixelMap('timer_stop') + ] + const iconsPixelMapNames: string[] = ['timer_pause', 'timer_stop'] + const notificationRequest: NotificationRequest = { + id: typeof timerId === 'string' ? Number(timerId) : timerId, + notificationSlotType: notificationManager.SlotType.LIVE_VIEW, + largeIcon: largeIcon, + smallIcon: largeIcon, + content: { + notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_SYSTEM_LIVE_VIEW, + systemLiveView: { + title: title, + text: content, + typeCode: 6, + button: { + icons: iconsPixelMap, + names: iconsPixelMapNames + }, + capsule: { + title: title, + icon: iconPixelMap, + backgroundColor: '#3778EF', + }, + time: { + initialTime: initialTime, + isCountDown: true, + isInTitle: true, + isPaused: false + } + } + }, + extraInfo: { isMute: true, hw_heads_up_enable: false }, + wantAgent: await WantAgentUtil.getTimerMainPageWantAgent('TIMER_TAB') + }; + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, `startPublishAlarmMissed success: ${JSON.stringify(notificationRequest)}`) + } catch (error) { + LogUtil.error(TAG, `startPublishAlarmMissed failed: ${JSON.stringify(error)}`); + } + } + + static async pausePublishAlarmMissed(timerId: number | string, alarmAlertTime: number): Promise { + const alarmAlertTimeNew = alarmAlertTime - UPDATE_INTERVAL; + const title = alarmAlertTime.toString(); + const content = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('countdown_pause'); + const initialTime = Math.ceil(alarmAlertTimeNew / 1000) * 1000; + LogUtil.info(TAG, `alarmAlertTime=${alarmAlertTime},initialTime = ${initialTime}`) + try { + const largeIcon: image.PixelMap = await NotificationContentUtil.createPixelMap('timer') + const iconPixelMap: image.PixelMap = await NotificationContentUtil.createPixelMap('timer_capsule') + const iconsPixelMap: image.PixelMap[] = [ + await NotificationContentUtil.createPixelMap('timer_begin'), + await NotificationContentUtil.createPixelMap('timer_stop') + ] + const iconsPixelMapNames: string[] = ['timer_begin', 'timer_stop'] + const notificationRequest: NotificationRequest = { + id: typeof timerId === 'string' ? Number(timerId) : timerId, + notificationSlotType: notificationManager.SlotType.LIVE_VIEW, + largeIcon: largeIcon, + smallIcon: largeIcon, + content: { + notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_SYSTEM_LIVE_VIEW, + systemLiveView: { + title: title, + text: content, + typeCode: 6, + button: { + icons: iconsPixelMap, + names: iconsPixelMapNames + }, + capsule: { + title: title, + icon: iconPixelMap, + backgroundColor: '#3778EF', + }, + time: { + initialTime: initialTime, + isCountDown: true, + isInTitle: true, + isPaused: true + } + } + }, + extraInfo: { isMute: true, hw_heads_up_enable: false }, + wantAgent: await WantAgentUtil.getTimerMainPageWantAgent('TIMER_TAB') + }; + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, `pausePublishAlarmMissed success : ${JSON.stringify(notificationRequest)}`) + } catch (error) { + LogUtil.error(TAG, `pausePublishAlarmMissed failed: ${JSON.stringify(error)}`); + } + } + + static async timeoutPublishAlarmMissed(timerId: number | string, alarmAlertTime: number): Promise { + const title = alarmAlertTime.toString(); + const content = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('timer_timeout'); + const initialTime = alarmAlertTime; + try { + const largeIcon: image.PixelMap = await NotificationContentUtil.createPixelMap('timer') + const iconPixelMap: image.PixelMap = await NotificationContentUtil.createPixelMap('timer_capsule') + const iconsPixelMap: image.PixelMap[] = [await NotificationContentUtil.createPixelMap('timer_stop')] + const iconsPixelMapNames: string[] = ['timer_stop'] + const notificationRequest: NotificationRequest = { + id: typeof timerId === 'string' ? Number(timerId) : timerId, + notificationSlotType: notificationManager.SlotType.LIVE_VIEW, + largeIcon: largeIcon, + smallIcon: largeIcon, + content: { + notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_SYSTEM_LIVE_VIEW, + systemLiveView: { + title: title, + text: content, + typeCode: 6, + button: { + icons: iconsPixelMap, + names: iconsPixelMapNames + }, + capsule: { + title: title, + icon: iconPixelMap, + backgroundColor: '#3778EF', + }, + time: { + initialTime: initialTime, + isCountDown: false, + isInTitle: true, + isPaused: false + } + } + }, + extraInfo: { isMute: true, hw_heads_up_enable: true, hw_keep_headsup_sticky: true }, + wantAgent: await WantAgentUtil.getTimerFullScreenWantAgent() + }; + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, `timeoutPublishAlarmMissed success: ${JSON.stringify(notificationRequest)}`) + } catch (error) { + LogUtil.error(TAG, `timeoutPublishAlarmMissed faild: ${JSON.stringify(error)}`); + } + } + + static async timeoutSlipUpPublish(alarmId: number | string, alarmAlertTime: number): Promise { + const title = alarmAlertTime.toString(); + const content = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('timer_timeout'); + const initialTime = alarmAlertTime; + try { + const largeIcon: image.PixelMap = await NotificationContentUtil.createPixelMap('timer') + const iconPixelMap: image.PixelMap = await NotificationContentUtil.createPixelMap('timer_capsule') + const iconsPixelMap: image.PixelMap[] = [await NotificationContentUtil.createPixelMap('timer_stop')] + const iconsPixelMapNames: string[] = ['timer_stop'] + const notificationRequest: NotificationRequest = { + id: typeof alarmId === 'string' ? Number(alarmId) : alarmId, + notificationSlotType: notificationManager.SlotType.LIVE_VIEW, + largeIcon: largeIcon, + smallIcon: largeIcon, + content: { + notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_SYSTEM_LIVE_VIEW, + systemLiveView: { + title: title, + text: content, + typeCode: 6, + button: { + icons: iconsPixelMap, + names: iconsPixelMapNames + }, + capsule: { + title: title, + icon: iconPixelMap, + backgroundColor: '#3778EF', + }, + time: { + initialTime: initialTime, + isCountDown: false, + isInTitle: true, + isPaused: false + } + } + }, + extraInfo: { isMute: true, hw_heads_up_enable: false, hw_keep_headsup_sticky: true }, + wantAgent: await WantAgentUtil.getTimerFullScreenWantAgent() + }; + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, `timeoutPublishAlarmMissed success: ${JSON.stringify(notificationRequest)}`) + } catch (error) { + LogUtil.error(TAG, `timeoutPublishAlarmMissed faild: ${JSON.stringify(error)}`); + } + } + + static async timeoutPublishAlarmMissedPC(timerId: number | string, alarmAlertTime: number): Promise { + LogUtil.info(TAG, 'startPublishAlarmMissedPC1 notification success'); + const title = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringSync($r('app.string.timer').id); + const content = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('timer_timeout'); + try { + const actionButtons = await TimerNotificationUtil.getActionButtons(timerId); + const notificationRequest: NotificationRequest = { + id: typeof timerId === 'string' ? Number(timerId) : timerId, + notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION, + content: { + contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, + normal: { + title: title, + text: content, + }, + }, + actionButtons, + extraInfo: { + isMute: true, + hw_keep_headsup_sticky: true, + hw_heads_up_enable: true + }, + isUnremovable: true, + }; + await notificationManager.publish(notificationRequest); + LogUtil.info(TAG, 'startPublishAlarmMissedPC notification success'); + } catch (error) { + LogUtil.error(TAG, 'startPublishAlarmMissedPC notification failed : ', `${JSON.stringify(error)}`); + } + } + + private static async getActionButtons(timerId?: number | string): Promise { + let timerIdNext = timerId; + if (!timerIdNext) { + timerIdNext = 0; + } + const buttons: ButtonInfo[] = [ + { + title: await ResourceManager.getStringByIdAsync($r('app.string.close').id), + wantAgent: await WantAgentUtil.timerWantAgent(TimerServiceType.Close, timerIdNext) as object, + } + ]; + return buttons; + } +} diff --git a/feature/timer/src/main/resources/base/element/color.json b/feature/timer/src/main/resources/base/element/color.json index e3eb225..37a7a8c 100644 --- a/feature/timer/src/main/resources/base/element/color.json +++ b/feature/timer/src/main/resources/base/element/color.json @@ -1,12 +1,85 @@ { "color": [ { - "name": "background_color_white", + "name": "start_window_background", "value": "#FFFFFF" }, { - "name": "shadow_color", - "value": "#1400001E" + "name": "teragray_left_color", + "value": "#203973" + }, + { + "name": "teragray_center_color", + "value": "#7384A6" + }, + { + "name": "text_picker_color", + "value": "#babbbd" + }, + { + "name": "line_color", + "value": "#d3d3d3" + }, + { + "name": "selected_picker_color", + "value": "#000" + }, + { + "name": "teragray_right_color", + "value": "#CCD4DE" + }, + { + "name": "timer_bottom_back", + "value": "#E2E6E9" + }, + { + "name": "stopwatch_bottom_back", + "value": "#0c000000" + }, + { + "name": "timer_background_color", + "value": "#00000000" + }, + { + "name": "timer_PC_input_hover", + "value": "#e5e6e8" + }, + { + "name": "timer_PC_input_focus", + "value": "#e8e9f7" + }, + { + "name": "timer_PC_label_font_color", + "value": "#747577" + }, + { + "name": "timer_PC_input_font_color", + "value": "#161616" + }, + { + "name": "timer_PC_input_caret_color", + "value": "#0A59F7" + }, + { + "name": "timer_PC_input_default_color", + "value": "#f2f3f5" + }, + { + "name": "timer_stroke", + "value": "#33000000" + }, + { + "name": "timer_picker_bg", + "value": "#F1F3F5" + },{ + "name": "snooze_button_background_color", + "value": "#33FFFFFF" + },{ + "name": "snooze_button_border_color", + "value": "#66FFFFFF" + },{ + "name": "snooze_button_text_color", + "value": "#FFFFFF" } ] -} +} \ No newline at end of file diff --git a/feature/timer/src/main/resources/base/element/float.json b/feature/timer/src/main/resources/base/element/float.json new file mode 100644 index 0000000..59d9b80 --- /dev/null +++ b/feature/timer/src/main/resources/base/element/float.json @@ -0,0 +1,552 @@ +{ + "float": [ + { + "name": "stack_timer_picker_padding_fold_18", + "value": "18vp" + }, + { + "name": "stack_timer_picker_padding_top_25", + "value": "25vp" + }, + { + "name": "stack_timer_picker_padding_fold_32", + "value": "32vp" + }, + { + "name": "stack_timer_picker_padding_fold_47", + "value": "47vp" + }, + { + "name": "stack_timer_picker_padding_fold_50", + "value": "50vp" + }, + { + "name": "stack_timer_picker_padding_fold_24", + "value": "24vp" + }, + { + "name": "stack_timer_picker_padding_top_32", + "value": "32vp" + }, + { + "name": "stack_timer_picker_padding_top_17", + "value": "17vp" + }, + { + "name": "stack_timer_picker_padding_top_48", + "value": "48vp" + }, + { + "name": "stack_timer_picker_padding_top_50", + "value": "50vp" + }, + { + "name": "stack_timer_picker_padding_top_30", + "value": "30vp" + }, + { + "name": "stack_timer_picker_padding_bottom_28", + "value": "28vp" + }, + { + "name": "build_text_height_56", + "value": "56vp" + }, + { + "name": "build_text_height_32", + "value": "32vp" + }, + { + "name": "timer_picker_height", + "value": "210vp" + }, + { + "name": "timer_picker_height_h", + "value": "180vp" + }, + { + "name": "timer_price_width", + "value": "18vp" + }, + { + "name": "timer_picker_item_height", + "value": "40vp" + }, + { + "name": "timer_picker_date_text_size", + "value": "20sp" + }, + { + "name": "timer_picker_selected_text_size", + "value": "36sp" + }, + { + "name": "timer_picker_width_picker", + "value": "220vp" + }, + { + "name": "timer_button_height", + "value": "60vp" + }, + { + "name": "timer_picker_text_size", + "value": "44sp" + }, + { + "name": "timer_picker_selected_text_size_h", + "value": "38sp" + }, + { + "name": "timer_picker_text_size_h", + "value": "36sp" + }, + { + "name": "timer_picker_date_text_size_PC", + "value": "56vp" + }, + { + "name": "timer_title_text_size", + "value": "20fp" + }, + { + "name": "timer_title_text_line_height", + "value": "28fp" + }, + { + "name": "timer_picker_padding_vertical", + "value": "16vp" + }, + { + "name": "timer_fold_padding_vertical", + "value": "24vp" + }, + { + "name": "mb_6", + "value": "6vp" + }, + { + "name": "timer_picker_padding_horizontal", + "value": "24vp" + }, + { + "name": "timer_hint_text_size", + "value": "14fp" + }, + { + "name": "timer_hint_text_line_height", + "value": "17fp" + }, + { + "name": "timer_hint_row_height", + "value": "10vp" + }, + { + "name": "timer_hint_margin_vertical", + "value": "15vp" + }, + { + "name": "timer_bottom_buttons_bar_padding_horizontal", + "value": "6vp" + }, + { + "name": "timer_bottom_buttons_bar_height", + "value": "77vp" + }, + { + "name": "clock_shadow_above_space", + "value": "26vp" + }, + { + "name": "stack_timer_picker_padding_bottom_3", + "value": "3vp" + }, + { + "name": "timer_picker_portrait_top_margin_18", + "value": "18vp" + }, + { + "name": "timer_picker_portrait_top_margin_19", + "value": "19vp" + }, + { + "name": "timer_clock_padding_17", + "value": "17vp" + }, + { + "name": "timer_clock_padding_20", + "value": "20vp" + }, + { + "name": "timer_clock_padding_19", + "value": "19vp" + }, + { + "name": "timer_stack_margin_11", + "value": "11.5vp" + }, + { + "name": "timer_stack_margin_42", + "value": "42.5vp" + }, + { + "name": "timer_stack_margin_28", + "value": "28vp" + }, + { + "name": "timer_picker_portrait_top_margin_40", + "value": "40vp" + }, + { + "name": "timer_picker_portrait_top_margin_48", + "value": "48vp" + }, + { + "name": "timer_picker_portrait_right_margin_58", + "value": "58vp" + }, + { + "name": "timer_text_width", + "value": "48vp" + }, + { + "name": "picker_line_height_col", + "value": "49vp" + }, + { + "name": "picker_line_height_row", + "value": "26vp" + }, + { + "name": "size36", + "value": "36sp" + }, + { + "name": "size28", + "value": "28sp" + }, + { + "name": "size42", + "value": "42sp" + }, + { + "name": "size38", + "value": "38sp" + }, + { + "name": "timer_bottom_borderRadius", + "value": "36vp" + }, + { + "name": "line_height_36", + "value": "36vp" + }, + { + "name": "line_height_18", + "value": "18vp" + }, + { + "name": "item_height_65", + "value": "65vp" + }, + { + "name": "item_height_52", + "value": "52vp" + }, + { + "name": "clock_margin_vertical", + "value": "32vp" + }, + { + "name": "timer_bottom_padding", + "value": "6vp" + }, + { + "name": "timer_bottom_width", + "value": "250vp" + }, + { + "name": "timer_bottom_padding_height", + "value": "60vp" + }, + { + "name": "stack_timer_picker_padding_bottom", + "value": "78sp" + }, + { + "name": "timer_bottom_margin", + "value": "74vp" + }, + { + "name": "timer_clock_top", + "value": "10vp" + }, + { + "name": "timer_bottom_padding_horizontal", + "value": "123vp" + }, + { + "name": "timer_buttonGroup_borderRadius", + "value": "36vp" + }, + { + "name": "timer_bottom_margin_bottom", + "value": "24vp" + }, + { + "name": "line_height", + "value": "500vp" + }, + { + "name": "timer_bottom_margin_bottom_h", + "value": "12vp" + }, + { + "name": "timer_button_margin_top", + "value": "32vp" + }, + { + "name": "timer_start_button_width", + "value": "65vp" + }, + { + "name": "timer_start_button_height", + "value": "65vp" + }, + { + "name": "timer_start_img_width", + "value": "40vp" + }, + { + "name": "timer_start_img_height", + "value": "40vp" + }, + { + "name": "timer_button_margin_right", + "value": "56vp" + }, + { + "name": "timer_button_margin_left", + "value": "76vp" + }, + { + "name": "timer_button_Visibility_width", + "value": "192vp" + }, + { + "name": "timer_button_width", + "value": "60vp" + }, + { + "name": "timer_flod_width", + "value": "252vp" + }, + { + "name": "margin_top_28", + "value": "28vp" + }, + { + "name": "build_text_height_108", + "value": "108vp" + }, + { + "name": "padding_bottom_4", + "value": "4vp" + }, + { + "name": "height_350", + "value": "350vp" + }, + { + "name": "size20", + "value": "20sp" + }, + { + "name": "line_height_56", + "value": "56vp" + }, + { + "name": "line_height_26", + "value": "26vp" + }, + { + "name": "line_height_27", + "value": "27vp" + }, + { + "name": "timer_PC_input_main_height", + "value": "150vp" + }, + { + "name": "timer_PC_label_size", + "value": "20sp" + }, + { + "name": "timer_PC_label_width", + "value": "113vp" + }, + { + "name": "timer_PC_input_height", + "value": "100vp" + }, + { + "name": "timer_PC_label_margin", + "value": "24vp" + }, + { + "name": "timer_PC_placeholder_size", + "value": "64sp" + }, + { + "name": "timer_PC_size", + "value": "68sp" + }, + { + "name": "timer_PC_input_border", + "value": "3vp" + }, + { + "name": "timer_PC_label_margin_top", + "value": "35vp" + }, + { + "name": "timer_PC_label_margin_left", + "value": "2vp" + }, + { + "name": "timer_PC_button_width", + "value": "114vp" + }, + { + "name": "timer_PC_button_radius", + "value": "57vp" + }, + { + "name": "timer_PC_button_round_width", + "value": "100vp" + }, + { + "name": "timer_PC_button_size1", + "value": "27sp" + }, + { + "name": "timer_PC_button_size2", + "value": "13sp" + }, + { + "name": "timer_PC_button_margin", + "value": "12vp" + }, + { + "name": "timer_PC_picker_min_width", + "value": "385vp" + }, + { + "name": "timer_PC_main_padding_left", + "value": "96vp" + }, + { + "name": "timer_PC_main_margin_left", + "value": "-229vp" + }, + { + "name": "timer_PC_main_padding_right", + "value": "56vp" + }, + { + "name": "timer_PC_time_picker_defalut_min_width", + "value": "1vp" + }, + { + "name": "timer_PC_analog_main_min_width", + "value": "300vp" + }, + { + "name": "timer_PC_text_line_height", + "value": "74vp" + }, + { + "name": "timer_PC_text_size", + "value": "56sp" + }, + { + "name": "timer_pc_left_max", + "value": "116vp" + }, + { + "name": "timer_Hints_tips", + "value": "23vp" + }, + { + "name": "timer_Hints_High", + "value": "97vp" + }, + { + "name": "slide_to_turn_off_button_border_width", + "value": "2vp" + }, + { + "name": "slide_to_turn_off_button_size", + "value": "50vp" + }, + { + "name": "current_time_margin_top", + "value": "68vp" + }, + { + "name": "current_date_margin_top", + "value": "5vp" + }, + { + "name": "ohos_id_max_padding_start", + "value": "24vp" + }, + { + "name": "ohos_id_max_padding_end", + "value": "24vp" + }, + { + "name": "ohos_id_text_size_sub_title1", + "value": "18fp" + }, + { + "name": "ohos_id_text_size_headline2", + "value": "72vp" + }, + { + "name": "ohos_id_text_size_headline6", + "value": "30vp" + }, + { + "name": "snooze_btn_margin_top", + "value": "16vp" + }, + { + "name": "snooze_button_padding_horizontal", + "value": "45vp" + }, + { + "name": "snooze_button_padding_vertical", + "value": "10vp" + }, + { + "name": "snooze_button_border_width", + "value": "1vp" + }, + { + "name": "snooze_button_border_radius", + "value": "46vp" + }, + { + "name": "slide_to_turn_off_font_size", + "value": "16fp" + }, + { + "name": "slide_to_turn_off_margin_bottom", + "value": "38.5vp" + }, + { + "name": "slide_to_turn_off_margin_top", + "value": "14vp" + } + ] +} \ No newline at end of file diff --git a/feature/timer/src/main/resources/base/element/string.json b/feature/timer/src/main/resources/base/element/string.json index 1e76de0..69091e3 100644 --- a/feature/timer/src/main/resources/base/element/string.json +++ b/feature/timer/src/main/resources/base/element/string.json @@ -1,8 +1,52 @@ { "string": [ { - "name": "page_show", - "value": "page from npm package" + "name": "timer_title_new", + "value": "Timer" + }, + { + "name": "timer_picker_hours", + "value": "hour" + }, + { + "name": "timer_picker_minutes", + "value": "minute" + }, + { + "name": "timer_picker_seconds", + "value": "second" + }, + { + "name": "timer", + "value": "计时器" + }, + { + "name": "is_counting_down", + "value": "正在倒计时" + }, + { + "name": "countdown_pause", + "value": "倒计时暂停" + }, + { + "name": "timer_timeout", + "value": "计时器超时" + }, + { + "name": "slide_to_off_timer", + "value": "滑动关闭计时器" + }, + { + "name": "repeat_button_text", + "value": "重复" + }, + { + "name": "time_to", + "value": "时间到" + }, + { + "name": "close", + "value": "关闭" } ] -} +} \ No newline at end of file diff --git a/feature/timer/src/main/resources/base/media/ic_clock_button.svg b/feature/timer/src/main/resources/base/media/ic_clock_button.svg new file mode 100644 index 0000000..15cf18a --- /dev/null +++ b/feature/timer/src/main/resources/base/media/ic_clock_button.svg @@ -0,0 +1,9 @@ + + + ic_worldclock_filled_activated_1 + + + + + + \ No newline at end of file diff --git a/feature/timer/src/main/resources/base/media/img_clock_timer_bg.png b/feature/timer/src/main/resources/base/media/img_clock_timer_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..9a8508f3bf88d76a2259d8adca64712fa97ba1c1 GIT binary patch literal 173387 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

N~Ei|1)D)Iz1eA)%;;H2Q~(hF{&KscFJ? zD^5o-7}*fOLiK4_o*E9{*cOR~@Ipw>^!}{jXn?32*XW>8>f7^m?cqtf@TAT6F2_$7 z#m&FBjuQi7zmi*r@CUSBIzCDyhL4IoK}1&Q8-8Q!?~{L1#A%j^NFZdoa{`y^@w^5k zwE!p=WVqT2bRT{ooCVa(yNGV=Zi_635ce%%1flWJ8Sv(m)r?HAyd0)XY`acLYQyi) z=s1Qn*;NcUWg4)z#rNMNg4;&-%kB%g+mF3iQdXWzXF0BOg-rw7YO-iwqZ#2V4IOg} zg=F2KEe6R2J=&+-5#l=F^6WA!YP7Cl?JFTbjf;6K3qxlpd3Tj)J!F_USW?eMzR@_u z@9{ir^6ssExD&2kS%#IJ4~$Q@laObRfE$mU=X)53cPH{#@C+CS9DgOnZTb3~siypA?m_AKQt6=Maj`=`k0_=6m3!GjDDt=(i_KDeKgK*RF+pN7ki0!bFJS=g=ci|?OY z_;vr-*WiDu>qUSQC~p|=_J)Ta?;aeJ`TIG$ARc*oI(It|vm{z@bA9oPg!dt8&~K?o zX(4Pn?sr%b$tNfG#LSktlT0BjNO^)wssIGbi~5_v~rLoSy$WxY>M{yc+}+8ip!Wvn>? z{EEHLq#*)izB~=+HW{59=_v@=@#PmNkxCIxC|3TbK?rkt1tDgx@m4|U@|MR*206GY zMXW?2h|Z!&B=6OQ*IwI?G&_f{;Ua*w9?GNl*FHp~FsT43XPZ>ahMF6ew087E6d`mIlhSB=V~6#1HH-Dg``64Hg@Hsij!zFBdQhS0nO^rXswFJ zfG(gZdRT4LK3N>{XYPf^R^K}s4%y6vVynMS2ZOIVHH0yDU|Hb7c;#s#cc40?63Pbh4H4o-MCn#d&zwN&v5Jls4Fq^T#nl{S#hW-YXw+MsP4+TK&iI z;V|bcAAXe01zRl;Xi$KGn|30M`qm2Mtw1k;!DlpZdagM%@>Whx4I4gX{qw&j{IDG5 zNBzUR^_ZEBPfd!P7@;$uRM?fX`k8wu2Z{0vo?V7=1a$x1w9B`Ir9h&C8R@Ja5Q%M$ zI1k29rGs^brv3t@AvIfe5dLy}@_jqi^z&B7Lz0zaYuamDc1*>(yHVKrvjlj4msXr_ zSs8vlL#eoAM!P~!0f`)T2=4#S3t(eX?Wy$_5xf2V0!n|U6+g>pgVF^%@SHyz$W1Fp zo;uQEAqEP4d?gQ60=!VzY0|5Kggck-adQq0V_QvRaWgqRW_(e=#IjA}++As6An)a62M@QH5|w`f)?- z25mJCC+$Eum|w$p?Kg2^z^(_fSQ~w*FX4{O>G`1TwZ`Qx&E;6QCMF*q}XBtH~6bL}e54oE9rK6-tK2g+olAT;)oOBdZ-3n zgd)K*^OFszIf~8FaCGLL)WcEZN6ueS)E5&CvB+tkeZKdQ!h{Ele1w8c?npg3d~q3y z7@nvt@UY4+9f|)A$K4lT{Z_(Xv>{X-}iORfvCz?l0Z4>hD)2aC zH-?i?-?$f@vylm8qBJkrm9i7S!k4!S;|=w1v%1T!ZbJF#m`qIC7z_Rio(q=S03*OI z4cy?41ss!K;pI4!fADW`H$t7tqf_5P%pR>p5%p5RWa3EQVQudGyYV(>Zv%P7#hb%# z85{>u%Z;>g^*?*37wIehDl2$mzAy~PX$cy;?o1Mg)iMl42|8J$)-sH<#kd1JrrBtg zg}c$Y_Z3RWUizqQb)$zX_76}`gqr<0J0lFO-F^Okd}$RY-;L%Vf0$0)Zp)Lzv%HXo zx)#JWk*_5(m*jsm+dBdNtzXWHmC$ZI{1h*cd=IP7m z8H`|lc_kBe%NC((xshPckJ8O?!(`Jlv{T`YATxk>7R z)g)aqv^t_fGs12zh}rMADY|8l*eOLbH5oM5dwE%Tg*e>(aPs`v(XAd?GoH&UB}Yzw zxf3gsnw@>9c7I&1?<9PQncOf?nosb}KR#DUn2hT!H!W$*!up}_C0CMneHv4rXIKN# zL1XgZCpFV~tG_|@gE9_Q32sdWIqqODpvtddvGUV*b=YXbiJqw9JD%M%?1Br1Qs$&+ zHA4UGOx`f43Qd!2aj84${%4moJMrV(I*z93bgQnY+4Kd&4>!kgD`me!b48ZU$0~)C zXU`wTA#i*77m=Y*!C~;jg~&6<=&i-oO~CVwu|c0gxNw-B$h&RN8Zb1-WAy9dimo3gT;7MV|NGz7#K&Me=(K=M1NUnJ0*mnV%_$h>1lqIYQj1> zRA+hq77!!p$vBhLw|WFUHYZdi@=6TW2F3oZyW7z`_iGkB}#! zrB88Q*|IjWI|L3=!NyraV@TJ=t}wZubRi^CLN!j_BhIVjPmZJYeX8AXN=jC)-3WNB zc%J6&0}aeB^}omc#MG8&#R!|GZrspp!L(;PH} z1@F{s$yN9JU*&oM5SjdXMwZ%rO~HI5{#S&bBoS@Z^XhjdyRQLrrbps&{A2rE?giZd zvI7a)*Bw4{a+8=%cO*R5WSZswZr6fs2JpvuQ37&;qy7DE_+SkB;vif)0O0`bq|<-9 zC?zVSGxF7m6P2WgSWp?kvZiF^ZUbu!Zvj_nfba*_<&d6MtCHV;x%XI9g55d*v83 z`qAOXqNbNC&SAR!r&dmrepfZCFW-DiTrwtpK@%eEb}^5RG?!&!n&Ul%LNhPu-|8s# zvzB4*uxUX&g3lQG@fh~=bfSJp+^K0oljXN#@b!>Hra!}yJ-+bM)5)^|5Vzqw%)c9I z>d6?u(c0JLjS#kCeQoe!i*xgn#7m#eK`DmmD-#eFHX?*(I1S{mq@y zIehWwX^`V(^o=fh8M=MJSoj;fntl9UM)D}0u-slU{a9v%BKsI$SSRJ~ej4YtKMf}- zm|MsyC50F_>iOpRX_bTTL4D=9Av2XyC`ajtRW2h@nDKfb3ITxRv9V3_RcMfze;C3C zKi$xMC~RMqPN0i7o>gtwtHL}y0UoL8BL%xK(frIC9}ioJROT2nZ?qkSML9F||3tZW z+sM}=Nudb2#AYb&5$h&RA;_KXOujEpdz1*TvF?|ZdO3;HMIBgOkT|xmtT*44`gXEy zzTGam#>N*`7xHt#Rvd{Xq2W`&mm6{23F#@lHh!d-Y6!Ti*_rV$e}I`nZ8#{VlceDk z`;4G%NnWYNmSD< zcu*3#PGo2h`%LewqaV;;@YlKmp14jDAKWwn4W2`HjvOuK{Gg-2vo$XhVIJa|DVML6 zr)*B&HZds+SQ%$eIhy)DWlFp4(0A#tvF=Ga#PLtLylcWT&)m2Cc20}dYMX#buD8U6 zheH1PVj?Se0%3H5i*)iJxDL~!v-mhBQCN=~Edg^%aO0PL58?V_PI~ES3z9pf zSx2w1l+N1O?pB+^KNVU|LD95AhOrYjDCP`$Z9S@Rg}^`==`o<=r5daIhI%5>&U6x< zse%77Lg@K!RPBz3`R}CZnE0jBZ!Z+BdRlnKmetS02$zBoWScCa zVCj0wYaB(;m}(D^xY<=6e6Mw8@c0QrpkehOhNspDZhX(SP^lhg?lndaz>6A*U!kyX~r3`X}z6| z?W$-8z3=eSzavq?b)94TqV?$lGYyxIdN0@^fjAI4mN(b`Mc8fE_}feAsW_9-irBM< z??fzrOGq1%I^nS*&OiR_a^PZHpATH8G6{bnfKcdWr%{kwV_vS>F#LV=_Dcbslz6Ol zWB0VrD0;Y4jG&7i&;+UuCFG$RnHDau6)mI3Hc8*sG|C$rTb`r~qHrnqBaMQ;eCr8qT z@R&qtv%vawGsKBiG6)wSR@-P|<~3hVDjkOxDaRT@CgK$KV784vvmHK(hJ-f{uhfN^ zrxdrSym(yj2;JF9Q6yN9 z8|k4KeLqF+k#^uxouwbAWEOK(@4~%sh zGUQyKUGgH^c<(9Uuwrj3PEuD+W8Ozl#^S=4{0m_YhGA58`Oj)^@N9PS=S`*WR%^|P zm9+JQ9?n3{rMm?Dz%4Z)nN_@os`RX z)9+n`!&w}MJQBDR6g_p^MqH2+r|9pzXJL~Y4l0Abodf78;qn*=_$lmWA65Z}kvbk+ zO!&YwdA|!ty(jgs1g3hH(T3cSnrHU|V;D(M^cgHl(dOu?ili7~t<%UelHA@sxEuJ; z@Sw7%0|902W2f*vIJMNLZNU=L;04Y-`$0*tH2d)$Tm5K-6kNzOlHSAyr-z<5Y1wLIyj)EL%m}Y0m;(k9+{&8-|39wY zIXKSl{~vD9ps{V+c4KWC+qSI+jh!?`gT}Va#@X17ZS&dQ-`|bTGv|-YWUkqnJ=c5B zdE!VP^-9iLjC9X#0|>ISyeNME%8y$s&h zaxBZ6MD*!CFgqXP1s|et4y|}?t;Yn^ojz15Dz5<%t!~(sZNykgL+^mE>)jkXScHqa zd19rzloAgxmAx%72PPl!L&lufpIf=KcvI%rd2Ndth8P+8&O2{bk#;w=YMw+NMK*TN znVUUnL8KVdWu2{7|;+_B?O}=jZg1xHoE~PZYj5~i`BkOAt2Lc z$WVP4ws}?jA=uRB?Mjgk$To($R7j(~C zRL)ynJBJE0Yx(s$`EC3cz8^2|Kj?H0UBBB7InIWbhyltfO6o7DL7 znH03}M8Hlv)3#t{ zbEaOVep>eJ%P_*?Q%{CZd-ui^ltT@)!$niqtF7bv+pDbo?Pd?eq-*d59o$JgqK(Cv zZK~W0df$6pExq1=r$Oo&wIlM>Nl?BIbLs4jsAM2c;{tG(H5)9nKe?GFc=S-(qmQ}4 zmP(h0OfEYc)@KN^s6T|n)}nk`-nnnRx6i`6e<6I&@389kY@NJ8vk0Rm_`hpI3_Rd5 z@7C>bKX1G*BuGW5555$-GM}F8ntKsZ#yM>%am9?pBwT3=J!!BKHpY+s?XEXO^YUY4ZFj5QrM0ictD+%<|E|Y(YFZZeb9UZU* zuVEs%)!tkE%Va0RRNM);bOw>DzV~Uv*>P2uo_-3cug5o&r>S_d{9fI2e+T5o*=!(7 zd?cwL`ENVYElIga;c{12hnv()1j$MGTc`F|d=muniN?lvr~# z@xVpN)7(M{TiZOW#8x@tX^H!5z-i^t3uu!t!CAQs9K^!rZ(W07$%@)i`*Law{uqZF4NJ5UagSUi2Hq6I{?d)N*! zJ8=gs{nDtC@pZ$x^iFB$v`mOPS2Y9STar3Tr-Ov8C-!eaXoG{)mmmGsyw5wDrVZ*T zp8m7|wvhZEL|4VPec8||>`NFDIHl1g6Duizyb#S5V4*-JtAaTds{Q^NQIV$yynLp$J)cuGNvMzumhNfCK!tNWUE)&d@pGBe_xGxqAA1(RgqN`)JUQ9ATgGO&S%tYP2!lg#N`#KD#;%I1Yg?F zlZ;%|x6^DtSn$z|=;3wianv(r=`sxL^QsJZ8=E+L|H$@?A^NKG(=7xr-h&k`=XLN& zXZ_#s97gqkMU4~a_64aOp1a60dlIL7=7&QO=su%JKZtPf6nkKH!b(X)X*tA1Q0&Vg7fV$3^BH&?%>B!;kMZ zu=&(uKjHM@Og83y6KT!lv%uT-@`r*{O94Tw75}&E9FAWL$<%W#Ki1CeHexhpai58R z*2HrJ&h!H%1Ydn6BQ0so)Dz?qQnZ`wEHhO2T20onn4|mL`crjk1R4l)so;iP7!rF(8of1O&Q->FlX8)(=@n*ds&AkgsxN z;t^pvOj9!KPCnn~YkLBR`)*qgl#D_@gwOpsKiv)f6A_3|I^A<%N$y-im1rg{IV~NJ z*YL4Xjh7?NUx&)zXy}!PqrAkF5I8Dx<1RVbVgmc`YDb4H4!}HUii8AbJ>BP#2%58M zmx`0B|A5+v!=bQfW{GJ&;Nm|2BaN~}mYZ>~oTH$EjLRr^JQBu`dUFRkG&FPojR5U8 zdTNqHymN2~^t1q?11I}f_G*6-HkC`wmFf}-^qDd_BRW^i2D8oXoEEtHRK@Kh(O5|I zOU6rTnN7%`+w3xH)wypt0Y^4OmfLI$GLe=dpOrM*Xd?kfFmGTz-&28Dn|pMPG?$R& z%?XDYv@hDVrESsfGR2*GjMub^^T(`2MTcr@#M@=7^9IH=xhnB^u3E^ymtH#L*V~?Y zC!bNm$N+OyD*=f%2#`h#NcHAVS)#KPack7MIQixkkH?*#)f~9O*h8^{PVWTY5|T;g z(>xY)AkS~U6d}YAinF31nN<;6);y)w0N1vWrkS6x)-cy0tNSPHEnH8vD0IZY%RzwA9c5SHcp z;HJz?QGbgl*fri8PBJN8WF0O!Zh)DA7{<}J;-$ptTWrNKAD@s}n)Exx?7It7%u5%||{Ob>R~?cXz`L>h#$1h|#YK>yZq_ zSxXh~5<10glG;FVfxcLu?hdos3gb$qc?O#8g8BQ*vyRd^v?q0jkIk#5(9=QwRv(g3 zRQ@8$68HS;XTBp7S@uP2UFU^_^0SN-P*;QHPSOMHN07Mk^DStzwLa=(3JHF)% zF;2jsON=!E?#%@SaRVZ3X)nQo5g6(`zK*@QiHa<}tYli0LmF#d6YHnF^^PSeIg#iw zzWFYT4v{&Xzu(}Wznxi-U-fcTA!AS(RRRVAHUvWQlLvLs#u$5S!Z>ztosm9dSwJxS zn1sF1jzq53Z&R2kF#r@2=6?+e13M;7*cmghlN5Cd}_y;1D)npAFdlp;uxsmZFh?f9`jlc2o+~BmVv>u z%2VPB{;BsnyEp|xFO)D|ns1}uAmwaq3u}NK_=LBFv~nm{2;3m9s{1!|V<79B@M)?+ zQo!8A9^IxaTK~t&(xJx!4!E%h{&flMen{GcbPC#parlVg-~>Hc;oGtp4?-&K;5#At zwgg6cRD9p17JWGcKO+s57BtLOpm5CY58%&kR@{=okMJ(Ew!7lB28HnP`xaJLb-d0m zUS*rXP$VKu5c#GRj33?&C{i-isO#MZgIH6%?MD!ug;0mXBX2z4p8!A|aM^+E zJxoTmUJ-}{gv+6zG~^fdW#yG*8*WUa>_afPwird%k;a72#j(Wa+7?Y#FWVA^X*Zu0 zr7vo~7v4&|&popJlSdHCZfZ*jZz_nd&W(|w>u!09WhalL-eJNI@kE%|*>aUc&o<1Y zz_WSXC)%p7D7BbT3*Q(u(k#JZ{Ri962qyTNttXwC4Mv|5s*&pyG zSW|-gDzNqVA3(ncPpJ({yi?1_>L;iS>Clk3tK8!1COwn(Cu9R6b>NLOAF8{8+ z!s3^cy2%Kl_lF@od1{+DLIK)ygUEg-*pjMFAn%X^7{+H?On}NzF2E*kCNFkGWke-HV@Uz+ zxkOXy{v#u!Ud}o>v0s-0)t#dD(P*kGfL6EC5h_Yz`W_VWsb%>La)3Ms?}^k--Yfg} z2zBP`hoiEpCrNDXu!TZ%R<^DD9zR^AEX1LwLXb5BR2iES_fRA|O}#q(AIQ^&PO zqOBG@aZr#zNN9I$t)|NrT`!}*?obJ_5V{6*IO!a#z{2Xu;1vyoxbzR5zU8SDefyFU zg)@rHqt@}*klo@E?36MA79Z0(cwZgF{mGQgrkDyX&j%e|SpjRnq)0-vVhtlmvoIJ2 zM!|r<=!VKAqI19hN4ll+}{KV|a1`QoES=JS48U zHLu4`>}hjR-J%%C9%V-41|Au(s3|_=;LDtW!%B5h0MIzU(>k*I<_UviU~vp@o3bb$ zzx8d?`=CHtm$yPKxHgJj7EOvnO=Hn~+VgoR(w=#JeSkE7LN-~%4x$BNhcj+P*bZW& z62XSl#^9{`6L=)F4rcy%81=tb9jF7xw1<}@j7W?H>eC|Z!1w^u+2HX>6IzBDwrH3i zVJ3P2LXzu}4{;dSW2C{mT~oK&K6EKd z?ldo{p~GRsZ!W7NDEhS`wAr6aq!(*9_#ki6Em7MyKTPG7RPLFZ4@S~UiRvZGtrd9p zKV4L>uwXWjXG(PCRc2ZBP-A-21u1YXf2msbWMMcmYxv;|$er?l&NvxxbYOSQBCcOC z8t`3eMB3Kif5^X6@C`@4%B2tZckL&CpvH6kJp#Ff>yr}L71&|qXs;>4A-W@yEj>Z$ zrf>}I-|xv%!h9?&U`vWMLk_jaq;4XXOnxlZ+@){9$Iv9z&i!NEgmFHes(Kv{A60ep z+bcUh`9oMHB%)C`&Mlw2kv3vt#Ok1h8N`BgsBzXwktAX`-oE`4VE&7cBwRD=-BwYK zosMrxa)s{9HpvWhS|rHc)Br0&Y#Aj#yNUCLz0{?2@Y`>*9En*DP`;yNE!mOBNS1RK z^^CT&#)uQS-koUFs#Q!el$(2W1f^&H=7`p2fZAJ)Wfk4whJ3`HdUC%VfAw$9DxBOc zo7BxSSlazDyT_nLL+(%A1rnmh{L>FT*tVh3|@383i#lCQ0a(lsuGYF}T&yZ*pzcw%PO+CHYf^^|>*&h3@cU>nMwB z7$kyP%CU(*@MEELVbkqGM7Ijo4e=v^%vl(xby;&UWfIGHODU(nj<>j5iDavgcBB7& zpY#Ft)B$kKpY|gD)-(M}s`8(t4HU_f#@)qKzD}6(g48_-qPX6p64{q@pTD!e11%Qbkw1 z|Ggb?P(L&+6wERc9{gyi3#Q1wryZrxX#Ws78Mf^xQ@(=Mdq^;6>RnH->7JNht4-** zCcU7lAid-Ec`S&h|0xPSJaS-UkZ$9yT&`))ML3ICIsx9=D zOY-btivMn+IK_u$s03VhUR}EFk25@AxNv=bU&p@>rVeI@*sAv*XeE@{y4SX8vCBqP zO@EnmtIwx2TKH54a8m3B+W|+Rjpfiffyu&xi5F9__?329SioDNhkU%`x5pgA%$whJ=dGg0t+D+CWyza>RidG57DuGQz>dPQ9dh7 zv~{y6e&!Dd<3kk}C(!GnWDx(p#<)Lfq?S_VWp3W`)hy9ETrf&ESrg;1`of#fb`_5G zR4ZXJK0pGg>Z|!%p$^G^cf|>II3GHKX7E#^5#y0%Y=TDxYbEsJSQ=^+R9Qx?)v!QK zdGhs~lb@0bTCp3D%1JcH%%j~7>%_c~xA`l%hFx_k0O1maN`qg9 z^&Z;|A?^O|M^@*Hqz$1eU)GiaRcQY>uBSbH0G_32#fLzZ81l=i_ou4-j>wE{{JZVC zkYpCSd}Yd&5D%VHI$MTg9YWN^Gd8nRJXPE~k& zf*P6h@YWz@QPrx_!*#2Gj~p%ExdGB6OlQN%-D?{xPglKKcqhDJ zTjHjGRVXJsj~dSE=%TaG4W+7w#~sKdP_q4P6aHkB zWCztLqMs?@J$bdyn)Iz63|>Nfg7nXc!|a(4tntLSm%eRm0dh9U+}ia%4Wfd`%4c0K zqg~~A%YU%Q1BWI)T?DaS;?UKfBP4BUor>h(1%kGkkEZ~9Kl6TfeVg)R+1+yM59)t* zi#^>DW={3R{yh0ySdzKuw$YpzqY^F2lS70>_j%j(fq6F7T}3$)CBrjx5Z6gQ9ZRVr zt`)xwS)yB{ouC-?Hb2LPbR%*lGkm6t^L6ZhehmW@Q^pp!Ar1oAE5;0XMj-0G0x}x& z55)hgCHa`~|1LMOBSFcwu)n&ctNj_uvjdcQlX5>Z&$rcHhc+jKV~Hph?l5rx>K>s3 zdP_v6IH=yJAqS8RNw;wEXLEnq5(_8uv>VG>?HbvYaHd&axH~60FFL(VFNwWU@PAT% ztjl?o6Mf~jqjkcEb`g&HHofFk;k-qZ&0pf*%Yv)<_&6f{%~;q5ctJ;i^8TCXE%)JO zI{!)UJWj(%Tvsty-$ADTM{93#tDq)h(bO(YAt~Dx-FR_OpP}d1xgkQtkS32X2(yT@ zO>jrJ&;e14SBB2(4@~_6H}l?$4%1{AIFpv_V}6dvXTY8RrrsB)DbF)ibk>dtG2hkvP={(IYW0vS--@x;M8`xnTD_YdNA=%~2KzCcXSe~v!Do^tGW8QV zB>d;bkrixtJ6fN!=Izif46^DGS=U}KWBuvZJ@>QhroP;Fjcv@Q)VtZY`x@T)7yFZ` zErmRNd>`XvCi69@*I)1Q_xEj3raaqBZ5B~PA`&>tUTwp&9PUfTMLkx5C*kEiEh3rU zU0WAjupmPD%miK!lp;uT8lBQW`<q zomg+yt>PCRM?BMF`;?WE_M^MbUfZ`pwIV}W=U##-|Gme}Wr?d@e|#siIc33ez%ZCj z77VU>W;`dUxRtmkgq_vX$?d!!^?%5Op zc(zw8$YV88A&icG)bftpyT7Fvme7_C=mGH--aEnJ%2f3memh*Lz8dD4A*3W z7xc_1C1HF#_g&>Z&n26?h#)ThB%J)v^V6 z&>c>B3SR&1Is_vzRouZZ6#ncnfpRJkUXGmAR+rB5Dt;z9c3gm;Xpb;6rH|Y>w%--BmR!*x60vcw1zpt?VP_Eo(xaHV*8H{0$hE3h2Pik*X@k@{^N6|i;8T# z^x=2YYhKsmXYYP@n!b_Syod%>C^%^MdY%;RE(kg1^JJn2)5fGnlH6ntrv{>PmC07= zun(P9sZS+%`~f&XM#s(sZ*cWcC^JJq$yG$#U~dkrN_!fB1C2M)uXm_sIe@&(vx~1*E#uImAG0 zFM9T8i&C=|t;RadfFcL!u-LpFy~qpx0wT#sTMq%JIZ@`J=|J0x`tc<$E|HJ@Z!O1TtsD@dmFy?z~$HRQJ{*x&23M@OhX^U$yLwABhvNYf7Gs&XNg+i}Dd5lEiQ=Fkluc9;@ ze8T6lEX{8d=_pJD2}y;iDrMj8h^GsDas>&}F00|ZElN+%-s+_XlRj)_(bU*CdQcRJxsHM zX<`rSkm4NN0*!BCdq%>Q`==5y2F1DMOjYu=hs|^IepAZ~XNt^yv-GjeRn6@J;pguD zIN%g>*V9~8qZRtMfQhEjk9A13lp(Ogg-epTqNJSiKgrS{ip;o~vIK)va!@k`(tHF- z9T!MJ8?$sm?-8yiN;)jz>)D^#zK^x%{3pPFKn6BuN0LhYG+AadVpu%mG)oPc?w-Mw zi-~Ijsz&p+in2wUlLqidxN+D^(XBUNEXF*Q>*l#qwn_$XVWW0pgj$r0yLm)1dsvms z$({f3jQQdz2$lI+sREd!(#?{#uy zCk$gQstwBr>yT^$RgSzcjIdI$|I-;zvq?!)`Hrz9>KGN2U1K%nrmAhB~_CcyHEIspX=}#9Ixhw;yjJ zYptC+CVTt0*8&r!ztxf@K>!X>f6w(suQmHe>-E0#)-z3~F6Gbr6ry+7B$#-Jg$k*7sWfJ^fPE`L)(_Zg0J= zQhuMSYI#-GHM?QG-OBk~bD{7$L*4f5C-0<|y(N)ffHc{zBDcr2HdSjg4V2mgSulfUE;HfxK#1{G%mw)o2AIJ(3WygtAM0HmX?} zarFC6-gSw8w1Bl}*&3ty~Qrf=J>obvx`|A+?;( z-EZMr=z#mDSijljW@VDLc*1t+Ae-4(>@e2dpxA&BB!);=TqiFv?MbuW1pF1P4mLlu zE3J%rfs!>KvRu+mZcSkwD}S~e^CIgI>SSCe*MBAmwUBxdkPZ*WQgDh;dkHiw%pOrA zAg9@ei7+E~v4>O?gAa200pq-65x2=bJ3xpYSdY&&e9fRvkwA^kX8=jlMSJ5s9w?!Y z-y}Z_3@9UiBTb11a>G_Ux7YChXZtm3399>c7JwJz;Qbc!Dv( zehpz&^??o7X37! z-3_{joT9>Lp!TIGnqe|chGSAeo$#JPy)I{*g_Z&>LG}b;AypP$2W2jt7u!*z_O5la zPyHrLAyafX1Xgsqard3<1dsMYZ_Q65EB1-qanf0nf^>A?@>H^_X^U>Wy zunq@%b5=fLyLDT@#?h}k&KiEZ>o&Vf-B4~3{A8dNmNgS2lbOpkYzJ!MF^pU^YTGu! zruviEEA9Vu%5_KsI4Vs0xPhp~QtZbV*j8J~ANicY-7-}ZpJ+H!6mlUXb(aKa+K!|b zI3#aPu1q?$@YT!a4qaM92SQk&FyYY6;AWcGftA{ZPt(Y(Ri>S-9UXN+mQp72oU%r6 zX_Q8UzptvZ+)jCHiWYu*8=K*6FyzQSTf$AE{Jv^dyf>E<^n1KpVZf)Hi7v|xiFM&6 zvpTE!nXC71)MZYO;(vA)MgK1w!9W;nswsMBB6HU1oAzeXMKQ=s`*IfV6qPR*Yy z4eO`y1!JM{Eg| z)HBFc|vVg`|F-{-K7__M$zCa$Fs3g zr~sr-4u_LekaOz63!H`}VJEvff8JlonIBc<(wx{IHH!!za^cmiOt#cRZb?}g+R!3Q_p~|A|FkV1IjUrlXVp_S62-0Eh;Sj>dU;r{l`!ATLjm^a!l`+d zvZi!GIV4837Gyo(Mu7S_rOATYip|I*C-|jt07X-B) z+|O4_Jic0a`tVP`1eGeM)T#26EkCZixVslw%gXWXh1RVFi9)red$0b+MWa}gVDQzD zr}ADaU8|zpjUwdyxvfATgr^ycB|QEm@mH1{71DRKKg`chjy!zz~L% zGbAs{Fs-OIQB%!Hx9MbkhGM>_&i--@v+6h2pnz#Uq);|8p=@{U;_1d|UzP=2la>=^ zYGH|}X>ps4KYTd3+)kD+%#_T zq=WBC_fKPTGZ2!q8WkQx)?1FEJQ*8qKCkSO*$_2kh^_Se5L;J3;dVr(0T&GK_hn=xp~YBSACY$4QN9@+XW7RHDvC5^OXodDN8o zZ#Go1J~yA3^Y!*s>m%Gg-I&{STs|FpJ);PmA4_p{3QFe-eeL#_$-|P#VY%EPPx}f& zWCkxI25a}Vrz%4iS~DI0`gTbNTg65rE#7%z_H5e5%V+$_a+!V6v?sBTCeW@qJz0(g zB^?@9J}@PnULGJZp!6Gmnl-q%UU>U1rfzcN=5hWBq}cv1GATMmcxp=T6YbUw@)glB zbKi~zL?JF`><;pdBgrJU=0M8VI$U!COm!ny`C|ThU;$ng#OU#wG~X$uA!nd`(cu8c zBlXS)+QmRQqzJC@&lJGQXe=|*6bO!KRI7+W!jaW&#KMBo3Bl)+x~*I875{F{`H8Un zK6N`Y=U6&%G%8lxTPf<{n-W+C6T|5(TdN{rO;`? z;X9?-p&<;i(ZK1#~@`7h^6qm>{zQah1Mf#_PLf}+Xr+G_b9PgA5gwJoC z24`=L4>flG)1vz@N+8qKV}6iFJoWPvU9S`toCQPS6h+!{L}IH|&gYWoi%V>Ji6Aur z%Cj4w^c+EMgq@?Cwi)47Xg87Wxm<3FSd?HY8b#@9$5yP6SkN{#-aLB~C6KFG8Xz7;}*&Wa!j^P&T~ zsvI6Dg@Q|F@ZwG>K5?K3rS8{&Ri%6+n}c|gf>+hdnQA%mYFa9B$CYBQ zsP|w|ipVq^L8_(q3$IOkGf~joOuWl#3bLR@F^79kJa`ae43f$6p5tRLU9jQf?x^a5 z4N=#TSFHU)huwSWvyS`tU5iibb=S3Otw(&9Z0Ar5zp{3l4CUd=bvfu@-%SS{?2fu! zo=c~F=gTHAJvdXn;Tb;Ku_YknS+>>~!U+QW;0r5ri>2HiDah0GuUc=^9|TR&DNy58-+$#JM`!>sx8uhI{=iarh9%aOGH$~`reS-F9n+FQZb z(~5M(EO^I=bMFuKXPLw9<-0?Zj}jCc#>zIDgqnmIsEezkr(~nX%HSE^Exzsep45`x z0ftMRKe6`z)&f8Qgtt=1UxLBU6$HK)e7*P*Zb@-jA9ewDW3-;Thf5EY$kPQ`mC=!9 zQ`xl@35FoXQ zkLUe~U=h|R;%7jK83mN_fL208(+@j?|MR;1-#fslJthnU=!*s8*!aq}WgsNrCXoc9 z0_g0ZJ4Ghl8)@%2p|(i#cDhLf_d%vyLatI?;`79YqCz|IrIb;`am=O+50=0$pHY}+ zpHBtlYN1w6gxld=EYYMoeEKmh1nSol+hp%yn!wNXmquYHEGk!JpyG`Zq2ZpsyuGnz^=WFh8wnA`hDPd~h8Y16nB=zOiI6s*Uaf zbfpRoZ%kM6vi2%AUB2hryyDm<;~fg?ng0WKSv7!#pY8sxk~^fJgb?{N`00bR ztArSAG%nm&MOcRUJSM!bTAgK!uxtmnDXwRc8BtNcRm(yqJ=ST~>uU%;<4){>7Atv% zd0u4NL4aJoW#lj+-b9#1(Jt*3Oysg$z z8-A_J?_3uvrY}v}Ja$5s<$OA9=K!E*zwpl}K|d+(yAg%{fOpkb)k&*QvIULCKhSpG zxgy~6P@VT1?}0}mnQGCbeLyM^#mY0nQ;J=k7e+5)myuw^f=J>?W2(jN&=syRWfw6U ziS6@2`SR{aEyc$u(M2ev0SaX~dGTR>rEEqVoNZm5kdW5$QH(6YIi5seH-GSHG@g|Y zH@HfCzxI5m-dk5qy?Aye{~11jQ4sSugd;I4-{LvQEn3$9f?RUaR$`cMq;BJ7BU4+e zCD}^ul($vqXmjo0FDo?*5H(ait@lPV%j|Kg`)+)Nq9`ydU)k9D;f2h*Rx^oWJk=Y0 zh+a-{XzZ?tmn<(ULF7=REG1uS&kMf)-*9Tk~N%cg{+!=_cwYNTr|7W+A%_g4E7z;L^u+6vys*AWZ#Io zzHfsy#jL)ss?SJGmBl6!R2`CJiv3nr8AB3~>!5%ywIRDLK0;@d25GIQFJ&y;*GFdF zt#hDnGGcomu=%V%cYRIw6pO=c#^Oh~yR?B-12E3gws@4H%Mwy=|&4}f5K!CC<1)vv}*A9;dSMG#5ila+@V zK9Mzce|Tn8jbnnW`8dBEG9edY=N5BR4Ewf7Cw;jpgyND-G4H zbm8;I?JxP+5@i_S!z{aBZGx)s*}xk{v}wrzO$ACPcd*RNIn|OVD;)z8B;ZmG0l$A> z?0mv(foQd)Y$y6yWC223K79IuWG=P^=mNWcAmA@}U`xVXL2Cfgux%~kOndKMosw@} z@TTm?byFx4{)R2%I?<3ltpM%O8I#oYV+lg~)zgoklt5X>6aWe$Bz~7`#>uAJkcg~p zC<|J`fGwOEX>etyZER!HH?_2p+?WC5(*(mcKKKhJwP$wwHUR&Mi5_PsSkUYZ z4)MISX-59A;SMWf2|!DK(08V#eLYEd{)_!D<974D<*84x3azj9^j)}!lzdkB&>>{v zf2d6pyMIVd0~1dVcTmn8x{nWzK3D|eK%}Hmi;#w_!8@&8c4dK&Au0s9Hxasp0jZd@ zx8Rg3Mr~U3rLcgAMeU|UoaH057o>Z)n%}HxdAV2E7~A@CU6f?NV20UAj2N^|*n?q8 zDs|C{U94w%tD@%`%!NVcOPK!v=~ua7A%W51+r;fJeoDMwzwdrcGos#`MYRGTC#yxx zh2Tz7j`Iy)G^S(sHd4~?5Iu-BJU^?hFeck}G?|Ybo_&Dl+Waa41M_`BVo~S%%f+st z5JK9zX-HeQn-?>ZZS;v@x+70;yg(&A=$l@GCdNwb=2w|Z*D6rYhXv?KKz_*<;ntk( zbUZ~05SI6m&0bS8m_f0YD#C#^cuJOnW7zTQjba@`rWVU`XrwZWo+R%Twqa$%?4pS0 zY9N?ve;PY~4A`1x6W>jPGCn%_T!OXHu8T9~wi;tii{56Ohgf%eR#y50kYFN|UcSx< zzQd%|Qc+DgAsJ0V;gkc8!2hGYUH#9&J<-XX^%F9vzUliH%F+Sa|BQ-2J7i<@0~4s> zYCuY37Lg?ol5T~+?_V?bu^i)%e=2qzNLs7sABZ!5H9o&W1Y3`>NuK-uf|Bp3X_63_C$~CRNE)sE`y+T9gqjY-MsME`b1mW`Ty+OZ7u$7Vct}-#Y*<@ zhl7zb>W>mclFtre9z?Y#3qHMD#g`kJZy>@m|I*z=IEpN*MCPKzwBm^=3#n;+iE-Xs zn=WQh`w%;lD*TIIzZYxIT5d){P>IDP%v?~1TBLK|)x$wE1=MX`wj5^vx#s8Gk{+ov zK&%iXr|yMCYJK>x=iqQ|+AE~Q*yxS36#I6?Cy4(*#5bX714hxM#8MHWZqOQSK07uj z8kD3)5KjSuY78n;>Fy$WjA~NpIYFile|O@uUR|$!eu>r4)X9i(_nAeaJyZV@hz>$ z*S7Kv$>eMLrQ%QwX>NqW8Tlvw%^a zc?!!So;dDblE;a!gPcV;Ih`?Z(Z_2$r#4rYsBcd(CpKY=q~E&?3d|Ey902u(lV;zN zcnfu&q==45sOSk3Xc!!?*l&tYm#9paud{6ZnFsehZh8 z<-3}-xkoK4&)BZKdf%t#Qz@i-sa2IW_Um8PFKzJYjA^Rt%G1jg=F)B(s71x&#bC$_ zP;gj@q!P&q-~8yF>Sr8#Cb29Dt@c#e?0q%0$-BN<)dBC%G%rVT0nB2t)CHN|xcQ2O^bJ(IS3`Ee~^waP-7j zv1u97s-U8(iONJ_0s`*8U+qWA5L*}LFx2>c;VtFjoZldv_GO>t^L>B4KE}r3-Q6l- z*NZYZCS$m@l}L!8LT6ZE+M%z;>q52mRbgWLqx_fWGWj0 zZyHKXn1Nd&zHVn9D?dP0)?oQQ0I-+z1(fOwe|kmR|F=#E=u!qzPI|ZT;(6bUOE2rBA#{VY>zN~y;mee%XkNwu4F__BATpL1ceI~M~ z$*e+;0g3~VjX1*CS{7sfX15Wurp=!dWxz(d;qT#o*F0c|%>n-}49hUe&L@^K0dl8! z**_Q$CwMlpUp#|*gfuGasuN+xfNcgRPZm%ly!u?RVm!3&HQrmN(@J9~=}3wmXf|e{ zy}z)GXE_UTRd8o@r^bi0ldvhpEok6XXNj{CXjv78Qs@5M%cp7?%qzr(%r^PSV0Gly zi#{_#HYP|uKU!H5Y1dF1zUAn(Ea&ES5oS@HpFJUsSJyp1Dsojy7p{Fut&L?0?*CA` zDRd5{hXvpL%BqKYnI+)yK>eSD?&S*z6#0hxtRfK^Zjg^nqjoiF?AA0irB>AcZIN~` zq9Ndq*)%<7!c+ZXCZL4D`e6m>ZGiM7P1&v?6<%@Pv32^Sv>pC-halx#=^ zUokI#@P4xlX3P!-A8TB&az=^G>lARJld5rwo4EYpBFaHgd4)z>!j{%gn85L{brk2JMW7Ew?w1A+Z`2V)yL*15{ zJrgX)3kU8@_{Jfs+j6r2xmQZ8SS>;Lq-O2UREVcHKPSR6iJTKz%%xNIZn;u~W3Dd2 zYn=5&aUXePttnpxh^2uzzA*3j(u6-u&vA@o zPW|ee^px00IRp~id$zwhWRCs^;3otR8w>;~NR{e^|MoC+sbW>4OE1yg>3*)4f{(J`|B*X%b(&2!ydW35$HbIzKzZouHQ<-Z_5UdZ|cw~0IxVJvxDHKVk8k{@1?$!A6B z5oK0I`8ASa@NoNlK(e`=JI#et(%@PY;T}_2nGV^%dLt*90ZWu6<)A`B#C-*6&I47w zn(`o!Awnzsy6LR8F|joFf8)5`5@>V1oJXsA%%tqt<#r~^WB4U6U$zEv8Q zCOty`Q?y`ZZ+9Bju?$<}_gwsvBkKoVf^j_tSi4>=xhU4j3t&!Kg0Kb{)_easK&Q(n z*u%^e&t#1GdSM?fQ(S&SCCrpy`~dR^E>X|CCNe99pDE5$JhkO! z>)a?{T(p^gQClIuko7qUBZsiB#V?9aSCOt?dS#gME!czx%BjZ@lUhYGe-IAesFDwu zPUc)HK#$evoWQmkuNH>1IQNl}t6(896TI9CmJP@o9x-&TP}; z!ye^&`*oUC0&m~TcRmo?JFbjSEFd&Gfh)~H0?ARI;p_eMl-g%*0uw{g+(tF=f;$iRdScF>i-y-tV{~*%JV{SL*+s zW<&1>PqShD0hyvrTlg>Lex`w!kDBN6ev!nduupi|yWe%<*=4*F-k)T+V!t@@XTIy*PEdESYGY@>965~e(SZEBf*{4AZ~{`rimuLe_Qt00)1Xy>|H%(BJ@o6`ZBO% z<}oPZ=VO}&nrz`VmU|-W)1gJ)KyRixN=vP>&)Fw2jW&EfTknF4-0ZLMdg9zr63-*| zKr@%10}h#|QC4x|x$nl2Ghi(dkWVo6bVH*9-K!P~nXWtTiYHczNwp1wf51+vupy)> zGMuwm9u*aC4*Aw>@x6QROxQXvF2A}t&M9+F*X2RUpb7Ku45QDfHE{{HhvO!C$>F-( zN2m9+w2~{30)Ekkxk=Q2VNIXvV0yp*m4Weh2$#qzaKWV+RP{3-Bl!29!7MIIXrlhc zACZE-LP~FdX{$A)P7=vf z7=%gunX2tpZ&kF=%-{y@XJA^5GP@o(`=i||lBswIIm6N7K$rw|+D*&#sZh>ojOM^I z2*q88-N?eM=pNRTl1s?0;Pd3}+!+C)NaMa5-Af>+7K+eVSa86v_sYn%vyATef#*#I z%ki$1&A66RGfy{nCprE>rz8K#EI#>YIGaCk0?qqiZi4-xC_`$KMPW=~oWE!K<{A{u z;elCjm>uw#ZF;|{mnB;RGf7uA{3SNpA9IS>_KH~ZTZoTeXBm@+)8R<-e#zl2ANq>qrx!yU|K_?om(tXpgHAgEJ z7@U`c_?PlV@7podj}ypC*&Sw#&%R3n5ya{^++>vWs9|2YMCS0GV+29KB4c&(Im0k6;9mAT7Tcdhg@eAqKx zt^)hwMe)~5{v=n`Wk;8K6Y~fn-eeSkHj-bC%C(rI!8!arpQCL_`Bu6;w}*v!@`4pcg5Fq-Q6IJTjP9vZ-IqqM5$96R zNZH`0+O73^z1w=X^xgj3xu-=FH)A)QxQ?CZrQMgE3miRbliWp;GT{YTBNpAMJA!yj zE%PtVq0=>xtSL61;d?On0`Ca0rmZ4~NQ3pTXtK;p8GlmtK-E&BGc_b_gw3U<)iNF+ zh0%9gFmi9m`9Ks>0@Vc_BTuM>T!$g^0-YcF0s{f>vKnD`C`d?a=jAAh(-I7d)6%=B z6cn+|4(AS~?U2}Ekz$ba8wt(xGj1t-wp(pBz>Sgqh5oaC<7)fUfqDd_wD@<{ySo6^ z?XQuo$SZF7VE>=njk`X^L>x`;C;LL9P7J!*0e!v9?70StDGk~9at&=dwupD8Qp^IL z#=iC=Vw#Vk9ClgRWUS%)mmrosM_)F^2qI6 zoXT%?SROR{?bpZti#c$F^=ydL2aL3t^1yhwXeW7&P?7WDT^wvnu0b-nyVbvRu9_P`*QYag9vSJR8rr`S`J9Jp91@@Ly$>L2bs$Y! z^TqtQNOFa+KzO4RCUY!mQFyFC4K85jD{@VZi)R)Pqz68uK9<QI`BQLc3>CO9T_t69d$1R$MmN}_JY`_uX>a;j+2NBW_q7^0vI5i*Y<55 z=JuG~6x+jGoR;YR0~0@8lcV5Z>CYOa^iy^OyFFR<2vq)3*)%jVAlLW}8-x3gPkAAw zzW*S7N$xe^yPukh`dg^Wf(-V8tN(*ZjIEb_gX({)M4EW4;D+1+N1w@(1@6kDQ&G`x zC3L!r6k*D$cIfwAA#X09Tdq@o^G&-LQ>xQbq>C%%pI&=|e<^+_+u?u7c0s!9VF-Bz zwe3UUo$M+pa(%Za;}@0Y&3>s%co_egs~72w-xw3K6n)fLs=ci)^TD5vP3xeVj_z9B ztnhRqc>h<_K7}Ip;53>%ydEUE^Lhs^-hmC+Vf(ZG@@VBAr!aggOQET<%G8yQX@Ui4 zYI7m1P`sxjjcC}FAO-zoZgNj1ul(V1ck`cTan%Jr2mlpkdEzT+WEMvJE6KV-UFA~& z)8n36^?+5IMWTb_v@52w$SpC9o?qR=kyX#sdCVehqLn)5gI^_pd!&gXT-q$(i1LSZ zXx*ed$X8Af0=Lc$hJdV6ucUH8xC268`%_L<+5Uvd;aGL<1$OpNeIQqTmG^^Gqji7?BfoLsy?b2K07w1uFNl&s+)ZF6F414LUQJ;>}vF@DjYyzY4n8lu>~|Yj0B|}Z$$NtPrF7s7IQ~g!#T5T_f0kNUrL>6HjF&%-Y0Obt{eYnOlMUb z{+q7l8KIp{JZg0%o>xJ98GQKchPKKu1UuEYNZ;oCg3|LlhIG4Uqp+lTn{>#+$mS-d zk}D>Q?fZ>8SH07dQK~IEZ%^kLe6+-5hhz8<#*P}ck-HPi^4h+rbzV|0eB91ZmCZR3 z<*srD)>A9k#r=wS8#}S?pk@!%RJk0sb8<{V(}V{z@;(b56cxufGe`yE728PF+0HLl z=AnMTNrhq?Nk}TOaoJ4ajVlD~ozD(32(H|z81IcN>0Rw zqx+%KL?m^U@k53z^((+9&!64AVlB5^E0GSbCh z8Em`ITIZ(rX)czH3J(C=1|u&~qXHR`D1j6n=DxK!N7-g@f>@C8@jbpmG9?o4PUd^qLhss@+bCUo@5pxJR3hFWjK zo3!!Pbq&{uzA8vRudn^Yah4?%)qeI+v!r8N#IBjb4{ewVQ)tJjnbJ?$^Oqn>eMfga zo}XcuIPNrO1N8GFTm4uH#mZ3FFFzUS0?8{-O_DykAeUE^3MWb0e~nK%Zfg;B6rO?hzJ0+QZ&*8;7Yk>)( zX}QpQ*$KJO%3&RDzt$N!kFcs4IZGC6+?lTt;kXwsR-Tk)F%3g|Dl1O`zgvb6?`#}3 zQ5MHv+?DB^q^`aTUOzODi60egHf;qT!d+VD*%E3Tj`^&F-L11Cqw>qZZ3bQ6%<2as z2CA;wiG)rUuFQQ(A)ptAWwie*4t0@S_TV^!tBKI2k{IQMaKMLbJK52t6 zRrtPcRw2~~{0|pj@jv&Nc1#~|Po8`~T=?K}JBi2r;f5ipQX*oy`qaW;|OkAqD z%}B^grPWLk<)-N6<@N%*CNmh{ZUtR|hl&s%sjUkMDtmLGL-@(pnwbg-=9}g;j|=)_ z$Rx|sw6d5_LvnH{!G=qWHB&>eu?cR}LX$2y${fY>2+GKy*C;%uqx{;;oo!H1OIJh_ zMYrCF3YM}KOg)2z(26+Y0bH&+qiXNam$Vq3bcX-b6P}S2Z%l9JC<1)rP)M()l01U! z4bqaRZi0OcGm~)U;P^t~4gW=;@D|J~*oC&kYbO8n6I4oRXWHP|_xBacq+lPlXiQWj z6tJQWV!PCrtf93u?WNxjqp`OUubjW``JQ&PN|*mspTSqApgeJmgDWLZsfE*RvNv2z z%i^}ab}CG?i|_z~Cf{+zbu)ZaMxueL7cO44JU1eoFDD`;!U`Ej>b5NSN`q~BPUS-k zRlJ@wZmm$aGI39i5wZP{!itg3F*Uu_J;3Tal($PLN*NQ5+O%0!Jq;e z0pop-NacUvryi9`uog;#J7ok+Yi*L#8&-oS8mNvP<#S;x3)Aun(h|U>)fU)v43?Jq zq$b7axaRSYc>~{wo_XivVUydYwlpy}g|{VYFQRcAliyZQBwv;NEI&%#&NB(Pl5+HP8$Q-9u<_bJlb=Eem% zhgl)!=O-lJU5Y@iL&(X?NlpI1gMj9as3@oRxym*@zuz?xeYm_g96wRRG)Gq_cS|ls zy1l^yt}K!uaw%(p$89z&)CkVm3j<BAyadbDS|l(QtX&m zzaN)kM>-oTw#oKSvB`~0r^FLmn6{<{Hi2|(_IXCVa|MK)6(W7bEMgR^S)Z)91Tr~^TB`+?UsRcSZa2qqaDQyP z8E?hwGLtQ8BP}0F8y!D_;#xCe$dM?jZ6}41|Dq$!Hr+>yvi*J0C4Y)y0oKTB1^)6g z`?N7GRSmK_3XyW)JHM&yv=dizl^$G@{qWX-x7VovZYJNeFTLquE>Is38PXP@nHE$7 z6O!55eP=QKJ#>jD3G+weLw7Usw>+$*sKFCAf0VQg*ha3H{o2L@_+R5P0Ef>4d81Rlvlm5-? z0Vp{q!mtf(3>f~4#c4Mv(H2CRkkTHDM zhVH{L&+jvK_e;^>PfVl)Uv>TDk7MGk+GOo_0ksaK9ufFH4gY3NLp zr5tmzjfDvdwW5*mv(7TJ<_mlKlYG8~^NGVhU0uH9FE1a9_lE&fScV&R!hT{;nh(L% ztKQ2h9GKd&Z$1k$PiJYW$vB*&Y1S-6;I3!yRgk36DoB?Pe!tpI_N>XwylIv^7otlf z4_K;tiv3p>f94g_ftT`)hEe(mt>NwsF1)6tF*1`}3Dl*60qpM-U>F%LcS5hmYnsmX zr===N!vZS?tn`6Qb(rWq=*{>I>!Tssta_mCBKV~++m?+#(% z36?l&mxCKTng&N{qFuRXC>?y5_;;hnT&G$=Xj~Odd=*wr-MahP{l_N>X+D2kkaY6?=}1LIg(`$>?bMyij`{h94-pBC=A@l; zl#L5B@G!})xG2rI zk_52^=P4or{F;M;+lKOpU?mH#S|?BYE8gU>dB=sN&9}cQ^DZr`MX!*)^57o(SuvoQ zg;lwk$Mxo!cI(jxQFO;8h01+s1KP>3+I$(vjkw$-5WG?I%KL8^2qk~^1z(mVdClbF~bX!$j(Bh>y!#PnE5gHC|k0R!*Di2ASa z9t$7AcA{^=Y`?PyO%0h&$$VCQiyzniGa+^Dz~~gdk^DSed33 z*q6DP6GW839Ad&%7#@8ztKZkJ_65d^1ZjVyCzdLDPc*Q5tTE$e%JJ6?G&en$wTEF( zb@k18EY*UAQ&@qpaCo)9uTRjEU{zv>qAU7n&>bt*7ggPKcF|@7=(0L@!5NWF`>ax% zAB0^LO{F-ARA^~4*+kk^X^FIczfFGr=vv#IKi~)|w$FbN!>I12XQHh@kU?MU=B$(( zMt|%UsA51_?_%8y*V^|7qYXuwSpSG=pe#H88uIHgCi4ga3yTzJor}}xJN`0oP&ST? z%MqnFSTb+y*2v!OOw9Q;FSZ8cFjelS0kw_ zW=#}!h4V8(Fl^-kOBmGZX*6t4t>DNsz!+~&# z%2MR3X^@CRnGkI5Yi*FKLACSt#@82h8A=A@!~cS$B7><7ZYqY^s>BE3NWgPy?*x83 zx1NwkG3CL|_}ycD?$F{!7 zLd!xAW*I5HFRVavYJ#nNwYAwXM;Be_^ybBy^feWwG|;gK2ygiAsD!eBR}TM+jBQ$N zcR>Jya0xeBTMcm85_g?LS5vBz8mp-O$C2bTpW$V-OAdz%jpvQDDwsR>%)_e4zKQvKP7MqZZmk0;6lZ^-VtQ z`dQ zhPF$sczb@{P;2Cu!t5nQozW23pF^FNj?9T!R5f-3feZM$+#$h=5inI@i3;{dInM7} z*axmgclb{lK5B5MRq$KogEgK*Br(of;N6xuq1>i}>!jKy*`qU!Esu!5*=r7zg;2e( znYa#qJ|>cRL>47JEK95{PV+N-pYognP|cYplv0`o8sccg%DK2$GkjK-w>2h|J`7cT z=0SiJtyc~dlO0LP*>j9B!q$dEZ|yijNPh|k5AE;jWE}J-|5lI z1g0?&U`eajz1NcfqA^3lF>z_)EC<5ld8uW|OMPt*DXax+d|h;a(f^1LJHY!Lb~UDq zVySz5dwLwCfXN$T1<9D|X`%o2;^0#ORwyoX=tNcq^>C4fjM$Xd zP8$4c6_Nq&zeX}=rJQ;`xu%CS?=;Dm)( zq9i28k6Z!FoS6 zkog-4M|v3ckpIJQYISQdae#X>u$9V|@d3$ig^>$J z1rOtL8hEGP4!9Z9w~r+YYwSu=&S}!*P9r~GF0Wr)a(NBsxccB}WQB2@q5)P4Nf-5L zS#YaNC`F!LUS__$NC z-GB#)oo#)pcqr3hD_P*LJ6qK1Nr8GU?4?E37R;T3s2){WpvNp-9Ho9#-WwAKNO|=8 z>~TU_Qi8fENoiglvGyj;;~IOe)Bkw|d0N8jbSIaUD5GJMd93ku2ZEdO=$5xKmRN=< zI8-ygB>)CLHG=BQDE<=(>P6pDdCkz;gn~CPj~)oPQD>UJAJX4C)?0;7sLa^Mjs~w1 z+;kigEc5M^7ZThA&Xe=bT)}7MZKJ@4a*B)4w8sq&+rXB}-cuj_f?T=GuXZ6p5sly; zT*HT%lR97@)_j2Nxq+gwcfQk{B0n)pxcYd3GpWyUMLjq)*jN3Fg-S&ItAn#z%gusi zarNgsqh-VE!Q;i=h14;PqB6a7`ee~+ofrxWH?h4@Wf(XrRrRzPuy&bvSRr0|X9^3r zK&ETic#+plLHdBQ;Qrb;`akQvwQ?HY4&urn&It(DKS$%Ob0JRA0ZRovxPf^OuGPEw z{#ME`DgC7cV6DIaXBMy$Ky8?kikcl@g<%rKwhn1UZBba5_#XP_f`_EtK5wrmM0P^gm=^Ryn5Iv ztbBQ#?#a4)b#@*^K3S%DG2){0alW-V*LTuw8*B`8Il&l__t84?d@x_K+yyVPJV8O; zI5;YKYATXqZp7F=u~Fpp)>)aKr*>}GDc;=t>e5J?xD-o#)nhc{N`d`2X!cFY*h(WOD;}SJbJ-be-2hZ&rv69 zS%O6faMsc$jP=}n)vU!i0D6-dOsiP>vM`t4@LN=Ui8(+z8U!T z_1;oyZPxS0kSc{zjBv%Pn@(r+KCFTb0fx^_b(ZTHae}_*9pLf2k0>$2>q1gfBFx^b zp${V_n>?|v-WHo~>v3v>%52?5L18hXvN!<=)gMX0%M^&&f(Egbt& ziVKeL6(k=LEC=+S+ki4T*bqbeA24ZE2l4|uI*QsASCld<*Pc_@5k#bzri?N2QNH$u zjQoM&s4wHK0F`l4i}FWil>KB0HMS&m00*xzN}IoPasgqnvZ4GY>zwz)e*W^&XqqW@ z>6LQL0OAgv-Lr<@*joxww!a?yiBY3N*l+(Lrn}cJ9{-k*#nY0(D z&AZc8pZ2F|;!J*Wq389$p3>E4`h(}$6+N@cSJsrC7eWy{Cztbd%fT-hUTQI=g~nSt zd?K;Wm+JaG2b-w#oy8aM@5zoeTsym&X4!yvO1f=wfpqiUEgDm>n9-j#_l_o)7whwr zl8#3Qza|HziE;xV|AOlh23Sm8x^^^54~TE%c!Y)TtklRjm1nxUjrP174-Ac+9-pEh$&9fhlUcs6I<<8!F+wsXg*KZ z1vWCX0!5oB&Wi2#yFTQ(ESH z8CK|5wv3^&$_zc38{5@%^h)oL^*(4rUJC6)g!kqJ+$@@++`g3ZxG(Zd2VilyzjF_{ z=C~nQ*4rRc!V%ybQB~grEz;8DwWTw%4Qz_aJLRwc+XUoF4_^?h22bFxdL&!`sIuxR z9x9|HEXdcM5xd#LVzf#{X%AOS%PI`^K#u9Ji3qCqez}B; z-E}BVdy!}49sG9LPGp|nwPZv*8EyFVe4BkxSJ+qn`5Ue31L6A} zzG!6hrw1*hMmu!pi&ck9UTX&Srrfzqn(m(cqAsU2RG#fIm{Vf<8Hac#~VgbNyqd~-Fa|e-pOY-DH&^0!B zOF(~pU&qgxRT@OoY$BD=4M7g~a|i#my_QCvNfDYDjglgYCvbT1GtHq2pgjKZsI^Y1 z!0p3CK_hU3Cr+1uB#)u1FX5LEGLR6C7sZ|UszZApA~PcT2&W$T8X1BBfO=}8qyqW~ zhzDTJQ6R9;LQMj#L7W?uvJP5vUvS{C-l6-4gCzE4-?RA2btwP}8CB;#g<;OTbMS8! z%hSZBOd-W~=sI9WjaGd!x@FU@y%y=F$KLx2oQC%nToS@3tI24Ah0J^zXns24ctYN? zW%bOSnNE0`%y`*K2luMsdE0tL)V0DwTCcFl@!tIshj?O2I~69Vmscg$?q3PLp9q}v zL;mpsunz~9PQ$U)i4)wtI93L5l;q+kmZMx)LV6a#lIl~$S8mFloj@<^nq|WN6Qwo8 zD}hi35FQ``FoxzQmC8jKO%-OytsyG=wn7G{mm8fMG0&VJ41EUv66ggI0Aw;M&V3BS zP{T9*o(56?P%pxJF0uX{qz9FWR4mH(DeCVCY2P>dueUi>E^oGRezQ&g5JVHi0(5qB zC+6vWVqNXoe;y`!TA}Gy^0}T?kA6&h>)o)-o}g7ff}&!)PUv!)ywUf^3fZu*)(0rm zhqx06ZMqcq0BaqSXU|M1zR8~Fo?;D7d*YYR&#s{j7GZD76+LB=V9^8zdhL?bOy)my zKNAWP`T;i-fELKX0V_M-yQRdt6_nq6l5&B&5GZVG13j=4ev^gf3sBfYT*(sO6Yq`E z9)!c0)z#t&(+VpM#)+LQMMXx_+VlMd^;fuDuL;kXCc9CyfK*=)^~^PxmN1jP0$ouA zmQ^@iKGG2OC<~#-yK;8=@sj497tp8JPsR0>Nb^15+;E2ns6fiPFXKumdiwZ z?#k4i*e;11{XSLV`^q_CYM+3xPGos$TC3k%L$#MBoRAO;1XeiVX!DTBbkN_nd@U z2_^{<0apd?5dVCh=4^k463{XXu)Uac1}Qo8r()m0`2j@(Bn@I2r8Fpp(VXGC5Ww&x zAcav*gK8N41|G`aA0z_EW>nIk9#(mm#i%mEh-syIsbM)cbPh5kI?2rTp9WdctWCt2 ztErDvwDmkYF}HG=d990c_B^Zuo=0v3h^K7p_{W;?eEjn3TWd$87&)y``Xt!;Rx-Qy zkLugmbftI)UTS6aTsw$z2II5zmvwf>wG!EIdDBDURa;uyZS8gX_t-ul2rsvJHB-KH z1?01e44YxrDej8Vg(h+3%N9HL+Un^!DWL2pk*EI!`4puxpD5p(*C_i#wS zGlY4UE%6f!$abyk1@R5m1N<)_$xa%K@|2NC@`X91%Exn91e5!d-;I31S$7b8{ zK!;!oilQg-`q|wg{y@BWxld=l0(J3MZ_PUS27l^C?XdL75*kJZKL`3H-Jv*-IMJ;Orp#yWO1lU(0AY;fxaLiKvsn!4s{7D z^9W^RCP)QPYLx@YI`lhMDo#!eqq{Kw6^Qi~r#c5gT>&;8BWAV#_dT~U5cA3L- zo3LHm`WV}ReZRFT!0;tiDs*fv?`Tu^LWyX#Me~IbuZPImYO9E5r4O?rX=I{dHq`BI3=T;$D@Qzx)wGEIQKu*nLlFy| zPS(F?xaN{}I(hds#3hJK(nmsuAwY5Hu0pa~Yt-MQi?B4|xsDO}Pc{|!{SxYmhgAqu zps?wqnG5lB;9R&VB#`n%Uu?v=C{ARmigv5>8o_S+N>3Fj;S21?6-2rzqxSYQMeU~+ z1%9@lk`f!v7&eUu5EB~V)WuZ{pBoPZsBjr;)HjMMWzEE=2G!_oc4)Ze!>>8#-pW<$ zW)d0;#kTwh;ddQ?QLgb*P7Rn^zOV^r>Er}HM z9FQ*UKtj~*G%W>uH0Y~MuPKiO+Iz|fIj+P7nti2w_D1qhgJE6SHzmy7b5VPnZr(DD z!5m1B{@{xTH#8MIP$;FEsV8z5*fOn%NfpDep~e=h>w-7S!d#lj=+6h&?tAv(&*!Wi zLeRf22-6BqxC8COJ+$^79f;>l%I`BmSVDkcSOKK)`x{&_OoMnv>A7utnIuXp^j`Qw zpc{w}@Uuc;J<1{=8YIQ87&6DGqH&PIs0KcRm^aBT$<|m0<5>3R8gxhO%**Lkb1UZx zKAkrj8Ae#pkyze+8?3Y(msrgx(74fKWTYsCTrA5Ulc3o z7?N2dQwRY>t!``#L5Y;yO~F(u&Vn-JKge>fAo=Y{1DzS{UA2^PMd4Zi+ z2bqi4MTE3`258RZLcAKcvKp`c`uoH3n=on1fb%a$z5)e4y`%i+Gx=_X%}rB0$L`OP zY5621yaNl9B|`jXN~=PKeZ}5;IMVJ0WdO=&!9&UUKIOyUMXwPDR>*5p1eA>YyarzT zl*|s!g6Y`cIz@8(87te>lAB0L1szrsp*7qSi@gVYD3u__&BBOt8EgmO_Z=N&!h_G- zC|@L{vJ8(=#_CBpwn9aU&P`a9`jDe^L$24Xbl%a%q9K7M!u1B9ki z(?|;ZiUNTI2xjc5cpFBNtiXnX1xm+2K*NHM{l?M-E_UOIIgUONPdXTuT!JbP|5M2U zvhWo^VmqMWcSjfj@1*?;14@^&E7X!`j&v=)wR9K|~tD zmG8@)?*9ZIDK65Qi~`u0j6E;VV#^*xX6Jsml;rK{a_C8|2etmejqOTUP^NQ+#4mX=S)IA7mQL`0UKACx#-K3t)UIH{Zm ztPcp`vK;J7X?pUSgD3D5y}{THOxt~$kzZMhvcLBu44huM%MC#XWNvAG9T8>iV2=Ob zWiBRu4H+nqCTcEpZm%;r6kQ7~N{Z`!N0n5 z_$0`E1&0XH++-VI&ut%StohTHb-6y){8RMZb+~tYwG}DxMeC7=5?9c2D}XmBBbktt zvq5H>V}EYrP90ko=jI-9PGB=)oDOuy@7%*Yh^Co>qP@u^ zjQlKr>~WET-ve-VaT!s}3klj1oDaGhd42Rb#vyvSck2VM$KmCd%M)%0yk3*Pgbap1 zX1?usIEzTvXkQCpd){$ruja=(%_Tep&sDMn(D^kWK1VTV$`4X5UOMjQQ%a6oUqY8M z7r77a*taM;QSrK^#1YD`qLt2umJC+!Pks7mN^WitHV9#&_v=7MYG3l)58gWK^VkE> z_u~^38CEkc!UuCwA^sw+KL04;c#5jXxwg;wZqWzgebS|_4F)iKS^p;BwQWMPSQPut zZ`4nT&}`oflZ5DramY$gckIe6$f7*=OhM#;zpB0OGASbIe_!lE(kw=KSsXKset4{% zPRcGRxBMT#Opp?wi0$t;@Y7S(hl6#d1;*xUxkD5{Q{U}LP|Z!L1BCVaJ(1Tdh;>OAY@eBV{_lbx{xdJ~x_|tCw!T?_O6-@)ILgI|X4lBnkU4Jm$BJb&#VuVfq!V*uk%Z zBA6$xT;b(HP5D*~QI`@P<|?C}i&l2);DvGDlMwTC_bps@i#q?(~d;){h9bU7cy zwxD=+5P*W*t1cLi|4qP%D>*;vO9Vmo9h2|r#t&yB9S+yRidwiF1zR8DFTrEfa_ab_&7+iTZ$)*~|ZK|Fp4{YH5`*N;YYmUqaev+j;8GYv!6= za#%w>L=U4~fg|lZZqGQ9M7WTluit+T2_)rfIUvA3IJ2u?&DC1Y9zfE`3CUmxt10^7 zGdL*IALQ)ax>bEqD4~rmiJ+bj*(1cqnIFY-apv76?N${p63Zpw>>X7=7BUDji;VH* zS`kw&AwQncNS~1P6$|VO`a474oq0onYE;qLKNg{IA)3N*14#lX3_>d2$~%#Ok11_s zeqOTevLV`!0E4&+sU(Ucv;g=aEg6=-pH*Gj8lCI}85%6DIcfyVgm=pRrRg#$MX+k$ zNXL9Q|Jf@2{@LK3B$jF#=iQ^xqSr+?f?y$lh9J_Hlgr}mw(b z?9m>*Lq#P6A!z_9N8A8&OGaCf0*YMmElE|3n8}niuk>zno}K{#T5Yy3X(1}8G44zx z#6?*W-LRB2a*X~hL@u2`*sO4Dbnj#$smuReT)zW|t~XvAWiEInDB!RlfD0HvNQLO1 z%M0fL6a_<#?_JNyMkDs1u-f$rW8f zyniBN63hfj{%SJ^1p^-nlmt-%;sBU`9~YoGh#inxVWWx|%?kplF0mexPJ(0r`8JiI z`C8b2;3m)->@aZP&{(Tky|m|we$?Y=mdT?SYD||Ck#mffh>XQ&~ z@A$s)VF=~EFAMdggy>VT+~{S-%C{W63UQBf1;wmWacSWs;`n!N0E z!N*N4oa}=7R>8(Dh+UQQsI1^_Idl=(7so-=u%T#<7?r!9}TLLr;;R)DrA=ecUm4VOxPy>2h*)V?LmCCx{E^tD%BzW%9`8x9xfrNEVV@G znEz2k;(i(4m1VmDJ}JAZy<4`~J~qcFr-0H9&@zMS^o@EW1_7Ji@?zdqM{z1QnUH9< z0HFGOY>kH6Hxe`$2oH!QJG-7av3X97&5tsqAo(;!cE3jk6fDYH)41IVUW6r*wo?fX zBAt9Ne6XG~$+)aZM^`SLQN6x{M5wK$*>s6$YbeT{;Tn5E(20Ubx>U9NA=8A7VRn;{uV%eK-;nhtu~}wg_nS!# zlO^sukB45@CqHXpsn|sU?8@0sr6DbPrS6K?)5X^{YR?6F(?eAj(UHaEvTkP#gZG{h z=p9VSYK4y3J#t;^12tmBO~Kgl#Ub6I?-9u1$tw%JiBrVi!37fqbG8Lven6k48;r6j zqjV?8`vtqsf#MCd&T;ng0y8|9QmVh;?Pfwey7p!Tmr+QAWEi{<(cbU?U!;*ybdEF( zu)4(9cj26~r%3nLS^fKI!%gu0>#WLE{`%8{*fn3vyeIRI2gwX)p?D`Akj> z+9B`0Z>S-gC^qVbzqwH>2CV;8=(MZ`^3JEbz3-*s7Usoocu%K5U2MgylV>Myp{OcZ?M&fQc_$adh^eBV<8?D_1Cq0< z4uW?~K2}-)JE$-GSk7dOM&w)zxQR$;1OCD0J(i$IXF+cOzKop}5Mc(~KrVW_R*X9% z39&FlWgs002N2FEK1T*_WGdx6opf=;d{sfTfJ8Rom25yX!fdoUF3J!Wh5*;k*}#CroT{|Gr{ z#qf8E!Q#G^)pI|^q`!IFSMnl2GF?pYbDy_@Gh9qlegauf68Z1(uB4fj(u()Hnh%bmk%sk9xeljFk>Sys;^n zzwtg54*WbkuG+}eg!KZ-e{S^23k(-~TDkoI0_F*T9+MbK1Ogq12!aFn0|J6rM781^ zKgyui!5IO;;1(c~Q9^@U28|s)8~FbZA0UHKLF1zg2Kxgy8`#=t8bNwd0U1zIaRYS0 zqh*(YNtp&t>(5QxGkENtB6%<PAD+&>Lzk5b{`5CIU z>sO3(Y0K1c1-MpWNiQi^MpBiZ(aUHuz$q4il-Q-#LjW(ni|uLgRcNJ}j#ILR;jlUT zIrbCxcrcB78yKP#NyuZ@P*{u@FoESJ7ygMpm3)WdXT|$zwDJ71WZZ3o{M2B|kIe3S z%GF*Fs(L8;;Ty1hEC}NLu=H?6X|vr!qLpa!2}ML%Q#1~F%?NZ?e-Nuo*p*_!sBjML zb{!NLxc5MK5KI?b1&C324k7?f8bP@O3lLTjaLfWgW@N(#H_bpWvH4M?SJ99 z+L)6$KJ7h<2$qgGeaF^w<#Y#W(o*qL7e~2w3Nl_Ps5~h?8Y+LMCG4BT&~Y^9o*qKCemwDjjgp^ zAuur;bY}ziQ7egY!-C5iU`c)NQ%~qN^6GKK(dt zYDyf!Jazld$`J1VUop$$ko${8*Y!t)5un~c{+vy3Ae91#2z(6?jqIU-LIRc@DRZcj zR8cgN^*1Z2g%jF$vzVt(vO<$kOA{P~W@f2(}jd~Y(&{~9agh=1(7RpniORQ&$hqb1mIm~I#gfYD06sLQt|5||fO8#cPJ zzvrVqmThh7i21pLeDj%0_pfTWD4OeO^O`9+%H{hL<1 zc~fEjrS;}!3T0*X()l`Lo5@^~yii(zvyvDF=5{d_pR&Pv-=vua2B%?nwmbogQ{46- z)uP-XO#&~j$|7b*f$;@W>Eougz#{y!(`S=)EQAx4hd~Vr9=iA7Ng1l`JQn^nEWg(|nkFp)$pUX_^Hx4g;f0_z6N6*`+~ z=~)@IyqSIdhtuSB3TJ;*J#H(Aw@Wt%vSV$eId)&j-}(g_G8IF)|Ql?}N_YBeZd zDGLvZ6YhpOcnekp@`ThH477gqJC0#Ew3L>jQjtmu;e?UB5l6B?psOTRwzxLw`J>vS-Pf!do~DPO z_B;4re%HR&6O?+k$i2XIt+@LQ3xj468Lm7DGZn*98^k!)H3fH+!P?79CF%@65CgYJ z5>t-{i9J^UWMf9kCd;3=B9K-fKU&1qi5zTaimF;$8x$W4c0}P9Oy9gQn^X5JLpQsU zQMOA7fkpRN0yQ6rmR~AlYpo_9nRuiFe4rkP1XZhB#wCHYP`mQc*_#|VRR^49`SdT1 zvAJ)|QV}(Uyng7@&B$xbADO;%INraC{ZV|*YrnY`DQBM6nrEf8T*nACY(Dho-!QE` z`1~V6JsLUS4BFj6~O z{W|5sorQ}*inpZWmjI?8>5o-HVQ&nh?=FLhH?t_ZXvpmj!jiPeLNVvmUA{Dv#juFT zCD-)nNKa4!zsVz+wbX+{1;+c(u##?3@lpr!&NN40PB268&jeM~Nft-$*$>M4_dCvA z4J}pj8`sjX`6F33Uh5iPgba2ygdBOO{_{n4EQ2xYS*YJ;J6LuN_5OhVGJK%5>?=So zCt^2J<-E7clLWU5hlFi|CxKzD0)ZXsK7d|tj8QgPGIj0z)18TjlGRy?G>vyWX=FU{ zmIhg>*xSdljG)BY@1Y+Ve!Be76DU2M_uWFMjbgx-f|M?Ab)>m#{#319TC{zbbL1xD zc~WQbB@k5~9HnB&o@W@GpJ#Bu3r`RAc11(&nVnkXKO1c}yXt_}KR?0Q+5>z(%l!>O z;9qEvM3kVw+fyT0ao!?bN6q3%+s(Y}SV%mP?ls8ZDkkh8o1ayJz=<28ga1^hFD9=_ zA!V0Z5+ND{n;oR`EzE?)Ve5y%R)<@armQ9!Pu6CO?LsoO5}wrW;}}Rf^oik{L=^=D zccEOgAt^;zR=cF}Y>jJu*(lZL47T#Jk8x@UbYTe-r;SA+)o56`mB!Ba8o5*|I*EK@ z2wZWW;biy2p2 z%g{UQ5wANt`NrLub8qV(*l$Ii*t;D)*P`zN-+CoTl;|JC8n6W~-+p{OW7hsr?3S0H z@n?T~$w;~zHyZc1PSZ}#2M3nD$cS@!O;@9W$)wl#&D*ksXK-}l))7HHmsv` z)+qz)$B#D%*Khj%jg;(-VN>eVp8_((-?GmsOMk*-B*lSoFLM(QfsEsZmM3nMT)d(1 z;j@r6!PG&yg<;tqG1~iV32TdSgO_KGZP!vr7)#ykbAcFEb;qbvPOe zq*@v&v6S;|qS)vET4du3ka-Ye7SUqkVYWfTXa}eztz2w~AtS=#AB%Q?w(12%ys)f= znm<#-fo9*)Hxpweful+JPFD!!BrnA|oEidKcxD(C00Nr-ZK3eKRne#hO@m@&Nq^flYpQndfVvbB`RYwk)Bgaon!El!7_$o8I zypoZl?ZRFy(iI8?&e^$_wlSTKH4VK?QpOP48_|F5ApN{85!*DaPn`w{j7F=jHf;%k zG;koLeC5i+iH$dRb7nTr{>pJK zegSVkUtc=sl20}_k8yhT-dSFelq!3^^dBQUljQ*xEJcR(YsVAUSZ0=YaeLBwgo+PlRE7Mr5{t;FOB$+h-XwpE#Nf6$nGiZ1a z17fXHCvyL$msO99xTT@yMGD(QB@Wd%i9!U`)vM7>lA^nZ7vFXxqq4;w{z+x&Am4!)vgm_(`(Wv)6cN~fxaQZfDqI^_}1OrTB^PT z$~1Hu14D#2C4q+BmCzvJ!gt4h!sp`=S-G%nE=A+7y8`Gze>C3lX*?o!Pfx=xDon&3vSOxrz?zp z$_#v~khRz*)*J~#W}WT0{a+V&IpXTw(Ge&bQOM+TT2#Q)D3g&``G`1Z;| z|D*l({Xay`zh{JI0S7*#K4DrfoYo-czK+x)m|-u5c^ZdwK-1Glah-D$+jPJQPYZe> z(YV6)t>95m!Id*_kBt6I-OTw`{Cc;K`!7GkmJ)!m(I4~Drv|3dVdo@J@^KgzWtoRa zmn-og(xBW4ZTJ0em5|=<>(t-)$Z`16V>SzvNI0Aq%ws!XCG`_VnhLCFy-A9s zKkc~WayWEhGJIt#BI$BVA-f?>4~sLBRYwon5L&mT7ebD!KlEF(8P`SrUj+4F?GNe6 zm*&2VZ#k&Qo@tuJgLM8eT5%GB;wtbP8hgI!Xk?~?#c807G&+UIP!4PDm>jhJ5Q0h^ z=?cxZwFLS?JV)liwHHz=gWQ!n5)$(GNr?BnwDf{zkCB3mn8ZO#W#Z5{2`J@_GxrFA zjd5ZPm>?gvEA!36?&-^|bVU}jG*+*zRQ zR6(_xiI+S?5^gyr8uUDz*Od|q$U8AmwCmhdKeEm;03kv%lgXBl4hn>;QqM8mU#qK= ze{)CoMVKQWFKDJ!Rfl4MgN^?XTVZG9=1)2yx=`M{Su)k(o*a~LY2>kZZq+h!(+$T~ z3exP2=-8gs)DIbOkM)mGhzqE+9O$9G%9zfY(t;winTJqbBzuX|AP(u!;t<$$3|G>O zj#}w!uMKIUItNU_T9sY|h!fzI8QwHUQk?=Zt$@G^Rs10dNX_9C{id9lEuVHPVqh?v zfczs<{2lj%|LoH$<}s-NWtW5xVyJv{O?f@yW}evg;b z2uUJ!L2<^##ic(psX?+6@H*ph2| z8^AU|LSM-~Z*lC-D`@X1PE%jwt0dn1@bF5%AMYMay`Z~6nWy_{h(3U3XyWy@PO4H^ z(q<&t2A95BP+>{!6M)rLGmk(>FjFA;w`;Eby&M62Dw;ISlj;o@w=)HYmHKd0YBoA^ z&FI*$YCp8WY!-s!i0gnm14Rz5fogA6-Ya+qgIDMqKeu5=d5^R_#W>^v~Bw6bnK!+#NdOx^LBFcB7 zdDU2U_{5|Ma$UH2xUzJ0@-p=32`2o|Yr{Xkiy@jJ?)GJ(&dv+bv$t8Fk6kLGLf9K@ z!1-{bb~0eVQTYl@pwUAd9z>nCRm4mr?nbrifon zvBc`G5s7%zktu@98l0<&kFR)&~m`X>$NUyt2Uaa5vB z{1sY5?4@auSok2$MPBM9ZUF|+H>fw!IXM<(D-I6z3tgms3f(YzkutvOIJj}iH?9|E znAn>9M-q7MHh4g`yBHQiSDHc&xXzYJCZrSZE%CJFWLE6a=dXAYjnUqHd$FVrF6QB2 zE~740#B`_47iyt?XH1Jv5meX-7x*YWP&-O50(adp-3KTn#rc7oYG@%;_CG}2bgbf>UGV~{0J?&Iw8|_X6_C|TA1LHOw z%uI_~C0d&rmXrUz@u`m>n!Y!7S_G$a>9p!_;bk-ATHAQhnjzymv=U@HvjviI?y0GY*?w6%6K`cNeurWsP)smaBKZ=WlV+PuK#f0rD*a zwsvbzgUsvq!^4Rc{=YJ(ESh8@Bd%eP$-FARsteR^M*{Io)Qd6t{$yoe-{kL_pBsG_ z>u}vdY>#slEWW7z+K z^{$|;Kq**1_G@~g8jAs4dN_#=t_zM<17jjyE?;2dj`js_UOFw%OYDI6a?SzZCax~H zUM{S* zzFbajB##lB!rm&xB_D?;+8{*8+25LFyG5-keXUI?>0x&*7OICA(T zrQv`4Qc#~%M91p3zN;ybE}`0xvURY>t(}+E_B6~Ul;V&YQIIK|hKmWP!qY5L6%RL{ zLL1+7hU%A*&BUPD5FX^ByosW?XHkV@79jL>jBtO-2=>HB;(}zJ(N1z?45hvK8GegD zW7Kbb=H?GmmRo1PjYsVRUfL4BU&h@TQ)A!_ylsm36Pd8_4VAj@Tknq))twZGVGCv) zB?c0ZC$N~7a>$Z`K+$+jO^ipTS7&Tb<+r*1;UflsWFmO)*3eN%2IQsA1n(pnd4wsa zs=Bc7b-K&X} zO(CX500cHxGoAr1VimbyV5cIlNYD9EAgx9mc>lsRd9PUQb|13uQ4b?FGQ9sbr>XS4 z&NG`jZi|1P;vMFEw-d~vgw>VS-y(Ix`omgTDA;K9-me1|y>;8l+!YhHao?`eTM!a& z_)6El_iN`Mqz)>eI|@>K#zPkWI?#$J0FBcU#&G-uw;8PDe~s_Sth20MFQ!;GQ~PL@ zLY*9$5_fd=BQ`_Mk-Rc2WAEcsywm(0Zyr!iY_&O*H96=B|&Q`ZopNnvf37;9vV42s94%FwM|1l(tL+Zsc5^udr!Fflr zf6bdP-u1ucO$hU_$2X2Ko%1q52Xs~4EI?q2$$AK@na z8y+W0mjvG?K8F%I{`kxHC-0B4f0)=78D2Klp*)eA7L49hxas7T5BEIht{HxQ)xK9x z&TY6+m(RzWzOtvn^nFAiV~jnfReHQqcl-sR>?uvGF!k_hJc6+2tLuo7Z>wbzb@@U!3IPd<6$>?thyi6jV(i)f}s>5*| z%<$LBt z8Q8^K$rBI8X%f(>dI>!C=qSY@e~`S&Hc}YU;S#-JT{+0*DA5)Z6I8Snt|~JubodHg zb}$@Un}L>4T8F!jUG0sjn7oq1KOw`|lE+4FV)1Wh-+m-odjZ4o@j2n=C-u$nx_C)= z-=H!N*x#@2?04j3en^`>eb86SuJ0->dFGtH;md5*F993AH$bY$4Ic zrYCcnQG9hbHt}Qb3rgLypyWnjN%tN3`#y6(WXaPu{>~M3-Sfr!`MK}yg3Vh0#>Ve9 zf)^{>@zz&4>bIvQhldT1S^pl#wLEm&SJvzc7Qykxa%oZQZ*2xr4H>2p_nb1Ga$Ous zZ<;UWT)9JVg~Vg!L}r;0&L!#gJM2l5lV+wo02&U^77l{9-Fc~OBZL}}%c+#g5Qv+4N#w*T$EyvQIwH1zBnqu3-fnOC_-=xarAYC-)L124tPW1iR6rXNMq z@3ApjINBCTK|W_x*^J_Xh6GC+9(PwKL;-iHeR*wjkUzy=H9Kx&jc#r;H@khRzMg;O zzt31q#<-y6Rnfrk>-1sE^UgLKGS-uHMsQwaS>ZIP>E7JIxp4Hxuoq~oK4C2T~SjB!zH6>9vH0R%j@wMR_Due3jAOs`ClIfucxbeIKsd<*<)P81qgqmT--U zhEp)5X~)FiD&n-6W!RdbB*YLLQRw}MGTq~ezLUo`7ttdwuE3yX`uEOZHF{xPjWot* zD^`Tb&`R(tI)k>XWHlnzfKKtL9#0HxHKrF^$a~8kt3Diu+=Q$AbC7lK24`nAQ!BQ@ zC0-S;Fb>@d)B+3OJieRUk{Y9*jeX}hg=5RT`aK4h7|*a*-Z3#1`h%0$_Of&H^(Zw{ z?xcyU#4e7~!<0!-fmM~fLtc_Pa7NtL%dX?>`rMc3+Ay_Mj0~_jiRlQO$Jjg&}d*Wys5`1{*Kxz}em@~nl zA4-))c#_rvK;`J2QtAO2C8sW&!Z2ZGG?Oh56EtCnL<<*Gs#Esk(qC- zY`Mid+k&7xnUi1{NAFy~yLS2T7&~I(-DH*b=_6v65yN~i6XIFpeXq^PbK{m(d(Y<^ z5$a&iI>C+iAdfoto=!1_Wx4#UvlH2?D@UzJ_gLP<0 zl67RSFwhr3!C5-0kwpuWcxM0acF#8aU!@)P+&S*0jUYiOXa!r7DcJvE`sDx?Qv7LVG zuMV;fvmb9swR{>Lxp@VT!k;ibGxRWpN$$+i&efRcaGHw{el{W&wPTpw8TRIsr67VR0>dv11FB{sc?A`t(7X_Zp*{l z98_j(_RMgG27d>h>CeeOpMQAkUd3 zH!0s=;2HX&gk#Flm|LWg#XLs9d{02%(+gtsDotwdQvHOUzNuZ<$Mw?aoUiB5{ZnlqNYb#UvzwE(j^h&;^6Coz^~0%i zR-MzE^bB}~0l9IjoQMj4%zfmkOcbTb2}~=MY{lF62V$mdKv6tK`r?qfW5JtqgpWz! z{l^tw4+%0d2RM(^{Tx$uD2vaK+@yuhkimBkYwvL$=j+x(QCn11D8-ho-oZS!V>FfR z(G-FNvP=VN@+ zpdA0AFQT3dZ}}Pos^4v$X}-i>48d1r!5F6-|irG9Eh&H5aVRG)vtmqNr@F zw@mT!SAd?G!kF=%DKIp8V~6oX793xw`hX0si78o+X{|PuPK^(W3G!jXMyGefm|=_i zw0Zv`+ghopBz{`1$~flA)5Rk(a-SFWZ~sdDWKb4R@=~~98&4#8uGQlL6HhGtUWw>& zalBwpH+l(DBHRoeNfG8_T^LTdI3XavjgKjj2&vvcjA-rjPF;@W=@`O5*AkQm@nlc$ zo~^a*e#fQb|F~0tPzXKYNy_Je0SQ4&XaS?Sx0SWi_tK zHo2n54`M-pvZLy#xGe%(F2oCQ*{i)V0wZxX0jWI^sOj*q=`-w9Lg>)JGls5r(UkVc z|3+_!6_G_Et;e-iBmpJsbBLsK52P$PsRKY~FR=@DF-oa+RrnwG7awmYcS;VyZBhOk!J5_j_1Gb z?4QdG*j}mc0$%&^$ zXbYHJvh=t=ubKKj{kma$F`FVR@ooB#x?}wCdDWSH@(_r&vZVYmCMvw&u_rdH+*N># z_L6Q0FyOggF=QGmDzKo|9ld5E6}bc@8BeQ$c}=ZUM7T~MT^`^&;2lhL`*Fi&;GK}w z^&Ca%wnj;)BHtz7Z%W}(H(h6EuEa=-pdW?QgqUVWwifP$$aHVin!>3rhW%i12EmwE z$O_<5*VLRz!+&&hz9+=WLbY6JWOukH=cl-2WJIitWXacoVAe~EVHM=k(-~QUn4xc zV?3Vn$w1^O&&mx~X1e){T4FC{Bh5^%tQzqmzy*&B<CxXQz~rzvi;M_P$znGY>ny{x)7Z`2^_ z{!gF;d5^oE!(0A>W1KN_C@HHHpk5Lx+E+fXUEuXoB6T%t`3TmrDO`*Qc2CQ8fe*@4eX$*r#7e<2`jrn9aL~1LbT|n4I&!VTSiTd6lBWINSpeA= z%0;0?j7tgne!oVWb9kHaECCP|=ib2k6`_K?2e@gDHT7o6Lz;#+e2Xi<*7+^ocIK2y zCrECuCfWK-&D#0%`EMHU2bTC}zJj&1&7!k#Z2IK4f?VNY@(PGZALg)F{x!ZaaSC2)h)H^xcmgx z_AeU}ZKM!4SWBKjXZGCw4=k0euB>@>>E?14u2u(;>Uxxt>}PWgL#B*RCv+364YP1d zC_if@>h;hjoG1u%qrAu_NCKOyjE5NDv0V9tY=goGR$SV(wT=dvz%Z%Luo>mw8op2n z5ep^LWS@}a#6ckwb)Cw8n^db+!RQQ zWFX4CBQJSic)q_bdGYGaPaNU{Qr`}-xovqfyj<&N8Uwai-H293@`AN?>-wn?4sVb_ zjWuDJO+>9Y5c9a9jIgX_3EY915<0UnUZXsRxIQu1dH&pXN9FS?hkD=PG=1|bJY{Lw=~)L}ysb&a$%&4pIw0aP-7^n6Vuz7s;F@aOB4d|#-fQgAQ;KB{ z@iE?0nqU%A@FBO9EN3_#VMD63)#H;5lUH=)P z@eADhXbSR9R7kNOkQ&n*vtY8P)&(_|TBz;nbETQmnnnN&#UZfm>g^ir8tt0wnpbSV z*rm;`eZ|GD!>;phuH+xkC!$UA59q^Suc7)!@ShK11lJpIN)A|M$BC*0+^w@)kXmsM zg8VjG<6=*iR|K&pkI<`(_g~M0xO#71-x9V#HQryW(X^^88UF0O60hDqZ*l*{#4-Hk zPBeyR<6N>Tlw*vA!MuEV=d7bq$mg4!gfmK{b4!;L!UO$`_R9B;?~D56*-wEpv8G^c zY84)tR+eJ+R;`lGL?#;sq30F}Is2?is|GEQyw*E|Vv?PX1t^Jn%guFwvsSGs>O6Z{ zr0gr*tgWsfOo@eWdQrcF#e@h?@}iO~dyeuJD7f@c7YUnqxDv(y0T0jWGBt;b=%n4Z z!}d+0It$XMd4DWYf!RH*^gqSIt|q}R{vp^Nj^(7-oNJfdD5Yg4j8Y}6Y0$tVF-S!@ zq7X0T$pi+RqRaB`IRLxarLswu(|W|$;pH_Iv{KDxXg$H2&s`wT(yI16wVW?MSxi&} z0}O@6**x7e$q-e85nJHfe#JTvA(Ssj<}^Vn2^iKJ855=O6;$&W2Yr#hM(%??40vM? z-#-}1H0U}1u?wVrQuhRs9!&mTWUSfdCeFVBY^m_Qj!#mPzLoA~`ahPZx0c_FJ&Smg zc$ROSF+at=G@Q5iIh`lB#%;@X>)i}0+U6f{s=Lo1Wy`wDe3F0m&|_|i$y}`!0O529 zH&yfS;EJpTc@*}dWX+>~YNXf>iDIUj4~QTY>@CpU#twTOn;nU%!CmLQ)x(PZXbfBz zSm@qFkfrLHpRCToZYoc=VL(0u*+iKgG05$}p+kR>@r}&6srcF_E|W*Pm7~j!<9nl~ zu`Q2#VpbbWHe+GMRXQzMo9=O9>3*_J`5(q|A&=ZlrTDU9w>ft=`l(2ej?Lb|`l|+m zJyok3*SsAq*Mc2AmuBmt9aC#Mj@79HAJgI2E>P@>l?l+&qRG6oVOzpCrST}sY*#+i zGmy6NweJI_Jt`F}zC=+dW?xLIr7l>=_?x{(u+`Q#R*IJFqcV~g&Dif3=-SJDCt9Ms zdbvAaH$5=)*cEQ;I(`c{XWKr_FNP6AxSgIoal8{WnpWrl%AYyz3w^%N&}^7z81JRY z^ESxu09SSts+-sfwT_8Bp(rxLk$?JyEs5|gr10z8sH-IjWKR~6!V`ntfTH2*_iy`f zQ6%Bl-zn*cOd9HTpi$Q}nzT$RpAoJJ?H#yQnk+AWHBBGmjz-BbtefG(MPp!53*pHV zUB0c{ah)U7CfsG&u=a~8+_kY`grYo@RH6CVnAA8Mo#1^#q?$92xC!KFBeI`N<0i3e zjf2!)9+mQ{%4}gC&u*UF*<3~cZ&dwa$g$Tht4-I}deID%HRFop3mk5>DN!dpa!$&- z%2hABeX8Y2j9pzA;4>TgE)(njm$05y5cZKWX<(GyHGW+@!(9+L~ zougH2sNSB`u+N(XQoSl~R1%`41{(uDavB4%Y2a?c=c&9r|0~7IlT_^E4sbt_A3QB0Ox|ugOi1wJ6f^NrA@wq$9 zvy%oIhfWnkwkXoOyLmKPK$djW$-_yc00LKYjx2gLOc!>zzwLIL!B0gt`k2XgnO>Fg zsRmN>Y;V=(tMnk>(1eiou$ugt`NKk$=;R{FP5FR*;}mD+rY7eWP)15sD|T@;dBro$;1zRJ znK>#$FVLC(R|Bu6d^L4|vI8)jmg1^AM?X(D_&!Gvh?obc!n+U}?Ef*$3%Ui=jWp9e z&UKjSI)kE`G_P%l#yQ&n5~sfvY@fZ+idskcyURjv@4|&S&cV|GZ)@rr9qu3LV8Tq9 zERmr;I`Q_TFLhTfy%TW;-VY1^aNRU~o~ruBeD-uf`!dJ)P&7&RRP<;aYJ{Qo3YJgr z-lO)1Yy593|3t(8n2D%}i$HdUgl)$V?-(?yZHPN)#tbYBvFFHE+zsht9#|TD?`!_s z?S1)0Qo&>OdE45x3bYX$OBB|T&VVJ(p#U?pH7V5RgHfq{!pWTn#XiykXK&td&|Afz!OuoMtr&w zxJ2Wrer!Jh1kl&gBHD~$=t6H;=J@R0VVy~f&Q9;B?O%!L=hh5_BeQV=N zKAns+muF``xK>H^p|CcIqFBJhvA%mFJ7&1wmY?5GpIR@4U@H*8(V~0v&O~TSbk4C! zHc6pKp!Kp30k?9!ZyEWIDsn?8q?q_q;Amh;78KM`;)o2TWF_rR`2m+v@iGn9EUW#< zGv+{87vv!EV~nD#na8|8K3bqGP@Y{>%9<}?_wwiErO8)YTQ1^QmocgXSq)OcT$O!j zR%8r84E7Dg>py0?qo+}scV-<4Nic?XoV3#W@Mj{UyD*C$ei_mynVM*1a1m0P`qGfXPTAqnyFH|AYIdaPnkcuSGsSQs;*e?H@CM`h7S zV^(}wTDc$hqPHNF*~@*8G|g7F$Q$$OpLl}7xVQT)y-a5#1!tKDY1!E{KlpAtmMw}k z?_MvMkU#{7btim;i^q&(@~ECK1*lKI6D1?)Gc%4&c&gjlnXR>xPtin+Y^B$^YEh+i zqdQ&BbYEY7(+~lzClknvlXYTirO|JF+%gaUm<(KFP+KYDFqt_MTPd&c4d7yPMnk^ZnmlpxGfs6+mn?I zRXJLLVj~mdl_?W}QS?<{^m3*7EQu z0~HtVcgk>&_jxtwOYla7lYUz3SR zLGb1`tRNs~H`Wd*8DkE6OBENG*|^tQ>cY_ET;1xCfae!qwE2ke9{hzN0+FF5$e<_*wSR9gb^h9 z;*~xGMmq~rER|miA)MU?_E!$KNL8HWii#?k9Yh-=3*AR}I6ZYn6jHPeWu4q?Y(Nz2 zBF%|^0(ExX!@kg`td6kB^y7&xRkoI=NM$u*w~#=Q@eQC6O7@1zwg&KmP^&1{P!0c6 zLUda3?#;*nq#|VNG~%T9vrPooV0L2A8`LMd?68^-knfbr3P=n>;ztYx1bc_~3CalB z6~Re44=C?|%do9+-T5ZE8W7Hl_y`u$$jH~whSnM)`Y7yzM#@rOY^!$K`F@Hvik!ho z%h!}RhkE7gXQpZCx_IOP#`(JyfzlKtV~NF!;P&S*;4bydGSNEM)XbZ4o}7ZT(o&4S z3Ox5tsLFD4Pk~Gnj-s4R?7q@G~a&#*Lo6hiq9CrR9+;ajeZ+e>73%W<@H=;Xf&N zP^#_^g_*guyM{&MO0%C^hDtAy&5wuqWMP(WqJT$a!C=o*s(jq;c+t`i=gL6O)^LyX zr5{MBY4!8ceqJ@?n)vr9zk7?A;Z;$wM}42G*CUe^SmBCj9^EAk$5vTS_^1tkUH?sc zGA1yA76MOgN=yrnEH;Tf%|&8TBF2ex$HX)wh3e6lbv(&cwJ)1qYmz!yHm=EJHoQPs zkgt;DH|FLke0P^_ajYkhbw##mG-4;^`HxvgNmi|$_aYTBUgp7OEI_=(V?R?W`-xwQ zF?o@lk?qXq*=?kGO2-ShG}3&pxOIV2-^&w1jJjUhpTVTXMZco4aAbp`O)B3gq0} z)Dm2@88Z^UTG~zIN=QQztez;LbU*yN%Sq-wHx6*u_jD%wmR5B$dGtxiIv4=RoALz%g!^sJ zl945W;6?L-pG!_0>BG0O{%9!Kt3J6t(03a+*8kOHEdPEG`!8ZO)%FXpfBR;%i$X>Hh*&Y1JB!A`IL!!i9D6Ldm2 zGO?7I$FfF8WYnsYVdKu%Bn*vlxzVT-c#;(>vtwe~5Ir@q!65?-5h`V)o#Ny(y|$vO z-!!kkhhjM_at8+9rtaK<5`*+wwaUY-n_uvd=H7s5&N7Y=eB!g0DG3eNphY&ngsCg&*fg#EZ-%Rq@lS^`X5Yp&R0Rydetgbn)9;uqp#yDkYi9`%L(Z;G2(z{&us!+Jj=;we>x=% zUOLNCN^~KHxGR^Jx|lY$lwG?7!NtuM#lfWtH9`%5OK&8k&Z4C0$D^P!bMJjQUhkLo zpM@&MzS)N~Udn{drK~mZUC{unx}C0T&c?Lj}fpfo!%Okv$KdLw=wF%0eN=d37oT2CW$dl^u>wkKTAL*QP0sf zLNa({cdg-m>r2@Kfe0T6tX~id5A;GF>~Ei5CnXKfZyN0&w{AYcHb*kU>=53=or@z< z9RJMPURQx9o&K{4+u(E{Qa#&@#b=y(yK;IB@n!sbHxu^l_kUCQy=zAh3<+i7S7OZB z2#H?63TJ5S15UC_6d^=7u4xW)6Zr?XxSC=D9z4*12>M5{MSb zYWkX~R-8N$o36n_t^&ES7SVK-7O!S3b>Oib{Cso`KcTtW1!m#?>p6|^?gU)QFOBPM#H;e zGr_9Q3?%8^uHXaO^aCumseM_9|L>>!%yZV7VTEyR(Qg~+#C{kX=I@n$*Ri1iF){?c zBSTx&p#c*SesAaCfjLUT&l8hW$ufobTCA1O+r-^1f8sR&oCA-gA6cO6CwQ5qj>Lu1 zcxB`Ai=aO8aM1WlsHA;A+Xg4?7S_kf#RO1-Ty%uRkvPl5ME9O#qtL@hP=;8No3+r_ z!^bmcY+}RN7U42K>8UEtlf4h*)wmR;Nu=sTzn4fnJfv&%f^+319$p0_i{xG{sOBE~uZa6j|B-0OjPZ=FJd z(DhOo`91fc4gUJR0DO1)BmWrqD%2;d>oFBnWx$4FnQ-hwaHA^V=Cgyrd>h>9v9aO4 zVL`S)kzDZW@|$`av|)2Vuvs5ziT9_VwD9lpm41yoLb9PkD$dSMC*LuJUM;(Tvn}SO@u&#$e0qW6F$ZiUl}wx)?xa| zt}J4Lg74T%s0+wyDVan&sa$Na>>>L2isMTq5;T5<$GlxO`TB9L{7?x!PlCC=e|k_8Gh_wkfzl7$}1PY&;n0sNws z;hvTG%3}KPBzfuqVTGwbHHvmHG~#U&Y!dAfk9k9MXSM|wR-}nJ)s0-EEvNM-S-!+< z=Q%UFDrNiUXEDl|fd@}hB^iC0cCx7U%}I>wCzw;%QBXMO`p=%579=G*!$oQqSBWixJePrqWxTE6GU-H17pYWkvX0Xa=5I+ z>L`r{tO`}BKP``_QzU*@F+utnYVTRnCDAoa6C_folfpmpb^lH~njk3AHnr+&N|*Cp z>M~o^zt;b+*t@`y=C^CIX^tVpldxFR+CuJBRB9*i;3I@}QGZrz3|nign5{`(-zXLF z??|!MeY~)x*(|4AOkX)lRj$ghu65?dseV5t$7oMEJ5S51jQf90BMg_ODlw zVUE5BuTr!1%#j4P#0(8LqINXIhChSfg^QC3>ZnX1%87m-;62~)k7gV(LBr#udSl*? zL{FqQz2ky^{^~gK0UQVSS5seG6Xq|mbzG7#g{cwts>v0!jlxpzDB%gSj7(`}Hj-+? zzqaB0@B#Ggc;H3#+g={so}BYJFHJ{``mg65;%us!?69uEQ&#*6+6>~E&J(s2X>ze} zxiq6t2LOaDj;`{3 zkx%=S%l;shoA|$Dv3WXS%%rR=Y}2B<#(sY>xryoF;Gl@;%{MQu*NbZH6^>;vOx~sb zl2NOZK>IP%fdn+0!joVvMgxOtTaw4&SpauiK*QrLg>JVKLUDd%XDCvetfP^3jmO&+ z^~DBg5;Yk@kq);%;H;%msfHmq^2DVlm{NvU*Bs?zyu88LwebGy z{4ky}CVXXh(`dMMy8d-52qr?m)8O=xQHp?;(qWhga(=I9ZgLM_-L*(BK}*(lj6*(}*E z_L(h{O_ObtjWb(^&6Dku4UDjbvWc>dvXN#hWizpzvZ1o2vZ=DIvazzYvbnOovcX2C zvB_Q*Paz&^vfZ-bvgNYrYO?XN^|JZ0{c6GjVZst2oD)_EGi>MDi^mv`Iezy#Ci(4} zckyhmyJLO%9Bf=abZ!0AdtEywtiHoN5d2~9@R#=*w|?>O`A1~*{5_neQ7td^?>XSh zHBa>qckbBzNTXVzx<^eOeW;XAc{x$$Ktck;@#OS233+BG^*9o&CN6^ z%dAe*J_(JKcBzLbgbWxi-9Ry9GjhogJGzB?c`HuiO`wT+!o1oYLOqy!C6BpAC)dcd*O?ZbHZn~Nb(c(E?~-XkH@fA-Ipw9e zX_4vw(2g}}JZ)Nc4DD&-Y3-kjowjf5w9AHV??(@W80z=qdwi8oaWRheBeB%}jbD<} z(+-T>m!E&`)+;KWh^OW-RaD$2=6T#IfJpsAl6u!kHk9%Gj8G1Q|J@S`yzHRZF;t== z#Q7uQrBzy-*brB;Jfw0XxW*3CBIANBR? zOAik3Ln30G;z@yGOOuu4^|VIY8XVDZX;UX891?U$vJfI$TM>?}vgSsm+0IS0tomYKmYpA7LJx!<_&|C9 zo1xSVtcDV%V{A%|ijln0D%yfoBFqEZqa?%6r3E5$h1ZdXM8Ur)ACAF@Wo{iX$rtN_uv0*-9Z;z+DW`9C&;%wzi`?-#7>{qD`8vP1A$O~SU-i==V>w^=a6dl zo|~j-Ngt5vFYz$nYso)N4<|`7$;nzkz1jcTv z9{`+rp0%zBN0J#CfGxo={TXVTHJb$=^|CXE{H>W#z0W_bH{&2~8}mSw~t)yi)f zW24P0jpIX-ct?Kr|by3%-3^Y{}e9SdXMG%OlP)>d#9IQ)|nR$JKmQbczuHPzI^fz^K)GM`#vM|!*}CeyKrjg zhxVOOQPGEgw5K~ORo*5kYTGqwRI8m60x~27#E#+R+;ZiFf#^64H$*p%@+VL1ff7ll zcz0(<-r_i;vZBI2kmt*;Zfx{o!arcWDDd_A_@h;>e4~!i&0#Nr)7M)`mTaKWx&<8` z;&mnj#4&F+0jdFE9YauVNNh)+q@F(HJLBy$_N4$GH^acHQt-)z6y*jQc@fp-C?5i% z8>Y$Uy<7^fp$9?_d}uv@Rp^j17VY8P>;hfEs2Pv##I#w(Lu>^l#AApx6%TO;6Xk(t zI$o&iA@ZMSOXHPplq*8fmc$Xtfkd8kr(-v+ifWodq-S|GV?0h?>8Yw8;*3W2C^03E z!|61){1d`F3Wziw>-zcevBgX4d+B}jk+wC2wI^oUOjAlPkJDDsNa(> z@-;rj#faG%UTXio_u7TCEQm0mt2KkJU_`YVWPqx_PQQ;UChe{*Jik>jwmX zwBG9e_uofer}~&-!HwIu-TrJAQNqiH?z)u~G3<^I4%G2XSqWatdh4yu7?QLQ5QZGb zct|Kb;7GV}J?^Q%n;k6{WG5C)iW&&#nco@WTnz^#HnL9=d%&Dk@LdK&t8hYuTq}H8 zt{G5@VO3FN@*NdEZH+?^f0h3RP&B7CAs`aqTXJT3s>v1RThay*Y&D&vnf z!aN|*aY~^UROuv%*SL|$G=p$s^!3J7nU6^%u>vk+Q_l&}Q2g`)8@V7kF`uPriR@iT z9olNPSk#mK{klIvk{WwDxO&x+5JjdH+97+Zxk5b_*ix@A&8q7`pnt&D%5JADe@F7+ zG0T>S{@{UogzdKmnf@yaCp^<@WLiX8IMQwR+m+2T!ZbXY)?xnG_LP@rXI$gu17F&* z`tDxZ6E?heJrD@>d-s*T(gzX>zh09;IfqoU*FryZ(X&Xk3cs92s&&q(r26lFj}WM~ zii%xQt?&;c)e6&i@+6WGRsk4>8QnhXzOtgUOcx3$#HgzXjN+>0nf9c53Tgfqh7}Kq ztxh4#88LkemPu%&^f+nnTz_Y0r=AkemqLh%K&%&qMm;HB&Nb64QS=~bg(Ux==c>X9 zAY28!fo~3P5kR50XF&v8PG^oIl#%i=gnMw?V$3wyG}~wv6M{zxt}P;dl2qG)g3#qc zn1_A{zk&DoH^4`;<{zq!Oq-_~e>C-27*dm=(6hd55}_SNpcUGYmq_EWPu^#q{A+k@ zGb8+LMBc^}+cd(VJ;CN2vplZqYJ@v`(v-!RgzFLN9#i6?$J6O_5mk7oF?&YvG*%O=VDI_!x)}`Wl33_FeEBoqX+*>AjP?US7Mn zcly-#e)$c;miMIx0-=6izRB179IsEPb^W(Ysz+aa6}gY4-_d%iNi-RsN2(_i>bQ`Y z2#JRHBJt5CEVkv!pW?L!7~FNH1GcT=szDifOq8tn9B(-Bf6aKYh|o-RJ9s%LQqYKe zOBeeP&HPWh7aEautu1knE2u7raN|mQ*Pm4rHf;IW8A=dm`RL&oo}>W{OD~L#oG+Tne@{Bp>3D##=4V zG-haw<(lM0PO0%`ilp?Msj9M$RhGPF z61i1~hO*BGgMlT|K%|?TFb^d);z_RY+>rERUS@fvsodg9B_<;AL4g zfOrV8jZy=#;}to=xr*ul905QvX7G*h zt}Ij$xvHd9^>QcRIfxjKOyh|`q~`$cORXhUtqi5oRiW#BYaKf@_5B+-Fp$QcGCwx8lQFT+Y5qy%OW zNXx6zB#DPYwGgN!)AzAt9)f9k@KEGKeQ$U8{uM~;``pq5%QW+CGD!l<1WbSZ_1793 zOHX-!#!H&%MPwca)Sa7TL|QGC$F*8>-|8xkfCuWU_*dKPF)znvbF9=lv?rD0n+Da= zR12z43#fiZn|)s6P)$}a398@on9%8}^(FigUG;6O$xd4>2}oRMs|D7AYFVq1Zk(5Z za1;0lTyNv?LpOvBYxg%;tGm)xV~xfNo^`QgxA~{I2Lm9Se+W64Nnej}+sZ@Jd`3H8 zq9{NBXhAcOZI)`RNmz7XW^RQ%rbW>Kb88|#_eDXuOir>w8(?c43$|52XU*Y?xl4#< zbuv_|fq(fi@YlfAyaxFAx+D_g&uG7~S=FasFwKu}Jr(1dB~rL>xx&Nu24TagJ)}Y( zi3e8cw9gj?VZ+lNUmnMN0i946tu%6vG&$z!*b!ew$NXByLY&-dLR>-Pr%UZoFv$1#S4_#+b!n}b36kPzbUFf7EUrzX#MgF8GKBXs=Z5|17b zEvvL(`Z-H!rA^|YJYYINE3Gi%wW`E}?{>LMt;#$E(`22c3QRvG_gPW0$OOcqYVg#p zOk1ud^%ysG_+Gae-z{X=7%53V|Y^JJ%9c@4F27P ze#tY@%7wxA-xoFV05IB65SaH^nYI3{-bf#|xAQmIecbE{#Bpfu)wFqr>jc|#Luki|ki@wG-UO=!xH(Mlvz%)=+ zcQ8Y@Fe7bq18Sqo-sXn2-D6G#?puTK;*oG+Aa9HQXpKG8 zLVbPzw#%H^qWAN&K4q!?Z~rX_{`-HQ*p|}LQa#m5UG%1jip8o~8ur<+U@w{hzPnqN zRV~%3FJIMNfLGS)ExFeNf(a22+~(E6vaCQbZo;Ah!NfT4f3USBf72!bX%~TD>K{Lb zDX#PgnVUh!3KkO#@dVO9xY7IbpMzUyv*l-s$DhG{Cf;ZUJ0Wn>w^-Nlo|h)=213JF z&t9eLNLyH_p(_8b!|)xrt{IMyyv;fnDiohZkp7tnm4xtDpc!FTEA(1=zJ%QgUefFJbWML zN6C%fq~-bAlXE7ub$OFpoX>UjCoRv{E^_$rTXG1-Z{gwxvVA#FYTp;KN>i1|hBdg> zNjyRxl;NZZtu%e~_zsclJ1o;S*$^)tSfzQm$MzB5JV&tm5$0`V&FOK204;QQLTJP; zo%H%i(gURNFP#oP9(^3%fB3Nf^3|*4_TJv`ErDA2*`92%8uB6_sH+@ zo4tp~j9@K9Kn#Ivi-5?aLxb$(bbvORAT0_reu{3bA2y>d=I)_XY<%IwC-Lb}(vG7y zZ<4*okK_OO+i%(JdOhi2Jb2HSPyGIU3DYemBWh5cUumrirh}I+1E-gk#6!vHrAvuN zo}Iq~)0+HUdh);`?b5hq+IjPM>MFP;#-00;k4#Am!?fB9sQE*zkf!c`g}!`~D>?!z zT{&0uT>W9s9)VS_g)lfcnA-Dr$uy`gLzgcI)yyalf6!hF35bLsFR@Y^R4cy#>ucT2GVc0BS}DH@s{-)mwHj__3NmKNKIApp zc+Dd~W6)u50_YZq+~iK%~=mQ zj7^bGegymoeEAWOtV4+pso<^H1bd-iJ-I?*hEj}8o^tWXDn+hAWN&%#$OhCO&`Poo z`I#Q@ZGdY$;5r%hm;{Y8=#1^;l&50He8(^`F*?bj;Do1TJYl)oe3=c9eDohYNL%Qp<;qSX4@o>)1RiJA{7P%7h=)o& zMVuu~lz2G3v??e8(n^BqM~@z5B}N0(Ka+oFOfx+Trq$0hpsvES>K*89TFUQZ`5$Lm zo$2|jwwvCgFS&9f;3Xed?oW212W%j=^d2PtnBx6|lHdt1m!`=-wwdXTq$1QLm0*A0 zRX>w}jLAPbJofwlkb6APmpLG;uk^nl0pSLrNrXYSDgn`C#Yqwn>gssp-l>k;JPrVb z^EP)ewvmC%4ou(Cf?MURoIZ5J=<6 zBRb;eG?PG(cS!D$n%`+$<0U722b~DGF>!YTT;eg`qc@B?y(9prPfk#9^kU?4338C+ z1(LOvT;u)u&uMgwlMue|H1Ti?H+iA_L>KZ7JEICr-;u-vxrYL^IIDCi@t}|2tpRU^ zRa)QEmk6f$ORSvMX}j`HeZ$K)0v@O@-{0g*ACYdg%crMLL!f$MyQkzSZzVI zfptYwtt$IPy=w3mW%U4Rf%G_3V>QmfdN2sO%omidTCYdA*6Xm3Cg9#f|BO)E^nLOI z()hQ3%i<3oa;OHZ6CgSv7)%rEwV*ow%U`k+{9VgboZ$haO}O)8wA7fH1=Yb2Pat)f zkf>Y3uQg)FpuM}B68Bed&8;e7?hla_#l}Kk+A-rk9mGjU@E2~bOb-$#%Vd*HvI{tnHJ%4US4Y|bA_pQ8m==)dc?$J@^Kw96^zvS6A`B0W=B^gUx za+YarKXaMI@;=9@`^B^*V~I-#Ze`MP#uL^_)yMMmhxEyhfQAMVBp~mKdVh27n6&-unbr9N5DvciCi~{z zJ*%bncSuN>jF>Uo(DEJv)Sm!oEIauZCV;GoIPu%JLHyl!*^b1F%y>WL5h=c^YY|px zBqC{p5WF~c<#w24L>7z9ogQX$1kgC7PKVq+V}#IVbwUh@D_3UehurLvR1=KaSe*wD zbBmz8L!=>n?yp1a&;w@ecL>%X4x76-`ZSoED5yHkgIw%xY%JefRB zTW6kwu$^E2X2Vh0UT1X^efd5{{{ntIuI6}*F_wPZT9(}OF!4BI%bq#$iPUss<>_;c zKWY2Ljjox`xWb0(djeU8G}94}!O%U|qEo(3`gp?!m;;m>8QWQp=^iJ19pK%ANr`y* zpyN}h95;GMLa0xEkDuWKW?T;lA)eq;FRsb6$JSO-1El$)%ZP@U$gQ1R;~g^LF!=4a z0HP1@u8+k)Yoyn&O)LEhV}+$xuY%5t7r{N+<>;*5Ae`rIGAbD;5mZqS*v@@1jH>~a1SX-{r2rFM*g9$JKQKzwA2a&bIQ#2 z9a**U29jdQNx7j!SiV!-M-8#u)R_I=2DoG7ABdg9!2Glt(VF#I4|%zHX3RNcmJz{a za!(+lU-R~X$yxVcrXabcZa|mpVGMC?9*=8{rE8khvV$ltvE%}fgvCnBJq%c zp!xW5BCkmn@erAZp3a%`Yrgy1`}=8|@5on7JXDfan)xUu@fhOGL!ra%`&4C;hLA?0 z9;#oGdGZiQ7jh97aR0XZ3p%DzXxv$>$5w^C#+2JQ?2rHvZ6Lf@9@x( zujT%#wf4ESrU*LHU$p2pyFuO^?>Tb)jw8&j*bv(vzLb(w9~ z>$XiNTd;JSo8~q_W7^zYw*g}&HU@YAu?dT`4Vd#7HDUi2%-rC{jfFau^_t20C`m-p z7^r2MhNmU97QeGp(m_i4IKX0z#XI}J3pq#< z@br%7cfk%YttWCiANiK<6&^gat>h*i+6EGjlOk-ml_ABzt!VbE6=Ibp>Y+;*H>^)> z3#8psMptgjB52FgkxZt_5CJjx$3Mn$&u=aa(`vf9ziG8D&qW(P`4K3A!ViTXfw2)- z{o2?tR6FTM_4uB!u@F={2?&!E|K>U%5R8(6HLmoWTRm6o-0O7&*-FSjjsWcYthSh( z$n(wMh$S=B0}BN6-FLxPY@ z!>dQsVro)5YeemqynxU>H)Tvc0dDjfoh*&FBHZnVT=2VzFfowc!J z6BH(3E>VP%BvZID;2N`cmJu9AQO|A0lVl-ukz0US=N`@8^8cp4W*?;dbloD)^Y4BH z{0N*Iff1C-H~0)nx?z|lV&>DNO}Lp~ul(W_R1+UXR%+>^c`;2{5f8JFsTK&6u^#ie zMg~U2LwWQ_1=0%85;FNPlf@a`G;Z;tIx-I;8|ENQxWb^1AN(%8^i&TR@5f=ngSOg$ z8PBpz4^J?Q4~~yxxx@Pf3-3d8+Iq5g4|xZg=Jc%svq<6rNXtUKt8Gm_WT|;7y9(5T zyhEv5z_Sa~qP#zN$t~VPLW-C8T!3`x5>FKi>+-TNt)`1eJv>ZnqT<@Frg(s{&`TXD2x01=C4|9zv1Pe z+N{+A>q1*yg0+jVSg}@PZMNVt@?rh$ojV~ilIYIu+ZJKbS*t(3f8V@y>sGzpYBdO_ z&}wc&;8?49EZc#Io0yWfSeFG`5frgnM+Vd(?)A9Y;!-aIu50aKBG?XF7@~o2pc=sD z4vvdGMOn-mv<0Q{VGUGsM^~Unz!^me4YB7rXvsE!=>Ur}?(nFKH99?|tt{2doKA2jgjG7r27I8qYBxCHaap_8@Adn`PoF-; zx9{E^IIA=k=j6l3kLi5?`u7hXGV>+Pgh2(Mnq-wOL7E*4rtbjKPUq~rc&Nzvm#$i+ zgBdVA?q6DcDE$YP>0P`ij;JtkW^?A8Ox1V(|{W^CWGxA}dy1>nXD0A9x0_sfg0^ zVPTf4?>xDX(F6bhKmbWZK~(i{+G<5uvHhX5z`86ScX*Tf z7&0L-z%m`2psj8&1O6`S30^>OP%S@JVdL;HJ3>E=wgb<4aYlsl6#7lx9}050{k*@2v1f?Y>#DeG|gQ&(M_0}E;nG%81)9nA=$ND>M>i0EWKd}qaL}~ zwYa(4eO>P)ZtQlmXH)tQMQl{Q``fV8Y7W+Aeo^Wwk?)fq0Y3s`BOs9GvtysLlBh-1 zg= zm^m*VUA|u}O@V1yrQHPaDA6gN*<;sjf&5^j3eOhmzVLca=DIay=&w% zS*o8DV>U?!GNP-lSgX~d7-umzX!W%yd_?4i@ zi#bs%PtdJR4Tjc7|M>Y9H~GiXnI;5v;!4$AI~O0MKHLDHLnw76N8=`vUJoFMDc z1Vf>;{rKfWBujM6XOSPhfubFjyQ_q0{$t~GH{Fs*i9-MeQK7MnzQk@jN9Q&}w4wZ`^#?H+eS0xZ<)wOG;* z!oVag0+>q^Ej_j1x{cMkL$$WaK@`rMiH!Jy-NrRY2f=g)T_)vqX{$Af5gAC(;#*%) z7F*~t8xS;67PT#XwY>z<2G$}2?hyvnSj0F4gJtq$u|Au;sPj;sk_T!V2LE==N8k*q zbB5CU89-Z}J_QkH0 z;W3)$s0(HDh$}Hk2S0d;Y=~PtO)?~(^)3*t35c4s*iQ$8Ay#NfL=bb-2aU8FzvEZ= z5Vv@E#7i1XGQ=M~B5Hhq0MS^V?Vu&~C?_0B4~;84rJjT53Gxn=SFO_8{#|b*`Igpl zS(yGGQ{)}$E8q#|wCIOc&f8~}d1$wGo^Jns=|^CE1oAuNc&^{}BXE8MR=-we0JYoA zfmJXp?F!Vo7}9iRI?<8BRBO1 zBuq$i;5xTd*U8Jl8gn)f~*Qi^WcXZ18F`s@GlTuL_6dY)8`kf!hOpsO+0TrbTR=E z*6ks;?(#gS%iKh~b<|Jc^sa!K_W3IoL=St!8t(zt1G&K?4C|1HEJMW_2aw~VhzAc~+D$st-*T2!+C@B6y%MJ9dGWw1 ztuzg$SMlVb{tomuFL~YcybC)mks)9%29W>u7-N<7=$qN4VE} zu(Kn;mM0LL<>BPx^t6elu+9xuF7nJ;&COU)ZD7p=Ml8}%hsjD?^=7?=`>bsY%$<6K zB^uTTu;o&(jYUjQ4PZwAwxl27fghS30sEg1 z6RgtCBCWcnm9|Fto3l!5>pgQvEVtqxdZHJPc`*I+&v`%jE@r-0tmvtk>%T>GG zYW9Y`twILUsbRrpuHhE`X~_!(%^l<*O?>46)>#__XmfKXoMVJG8xJBmbR*F=X$XF- zWuXpaxdyNctF>?%U`F>TZy}-x)zRM`-eB7}!t(R$f%klc8kR=4T&`dG5x5*9aK>)7 z##;%}9H9M{qiVp+w4#3&0)ykJ8x|nbXL_^73_3|ezSe8#|49dtkB=Z1@ z*#VYlT;kzZ8d-+`yNk8D4EdhX=H5KU@(XWjRp?xTj_h`9a|qiW`jqM(ioVQRa!7TKpQP?L;2}IsMz99-&hY-7+fBU!G z69{=#Paxc^f``0%fU=@2Vl4_{bky8>gBb50_t<8}ka+~*rWovTQzPszPGkE4WSb?- zriX-s^wX!T#_W3nzGT$TxM|AiZ1(8>{TS~f=`gLQjYh;%-hM(fG;ZQ?5~ooD7{}b& z6CC8lQBWI#!ZGlR`>jH&Y5CN_=PZYgXArsSA{rZEjnP_jb2X14D$|WtFKIvJB}9+3 z?dB_(cI)D3K?jM2JR4XQ%I!~T*6OoA?%5g@eDWi3DM!GrV7=)#qAj?_b6^%=qq@pJ z6IyDUN`h!vpNA}~F9T%q;NG;y=jMb!EQC(n{mO z12EV3Mv9w{>>ZO0nT(k6eU>2wDLr@GLMQ!+?YyGu#(j(2>Y$;*+F zvr2zrU6T$wL`dWZ{+YaE45pnI4}G_mNmgmTcW1%0>dK4LBIz^3^dg7k>-Z5UN5BJh zITU_ni4j*gY;?o?dt39R*MYO*BJ7GuKj>UEoN=53%_uZ(RhRe=L*?OPTkx!ik2=<_|c z6~bK4cDZS1_~T1c%8zkY9_%#b*oSW?iFp zA=^Rl#{ay)$vFhjT>JVwP(Us}Obc%vbc4k?K-v-ZX*ghs-Z>cH>Yd4ipX>>!v|QuGxXMe9u{@t5@xpsY(*1OFL^Q;Dx7BX; z0OG;cR%`G9ztSLqJ;JkCu>mOay5a&XIUn zdrdwpJciizjJGOGtJ>2Ub**SUnTNW_uZem4@#TI5iV@&D!zVuiD~-T<*UlXI$HCYk zEvl)0BdAq+^28S7Zx#qfnG+-+uI>+R1#R^-35cqEThy!StCh7)^u^$@;w1v?@7~$* zJY=0f3+MFJ&J#!jct8E>tMDFDkZ%d8rl2r*@~Hj5F?=H0Vq8R9>~*`%ulDv3cC;EN zxZ1-)g;d*;c?6NvgPXHSJ}B#OGuH$~q#tr_zKW= z9qXFh?bY$AC+{F81p&C>cbl|`eA|eb^U6$XttAm0*^P{Q%C5q6bzMcpcK%nQ%{X#f z*15NNceYA*=i0gq--jECe0i|%Iramp1SDajI&7FlN<7^Fy`Z^ z0EhY!DsLSzgw2y6xxvHz9TwscU9(LTl+*+78z`s%ZM4tK_d|AtMgwiMym#0+;#tf;(^35)OFhe6>9L}lOdVm5ske52`#d8$ra z6kH3ej{xd?x*~OumsGuhNMDTykf1IrI+pN+iHWxfIMYY#(b(IIn2Hqb;am@AAa%^r z^{sln!JWR2F1m@nvf0F+HDXS|v7#+%;$o9OPP-P?T4|%!Vdh~Q>z4pK2Wp}%5;76J zwftA7ZSJIUx7P%y8|niKH?I4nObIb}Weq4>@C-~#V_i;SBu_AH+w4|4Yqsc!mt7l_ zYfC0RWs~xEZJ%vffq(NOu=WTzLgex*R%Kk)RhNS_7pLWOE$6t*LLtHPZ9#Jm)of$V zRUoBYBMm*Lc-GX0Mj^=w5(DW9l5v@bXArsH+{(#P!>p2UqM=Wb-BN*N8RD+sU{otsH1!fEuImKh28a$dBrIf>E7}25WklS7oKbp zF8t&aZAf~WDlkh8!?i#<0i>C!XdunLgYGxqr1+W6g12vjRY2NVq}A8&dQ%z$X<4SL zL0Tm5P?`qQGeKGnFWNG#eXMSs=Fj7kAA!|Jz-y>iA3NW2bOe^Cpr=p6X;x&{^;1@! zxDA#IYpS=m1EPk7!Tx@w_2{ZX-QIJ$YFTgeLIbVD4bIL-7M=$cZMDMdc8W4h(9xvD zZmShaTkT$t{`9Ay!~T2hU)t!NWGF|>L*nkPNsBn6#(hQ_eCYQ>jxo4{wlQJi<3}vt zLqNX4%>0i;T*M+3_Rvt*w!hBqHF1j9<9;k+?H&ckOl-`!!4f2f`*fB<(drXk1p)}8 z#*JMtz+5K?LfgD31sr5`BTRX%MnpGhuCd6$#Vn<<7HC*ID1%9l0uId17Wqxg5$55| zMKTzyDPm!!X1b(Q$4~(ntmnExu{Vr+!Ws(sujA#t_-}bO)b>XGB8_djY`^p)U?VWf zKDzdFaanHhW_dQj&@MtvvNI>K8= z!cW=Af~=5-H5v&AFZPR8I^@g7CLbQNeah#?CL_iv6BDV!HGhhv%G&Y)l+WpYip+z& zA#*DRecW|)!S!LQH9Q8Y!`^WMJg3AzO!k;haTwlC-+y?K>HC7o)LFztX8DvR7I{$P z`=rHp*Bi=CIj9;I>GV&3%5~EEPJ8|w>%oytD3nh6&i(tDzMovkaD8tn$rYZy(_E5O zdR!~*%F2m{#iT>~?xgT|RvZJAc1g9E*f7z984bRqO)j}w2+Z7Oem_+jEcWKpT6e=jyhEHJWG7rH?e|C68y!QU#j zYG%M|%Y`r3O~Vq8V*ii~MA68U2==nAzLY526_}EG3J$}hAcU0M#)sxQp!J{snH@cR z7&Za^Vf}zKBa|5o$OibE_cn3^Ws$(?^(OuWtZNY#n$bCg# z1Q41$cJ5Ku!ZN09`ME_7VixQUaG+o?X36GoK=+`Hxmgxz>93i~6gRP0_kaX~%|uNO zTUKg$7Rm(W7O@0MHHpb?($bDSveq`1C|a8(z}QZ0#xS)l1?$i*$F9!M9>wp`_DiL3*QH5O?;V?F%C%R1f1gGZlr z1#?l9!yzCYC%v8mzu>V4w|f14%uGfKaQ?#&*x&Kcfg8=V#iYaMXq%ZdXs-bb2yA2o*149# zd0&JFV~4n?^rLJxBdVgLAFiGQ>Z-PSME+4!e5c8VRj4N45c{uxEy4O*@~ZL=HMI$g zS}Br6TXY*m)J26dAJC)#4Y z9mjRT5;usoSZ^W!XyDHpYjz9TZ83?e1!_oVt$;CYCNfI)(c&<)lvJe6jb9NKsS~J9 z3$Fp1?AVeoYaVwa3Bff$mNtM4z}vLd`f*-ko4Jgp@{lYrS4$_1FOGtrPrf@yw?qITAdV8VLKhcrJx4TiZ^@l=P*BSa4k zveKa>93oefa_}@r+Gq>HB8dmk+y|=rgaGdkae#+Q%mI$0V}9fg<*9=xhjEQyEShvU zJUU8l@gw;aUOKv1rMHlB{H}=%{eCK~v?d*n>!hop!rfq1{Yfit(!|5xi^>A&;MZSE zH+Zg(KrrnlFOoS?=26i~yLQtiATApH(DkF`($?wRGF`bis=Y0=zE0bPcHk>r{}Cwe zRM-DR{TY6i5m@hfnxm;c7(0bcLbbYfV7(|*tBrxRRtmT-?bb_x^&t})iU0GqymCc33mrw4fRbv?43!OQ^SnP^6V;;$j@PsZY7A)K;#Ei%l$Y zO-#^rfJD}60>zXQ7{$<%mBAlou`bq&ATf{)WjS0ISB?NX=OaN;VQQpOo{CDNivr4B z9lyM)Bj7-~B%AXqx+b_pAY;n#v>`aQTG|#YbM9`sp>njX7tm~OJXB3l4doI~R%o@C z3p{*I;?o%mG%zkemI0V$I>7(rfDcx$t_g>cS(nWcotNhpYCc;4HwNP<>rvk<(s=0@ zq)p;40nNy(VCmYhkITC*AkAdMA?`EHPu?yNEn~IZ-Mz<`^gSjY3Z(Clucufaw4)+0 zi_=NlL_^%*Q4|D)Ra&;`J@+Jx^+iBh&1IRk1Vk%UL0a33!1OebR{PHc)A?9vhpVM^ z+po4)-|E_pKyhojcIV|!=*x}3de_tiL-jQ2M|HSX0&CpesgDwLxX46cnJyRZcpHxOF`H_091o|Wx)opC9KY17pbpJ;7T4S zgcX}|owHFkOBo++onurx<>X^x)l@TYAP84|pRV=@a2de5Bzvo_2%%-v0!g0C8cjW{(8xcuj7(!KkRAZW`q=f+NA-?!kRI?LXNcB0jyjzoR_7t& zY9d5(Z?(2dxLy*h)wZ-+W3iUIJqOmufc5_lSi4Z>f@)c; zw+pbA#rhUt&7*pyCvu*PnPNy>Qwww1JY=!8?^@cxZBJ1*G(t}r`rHE(9Qfu+Sbe(&bBnxHOdv* zybhEA+My&Mf^C*5o0?&1au9rhU=K`{Cx*6nT|*))mbolMC4y@iyRl9dmT6T}P5bYy z)%j>#nRouuIYjG-4Q&e9hSnWK7x}szxCX3*Ml8k6(m~^FhU#1b1SJw!%S(qMikizi z?7c(>HH)-G5;fg2*AcGoYDkk4q#Xb?uuO+Tez|Glpe7O#bF*3NVR??_OS%I{AEJv!CmnBNm41JEnh=yFX*_zA9KL>? zBHchY3Y`d5D-)tFv8J{fpkg@q@+8gL4?s8NQX*yjFOTN7>J~_&wl^s?OvSE zzUMyYI(J<6b>o_SdsU;1t?*^Yks$;{E{u$a;O93hD=AN|G8?@ZU}m4m47i=&#hiT| zQYA+W**0$5Js$L1BRKpu!L63A)#A1WT-*4{+?T+9^jmZH6U?-j08M@0y-!M|PL;!K zXy_)js6+E}fLVu_mI6-*y$69n#(wKPO-QJtFdWQX0Vs#L*m0$peWK^AAb|{iSecb~ zWzMGEo&baP2yq^+OUFqqhhw6gM7Wva1xWz0g@FV7Lpa1z{Bb@Gpb%)s<{rZW`OBbh_~s2^Lb4u=+Mi78 zXuW5L$Sl@XH};gSQtvOHaEDtj-CScNu_U#j>BPP)BwSKKI@$LXPl7-GC~3pN4=N-&y>sDe&eM*<)fzu&1|%adObs?)cUo6-hZ3sXwx^A&3l!gH_k*b@f}&3>*|^jR zYSZGI`J25KYSq=3x(Q-7fLSAIo}H+8bl{W}$OSHexf7# zp=9bE%yl`JhV?1+HeyL9S}78}&xlYNGXEckanjqT^( zyw5CCzb|>5CIZ%FFQFO}tM{x8P4jUdBdtQUtIoc~K71kHPQv)U|74?V@$<9&vApMB zmhgh8=WKm*;4I`*S{Ffu4}>9|;D~jT8JgxBA0Y|aGiwAU&UE;(CibH6p!5pa>@C^~ zF`$psqU_b+^6r1iWcSCEJ}rhPVx5y>SIE}%XT5>=ReR@_#dTOSLWC1gGbYt8FY7@o(Z*kD!%)M_gKCK!io{2yC}rGQOosc8 zh8<6bjqpR$fCNr)Li7oF_`k*6v$^+@b=D2Fo2SgDzD=rOOlo;882{nS=ww2T>8L=8 zeB2E_kV0A{B&=Utxc&w44`Rh%nTPVhWMAyd-~N1DmS|5!)#9n|WTcc9gY!qR#ekPG zo%8Yv;FMK|srENUp!=LXKYtQ;UeIi<&1f7IFfH$RWGs4mJA6r; z&V&FgGYw4*{yMOrLb@g)%)bIs=NQ5=)7Af7R#%!V$&yJDhhsfr05wt z_1Fz>%FR8v<@DM|T4;;4T$4Fjq-N^7-B)|nrGVe-w?cBs=zei|=-eC4)#5PWI1MGE zZjpd&U+#^pd3*fZ;LWyMUeUh+2)~cPz{-N3p8|tSAE}`PjMcIg;?9XpO%QWcR^jJN zM%8*y8Vfg*EzUWT_(>Eh#gWPkv3+zc=xK*?cxW>@pq7A-Pwgc<+g5aqUbQkERCx$_ zKNx3DUB?_q^@XQuo#2lGoKA{t zvOA|#qL-#gad_lngqHb88p;vVk87_V&VWW5UC}Tc?VkA2ymcRji~Q*Q@I%&mV~1B% z*R!wO5x#URV~=(D^o28AbzKYLDDyFRd!;{n<0;Sq(0pFKVsZS9iP+&0pP=kJK}@B7 z!ABXCtBJwgOYo3w9~7g<&{M$mnHjk0}@$iT9`| zMbV2+?2v~o*N|8buT?!n%|?uXj}@&I0LNL zoa3rvQ>Q~4>2Ib#?s~La8c=@AFBy~Bu{pC*{NqQtFarBpavu{TYav4IVQ1z*I!0CL zMfdq3S<+zShv&%!oS0y3{Pj|CKYbk1bw2?a0|K9CrubKOMMD$9 z4dz|(4}85=UJMsbl(S-i6(wg_BY}-D4(%o9oZBkP!9JhDQKWDzCt&G^q$ybihlvl` zh2<3^WTjTmhR(>=1{l3tzdvEq6r5L><%EP}Q6^1`^1! zXvd9yAbUZk;FtMObE+ld!1g?qx|*|UVE)P3-(meem#gjZ@* zM_OV(>F~7Yv#&#YUl<5@V6P)gGe4^-dx+HqNypYZJtiJ+pIYBKbh0xOpk*E5CQ2jw z_zc|U^j^*7^&18!TtD{rBs+Uxls{SoFdTncdK8XR{ zzXCy}7Q*bWRqjT0tt6D(-UnUrGQn>XJxGAxIQkNpux4%%B%HgB6Cp)c306xnAjB>& z%v}@9bQmyZ5X%=3Bhd_6#V0A9969J@$&p{syyOqm$sS}9PoZXO=40JqaFGRkc_P>x zmdg!2k+np-^ftpKlX+miv|-cVo*~u;mG!(VO%YS?W}3ZwwUh{ApFoLqh0uHiQ$FQI z;s8gRk_cm20B+QCL1{rcv+3toC6stBBc+L?-vZy}m5U?w$5Ku_mxuiYmpATIhcz#FyW7SBw)2=WfC~G=%yxyKXCvy-b}Y`Xu`}CK`Dk;W78F%Q3QX1wp*-UrE7mD9*_1q zLN2j#YIg4&A8kk^cL*Y;H2C!r#qwmLU72o@5a;Wv*X-Hs44lGlY)RSPySG|oC2LL{ z+B1s2LU_}?N8Ln<@J9aVfV!(2HF+rI%pBJ#2J0b-1A>UIu*|WhL~Qi=17KLwB*&ys zUS2at_FM&-7l3x2%OOM?2cymj43A*Xpa9d*KIS{eNQt^Kk9r{cQy`FjDCI1a{#=HU2fs}E+ z6l0Y|%vQ0vcbn{RW`%S7o~Rgt@$BR5_{y9FmnJcPJ`P=PZ6@J^wjE?d{uG z41j#iFYRf@OC=BbQ=Qkl@t|5|L+=^p41J(U@7j5G zDD&F8@N%zifK+yzS6OeMY2i+48_5tYyvVy90G-o-#S)&#pUd4!mKNWo#w6dsb15m7!YRurI9?*CME!nc{7A{8Z-Y-I_hXIVv8+3h zqT8PE%&F#J)|(RQ%8=J@O1_>%dyf5AvinV=j=FzqU-o^U!$Xe{D}qjEr@^Aq5b9L0hYJ?k32{JDAkJ@jFJiicp5G#9yxn115f#10GCy@p3uLL`s- zC+=b$3xmtN+K>?Om(>1r68ABhv0dsRDyDJw0_tpf`ONePwLJI1HyZ1D!Da<%)}2$k zcxy!s`YNpMGsj~ijofPKqKnU0Le???(AIW|EI*O_78Pb})7YK| zbYNpB7G?aDn4R9Cz}1D*o|d>6TA{hW zk7q_+83V&>!Np@oBZusJ`qlJ4x%yWPK;@82!!&dtQHk@x;hbdgOE9eR6B@T_GmZB4 z;F*SGbedPT@gwPCYB&tR)jMcrKLbq)jJESSqosk60l|5fmAsFiRXmVjfZJ11PV(EQ;}v z4}?)V^{0Chn7nKa*mXxcM>q+T2EHJ#hccheXkImxu_4oamV_p6mFNPJKL@KwLPn*Y ztc!99UWCCOZe*qMQ8@Quf12H7em<#9St(}d@1`nAq@YJ!ulAjbFWul` zh6FJh*7zFNi=59180JDy(sD7w{p)Q=3r5-p^g{h`RF%RPUl8`xpdDHX5r2*0vqZHi zG1*yEMoa>A&lD7_@#-_9skS($NN8EMxy$q|N27dL4Mzklh$@d`vn2iFtsK=#&(IVz z=C)n^Ho_l$(nu1{r7<`!(nu`AZS?IlDu!nQ7F%B?;jC%E%0zF?@GN^;uUa#Az^Rl& zPw~;I(M*uEM{3Qx@upG)KUmAnDhM{)?v0Y#{#f^Box>|Pao1|U8Cd+~?FAe`QseWN81Lj}d?QYEZ>W1RFW3im<-2WU7 ztasTwk$Xfqgd8_&P`-Mdo(aWsf{BuO0}rMZB|17tyC>5hVLi;5^p}rzpBGK85srt$ z&bh@HwDZcd6>&^X5{PL}cWQcENsL;qmf)@uV5S5E!PuXMpLf8fRAp4*xK{w=E$u+RC!I@Kn&e_Br`bx4AU zCJ}*EE|UN<9mWhd9X^41Cr;BRi*)!Amt^z{PlfM>raZ+0itncU9!;6CY0sHlzyLWk zI=2QeGZKcp*sunduFvQ*<93k+pK$;At_iurB(=N$nKAIX!8#$|%cbVrR^RugJ+kc_ zUCSpy)7o+FPpt)>C~+|c09f{p zw+7KH*>$kd`Dxx+6{SE%dWQprO`WHT6uYVT8zq*iD4`WTN^8ra1C`~>SzcYH_yDvK zMOA@0awu)JH(8-BOC7mirHU#*XDIwt1AWR!r#*h4GDEpSmkGsfT6Xx{7T*a0-82Wv z3F%H-6Y2Y|avK{NWSo@KFSIrDZW~(O2T_RvNcvx5PN+T=@=Pn+M!fJh;#?&BO0^z~ z7gjNgn9Z*-E4#WOt(?-VuG3@K4idKkE){vWOjBNCd6%nlJhTt~M8{2qGHGK{xkF@+ z$!;`+HAcBwd`)W}+?cM%XUL^=JE3!EM^w3@l&u%>i}qe%7KK%=GkDk*zJ9jj6xNBp z)ePOjDcrbtE1HGKZ_;?J=$w#Q79mMd_UXC&Y-PPBSqbXOppzcTT2@VUxUbjvTw#K( zs!3A2nmTiD0xqCgp?b=^SoNCS+CsFg0#hnoyBtJo<3gQ%n7`XIt!HGG$R?9sOzd-@MVR&A2{3pYf)pU12JP9nsoqCF>uH99f7@TvOelA-9F?U*RJ$g?=VMEt9%qEEY# zq|Fa?CFPwIy~`BOnJ)0BZ|z`bfrOLt?7VPfcte@C{Jj@U5OaI1bphWFwqtXfdIn3~ zz;o?fulKL8S*E6;(%y z!CIM=XsWXkcsq6!GZv*AHH8U=uWZ5_{JYjzXzU2(U#&Qdl?VV9RGz@uS>QjSRm^Z& zEG{W*6hAo8lwMWUMxYwOWIv994--&)Le7mgMz(MB_?}j=mhxTiu~rf6mRO&V@NiSU z?c1@C2V8(d_f-xh`@Pv7ZfER(*9E0BecI~{e`UH>4F%SebR>ui6uE>}d)|=O3cpFH z;Nf1+kNEsD%y(6aBbufIC8+;Uk`r5iuvhVZTU?<$30rh`ehYUf#A%uva7OT=9R;R{ zix^m!YY9eT@n^%$uV+v_lM=Q*yv!pRjQ(dQUR`CQZ=-olzy)tIQn!C2} zHWq6Eto=cl-HA|OQUk~jXTRNUNIhl)-|(3EK@8s$!0%pH&TBfgcJ*~8XboJ16-Q$E z&!2j#{xaElx9((V4#% zNoSpx!SdCTcY1pLP3br>`_gsXBR(WUx4u+sxiUa~;Hds6RRu6vJgn8haBO4aso^l) zcATGA^3LTfu5pb>^O}Z)a(O~?kzlxCh@>?Qh{DUwF-+fUE`>1np|5Ta{rD_S?!C+7 z9tTSZTazxt{1O(SVAN$bDI>PRdU1R+`kK^T;f0g6N7RT^1mnW@nD z{2%7c_sg9EdHzNt0>?*(hN@Dbxtd^Vihoy_x&W=TRkGx+Umf9eJ`5TDm4Wn?Stz}= zfA=>+*d}Z~w#Cs7k1?bG$EZs&Dbe=jc(weepx1Y!iwU9^++xA_6+yNJZtJ>OtXd~_ z*$K43t@3%H8zE&_gCsXKDF(Yf3P;h$YnmIODFds^Xb>4P6t%dRcjGpeP=&EVpYrD< z3&y(z`16)U*L9LY8@WE%v16-&IFNrF@80#YKyDb4ui0tEb?fOcQpv2o^5KVy{-di_ z-XA3G81K`6GiNz46~m3hVjm==xPRuZchK=VeF!id+Kk~-v{_}9mLw``;Wst37% zoGPdH1u45y8B+i;4IrY~i4F7Ua$oA?@f2po>Q3am)HRUv=`(kktLT$r@DqV>>(7j3 znyM$oq;!BCM9k4=9%q6^IhJFoACDQNV`Fr*kmFf!a=W^%kA;u2R`4F-MA-*A zxZrn%p9JYB?=zu=|9oie+q6sZfS?@svxhzhlG|F1C)(c2K3UIQU$r0n)=M|RtRA9g z7JNSPMbdphz^ zw&+*%WvEVaJr?cisZ=o~lCrrm_^Jahtn5w;ba|(#xbw|Xj|9Y+$(XZ7gB=*&2jX4to_zPQ!oBUW-TfmEA}s(L;T1MY&lTi`)eTh8d6N0ccl7Yp}eOkIbZ3k1SkAB@Y7MeSxvYxXp! z8#Av*(Mb%hLd+l(Jk_(lK%-7!oT}D_Z zgsI|$M#DE}dvXgEQw`~oYkVt;cT9yExxR8n2981`T*woLc{d;fvNgKRID52bV}qel z4?WlAxNaSSF85xyE$@Bv-=3@sdmkn^P9tpf!XN-zvHwnJ>Q+`4>~~b5A)@!@uI?W_ zWJOvRsTS50r`N=b$H&m6+J#hyFqjpyX<%C=2o*XDaHVN@Pg(REcpW`z)sQ1zK1ez& zCrHV6kj6EKKqKn}IKmoSO=$OWNF41n_lBr!&I(FDa>kRbT)(>05s+OVq0)7U)!AGj zulotVvF*C}FIK*4Eo!@bjVP&Jl{FPq`zZ;N8i2Yh;UauUfwkMdM-$)WTA$IEed73zZIbyf+!NZcT|n7AFD}DksGCK> zqq&e`->KP?@{8pgWb0IYh*G=!ZSdHD zGnJVtm|Sr)Ct?8q{Jlm86mZ6` z|7Vixh!p^RCX$}6PD_Tj!*zyC?{=~RI8~Xg(+g*?icMgH;?F3F2X~Lkl`-b`pT^D7>D;yjiO&twYBW}o>^{k)Tx2l)avQGh+ zH-&}NRVG-@{fVrRpxcATG5bm82zs81{QxyA! zVH85kT?E5t6pEUL&8ki7i;%qX>+%F7PxEZxl{idNM1W8!K#Y2r^rKg$L*6+4jzg=j zp}x6Ua}DiJf& zy%^}xczW25C)UEQC?@Fb_|9CpG(|m>*cZpXV-4Sas}E4p>!jOmT6Ve@b#On0?FyxqfPix&q z3vvhZ#;rs@X4R8TvghTj#)o^fui%@ucU6UrQH0zxy_f=5KP5tg-!Rn9X|iiK$4ok5 zI;bzdqY37Rz0fd0qWWc=3lfMDem#BpF`G()Ld>pmvJzJGR$OC;Yhaq1`e2Jtgqf9H z({myk)GqEISf3BIJANC(t&fF>0^T;oT=+nk(}n0qmBF$!csSe|M(In1H=B(AD|x$q(AQ~ zmS&BFi(~d*^SOkF;#D_#Hc8f3h$Io(G;agIVL}OB0)kB;5wp22c;o&*wXbk_>KPOr zwcTtGlO!h;45+d$REA1V4yP&HIMFHe9Zayl+)}{LcdSz>@u6<)AQX%hOWtEqbC;RY zY9e<(PF>|Tm?CAtIg=ik!7Gx=F&r{0M*Zf@?Gcs+4J+{4SR48uOg2x8Q`KA>%SAU^ z=6+7_%`gSov!&rI8C&*0tdXt`KjXXAh5aK6H0N>E+zzGxe(UcER<=xZDly>gaM8*%ftv+RCfzNOoZnJUgz>t z;?^gT_V7V83{h6q3Aoa_`Y!4B%%Z^JvWGrftStsF`iNr(i{@$5qZ9}%Y!8izzY}A8 zDq|Cmr&5Gwo>wsze+7oQn4$EuYJvcRJwI{$)b-w?4`K|1s9)3*%u7v)Mq7K*WKOHV zqKfxE!8NJ|b7ImN30EEL87=U3sog){Qn50IjT}Zp2jNB|CJ%g0&q=lMTI5V$_o7j( z2H{TSfHJL292p40^Id}*TnrZt+xhxp*eSy!{=t9~RgLGkR(x$1-$BmzmM}+Xy?sLF zy?h;Bw(h^tQ#f5{RHHP@7utfzZBE|x53VDpglLEOi95PGiRi};&5ekzb{H*jgPLG| z$r*1BC-YyQYt`h9bj4?Qslh*1$8GGmQs0UlPkK+XzS-fU^|atsu^k$}RINrp7{fx0 zad&n8xX=f9*)REmolK-DO6bGv3wXi7MqJ?KJiuNCSFWw;_lGsM94*f|_`On)50cRZ z-iQAwXG|xCbxmT^knZ&V1#kQGQOo)aX#nsGq@uEdBP4M0wY< zr$VD^OE0t}P=Fie4DgMW%g+8M7qQd9x4}W5T%Jj>5LHI-6+|i6i5Mpw*PxrN%ZTTA zz?6GaLI?lQD@p$^GY#S;Jy}tR;4&pFvVZWlarK+u zX><~Y#Lw@JpL!adz-h$opFZudzyCxwgqg3iPs!9uMBu5=7Jpwi!%3)C#N!XrBYIyt z{{%l*Z&xiO3+!~#j|=u87&c}@o>=4(PBr0xT?>zwgiDy5-+`;b&xB$?HwqbHPd4uo z45W=(qxX(qo-BvpiwqEt-cEP1M(-x|C8@{(@>;D--aE5>x333bWF5lK<9FkntC6G@ zzl0+op}1dseSWE4X!L+K0gXqn&U%}|qIN>S*Vl+yV6Ct-ci8Z^Z$(kOH%vhw+DOec zYZ8m7V4)$buy{dlGj20(3u5uvbguW}7mippcq7ft!)Ha!*W@1Cj9e?kB!=9gG#h)L z3_d8*kakTu+wMt|#G%z8<{_u)k8FT&qpS2ZV{^CE+^u;}+;LF<3#6TOth}5l*|&@> zGG^0?7de%-%V2PYN}dLrS&F?Yy6jv0<`YVG)@}%RGJdeFw(UWH|5oj%aJ!OCJM)&e z#X%43@HJA5c)0CaaHU>Rl+&$fb`knJhPCZ|JY{{CHMf{ka%G*|?EkU86l8s+j1FX{ z<6pq7pcR_X(vr@ZU78o1G+75rSPSaL*+DK`iA^ zsyhTRUafpylv9^BpQhAoSGtIxHf8~M)~tP!x%W(SXuegG+b|^HO{v$(C(lkT>|HsK zirzn=$)*vi5OTCP`vNjIt3%}z&I?q7>rj@FFxMpWstg$tEbi=-xP1Vcr&8&^HA+1P zW8a!AHanur-nU(Wytt_E^jKyL|EkKdo|%^E14l&`HQiF4dZYknyA<$ z%>76YW_Hq%=i4hByGFbapaHsy04A$i>fMHqX9V^Wf6XC`<3W!7JmVsPjy6K{NJuN4t2m%KEh0S=zS_Ek!Mt^tKM zA?5`9paneN=4MW!<*d|L4NaC8fv}p)q^p-j-uzi%&uAc+Nc*PIuVfnh zg3TQ<#Er(NSSjZ9O~;mXSrq#X!*(66D-W@p9o}y#i;JP$mGA9?Kuq{s)gvt) z?!SYKlDhKKzr6wa@mutO6uv%m>c^--oj#@ghlu3v!{nA^k~F^4p1Z=1Y~orxvq^$o zd-lAlIv3vRth&UG*Pnh=yZ|D$HozjZI3oZdCy>(iXKC7hcKSP^5-{9}4>VY<6rPXA zrF}e1;1XMDVXwMn3Z6N`K&_SKR157YF4KNXOCmRxM)^kw4!pK-M;_@#yq>GHlWid7 z$caRe8QS403=IpL%dv@Y4A>CEc-fzDJ`Eqe(VV;PFwr0ndP1OC&4Ar@ewC-Mw?EK> z^F+n~&H;Ou0i(LnFd3+v?)1x=P%{u!qYG+-|9808dc6LA3PNc?yyk1` ze|{?5x$qOS?f?;Be2T!)Pn4lA0~m49`z8!7al^7-yU!(iR!xXl(qk$Wc=HY}FOTI0 zL5n<-psuudCrerml%(;+eF(`?qw{7icC_gho$iH*&mU$RpY3TkqTPNIUt~h0`MU4| zgPm>b9JyBYK-AURDms-1XZ{rx5E-?bFm@u$WYVP)q&Ot5EI9N^u>k+u^KhFco~E)R zJ$D3;3i{-|;+5?b4`7j%3VKY(37P|-F?KO z0LW{LQWp~RBJy~ZU|h8H0|Rw4DA*0&MgmQV$Oct4f#lVw>C!CGUx}@F2f?r>7tQ#P z>g|WGsOKaG9RgyIKg(=dy9^4fHgyBJEiIb6MG*6@V3C{A9qL1TGe z9KB0m&$jP3!vIx4e==}||IIIldt(Lf5`l;oIk&67JJUCL)0m3uoaxR9tmKjhHqKjR zobNnZOzLrb03Wj)ARATzR^+1_-8&Q4oc)N?ik31g)91i&+w>hQcN*rW2>xv%gH$G{ zHik7@`s=B#Tr^IoxIH{$!E!&mnJ4HN@;s<^Ox&%>rXa*KHm8;8VC z^wXTO~V)den83U#P!x({E0^s1!MT-0*VHBj^}dp1;(;`YyK4-4U&{)CUB=i z8#WkH;hT^5G@3}PQ%D=@L7w|S*hKy`%LPRHN}n>DtvdzLKLxz;#vc#f3!N%GR`||I-ms7{?7yy%kgC_JgwAkmO0*UZWnA^8}vD+=J+Y zV0+LEc@GFh(@2u?ds@fi_Dy?y!}eywVT!)cxoF7E7@u(4t$*LR z`Ig1CS@34OhECVb zSzC1#G>qIA8aFnqt*sv4JnGVQ)c^9X_&M$L=|eXbMnC)Ghtwp-0YH_B2uGin0Hkzg zy~c>HmeiBS7YCQs1hyDPn+ykzzyD8lPip8KTeTp=7OyxM<&PG%eyRqJUtazc6bEcm ztbbswd#d*o(4>h)qf=`pjRi~6(1X)wro29Wed{rM->E21OUZO zbFHN_*Z!&FtyMnkqHENK%6|TeRbD_+6?V7M!%+QUp_6>6>&5r-?UWo2y>;C$_;b?E zCqfgA{LS?IS%>l)t*OZ^7t={nYZrtc;UWdSMlytE1WijWoSQn^fL_vMSJ2_( zOib`~A;IF?za!cj7A(SJZ4};u31MHhPMQ zLIzFZ2K(JES{p(n(J(>(kR9p%EYlLk2I(Z38M{7bnvf$Q+{w9Sa3MWSP)F@@0pYBm zDiV{veODN2H6pG^Qb8$jH(_xuo|N--lg#ZmF~Vo2tM%*7#BFL)&rU5yst9b0WH99> zcnQ`5Ha@9u=FZ_rXsDF-;Q9jU<-=J%gfKYi5_kXqu0c zqwaHsssS02GUE`#8cEp7*E7V_vAs+XzBCcLOc3YY-kUb+g#%P4p=lk#962UWs%Gwb z#Dy7=bwMS*al()Ljv(&*{cp`iR;Qz+tpZmen7+RyeUwlzm& zhS-HcUSuZc%J!H$`M>QsCMX8CD-HftItdZ(1v`GJ5c)=uis#~*NB{0?QSe*wEjp6e zi*5n;>O+Ji#o(NSF@GW9*L^QzPSCO$lv%8Ltf^rrTyRLr;gHq6w(8S7fzzQg8#ao7 zkxk3Xxn-dFFSoQHzlmLd(mTJ0P5%6kaN;$8m1|f-ZAyfYv93Ws(Q7iTc7c9GF}E7& zIaoM6EQQItni06pU16eB-vxE^`s4k$*^GSMEK1t*>EP`qmS5T0*5tUv9~Q7#WU)ex z8UK0ED1$p*Tjv?R5uQe7*}m_|tWiDQ#tdyK^g&F+MijiMuo6~fQ} zE7B1Bu3ci63-B9S#5~+N;b%D!=BGiY-j~y7b%`E5zK7BTOT32diHsJ%C+hu0r@vPj zcJY%NC`5V0ogf1@ukrx<9`3$3i##gr%8ciXhx-O#E8k@F6NB)*2%Sd~!fs8RHJ$#k z1IUJpt9b`}X|f0N4yL!U7Bg7WpLgLK!4xU^$oKVeG5SMleB{bFe>?c^XnxBf2oUd&U7|AKJlw3{sd53PW zkc=@{{#()G5B@h?=wM&l-0I|B`y1n>g1l%Ufer|$npOSUM|T=8CA`OKmtBf;*PGQ} zivL>wyRQYo@O0lG9S(p!lZ&-p94$2=2oCqiA`Zdg%4^`BhFy_=86V3|joh>>aAa3HX#<=OO3IosR1|JOE4 zV1~!xeqfP$e{WaUOYCy*RnKVzI{;RYueTWz;VcE# zy9i~keQb&eymSF!_P_f@;(zigLH?8#@yGT^({}Zb1oKr*9wVsbV<{pRzg!vXt8yH$ zZ96muRG39t=A)9=^88&KvVRUM?DQRbR9J88u|jmp-1S!z{k*ua857LW+1611Kk#V} zSfNAgOY^vBA>q5th#-CW4(%{B)mWoiJ68vBa;7GD@BZ!vhWo2*C~E(&irYxpSOoH- z3*9R0b=g&meJaEOdC^hTo;&xl*sI0?kGl%`fm83Ru0UBYN>{t03i%Fdb4g=c*|7Xu z4sUwlnNayYE^$gRri&`fm&i{-1?-5iUb-8*n@&=(#}^@F5>WV49fDtrwaB#>&B>+D zo^=k|#`5tX-y`91`OiA;cV=O2vXzt^%JKJXp_kU^jq65Zt@U4pTHw`IuU}2L3p_<# zhB3AIdu=pH31($JdRKo(Dh7gQ?u<1j>abgI&289y{b_xoC#ffc1uk@XJaAhSdJUyJ z>FW8c2Y~ChujKiH({KjoRwP{t@!AKHR%$CmI*s3XJ3WV;I$i9dPk)szv#jR&jJf!m zq_r=HEw>M`xpA5(muG-3d%NJBHzK#hKX$4As05e(yS?9HWW`-6lP2r3o?4-i>5BLX zS!rA#&$owJ>Y^|Px-+36|APPQWWAC0T{YI-;DP!0{42B%&c!cIjtR=N{W7eOBOQ{p zo?kV;#78+$!klQfK7Y6t95R6A?M;~4EeF>Lz4B|7UzP9t;eWLx_Ni)vcv8Eq%e>7YZfe? za);zta`<+Ef1MJ_V4)_e8F-GRhLZ`JU8c%m$CUxwUYJlM1bwz(xNq z`63;5_+n6Wg!+pM&LNhP8FBXAtV{|YdtD;evCmv46Fd$toEO+V~8G|3Lax-hxSA{U+@^f3}MlxqpN}02cQ9)i2)n{!l>+ z{6VaQ_=eqM83+qSB5m^CO)A$#V2>%E{~#f~?$`fd`ny^Wqk?d^E!G8h!56uZ@WCcK zz07L{{w-CjHD4bWDJkcswU`h%o?oky0LGg|Ft!jGMzpDA=JF={d)w|mUA$(6Lm!pp^Wtrv=-)n&R{0linDbW}Ea+6) z??1uM;MMg9)8=JaY){^-CG{|3uWRRC=<&=c(K`0;CG~s{>6a!)zeT|1}rf$hkdz9a>i((D; zFW>TIJ^IadqiNkIOD;x=A2S#47EeTJ#SED9;;=jJmCL)tHUT#`^9?xWtl>VP1!mo~ zUta&e6}zWIJ;b`IIN&hsC;kgC@6AH>-$LNes=|s-mjYT-kDOF0Fdf(XuYnvqXMR@y2-nMbT$*@4*-TP(|e)IN@7~6AusaV)f zUxQ|zk9ZJqW7gBQ@Gnost#5rO?SYn;(g!rbNj32|-GAE!$`rrXy8kV%|M}^b#mHbJ7{IIC=VAU;i%b4D%Uep* zQ;TG)Qu{Z66_WIv$#&b9)`0K$!W;Xbp(2HMXfF@M|9Gi~JVMDLb<%+z+TzzdY0 zuOy5!m)J%O6YTj*`<0i56TwLV+kB^_30}_uY$WOJ3=%{bJ1b$x04ed}XO<<4)%U+l zhd=;t02$OC^D22&;Fm1Gk~BT~aD8Lxs^Gz1Vc65EahpsL^eXS_P{@F-E)^(wO z`D_-+r}q=T)jkEQgBP1RtV~*u7=DR_CWYIQkUvU(_mdrH6hXquX}HrC-%Xs#URRel z{$TppAoDxkdD``lt^=>dmWP;vf2Y4Dw#JaZ7iiu&QqCSTDok(M&y=SpS~YbPc#XZh z;8o#_>!}ke7f8o}D*>P-qY4rRkM1#+DC2WrTIVNB+SpO`jLMA7mym8)p!wd!uYdP< zc`Y0cO3jb*Xy_>nyn@>9P3K*h23;_Agim7u4k&nj75Is~Y9^@?uK+fK>ihh)k!9*u|qF7#Le?-sN%9P`;5 zA!{8xDg$g^Z&rzJjW6P&KKF%tr`GdK)~j5Rv{{oIb5F*qTwyMXAZ)>3pKleYUkq0) z-NAdN|KxulhHl4{a|XXJ5hu?9e*O;fB@t}Rn-kVagP2*VCfs!b zJ<=GEXy<05S0wi4Gjvq@P5yuLEaf!O3wUp}PG(*5evTWx`>n;p@^c&dj<&U?>fc)% z*U=X$vP8UmiY(_A^?-b(yz@@I9AESTzE{~<#4JC@k(qil=tNv&XySFXL&aI;cLij# z6+M>k1G;>p!(GlD$rhpN7j`(j&HnxL?fx^H1>S0gUO0D2b#rST=ed;|Z2!(VwPW|6 ziuh}M8@HaXpMK1ZiDeu2m3bxGuiu%-!l5IW@mT77$#*NpmMxnKH8b*Ce%s{u7xcNd zoG;p>`|5tHpRe$e$t|jllR38--kir1TFhahb>Z08dB6jXeoqMFi|Uiw^}X$B^uNWw zpKkkex>)g-Q(`Ma;~%D6pAA26zvN-zh+)08>m4`in=5}Ngc<2nbKiAuf2x`3_xxq0 z-s`t4FG5ty7lX`aWVtmff}P>I3sW!W7E?A6zU!Cwaa>lvCe9sFD!fSi`(#;8ffL#n zuBFV{zSc|KAt7$T-Hi*9PV9TiwO!ip-=3AVe@4)(2cm1%MwO6ZZl?4~|It03{rs?X(G`V#C8iPObB1F{$r9}io3F-^?U17J&WU;zX{Kq zJW*h-iVmm14ACE!zq`aa1Qb4mDzJ5bdHz~k*RK0u?mPePueQN+pWXS*dHH3np3nT3 zwJUX5jJUp(9@kWES;Q%DL#JR~;gZSvHhJ$>F6Phq-QAoWt!=YjbhAAplX&B2pO)!r zk%|rr79IF`d`h6x_nn76-BDF^Ie+qoU&8^d#?StBt1aI6G#og{bZo|U{&|0P{MQy^ z54P8M_xR7D$H(2L$vISZGB7gTX7MSzeMiQ%;XoDR4=%Z0n^KeD{ym~r9N4r^^jrg+vK*LYi-Xmtgb^`C*gL?(9b z8AtfoLS-14+Bqa<^CjBkDl&b^+#Pn9`|90Rtwr#fiJ3oH==v6JlO3UfufK?w6-7r$B^4 z!lTHB6mcdNKjnlo+P9}V6-VZ$D?2Qh)NpdC(4Nae3Kym{Y%GW`=Q#KKkBo1F!*eFJ ziRY{XzH$m^s2RMf(9{vH?zR+UWZKKsW99heR{SfV8hNI*{cD|ya0R-JAPK;Y@>=d#Wzp$P!8iQM-9 literal 0 HcmV?d00001 diff --git a/feature/timer/src/main/resources/base/media/img_clock_timer_dial_secondhand.png b/feature/timer/src/main/resources/base/media/img_clock_timer_dial_secondhand.png new file mode 100644 index 0000000000000000000000000000000000000000..9a8508f3bf88d76a2259d8adca64712fa97ba1c1 GIT binary patch literal 173387 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

N~Ei|1)D)Iz1eA)%;;H2Q~(hF{&KscFJ? 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 literal 0 HcmV?d00001 diff --git a/feature/timer/src/main/resources/base/profile/main_pages.json b/feature/timer/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000..feec276 --- /dev/null +++ b/feature/timer/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/index" + ] +} diff --git a/feature/timer/src/main/resources/dark/element/color.json b/feature/timer/src/main/resources/dark/element/color.json new file mode 100644 index 0000000..a25a885 --- /dev/null +++ b/feature/timer/src/main/resources/dark/element/color.json @@ -0,0 +1,41 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#000000" + }, + { + "name": "text_picker_color", + "value": "#FFFFFF" + }, + + { + "name": "selected_picker_color", + "value": "#FFFFFF" + }, + { + "name": "timer_bottom_back", + "value": "#202224" + }, + { + "name": "play_btn", + "value": "#FFFFFF" + }, + { + "name": "timer_stroke", + "value": "#33FFFFFF" + }, + { + "name": "timer_picker_bg", + "value": "#000000" + }, + { + "name": "timer_PC_input_default_color", + "value": "#000000" + }, + { + "name": "timer_PC_input_focus", + "value": "#000000" + } + ] +} \ No newline at end of file diff --git a/feature/timer/src/main/resources/dark/element/float.json b/feature/timer/src/main/resources/dark/element/float.json new file mode 100644 index 0000000..409d058 --- /dev/null +++ b/feature/timer/src/main/resources/dark/element/float.json @@ -0,0 +1,471 @@ +{ + "float": [ + { + "name": "stack_timer_picker_padding_top_15", + "value": "15vp" + }, + { + "name": "stack_timer_picker_padding_top_25", + "value": "25vp" + }, + { + "name": "stack_timer_picker_padding_fold_25", + "value": "32vp" + }, + { + "name": "stack_timer_picker_padding_fold_50", + "value": "50vp" + }, + { + "name": "stack_timer_picker_padding_fold_24", + "value": "24vp" + }, + { + "name": "stack_timer_picker_padding_top_32", + "value": "32vp" + }, + { + "name": "stack_timer_picker_padding_top_17", + "value": "17vp" + }, + { + "name": "stack_timer_picker_padding_top_48", + "value": "48vp" + }, + { + "name": "stack_timer_picker_padding_top_50", + "value": "50vp" + }, + { + "name": "stack_timer_picker_padding_top_30", + "value": "30vp" + }, + { + "name": "stack_timer_picker_padding_bottom_28", + "value": "28vp" + }, + { + "name": "build_text_height_56", + "value": "56vp" + }, + { + "name": "build_text_height_32", + "value": "32vp" + }, + { + "name": "timer_picker_height", + "value": "210vp" + }, + { + "name": "timer_picker_height_h", + "value": "180vp" + }, + { + "name": "timer_price_width", + "value": "18vp" + }, + { + "name": "timer_picker_item_height", + "value": "40vp" + }, + { + "name": "timer_picker_date_text_size", + "value": "20sp" + }, + { + "name": "timer_picker_selected_text_size", + "value": "36sp" + }, + + { + "name": "timer_picker_width_picker", + "value": "220vp" + }, + { + "name": "timer_button_height", + "value": "60vp" + }, + { + "name": "timer_picker_text_size", + "value": "44sp" + }, + { + "name": "timer_picker_selected_text_size_h", + "value": "38sp" + }, + { + "name": "timer_picker_text_size_h", + "value": "36sp" + }, + { + "name": "timer_picker_date_text_size_PC", + "value": "56vp" + }, + { + "name": "timer_title_text_size", + "value": "20fp" + }, + { + "name": "timer_title_text_line_height", + "value": "28fp" + }, + { + "name": "timer_picker_padding_vertical", + "value": "16vp" + }, + { + "name": "timer_fold_padding_vertical", + "value": "24vp" + }, + { + "name": "mb_6", + "value": "6vp" + }, + { + "name": "timer_picker_padding_horizontal", + "value": "24vp" + }, + { + "name": "timer_hint_text_size", + "value": "14fp" + }, + { + "name": "timer_hint_text_line_height", + "value": "17fp" + }, + { + "name": "timer_hint_row_height", + "value": "10vp" + }, + { + "name": "timer_hint_margin_vertical", + "value": "15vp" + }, + { + "name": "timer_bottom_buttons_bar_padding_horizontal", + "value": "6vp" + }, + { + "name": "timer_bottom_buttons_bar_height", + "value": "77vp" + }, + { + "name": "clock_shadow_above_space", + "value": "26vp" + }, + { + "name": "timer_picker_portrait_top_margin_48", + "value": "48vp" + }, + { + "name": "timer_picker_portrait_top_margin_19", + "value": "19vp" + }, + { + "name": "timer_clock_padding_17", + "value": "17vp" + }, + { + "name": "timer_clock_padding_20", + "value": "20vp" + }, + { + "name": "timer_clock_padding_19", + "value": "19vp" + }, + { + "name": "timer_stack_padding_19", + "value": "19vp" + }, + { + "name": "timer_stack_margin_11", + "value": "11.5vp" + }, + { + "name": "timer_stack_margin_42", + "value": "42.5vp" + }, + { + "name": "timer_stack_margin_28", + "value": "28vp" + }, + { + "name": "timer_text_width", + "value": "48vp" + }, + { + "name": "picker_line_height_col", + "value": "49vp" + }, + { + "name": "picker_line_height_row", + "value": "26vp" + }, + { + "name": "size36", + "value": "36sp" + }, + { + "name": "size28", + "value": "28sp" + }, + { + "name": "size42", + "value": "42sp" + }, + { + "name": "size38", + "value": "38sp" + }, + { + "name": "timer_bottom_borderRadius", + "value": "36vp" + }, + { + "name": "line_height_36", + "value": "36vp" + }, + { + "name": "line_height_18", + "value": "18vp" + }, + { + "name": "item_height_65", + "value": "65vp" + }, + { + "name": "item_height_52", + "value": "52vp" + }, + { + "name": "clock_margin_vertical", + "value": "32vp" + }, + { + "name": "timer_bottom_padding", + "value": "6vp" + }, + { + "name": "timer_bottom_width", + "value": "250vp" + }, + { + "name": "timer_bottom_padding_height", + "value": "60vp" + }, + { + "name": "stack_timer_picker_padding_bottom", + "value": "78sp" + }, + { + "name": "timer_bottom_margin", + "value": "74vp" + }, + { + "name": "timer_clock_top", + "value": "10vp" + }, + { + "name": "timer_bottom_padding_horizontal", + "value": "123vp" + }, + { + "name": "timer_buttonGroup_borderRadius", + "value": "36vp" + }, + { + "name": "timer_bottom_margin_bottom", + "value": "24vp" + }, + { + "name": "line_height", + "value": "500vp" + }, + { + "name": "timer_bottom_margin_bottom_h", + "value": "12vp" + }, + { + "name": "timer_button_margin_top", + "value": "32vp" + }, + { + "name": "timer_start_button_width", + "value": "65vp" + }, + { + "name": "timer_start_button_height", + "value": "65vp" + }, + { + "name": "timer_start_img_width", + "value": "40vp" + }, + { + "name": "timer_start_img_height", + "value": "40vp" + }, + { + "name": "timer_button_margin_right", + "value": "56vp" + },{ + "name": "timer_button_margin_left", + "value": "76vp" + },{ + "name": "timer_button_Visibility_width", + "value": "192vp" + }, + { + "name": "timer_button_width", + "value": "60vp" + }, + { + "name": "timer_flod_width", + "value": "252vp" + }, + { + "name": "margin_top_28", + "value": "28vp" + }, + { + "name": "build_text_height_108", + "value": "108vp" + }, + { + "name": "padding_bottom_4", + "value": "4vp" + }, + { + "name": "height_350", + "value": "350vp" + }, + { + "name": "size20", + "value": "20sp" + }, + { + "name": "line_height_56", + "value": "56vp" + }, + { + "name": "line_height_26", + "value": "26vp" + }, + { + "name": "line_height_27", + "value": "27vp" + }, + { + "name": "timer_PC_input_main_height", + "value": "150vp" + }, + { + "name": "timer_PC_label_size", + "value": "20sp" + }, + { + "name": "timer_PC_label_width", + "value": "113vp" + }, + { + "name": "timer_PC_input_height", + "value": "100vp" + }, + { + "name": "timer_PC_label_margin", + "value": "24vp" + }, + { + "name": "timer_PC_placeholder_size", + "value": "64sp" + }, + { + "name": "timer_PC_size", + "value": "68sp" + }, + { + "name": "timer_PC_input_border", + "value": "3vp" + }, + { + "name": "timer_PC_label_margin_top", + "value": "35vp" + }, + { + "name": "timer_PC_label_margin_left", + "value": "2vp" + }, + { + "name": "timer_PC_button_width", + "value": "114vp" + }, + { + "name": "timer_PC_button_radius", + "value": "57vp" + }, + { + "name": "timer_PC_button_round_width", + "value": "100vp" + }, + { + "name": "timer_PC_button_size1", + "value": "27sp" + }, + { + "name": "timer_PC_button_size2", + "value": "13sp" + }, + { + "name": "timer_PC_button_margin", + "value": "12vp" + }, + { + "name": "timer_PC_picker_min_width", + "value": "385vp" + }, + { + "name": "timer_PC_main_padding_left", + "value": "96vp" + }, + { + "name": "timer_PC_main_margin_left", + "value": "-229vp" + }, + { + "name": "timer_PC_main_padding_right", + "value": "56vp" + }, + { + "name": "timer_PC_time_picker_defalut_min_width", + "value": "1vp" + }, + { + "name": "timer_PC_analog_main_min_width", + "value": "300vp" + }, + { + "name": "timer_PC_text_line_height", + "value": "74vp" + }, + { + "name": "timer_PC_text_size", + "value": "56sp" + }, + { + "name": "timer_pc_left_max", + "value": "116vp" + }, + { + "name": "timer_Hints_tips", + "value": "23vp" + }, + { + "name": "timer_Hints_High", + "value": "97vp" + }, + { + "name": "rect_margin_left", + "value": "30px" + } + ] +} \ No newline at end of file diff --git a/feature/countdown/src/main/resources/base/element/string.json b/feature/timer/src/main/resources/dark/element/string.json similarity index 34% rename from feature/countdown/src/main/resources/base/element/string.json rename to feature/timer/src/main/resources/dark/element/string.json index 22859d2..9972a3c 100644 --- a/feature/countdown/src/main/resources/base/element/string.json +++ b/feature/timer/src/main/resources/dark/element/string.json @@ -1,16 +1,24 @@ { "string": [ { - "name": "hour", + "name": "timer_title_new", + "value": "Timer" + }, + { + "name": "timer_picker_hours", "value": "hour" }, { - "name": "minute", + "name": "timer_picker_minutes", "value": "minute" }, { - "name": "second", + "name": "timer_picker_seconds", "value": "second" + }, + { + "name": "timer", + "value": "计时器" } ] -} +} \ No newline at end of file diff --git a/feature/timer/src/main/resources/dark/media/img_clock_timer_bg.png b/feature/timer/src/main/resources/dark/media/img_clock_timer_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..0e2e10f92a333f164172eb7b420a24ae6f1a7958 GIT binary patch literal 225397 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

49~OC5g=#oBmQ`ib{X-b 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 literal 0 HcmV?d00001 diff --git a/feature/timer/src/main/resources/dark/profile/main_pages.json b/feature/timer/src/main/resources/dark/profile/main_pages.json new file mode 100644 index 0000000..feec276 --- /dev/null +++ b/feature/timer/src/main/resources/dark/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/index" + ] +} diff --git a/feature/timer/src/main/resources/en_US/element/string.json b/feature/timer/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000..75958d7 --- /dev/null +++ b/feature/timer/src/main/resources/en_US/element/string.json @@ -0,0 +1,52 @@ +{ + "string": [ + { + "name": "timer_title_new", + "value": "Timer" + }, + { + "name": "timer_picker_hours", + "value": "hour" + }, + { + "name": "timer_picker_minutes", + "value": "minute" + }, + { + "name": "timer_picker_seconds", + "value": "second" + }, + { + "name": "timer", + "value": "Timer" + }, + { + "name": "is_counting_down", + "value": "is_counting_down" + }, + { + "name": "countdown_pause", + "value": "countdown_pause" + }, + { + "name": "timer_timeout", + "value": "timer_timeout" + }, + { + "name": "slide_to_off_timer", + "value": "slide_to_off_timer" + }, + { + "name": "repeat_button_text", + "value": "repeat" + }, + { + "name": "time_to", + "value": "time_to" + }, + { + "name": "close", + "value": "Stop" + } + ] +} \ No newline at end of file diff --git a/feature/timer/src/main/resources/rawfile/pauseToPlay.json b/feature/timer/src/main/resources/rawfile/pauseToPlay.json new file mode 100644 index 0000000..63a9f4f --- /dev/null +++ b/feature/timer/src/main/resources/rawfile/pauseToPlay.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":53.6990661621094,"ip":0,"op":10.0000123209202,"w":300,"h":300,"nm":"开始启动按钮","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":3,"ty":4,"nm":"按钮切换","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[144.73,145.578,0],"ix":2,"l":2},"a":{"a":0,"k":[0.114,992.753,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0.881,"s":[{"i":[[0,-4.706],[0,0],[4.706,0],[0,4.706],[0,0],[-4.706,0]],"o":[[0,0],[0,4.706],[-4.706,0],[0,0],[0,-4.706],[4.706,0]],"v":[[8.52,-36.48],[8.52,36.48],[0,45],[-8.52,36.48],[-8.52,-36.48],[0,-45]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":2,"s":[{"i":[[-0.615,-3.98],[0.695,-1.379],[4.517,-1.289],[2.978,0.608],[0,0],[-5.451,-1.627]],"o":[[0.575,1.419],[-0.74,3.931],[-4.617,1.352],[0,0],[1.549,-1.598],[4.433,1.072]],"v":[[14.381,-23.85],[13.762,21.826],[4.884,34.716],[-11.143,38.466],[-11.143,-41.001],[4.408,-37.728]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":3,"s":[{"i":[[-0.85,-3.704],[0.961,-1.904],[3.733,-3.362],[4.114,-0.954],[0,0],[-4.641,-4.328]],"o":[[0.795,1.96],[-1.022,3.635],[-3.678,3.312],[0,0],[2.139,-0.413],[4.641,4.328]],"v":[[14.002,-18.213],[13.511,15.489],[4.497,27.699],[-11.714,36.128],[-10.964,-39.262],[4.589,-32.491]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":5,"s":[{"i":[[-1.153,-3.346],[1.304,-2.585],[4.52,-3.271],[5.585,-2.978],[0,0],[-2.914,-2.408]],"o":[[1.079,2.661],[-1.388,3.252],[-4.213,3.049],[0,0],[2.904,1.123],[2.914,2.408]],"v":[[16.975,-11.115],[16.4,5.517],[7.209,14.358],[-14.419,26.848],[-14.169,-32.418],[6.066,-23.572]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[-1.167,-3.329],[2.758,-4.118],[4.499,-3.213],[7.071,-2.043],[0,0],[-3.551,-2.322]],"o":[[1.694,2.591],[-1.337,1.995],[-4.244,3.027],[0,0],[3.955,1.653],[2.887,1.887]],"v":[[17.169,-9.829],[16.355,5.629],[6.97,14.688],[-16.341,27.13],[-15.841,-30.892],[5.164,-21.167]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[-1.192,-3.3],[1.348,-2.672],[4.461,-3.112],[5.772,-3.235],[0,0],[-3.856,-2.69]],"o":[[1.115,2.75],[-1.434,3.204],[-4.299,2.989],[0,0],[3.001,1.317],[3.256,2.364]],"v":[[17.51,-7.572],[16.276,5.827],[5.673,14.828],[-17.211,28.806],[-17.211,-31.212],[4.96,-17.634]],"c":true}]},{"t":7.99982652317694,"s":[{"i":[[-1.354,-3.084],[3.087,-2.565],[4.461,-3.112],[5.772,-3.235],[0,0],[-3.856,-2.69]],"o":[[1.354,3.084],[-1.434,3.204],[-4.299,2.989],[0,0],[2.824,1.848],[3.256,2.364]],"v":[[17.51,-7.572],[16.276,5.827],[5.673,14.828],[-16.711,29.181],[-17.211,-31.212],[4.96,-17.634]],"c":true}]}],"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[24.02,993.163],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0.881,"s":[{"i":[[0,-4.694],[0,0],[4.694,0],[0,4.694],[0,0],[-4.694,0]],"o":[[0,0],[0,4.694],[-4.694,0],[0,0],[0,-4.694],[4.694,0]],"v":[[8.5,-36.5],[8.5,36.5],[0,45],[-8.5,36.5],[-8.5,-36.5],[0,-45]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":2,"s":[{"i":[[-0.292,-4.556],[0,0],[4.884,0.043],[0.054,4.84],[0,0],[-4.644,-0.006]],"o":[[0,0],[-0.391,4.61],[-4.759,-0.033],[0,0],[0.07,-4.869],[4.837,0.011]],"v":[[10.626,-35.701],[10.626,35.915],[-0.28,45.198],[-11.728,36.5],[-11.744,-36.401],[-0.198,-45.049]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":3,"s":[{"i":[[-1.123,-4.162],[0,0],[5.421,0.167],[0.206,5.255],[0,0],[-4.503,-0.023]],"o":[[0,0],[-1.503,4.369],[-4.941,-0.125],[0,0],[0.269,-5.365],[5.244,0.041]],"v":[[14.666,-33.054],[14.416,33.877],[-1.076,45.76],[-12.534,36.5],[-12.598,-36.12],[-0.76,-45.19]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":5,"s":[{"i":[[-0.615,-7.689],[0.636,-9.342],[6.491,0.412],[0.509,6.081],[0,0],[-4.22,-0.058]],"o":[[0.615,7.689],[-0.636,9.342],[-5.304,-0.309],[0,0],[0.666,-6.353],[6.053,0.1]],"v":[[19.698,-30.031],[18.949,29.816],[-2.662,46.879],[-14.141,36.5],[-14.298,-37.811],[-1.879,-47.72]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[-3.404,-4.288],[0.222,-5.923],[6.818,0.487],[0.602,6.333],[0,0],[-4.134,-0.068]],"o":[[0.427,5.344],[-3.275,6.171],[-5.415,-0.365],[0,0],[0.787,-6.654],[6.3,0.119]],"v":[[23.967,-33.053],[23.841,31.081],[-3.146,47.221],[-14.632,36.5],[-14.817,-36.953],[-2.221,-47.119]],"c":true}]},{"t":7.99982652317694,"s":[{"i":[[-5.657,-3.046],[0.295,-4.336],[7.066,0.544],[0.672,6.524],[0,0],[-4.068,-0.076]],"o":[[0.285,3.569],[-5.264,3.321],[-5.499,-0.408],[0,0],[0.879,-6.883],[6.487,0.133]],"v":[[35.104,-27.745],[37.211,26.262],[-3.513,47.48],[-15.003,36.5],[-15.21,-36.304],[-2.48,-46.664]],"c":true}]}],"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-23.813,992.342],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 1","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":10.0000123209202,"st":-46.553240691139,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"形状图层 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[146.11,145.58,0],"ix":2,"l":2},"a":{"a":0,"k":[-7.278,-2.768,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"sy":[{"c":{"a":0,"k":[0.039215687662,0.349019616842,0.96862745285,1],"ix":2},"o":{"a":0,"k":20,"ix":3},"a":{"a":0,"k":90,"ix":5},"s":{"a":0,"k":23,"ix":8},"d":{"a":0,"k":13,"ix":6},"ch":{"a":0,"k":0,"ix":7},"bm":{"a":0,"k":1,"ix":1},"no":{"a":0,"k":0,"ix":9},"lc":{"a":0,"k":1,"ix":10},"ty":1,"nm":"投影"}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[260,260],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.349019607843,0.96862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.278,-2.768],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":54.0000665329691,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/feature/timer/src/main/resources/rawfile/playToPause.json b/feature/timer/src/main/resources/rawfile/playToPause.json new file mode 100644 index 0000000..8dc9b14 --- /dev/null +++ b/feature/timer/src/main/resources/rawfile/playToPause.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":53.6990661621094,"ip":0,"op":10.0000123209202,"w":300,"h":300,"nm":"开始启动按钮","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"按钮切换 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[144.73,145.578,0],"ix":2,"l":2},"a":{"a":0,"k":[0.114,992.753,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":1,"s":[{"i":[[-1.354,-3.084],[3.087,-2.565],[4.461,-3.112],[5.772,-3.235],[0,0],[-3.856,-2.69]],"o":[[1.354,3.084],[-1.434,3.204],[-4.299,2.989],[0,0],[2.824,1.848],[3.256,2.364]],"v":[[17.51,-7.572],[16.276,5.827],[5.673,14.828],[-16.711,29.181],[-17.211,-31.212],[4.96,-17.634]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[{"i":[[-1.192,-3.3],[1.348,-2.672],[4.461,-3.112],[5.772,-3.235],[0,0],[-3.856,-2.69]],"o":[[1.115,2.75],[-1.434,3.204],[-4.299,2.989],[0,0],[3.001,1.317],[3.256,2.364]],"v":[[17.51,-7.572],[16.276,5.827],[5.673,14.828],[-17.211,28.806],[-17.211,-31.212],[4.96,-17.634]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[-1.167,-3.329],[2.758,-4.118],[4.499,-3.213],[7.071,-2.043],[0,0],[-3.551,-2.322]],"o":[[1.694,2.591],[-1.337,1.995],[-4.244,3.027],[0,0],[3.955,1.654],[2.887,1.887]],"v":[[17.169,-9.829],[16.355,5.629],[6.97,14.688],[-16.341,27.13],[-15.841,-30.892],[5.164,-21.167]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[{"i":[[-1.153,-3.346],[1.304,-2.585],[4.52,-3.271],[5.585,-2.978],[0,0],[-2.914,-2.408]],"o":[[1.079,2.661],[-1.388,3.252],[-4.213,3.049],[0,0],[2.904,1.123],[2.914,2.408]],"v":[[16.975,-11.115],[16.4,5.517],[7.209,14.358],[-14.419,26.849],[-14.169,-32.418],[6.066,-23.572]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":6,"s":[{"i":[[-0.85,-3.704],[0.961,-1.904],[3.733,-3.362],[4.114,-0.954],[0,0],[-4.641,-4.328]],"o":[[0.795,1.96],[-1.022,3.635],[-3.678,3.312],[0,0],[2.139,-0.413],[4.641,4.328]],"v":[[14.002,-18.213],[13.511,15.489],[4.497,27.699],[-11.714,36.128],[-10.964,-39.262],[4.589,-32.491]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":7,"s":[{"i":[[-0.615,-3.98],[0.695,-1.379],[4.517,-1.289],[2.978,0.608],[0,0],[-5.451,-1.627]],"o":[[0.575,1.419],[-0.74,3.931],[-4.617,1.352],[0,0],[1.549,-1.598],[4.433,1.072]],"v":[[14.381,-23.85],[13.762,21.826],[4.884,34.716],[-11.143,38.466],[-11.143,-41.001],[4.408,-37.728]],"c":true}]},{"t":7.99982652317694,"s":[{"i":[[0,-4.706],[0,0],[4.706,0],[0,4.706],[0,0],[-4.706,0]],"o":[[0,0],[0,4.706],[-4.706,0],[0,0],[0,-4.706],[4.706,0]],"v":[[8.52,-36.48],[8.52,36.48],[0,45],[-8.52,36.48],[-8.52,-36.48],[0,-45]],"c":true}]}],"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[24.02,993.163],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":1,"s":[{"i":[[-5.657,-3.046],[0.295,-4.336],[7.066,0.544],[0.672,6.524],[0,0],[-4.068,-0.076]],"o":[[0.285,3.569],[-5.264,3.321],[-5.499,-0.408],[0,0],[0.879,-6.883],[6.487,0.133]],"v":[[35.104,-27.745],[37.211,26.262],[-3.513,47.48],[-15.003,36.5],[-15.21,-36.304],[-2.48,-46.664]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":2,"s":[{"i":[[-3.404,-4.288],[0.222,-5.923],[6.818,0.487],[0.602,6.333],[0,0],[-4.134,-0.068]],"o":[[0.427,5.344],[-3.275,6.171],[-5.415,-0.365],[0,0],[0.787,-6.654],[6.3,0.119]],"v":[[23.967,-33.054],[23.841,31.081],[-3.146,47.221],[-14.632,36.5],[-14.817,-36.953],[-2.221,-47.119]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[{"i":[[-0.615,-7.689],[0.636,-9.342],[6.491,0.412],[0.509,6.081],[0,0],[-4.22,-0.058]],"o":[[0.615,7.689],[-0.636,9.341],[-5.304,-0.309],[0,0],[0.666,-6.353],[6.053,0.1]],"v":[[19.698,-30.031],[18.949,29.817],[-2.662,46.879],[-14.141,36.5],[-14.298,-37.81],[-1.879,-47.72]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":6,"s":[{"i":[[-1.123,-4.162],[0,0],[5.421,0.167],[0.206,5.255],[0,0],[-4.503,-0.023]],"o":[[0,0],[-1.503,4.369],[-4.941,-0.125],[0,0],[0.269,-5.365],[5.244,0.041]],"v":[[14.666,-33.054],[14.416,33.877],[-1.076,45.76],[-12.534,36.5],[-12.598,-36.12],[-0.76,-45.19]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":7,"s":[{"i":[[-0.292,-4.556],[0,0],[4.884,0.043],[0.054,4.84],[0,0],[-4.644,-0.006]],"o":[[0,0],[-0.391,4.61],[-4.759,-0.033],[0,0],[0.07,-4.869],[4.837,0.011]],"v":[[10.626,-35.701],[10.626,35.915],[-0.28,45.198],[-11.728,36.5],[-11.744,-36.401],[-0.198,-45.049]],"c":true}]},{"t":7.99982652317694,"s":[{"i":[[0,-4.694],[0,0],[4.694,0],[0,4.694],[0,0],[-4.694,0]],"o":[[0,0],[0,4.694],[-4.694,0],[0,0],[0,-4.694],[4.694,0]],"v":[[8.5,-36.5],[8.5,36.5],[0,45],[-8.5,36.5],[-8.5,-36.5],[0,-45]],"c":true}]}],"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-23.813,992.342],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形 1","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":10.0000123209202,"st":-46.553240691139,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"形状图层 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[146.11,145.58,0],"ix":2,"l":2},"a":{"a":0,"k":[-7.278,-2.768,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"sy":[{"c":{"a":0,"k":[0.039215687662,0.349019616842,0.96862745285,1],"ix":2},"o":{"a":0,"k":20,"ix":3},"a":{"a":0,"k":90,"ix":5},"s":{"a":0,"k":23,"ix":8},"d":{"a":0,"k":13,"ix":6},"ch":{"a":0,"k":0,"ix":7},"bm":{"a":0,"k":1,"ix":1},"no":{"a":0,"k":0,"ix":9},"lc":{"a":0,"k":1,"ix":10},"ty":1,"nm":"投影"}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[260,260],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.349019607843,0.96862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.278,-2.768],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":54.0000665329691,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/feature/timer/src/main/resources/zh_CN/element/string.json b/feature/timer/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..37bf250 --- /dev/null +++ b/feature/timer/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,52 @@ +{ + "string": [ + { + "name": "timer_title_new", + "value": "定时器" + }, + { + "name": "timer_picker_hours", + "value": "时" + }, + { + "name": "timer_picker_minutes", + "value": "分" + }, + { + "name": "timer_picker_seconds", + "value": "秒" + }, + { + "name": "timer", + "value": "计时器" + }, + { + "name": "is_counting_down", + "value": "正在倒计时" + }, + { + "name": "countdown_pause", + "value": "倒计时暂停" + }, + { + "name": "timer_timeout", + "value": "计时器超时" + }, + { + "name": "slide_to_off_timer", + "value": "滑动关闭计时器" + }, + { + "name": "repeat_button_text", + "value": "重复" + }, + { + "name": "time_to", + "value": "时间到" + }, + { + "name": "close", + "value": "关闭" + } + ] +} \ No newline at end of file diff --git a/product/pc/.gitignore b/feature/worldclock/.gitignore similarity index 66% rename from product/pc/.gitignore rename to feature/worldclock/.gitignore index 5a6ba80..4f9a973 100644 --- a/product/pc/.gitignore +++ b/feature/worldclock/.gitignore @@ -1,4 +1,3 @@ /node_modules /.preview -/build -/.cxx \ No newline at end of file +/build \ No newline at end of file diff --git a/feature/worldclock/BuildProfile.ets b/feature/worldclock/BuildProfile.ets new file mode 100644 index 0000000..3a501e5 --- /dev/null +++ b/feature/worldclock/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/feature/worldclock/build-profile.json5 b/feature/worldclock/build-profile.json5 new file mode 100644 index 0000000..c2b5c40 --- /dev/null +++ b/feature/worldclock/build-profile.json5 @@ -0,0 +1,17 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + } + } + } + }, + ] +} \ No newline at end of file diff --git a/feature/worldclock/hvigorfile.ts b/feature/worldclock/hvigorfile.ts new file mode 100644 index 0000000..9d84ef8 --- /dev/null +++ b/feature/worldclock/hvigorfile.ts @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +module.exports = require('@ohos/hvigor-ohos-plugin').harTasks; diff --git a/feature/worldclock/index.ets b/feature/worldclock/index.ets new file mode 100644 index 0000000..57cc4bb --- /dev/null +++ b/feature/worldclock/index.ets @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { WorldClock } from './src/main/ets/pages/index'; + +export { EditCities } from './src/main/ets/pages/EditCities'; + +export { EditCitiesForPC } from './src/main/ets/pages/EditCitiesForPC'; + +export { WorldClockInfo } from './src/main/ets/manager/types'; + +export { WorldClockManager } from './src/main/ets/manager/index'; + +export { WorldClockUtil } from './src/main/ets/utils/WorldClockUtil'; + +export { CityClockCardUtil } from './src/main/ets/utils/CityClockCardUtil'; + +export { MANAGE_NEW_WORLD_CLOCK, MANAGE_EDIT_WORLD_CLOCK, MANAGE_EDIT_WORLD_CLOCK_PC } from './src/main/ets/utils/index'; + +export { AddCity } from './src/main/ets/pages/AddCity'; + +export { FaManagerCity } from './src/main/ets/pages/FaManagerCity'; + +export { AddWorldClock } from './src/main/ets/pages/AddWorldClock'; diff --git a/feature/worldclock/oh-package-lock.json5 b/feature/worldclock/oh-package-lock.json5 new file mode 100644 index 0000000..688e757 --- /dev/null +++ b/feature/worldclock/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@hmos/common@../../common": "@hmos/common@../../common", + "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har": "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har" + }, + "packages": { + "@hmos/common@../../common": { + "name": "@ohos/common", + "version": "1.0.0", + "resolved": "../../common", + "registryType": "local", + "dependencies": { + "@ohos/lottie": "file:./src/main/ets/libs/lottieArkTS.har" + } + }, + "@ohos/lottie@../../common/src/main/ets/libs/lottieArkTS.har": { + "name": "@ohos/lottie", + "version": "2.0.5", + "resolved": "../../common/src/main/ets/libs/lottieArkTS.har", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/feature/worldclock/oh-package.json5 b/feature/worldclock/oh-package.json5 new file mode 100644 index 0000000..447695a --- /dev/null +++ b/feature/worldclock/oh-package.json5 @@ -0,0 +1,14 @@ +{ + "license": "ISC", + "types": "", + "devDependencies": { + "@hmos/common": "file:../../common" + }, + "name": "@hmos/worldclock", + "description": "a npm package which contains arkUI2.0 page", + "main": "index.ets", + "repository": {}, + "version": "1.0.0", + "dynamicDependencies": {}, + "dependencies": {} +} diff --git a/feature/worldclock/src/main/ets/components/FaClockCard/index.ets b/feature/worldclock/src/main/ets/components/FaClockCard/index.ets new file mode 100644 index 0000000..0ce7d99 --- /dev/null +++ b/feature/worldclock/src/main/ets/components/FaClockCard/index.ets @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogUtil, Card } from '@hmos/common'; +import { FaCityInfo } from '../../manager/types' + +const RESPONSE_REGION_OFFSET: number = -12; +const FLEX_GROW_FILL_FULL: number = 1; +const CITY_COUNT_ONE: number = 1; +const TAG: string = 'FaClockCard'; + +interface DragParam { + selectedIndex: number; + insertIndex: number; +} + +@Component +export struct FaClockCard { + @Link faCityInfoList: FaCityInfo[]; + @State isDrag: boolean = false; + @State dragFaCityInfo: FaCityInfo = { + cityIndex: '', + cityName: '', + }; + private dragIndex?: number; + private isChanged: boolean = false; + + private dealWithDragResult(dragParam: DragParam): void { + if (!this.isDrag) { + return; + } + if (dragParam.insertIndex) { + if (dragParam.selectedIndex !== dragParam.insertIndex) { + this.isChanged = true; + } + } + + LogUtil.info(TAG, 'this.cityNameList:' + JSON.stringify(this.faCityInfoList)); + LogUtil.info(TAG, 'this.dragFaCityInfo:' + JSON.stringify(this.dragFaCityInfo)); + + if (dragParam.insertIndex) { + this.faCityInfoList.splice(dragParam.insertIndex, 0, this.dragFaCityInfo); + } + LogUtil.info(TAG, 'this.cityNameList:' + JSON.stringify(this.faCityInfoList)); + this.isDrag = false; + } + + @Builder + renderDivider(index?: number): void { + if (index) { + // Render divider above items except for the first item with index zero + Divider().color($r('sys.color.comp_divider')) + } + } + + @Builder + buildLeftPart(index?: number): void { + Row() { + if (this.faCityInfoList.length > CITY_COUNT_ONE) { + Image($r('app.media.ic_public_drag_handle')) + .height($r('app.float.appbar_icon_size')) + .width($r('app.float.appbar_icon_size')) + .margin({ right: $r('app.float.card_margin_start') }) + .draggable(false) + .responseRegion({ + x: RESPONSE_REGION_OFFSET, + y: RESPONSE_REGION_OFFSET, + width: '200%', + height: '200%', + }) + } + Text($r('app.string.fa_select_city')) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + .margin({ right: $r('app.float.card_tag_margin_end') }) + .fontWeight(FontWeight.Medium) + .width('50%') + } + } + + @Builder + buildRightPart(item: string): void { + Column() { + Row() { + Text(item) + .fontSize($r('sys.float.Body_M')) + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) + .width('80%') + .textAlign(TextAlign.End) + } + } + .alignItems(HorizontalAlign.End) + .flexGrow(FLEX_GROW_FILL_FULL) + } + + @Builder + cardContent(item: string, index?: number): void { + Column() { + Row() { + this.renderDivider(index); + } + .width('100%') + + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + this.buildLeftPart(index); + this.buildRightPart(item); + } + .width('100%') + .height($r('app.float.fa_card_height')) + } + .backgroundColor($r('sys.color.comp_background_list_card')) + } + + @Builder + pixelMapBuilder(item: string, index?: number): void { + Card({ needPressStyle: false }) { + this.cardContent(item, index); + } + } + + @Builder + buildCityClockSelectContent(): void { + if (this.faCityInfoList.length === CITY_COUNT_ONE) { + Card({ needPressStyle: false }) { + this.cardContent(this.faCityInfoList[0].cityName, 0); + } + } else { + Card({ needPressStyle: false }) { + List() { + ForEach(this.faCityInfoList, (item: FaCityInfo, index) => { + ListItem() { + this.cardContent(item.cityName, index); + } + .transition( + TransitionEffect.asymmetric( + TransitionEffect.IDENTITY, + TransitionEffect.scale({ x: 1, y: 1 }) + .animation({ curve: Curve.Friction, duration: 0 }) + .combine( + TransitionEffect.OPACITY.animation({ curve: Curve.Sharp, duration: 0 }) + ) + ) + ) + .editable(false) + .onDragStart(() => { + this.dragFaCityInfo = item; + if (this.dragIndex) { + this.dragIndex = index; + } + this.isDrag = true; + this.faCityInfoList = this.faCityInfoList.filter(faCityInfo => faCityInfo.cityName !== item.cityName); + return this.pixelMapBuilder(item.cityName, index); + }) + }) + } + .onDragLeave((event?: DragEvent, extraParams?: string) => { + if (event && extraParams) { + LogUtil.info(TAG, 'List onDragLeave, ' + extraParams + 'X:' + event.getX() + 'Y:' + event.getY()); + const dragParam: DragParam = JSON.parse(extraParams); + if (this.dragIndex) { + LogUtil.info(TAG, 'List onDragLeave dragIndex:' + this.dragIndex); + if (dragParam.insertIndex) { + dragParam.insertIndex = this.dragIndex; + } + } + dragParam && this.dealWithDragResult(dragParam); + } + }) + .onDrop((event?: DragEvent, extraParams?: string) => { + if (extraParams) { + if (event) { + LogUtil.info(TAG, 'List onDrop, ' + extraParams + 'X:' + event.getX() + 'Y:' + event.getY()); + } + const dragParam: DragParam = JSON.parse(extraParams); + dragParam && this.dealWithDragResult(dragParam); + } + }) + } + } + } + + build() { + Column() { + this.buildCityClockSelectContent() + } + .margin({ left: $r('app.float.fa_card_left_margin'), right: $r('app.float.fa_card_right_margin') }) + } +} \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/components/ListCardForPC/index.ets b/feature/worldclock/src/main/ets/components/ListCardForPC/index.ets new file mode 100644 index 0000000..0e9b2f0 --- /dev/null +++ b/feature/worldclock/src/main/ets/components/ListCardForPC/index.ets @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@Builder +function dummyFunction() { +}; + +interface ICardBgColorOptions { + defaultBgColor: ResourceColor; + hoverBgColor: ResourceColor; + pressBgColor: ResourceColor; +} + +/** + * Card Component + * + * @since 2022-07-16 + */ +@Component +export struct ListCardForPC { + @BuilderParam content: () => void = dummyFunction; + @State bgColorOptions: ICardBgColorOptions = { + defaultBgColor: $r('sys.color.comp_background_list_card'), + hoverBgColor: $r('sys.color.interactive_hover'), + pressBgColor: $r('sys.color.interactive_click'), + }; + @State bgColor: ResourceColor = this.bgColorOptions.defaultBgColor; + // Whether a content contains multiple items. default value is 1. + private isMultipleSubItems: boolean = false; + private needPressStyle: boolean = true; + private isHorizontalPaddingLeft: boolean = true; + bgHoverHandle = (isHover: boolean) => { + if (isHover) { + this.bgColor = this.bgColorOptions.hoverBgColor; + } else { + this.bgColor = this.bgColorOptions.defaultBgColor; + } + } + bgMouseHandle = (event: MouseEvent) => { + if ((event.action === MouseAction.Press) && (event.button === MouseButton.Left)) { + this.bgColor = this.bgColorOptions.pressBgColor; + } else if (event.action === MouseAction.Release) { + this.bgColor = this.bgColorOptions.hoverBgColor; + } + } + + build() { + Stack() { + Column() { + this.content() + } + .borderRadius($r('sys.float.corner_radius_level10')) + .padding({ + right: this.isMultipleSubItems ? 0 : $r('app.float.card_inner_padding_horizontal'), + left: this.isMultipleSubItems ? 0 : $r('app.float.card_inner_padding_horizontal'), + }) + } + .id('id_card') + + .borderRadius($r('sys.float.corner_radius_level10')) + .backgroundColor($r('sys.color.comp_background_list_card')) + .backgroundColor(this.bgColor) + .onHover(this.bgHoverHandle) + .onMouse(this.bgMouseHandle) + } +} \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/components/WorldCityCard/index.ets b/feature/worldclock/src/main/ets/components/WorldCityCard/index.ets new file mode 100644 index 0000000..aaf89ed --- /dev/null +++ b/feature/worldclock/src/main/ets/components/WorldCityCard/index.ets @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Card, CardSize, CommonGrid } from '@hmos/common'; +import { WorldClockInfo } from '../../manager/types'; +import { WorldClockManager } from '../../manager'; + +const RESPONSE_REGION_OFFSET = -12; + +/** + * World City Card + * + * @since 2022-09-26 + */ +@Component +export struct WorldCityCard { + @Prop isLarge: boolean = false + @Prop isGrid: boolean = true; + @Prop isPCView: boolean = false; + @State cityTimeZoneInfo?: string = ''; + @State hoverBackgroundClose: Resource = $r('app.color.color_hover_disabled'); + private cityName: string = ''; + private cityInfo: WorldClockInfo = {} as WorldClockInfo; + private onDelete: () => void = () => { + }; + + /** + * handleButtonHoverEvent + * + * @param isHover + */ + private handleImageHoverEvent(isHover: boolean): Resource { + if (isHover) { + return $r('app.color.color_hover'); + } else { + return $r('app.color.color_hover_disabled'); + } + }; + + async aboutToAppear() { + this.cityName = WorldClockManager.getDisplayCityName(this.cityInfo.cityIndex); + this.cityTimeZoneInfo = this.cityInfo.tag; + } + + @Builder + buildCardLeftPart() { + Column() { + Row() { + Column() { + Image($r('app.media.ic_public_drag_handle')) + .height($r('app.float.appbar_icon_size')) + .width($r('app.float.appbar_icon_size')) + .draggable(false) + .fillColor($r('sys.color.ohos_fa_icon_primary')) + .responseRegion({ + x: RESPONSE_REGION_OFFSET, + y: RESPONSE_REGION_OFFSET, + width: '200%', + height: '200%', + }) + } + .margin({ right: $r('app.float.button_icon_right_padding') }) + .padding($r('app.float.button_icon_hover_padding')) + .borderRadius($r('app.float.button_shadow_radius')) + + Column() { + Text(this.cityName) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Medium) + .alignSelf(ItemAlign.Start) + .lineHeight($r('app.float.card_title_line_height')) + Text(this.cityTimeZoneInfo) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + .alignSelf(ItemAlign.Start) + .margin({ top: $r('app.float.card_title_line_margin') }) + .lineHeight($r('app.float.card_content_line_height')) + } + } + } + .alignSelf(ItemAlign.Center) + } + + @Builder + buildCardRightPart() { + Column() { + Image($r('app.media.ic_public_close_filled')) + .height($r('app.float.appbar_icon_size')) + .width($r('app.float.appbar_icon_size')) + .draggable(false) + .fillColor($r('sys.color.font_secondary')) + .responseRegion({ x: RESPONSE_REGION_OFFSET, y: RESPONSE_REGION_OFFSET, width: '200%', height: '200%' }) + .focusable(true) + .onClick(() => this.onDelete()) + } + .id('id_closeCity_img') + .backgroundColor(this.hoverBackgroundClose) + .padding($r('app.float.card_inner_padding_horizontal')) + .borderRadius($r('app.float.button_shadow_radius')) + .margin({ + right: this.isPCView ? $r('app.float.card_inner_margin_horizontal') : '' + }) + .onHover((isHover: boolean, event: HoverEvent) => { + if (event && event.stopPropagation) { + event.stopPropagation(); + } + this.hoverBackgroundClose = this.handleImageHoverEvent(isHover); + }) + .onMouse((event: MouseEvent) => { + if (event && event.stopPropagation) { + event.stopPropagation(); + } + if ((event?.action === MouseAction.Press) && (event?.button === MouseButton.Left)) { + this.hoverBackgroundClose = $r('sys.color.interactive_click'); + } else if (event?.action === MouseAction.Hover) { + this.hoverBackgroundClose = $r('sys.color.interactive_hover'); + } else if (event?.action === MouseAction.Release) { + this.hoverBackgroundClose = $r('app.color.color_hover_disabled'); + } + }) + .alignSelf(ItemAlign.Center) + } + + @Builder + buildContent() { + Column() { + Card({ + openHoverStatus: this.isLarge, + openPressStatus: this.isLarge, + cardSize: this.isLarge ? CardSize.LARGER : CardSize.NORMAL, + needPressStyle: false, + isMultipleSubItems: true, + isHorizontalPaddingLeft: false + }) { + Flex({ justifyContent: FlexAlign.SpaceBetween }) { + this.buildCardLeftPart() + this.buildCardRightPart() + } + .height($r('app.float.card_content_height')) + } + } + } + + build() { + Column() { + if (this.isGrid) { + CommonGrid() { + this.buildContent() + } + } else { + this.buildContent() + } + } + } +} \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/components/WorldClockCard/index.ets b/feature/worldclock/src/main/ets/components/WorldClockCard/index.ets new file mode 100644 index 0000000..0808e1c --- /dev/null +++ b/feature/worldclock/src/main/ets/components/WorldClockCard/index.ets @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Card, CardSize, TimeUtil, ResourceManager, TIME_TAG_ID_LIST_IN_ZH, LogUtil } from '@hmos/common'; +import { WorldClockInfo } from '../../manager/types'; +import { WorldClockManager } from '../../manager'; + +/** + * World Clock Card + * + * @since 2022-09-26 + */ +@Component +export struct WorldClockCard { + @Prop @Watch('updateCityInfo') isChanged: boolean = false; + @Prop @Watch('updateCityInfoByHourSystem') isHourSystemChanged: boolean = false; + @Prop cancelDoubleCardMargin: boolean = false; + @State cityTimeZoneInfo?: string = ''; + @Prop isLarge: boolean = false + private cityInfo: WorldClockInfo = {} as WorldClockInfo; + private cityName: string = ''; + @State timeText: string = ''; + // Tag of time , Exp: am pm + @State private leftTag?: string = ''; + @State private rightTag?: string = ''; + private minute: number = -1; + + aboutToAppear() { + this.updateCityInfo(); + } + + onPageShow() { + this.updateCityInfo(); + } + + private async updateCityInfo(): Promise { + this.cityName = WorldClockManager.getDisplayCityName(this.cityInfo.cityIndex); + const currentMinute = new Date().getMinutes(); + if (currentMinute === this.minute) { + return; + } + await ResourceManager.preloadStringResources(TIME_TAG_ID_LIST_IN_ZH); + const timeObj = TimeUtil.getFormattedTimeZoneTimeObjByTimeZone(this.cityInfo.timezone); + if (timeObj.isTagLeft) { + this.leftTag = timeObj.tag; + this.rightTag = ''; + } else { + this.rightTag = timeObj.tag; + this.leftTag = ''; + } + this.timeText = timeObj.time as string; + this.minute = currentMinute; + this.cityTimeZoneInfo = this.cityInfo.tag; + } + + private async updateCityInfoByHourSystem(): Promise { + const timeObj = TimeUtil.getFormattedTimeZoneTimeObjByTimeZone(this.cityInfo.timezone); + if (timeObj.isTagLeft) { + this.leftTag = timeObj.tag; + this.rightTag = ''; + } else { + this.rightTag = timeObj.tag; + this.leftTag = ''; + } + this.timeText = timeObj.time as string; + this.cityTimeZoneInfo = this.cityInfo.tag; + } + + @Builder + buildCardLeftPart() { + Column() { + Row() { + Text(this.cityName) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Medium) + .lineHeight($r('app.float.card_title_line_height')) + .focusable(true) + } + .alignSelf(ItemAlign.Start) + + Row() { + Text(this.cityTimeZoneInfo) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + .margin({ top: $r('app.float.card_title_line_margin') }) + .lineHeight($r('app.float.card_content_line_height')) + } + .alignSelf(ItemAlign.Start) + } + .width('165%') + .alignSelf(ItemAlign.Center) + } + + @Builder + buildCardRightPart() { + Column() { + Flex({ justifyContent: FlexAlign.End, alignItems: ItemAlign.Baseline }) { + Text(this.leftTag) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + .margin({ + right: $r('app.float.text_margin_right'), + }) + Text(this.timeText) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Subtitle_M')) + .fontWeight(FontWeight.Medium) + Text(this.rightTag) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + .margin({ + right: $r('app.float.text_margin_right'), + }) + } + } + .margin({ top: this.cancelDoubleCardMargin ? $r('app.float.card_content_text_margin_top_11') : 0 }) + .alignSelf(this.cancelDoubleCardMargin ? ItemAlign.Start : ItemAlign.Center) + } + + @Styles + largeStyles() { + .padding({ + right: this.isLarge ? $r('app.float.card_padding_horizontal') : 0, + top: this.isLarge ? $r('app.float.card_inner_padding_10') : 0, + bottom: this.isLarge ? $r('app.float.card_inner_padding_11') : 0, + }) + .borderRadius(this.isLarge ? $r('sys.float.corner_radius_level10') : 0) + } + + @Builder + buildCardFoldsLeftPartPrivate() { + Column() { + Row() { + Text(this.cityName) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Subtitle_S')) + .fontWeight(FontWeight.Medium) + } + .alignSelf(ItemAlign.Start) + + Row() { + Text(this.cityTimeZoneInfo ? this.cityTimeZoneInfo.split(',')[0].toString() : '') + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + + } + .margin({ top: 4 }) + .alignSelf(ItemAlign.Start) + } + .margin({ top: $r('app.float.card_content_text_margin_top_11') }) + .width('100%') + .alignSelf(ItemAlign.Center) + } + + @Builder + buildCardFoldsLeftEndPrivate() { + Column() { + Text(this.cityTimeZoneInfo ? this.cityTimeZoneInfo.slice(3, this.cityTimeZoneInfo.length).toString() : '') + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + .alignSelf(ItemAlign.Start) + .margin(this.cityName.length > 6 ? { bottom: 12 } : { + top: $r('app.float.card_content_text_margin_top_24'), + bottom: 12 + }) + } + .width('100%') + .alignSelf(ItemAlign.Center) + } + + @Builder + buildContent() { + Column() { + Card({ + openHoverStatus: this.isLarge, + openPressStatus: this.isLarge, + cancelDoubleCardMargin: this.cancelDoubleCardMargin, + restrictAlarmCardMargin: true, + focusRemoveLRMargin: this.isLarge && true, + cardSize: this.isLarge ? CardSize.LARGER : CardSize.NORMAL + }) { + if (this.cancelDoubleCardMargin) { + Column() { + Flex({ justifyContent: FlexAlign.SpaceBetween }) { + this.buildCardFoldsLeftPartPrivate() + this.buildCardRightPart() + } + + this.buildCardFoldsLeftEndPrivate() + } + .constraintSize({ + minHeight: this.cancelDoubleCardMargin ? $r('app.float.card_double_height') : $r('app.float.card_content_height') + }) + .largeStyles() + } else { //这里 + Column() { + Flex({ justifyContent: FlexAlign.SpaceBetween }) { + this.buildCardLeftPart() + this.buildCardRightPart() + } + .constraintSize({ + minHeight: this.cancelDoubleCardMargin ? $r('app.float.card_double_height') : $r('app.float.card_content_height') + }) + .largeStyles() + } + } + } + + } + } + + build() { + Column() { + this.buildContent() + } + } +} diff --git a/feature/worldclock/src/main/ets/manager/TimeZoneTaskPoolManager.ets b/feature/worldclock/src/main/ets/manager/TimeZoneTaskPoolManager.ets new file mode 100644 index 0000000..c7793f5 --- /dev/null +++ b/feature/worldclock/src/main/ets/manager/TimeZoneTaskPoolManager.ets @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import I18n from '@ohos.i18n'; +import intl from '@ohos.intl'; +import { LogUtil } from '@hmos/common'; + +interface cityInfo { + settingSummary?: string, + zoneId: '', + cityId: '', + cityDisplayName: '', + offset: 0, + zoneDisplayName: '', + rawOffset: 0 +} + +const TAG = 'World Clock Add City' + +class TimeZoneTaskPoolManager { + public cityList: cityInfo[] = []; + public timeZoneCityItemArray: cityInfo[] = []; + + getTimeZoneInfo(locale: string, isFirstLoading: boolean, firstLoadNum: number): Array { + + + // if (this.timeZoneCityItemArray.length < 1) { + // this.timeZoneCityItemArray = I18n.SystemLocaleManager.getTimeZoneCityItemArray(); + // LogUtil.info(TAG + 'get timeZoneCityArray success') + // } + + let cityId: string; + let timeZone: I18n.TimeZone; + let rawOffSet: string = ''; + let is24hFormat: boolean = I18n.is24HourClock(); + let itemCount: number = 0; + + + // 获取底层时区信息 + // let timeZoneCityItemArray: cityInfo[] = I18n.SystemLocaleManager.getTimeZoneCityItemArray(); + LogUtil.info(TAG + 'get timeZoneCityArray success') + + + if (isFirstLoading) { + this.cityList = [] + } + for (let i = 0; i < this.timeZoneCityItemArray.length; i++) { + cityId = ''; + timeZone = I18n.TimeZone.getTimezoneFromCity(cityId); + let getTimeInfoFlag: boolean = (isFirstLoading && itemCount < firstLoadNum) || + (!isFirstLoading && itemCount >= firstLoadNum); + if (getTimeInfoFlag) { + let currentDateFormat: intl.DateTimeFormat = new intl.DateTimeFormat(locale, { month: 'long', day: 'numeric' }); + let dateFormat: intl.DateTimeFormat = new intl.DateTimeFormat(locale.toString(), { + month: 'long', + day: 'numeric', + timeZone: timeZone.getID() + }); + let timeFormat: intl.DateTimeFormat = new intl.DateTimeFormat(locale.toString(), { + timeStyle: 'short', + hour12: !is24hFormat, + timeZone: timeZone.getID() + }); + let obj: Date = new Date(); + let currentDate: string = currentDateFormat.format(obj); + let date: string = dateFormat.format(obj); + let time: string = timeFormat.format(obj); + if (date === currentDate) { + rawOffSet = time; + } else { + rawOffSet = date + ' ' + time; + } + } + + + if (isFirstLoading) { + this.cityList.push({ + cityId: this.timeZoneCityItemArray[i].cityId, + zoneId: this.timeZoneCityItemArray[i].zoneId, + cityDisplayName: this.timeZoneCityItemArray[i].cityDisplayName, + offset: this.timeZoneCityItemArray[i].offset, + zoneDisplayName: this.timeZoneCityItemArray[i].zoneDisplayName, + rawOffset: this.timeZoneCityItemArray[i].rawOffset, + settingSummary: itemCount < firstLoadNum ? rawOffSet : ' ' + }) + } else { + // 第二次只替换clockSummary讯息 + if (i > 49) { + this.cityList[i].settingSummary = rawOffSet; + } + } + itemCount++; + } + return this.cityList; + } +} + +export class BasicTimeZoneInfo { + public keyArray: string[] = []; + public titleArray: string[] = []; + public clockSummary: string[] = []; + public extra: string[] = []; +} + +export class ClockBaseMenu { + public key?: string; + public title?: string; + public summary?: string; + public extra?: string; +} + +export default new TimeZoneTaskPoolManager(); \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/manager/WorldClockManager.ets b/feature/worldclock/src/main/ets/manager/WorldClockManager.ets new file mode 100644 index 0000000..8cf4c0e --- /dev/null +++ b/feature/worldclock/src/main/ets/manager/WorldClockManager.ets @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dataRdb from '@ohos.data.relationalStore'; +import i18n from '@ohos.i18n'; +import Intl from '@ohos.intl'; +import { DatabaseManager, LogUtil, EventReportUtil, EventName } from '@hmos/common'; +import { WorldClockInfo } from '../manager/types'; +import { BusinessError } from '@ohos.base' +import rdb from '@ohos.data.relationalStore' +// import hiSysEvent from '@ohos.hiSysEvent'; + +const TAG = 'WorldClockManager'; + +const DATA_TABLE = 'WORLD_CLOCK'; + +const SQL_CREATE_TABLE = ` + CREATE TABLE IF NOT EXISTS ${DATA_TABLE} ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + SORT_ORDER INTEGER NOT NULL, + CITY_INDEX TEXT NOT NULL, + TIMEZONE TEXT NOT NULL, + CITY TEXT NOT NULL, + OFFSET INTEGER NOT NULL + ); +`; + +/** + * utils for world clock management + * + * @since 2022-10-19 + */ +class WorldClockManager extends DatabaseManager { + /** + * rdb store + * + * @return rdb store object + */ + async getRdbStore(): Promise { + return await super.getRdbStore(SQL_CREATE_TABLE); + } + + /** + * get all world clock + * + * @return world clock object array + */ + async getAllWorldClock(): Promise { + const rdbStore = await this.getRdbStore(); + let resultSet: rdb.ResultSet = {} as rdb.ResultSet; + try { + const predicates = new dataRdb.RdbPredicates(DATA_TABLE); + predicates.orderByAsc('SORT_ORDER'); + resultSet = await rdbStore.query(predicates); + return this.getWorldClockListFromResultSet(resultSet); + } catch (error) { + LogUtil.error(TAG, 'getAllWorldClocks failed: ', (error as BusinessError).message); + } finally { + resultSet && resultSet.close(); + } + return [] + } + + /** + * get world clock list from result set + * + * @param the result set returned from querying database + * @return world clock object array + */ + async getWorldClockListFromResultSet(resultSet: rdb.ResultSet): Promise { + const worldClockList: WorldClockInfo[] = []; + while (resultSet.goToNextRow()) { + const worldClockInfo: WorldClockInfo = { + id: resultSet.getString(resultSet.getColumnIndex('ID')), + sortOrder: resultSet.getLong(resultSet.getColumnIndex('SORT_ORDER')), + cityIndex: resultSet.getString(resultSet.getColumnIndex('CITY_INDEX')), + timezone: resultSet.getString(resultSet.getColumnIndex('TIMEZONE')), + city: resultSet.getString(resultSet.getColumnIndex('CITY')), + offset: resultSet.getLong(resultSet.getColumnIndex('OFFSET')), + } + worldClockList.push(worldClockInfo); + } + return worldClockList; + } + + /** + * add world clock + * + * @param world clock object + */ + async addWorldClock(worldClockInfo: WorldClockInfo): Promise { + const rdbStore = await this.getRdbStore(); + try { + rdbStore.beginTransaction(); + const ret = await rdbStore.insert(DATA_TABLE, + { + 'CITY_INDEX': worldClockInfo.cityIndex, + 'SORT_ORDER': worldClockInfo.sortOrder, + 'TIMEZONE': worldClockInfo.timezone, + 'CITY': worldClockInfo.city, + 'OFFSET': worldClockInfo.offset, + }); + LogUtil.info(TAG, 'addWorldClock success: ' + ret); + rdbStore.commit(); + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.WORLD_CLOCK_ADD_CITY) + } catch (error) { + LogUtil.error(TAG, 'addWorldClock failed: ', (error as BusinessError).message); + rdbStore.rollBack(); + } + } + + /** + * delete world clock + * + * @param world clock object + */ + async removeWorldClock(worldClockInfo: WorldClockInfo): Promise { + const rdbStore = await this.getRdbStore(); + try { + rdbStore.beginTransaction(); + const predicates = new dataRdb.RdbPredicates(DATA_TABLE); + if (worldClockInfo.id) { + predicates.equalTo('ID', worldClockInfo.id); + } + await rdbStore.delete(predicates); + rdbStore.commit(); + LogUtil.info(TAG, 'removeWorldClock successfully.'); + } catch (error) { + LogUtil.error(TAG, 'removeWorldClock failed: ', (error as BusinessError).message); + rdbStore.rollBack(); + } + } + + /** + * modify world clock data + * + * @param world clock object + */ + async updateWorldClock(worldClockInfo: WorldClockInfo): Promise { + const rdbStore = await this.getRdbStore(); + try { + rdbStore.beginTransaction(); + const predicates = new dataRdb.RdbPredicates(DATA_TABLE); + if (worldClockInfo.id) { + predicates.equalTo('ID', worldClockInfo?.id); + } + await rdbStore.update(this.getDbBucketFromWorldClock(worldClockInfo), predicates); + rdbStore.commit(); + LogUtil.info(TAG, 'updateWorldClockDatabase successfully.'); + } catch (error) { + LogUtil.error(TAG, 'updateWorldClockDatabase failed: ', (error as BusinessError).message); + rdbStore.rollBack(); + } + } + + /** + * transfer world clock object to value bucket needed by database + * + * @param world clock object + * @return value bucket needed by database + */ + getDbBucketFromWorldClock(worldClockInfo: WorldClockInfo): dataRdb.ValuesBucket { + return { + 'SORT_ORDER': worldClockInfo.sortOrder, + 'CITY_INDEX': worldClockInfo.cityIndex, + 'TIMEZONE': worldClockInfo.timezone, + 'CITY': worldClockInfo.city, + 'OFFSET': worldClockInfo.offset, + }; + } + + /** + * getDisplayCityName + * + * @param cityIndex cityIndex + */ + getDisplayCityName(cityIndex: string): string { + const locale = new Intl.Locale(i18n.System.getSystemLanguage()); + const city: string = i18n.TimeZone.getCityDisplayName(cityIndex, locale.toString()); + LogUtil.info("TAG", 'worldClockList index = ' + cityIndex + "; local = " + locale.toString() + "; city = " + city); + if (city === '' && cityIndex === 'Beijing') { + return '北京'; + } + if (city === '' && cityIndex === 'Paris') { + return '巴黎'; + } + if (city.includes('(')) { + return city.substr(0, city.indexOf('(')); + } else { + return city; + } + } +} + +export default new WorldClockManager(); \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/manager/index.ets b/feature/worldclock/src/main/ets/manager/index.ets new file mode 100644 index 0000000..58b761d --- /dev/null +++ b/feature/worldclock/src/main/ets/manager/index.ets @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import WorldClockManager from './WorldClockManager'; + +export { WorldClockManager }; \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/manager/types.ets b/feature/worldclock/src/main/ets/manager/types.ets new file mode 100644 index 0000000..e9e28b8 --- /dev/null +++ b/feature/worldclock/src/main/ets/manager/types.ets @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Model: World Clock Info + * + * @since 2022-09-26 + */ +export interface WorldClockInfo { + id?: string, + sortOrder: number, + cityIndex: string, + timezone: string, + city: string, + offset: number, + tag?: string, +} + +export interface FaCityInfo { + cityIndex: string, + cityName: string, +} + +export interface FaCityData { + cityName: string, + cityDate: string, + hoursWests ?: number, +} diff --git a/feature/worldclock/src/main/ets/pages/AddCity.ets b/feature/worldclock/src/main/ets/pages/AddCity.ets new file mode 100644 index 0000000..68509ed --- /dev/null +++ b/feature/worldclock/src/main/ets/pages/AddCity.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogUtil } from '@hmos/common'; + +const TAG = 'AddCity'; +const SETTINGS_BUNDLE_NAME = 'com.ohos.settings'; +const SETTINGS_ABILITY_NAME = 'TimeZoneAbility'; +const topMargin: number = 30; + +/** + * Page: Add world city + * + * @since 2023-01-06 + */ +@Component +export struct AddCity { + build() { + Row() { + Column() { + // AbilityComponent({ + // want: { + // bundleName: SETTINGS_BUNDLE_NAME, + // abilityName: SETTINGS_ABILITY_NAME, + // }, + // }) + // .onConnect(() => { + // LogUtil.info(TAG, 'AbilityComponent connect'); + // }) + // .onDisconnect(() => { + // LogUtil.info(TAG, 'AbilityComponent disconnect'); + // }) + } + .width('100%') + } + .height('100%') + } +} diff --git a/feature/worldclock/src/main/ets/pages/AddWorldClock.ets b/feature/worldclock/src/main/ets/pages/AddWorldClock.ets new file mode 100644 index 0000000..be121e6 --- /dev/null +++ b/feature/worldclock/src/main/ets/pages/AddWorldClock.ets @@ -0,0 +1,529 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * + * 世界时钟-添加城市 + * + * */ + +import { LogUtil, } from '@hmos/common'; +import I18n from '@ohos.i18n'; +import router from '@ohos.router'; +import TimeZoneTaskPoolManager from '../manager/TimeZoneTaskPoolManager'; +import taskpool from '@ohos.taskpool'; +import deviceInfo from '@ohos.deviceInfo' +import { MyDataSource, TimeZoneUtil, cityInfo, Obj } from './TimeZoneUtil' +import { TsUtilManager } from '@hmos/common' +import { WordClockTsUtil } from '../utils/WorldClockTsUtil'; + +const TAG = 'World Clock Add City' +const FIRST_LOAD_NUMBER: number = 50; //先计算50条 +const TABLE_PAD = 2560; + +@Concurrent +function getTimeZoneExtensionInfoAsync(locale: string, isFirstLoading: boolean, firstLoadNum: number): Array { + return TimeZoneTaskPoolManager.getTimeZoneInfo(locale, isFirstLoading, firstLoadNum); +} + +@Component +export struct AddWorldClock { + @State pageTitle: Resource = $r('app.string.add_page_title'); + @State num: number = 1; + @State value: string[] = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', 'X', 'Y', 'Z']; + @State tabletList: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', 'X', 'Y', 'Z']; + @State objs: Obj = {}; + @State @Watch('selectTop') select: string = ''; + @State selectedItem: cityInfo = { + zoneId: '', + cityId: '', + cityDisplayName: '', + offset: 0, + zoneDisplayName: '', + rawOffset: 0 + } + @State isSelected: boolean = false; + @State confirmBackground: Resource = $r('sys.color.comp_background_tertiary'); + @State cityList: Array = [] + @State @Watch('change') scrollDistance?: number = -1; + @StorageProp('addCityWorldPortrait') isPortraitOrientation: boolean = true; + @State deviceType: string = deviceInfo.deviceType; + @State isPC: boolean = false + @State isHover: boolean = false; + @StorageProp('currentAbleScreen') foldState: number = -1 + flag: boolean = true; + flags: boolean = false; + isFirstLoading: boolean = true; + scroller: Scroller = new Scroller; + private data: MyDataSource = new MyDataSource(); + + @Styles + normalStateIconStyles() + { + .backgroundColor(Color.Transparent) + .borderRadius(0) + } + + @Styles + pressedSecondStateIconStyles() + { + .backgroundColor(null) + .borderRadius('8vp') + } + + @Styles + pressedStateIconStyles() + { + .backgroundColor($r('app.color.pressed_state_icon_background_color')) + } + + @Styles + normalSecondStateIconStyles() + { + .backgroundColor(Color.Transparent) + .borderRadius(0) + } + + private portraitLisener() { + LogUtil.error('isPortraitOrientation ' + (this.isPortraitOrientation)) + } + + private onTouchEvent(event: TouchEvent): void { + this.isSelected = true + if (event.type === TouchType.Down || event.type === TouchType.Up) { + this.isSelected = false; + } + } + + private getBackgroundColor(): ResourceColor { + if (this.isSelected) { + return $r('app.color.color_hover_shadow'); + } + return $r('app.color.transparent'); + } + + private updateHover(isHover: boolean): boolean { + return isHover; + } + + private onMouseEvent(event: MouseEvent): void { + if (event.button === MouseButton.Left && event.action === MouseAction.Press) { + this.isSelected = true; + } else if (event.button === MouseButton.Left && event.action === MouseAction.Release) { + this.isSelected = false; + } + } + + async aboutToAppear() { + LogUtil.info(TAG + 'add world city page ~') + this.deviceType = deviceInfo.deviceType + if (this.deviceType === '2in1') { + this.isPC = true + } + await this.loadTimeZoneCityList(); + } + + change(): void { + this.scroller?.scrollToIndex(this.scrollDistance); + this.scrollDistance = -1; + } + + selectTop() { + this.num = this.value.indexOf(this.select); + } + + computeTaskAsync() { + setTimeout(() => { // 这里使用setTimeout来实现异步延迟运行 + this.loadTimeZoneCityList(); + }, 15) + } + + async loadTimeZoneCityList(): Promise { + let locale: string = TimeZoneUtil.getSystemLanguage(); + // 第一次加载 只计算前50条的时间 + let task: taskpool.Task = new taskpool.Task(getTimeZoneExtensionInfoAsync, locale, true, FIRST_LOAD_NUMBER); + try { + this.cityList = await taskpool.execute(task, taskpool.Priority.MEDIUM) as Array; + this.cityList.forEach((item: cityInfo) => { + this.data.pushData(item) + }) + } catch (e) { + LogUtil.error(TAG, `catch exception when getTimeZoneExtensionInfoAsync [first loading], errmsg: ${JSON.stringify(e)}`); + } + if (this.cityList.length < 1) { + LogUtil.error(TAG, `get timeZoneInfoObject [first loading] failed`); + return; + } + LogUtil.info(TAG, `get timeZoneInfoObject ${JSON.stringify(this.cityList.length)}`); + + if (FIRST_LOAD_NUMBER < this.cityList.length) { + task = new taskpool.Task(getTimeZoneExtensionInfoAsync, locale, false, FIRST_LOAD_NUMBER); + try { + this.cityList = await taskpool.execute(task, taskpool.Priority.MEDIUM) as Array; + this.data.refreshData(this.cityList) + } catch (e) { + LogUtil.error(TAG, `catch exception when getTimeZoneExtensionInfoAsync [second loading], errmsg: ${JSON.stringify(e)}`); + } + if (this.cityList.length < 1) { + LogUtil.error(TAG, `get timeZoneInfoObject [first loading] failed`); + return; + } + } + } + + indexerSelect(index: number) { + if (this.value[index] == '#') { + this.scrollDistance = 0; + setTimeout(() => { + this.select = '#'; + }, 50) + } else if (this.value[index] === 'A') { + this.scrollDistance = 0; + } else if (this.value[index] === 'Z') { + this.scrollDistance = this.objs?.Z as number; + setTimeout(() => { + this.select = 'Z'; + }, 50) + } else { + this.tabletList.forEach((tablet) => { + if (this.value[index] === tablet) { + this.scrollDistance = WordClockTsUtil.transIndexerSelect(this.objs, tablet) + } + }) + } + } + + handleScroll(start: number) { + let pinyin = I18n.Transliterator.getInstance('Han-Latin'); + let str = this.cityList[start].cityDisplayName; + let res = pinyin.transform(str as string); + let pinyins = I18n.Transliterator.getInstance('Latin-ASCII'); + let res1 = ''; + let obj: Obj = {}; + if (this.flag) { + this.flag = false; + for (let i = 0; i < this.cityList.length; i++) { + let k = ''; + if (this.cityList[i].cityDisplayName === '长春 (中国)' || this.cityList[i].cityDisplayName === '长沙 (中国)') { + k = 'C'; + } else { + let head = pinyin.transform(this.cityList[i].cityDisplayName as string); + let heads = pinyins.transform(head); + k = heads[0].toLocaleUpperCase(); + } + this.getTabletDistance(k, obj, i) + } + this.objs = obj; + } + if (str === '长春 (中国)' || str === '长沙 (中国)') { + this.select = 'C'; + } else { + if (this.flags) { + this.select = res1[0].toLocaleUpperCase(); + } else { + this.flags = true; + } + } + } + + handleSelect(city: cityInfo): void { + if (!city) { + return + } + // LogUtil.info(TAG, 'select city is ' + city.cityDisplayName + '=>' + JSON.stringify(city)) + // AppStorage.SetOrCreate('cityId', city.cityId); + router.back() + } + + getTabletDistance(k: string, obj: Obj, i: number): void { + for (const tablet of this.tabletList) { + if (k === tablet && !TsUtilManager.getItsProperty(obj, tablet)) { + WordClockTsUtil.transHandleScroll(tablet, k, obj, i) + } + } + } + + @Builder + titleBar() { + Row() { + Column() { + Stack({ alignContent: Alignment.Center }) { + Image($r('app.media.ic_back_night')) + .fillColor($r('sys.color.comp_foreground_primary')) + .width($r('app.float.dialog_padding_horizontal')) + .height($r('app.float.dialog_padding_horizontal')) + .draggable(false); + } + .width($r('app.float.header_textHeight')) + .height($r('app.float.header_textHeight')) + .hoverEffect(HoverEffect.Highlight) + .margin({ right: $r('sys.float.padding_level4') }) + .flexGrow(0) + .flexShrink(0) + .align(Alignment.Center) + .onClick(() => { + router.back(); + }); + } + + Column() { + Text(this.pageTitle) + .fontSize(20) + .fontWeight(FontWeight.Bold) + } + .align(Alignment.Start) + } + .width('100%') + .height($r('app.float.add_city_title_height')) + .padding({ + left: $r('sys.float.padding_level4'), + right: $r('sys.float.padding_level4'), + }) + .justifyContent(FlexAlign.Start) + .margin({ + bottom: $r('app.float.world_edit_bottom_margin'), + }) + } + + @Builder + LazyForEachContent() { + LazyForEach(this.data, (item: cityInfo) => { + ListItem() { + Column() { + Text(item.cityDisplayName) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Medium) + .width('100%') + .margin({ bottom: '2vp' }) + Text(item.settingSummary) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + .margin({ + top: $r('app.float.card_title_line_margin') + }) + .width('100%') + Divider().margin({ top: 10 }) + } + .onTouch((event?: TouchEvent): void => { + event && this.onTouchEvent(event); + }) + .onHover((isHover) => { + isHover = isHover ? true : false; + this.isHover = this.updateHover(isHover); + }) + .onMouse((event?: MouseEvent): void => { + event && this.onMouseEvent(event); + }) + .onClick(() => { + this.handleSelect(item) + }) + .borderRadius($r('app.float.swipe_image_height_and_width')) + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Start) + .hoverEffect(HoverEffect.Highlight) + .padding({ + top: $r('app.float.card_content_text_margin_top_11'), + left: $r('app.float.card_content_text_margin_top_8'), + right: $r('app.float.card_content_text_margin_top_8') + }) + } + .width('100%') + .borderRadius($r('app.float.swipe_image_height_and_width')) + }, (item: cityInfo) => { + return `${item.cityDisplayName?.toString()} + ${item.settingSummary?.toString()}`; + }) + } + + @Builder + WhiteCardForPC() { + Column() { + List({ initialIndex: 0, scroller: this.scroller }) { + this.LazyForEachContent() + } + .cachedCount(5) + .width('100%') + .height('100%') + .padding($r('app.float.white_card_padding')) + .scrollBar(BarState.Off) + .onScrollIndex((start: number, end: number) => { + this.handleScroll(start) + }) + + } + .backgroundColor($r('sys.color.comp_background_list_card')) + .height('90%') + .borderRadius(20) + .margin({ + left: $r('app.float.appbar_icon_margin'), + right: $r('sys.float.padding_level12') + }) + } + + @Builder + WhiteCard() { + Column() { + List({ initialIndex: 0, scroller: this.scroller }) { + LazyForEach(this.data, (item: cityInfo) => { + ListItem() { + Column() { + Text(item.cityDisplayName) + .fontColor($r('sys.color.ohos_id_color_text_primary')) + .fontSize($r('sys.float.ohos_id_text_size_body1')) + .fontWeight(FontWeight.Medium) + .width('100%') + .margin({ bottom: '2vp' }) + Text(item.settingSummary) + .fontColor($r('sys.color.ohos_id_color_text_secondary')) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontWeight(FontWeight.Regular) + .margin({ + top: $r('app.float.card_title_line_margin') + }) + .width('100%') + Divider().margin({ top: 10 }) + } + .onTouch((event?: TouchEvent): void => { + if (event) { + this.onTouchEvent(event); + } + }) + .onHover((isHover) => { + isHover = isHover ? true : false; + this.isHover = this.updateHover(isHover); + }) + .onMouse((event?: MouseEvent): void => { + if (event) { + this.onMouseEvent(event); + } + }) + .onClick(() => { + LogUtil.info(TAG, 'select city is ' + item.cityDisplayName + '=>' + JSON.stringify(item)) + AppStorage.SetOrCreate('cityId', item.cityId); + router.back() + }) + .borderRadius($r('app.float.swipe_image_height_and_width')) + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Start) + .backgroundColor(this.getBackgroundColor()) + .hoverEffect(HoverEffect.Highlight) + .stateStyles({ + normal: this.normalStateIconStyles, + pressed: this.pressedStateIconStyles, + }) + .padding({ + top: $r('app.float.card_content_text_margin_top_11'), + left: $r('app.float.card_content_text_margin_top_8'), + right: $r('app.float.card_content_text_margin_top_8') + }) + } + .width('100%') + .borderRadius($r('app.float.swipe_image_height_and_width')) + + }, (item: cityInfo) => { + return `${item.cityDisplayName?.toString()} + ${item.settingSummary?.toString()}`; + }) + } + .cachedCount(5) + .width('100%') + .height('100%') + .padding($r('app.float.white_card_padding')) + .scrollBar(BarState.Off) + .onScrollIndex((start: number, end: number) => { + this.handleScroll(start) + }) + + } + .backgroundColor($r('sys.color.comp_background_list_card')) + .height('90%') + .borderRadius(20) + .margin({ + left: $r('app.float.appbar_icon_margin'), + right: $r('sys.float.padding_level12') + }) + } + + @Builder + IndexBar() { + Column() { + AlphabetIndexer({ arrayValue: this.value, selected: this.num }) + .popupPosition({ + x: 60, + y: 196 + }) + .alignSelf(ItemAlign.Start) + .selectedColor($r('sys.color.font_emphasize'))// 选中项文本颜色 + .popupColor($r('sys.color.font_emphasize'))// 弹出框文本颜色 + .selectedBackgroundColor($r('sys.color.comp_emphasize_secondary'))// 选中项背景颜色 + .popupBackground(0xFFFFFFFF)// 弹出框背景颜色 + .usingPopup(true)// 是否显示弹出框 + .selectedFont({ size: $r('sys.float.Body_S'), weight: FontWeight.Medium })// 选中项字体样式 + .font({ size: $r('sys.float.Body_S'), weight: FontWeight.Medium }) + .popupFont({ size: $r('sys.float.Subtitle_M'), weight: FontWeight.Medium })// 弹出框内容的字体样式 + .itemSize(16)// 每一项的尺寸大小 + .alignStyle(IndexerAlign.Right)// 弹出框在索引条左侧弹出 + .onSelect((index: number) => { + this.indexerSelect(index) + }) + .onRequestPopupData((index: number) => { + return []; + }) + } + .width($r('sys.float.padding_level12')) + + } + + build() { + if (!this.isPC) { + Column() { + this.titleBar() + Row() { + Stack({ alignContent: Alignment.TopEnd }) { + this.WhiteCard() + this.IndexBar() + } + + } + .width('100%') + } + .width('100%') + .height('100%') + + } else { + Column() { + this.titleBar() + Row() { + Stack({ alignContent: Alignment.TopEnd }) { + this.WhiteCardForPC() + this.IndexBar() + } + + } + .width('100%') + } + .width('60%') + .height('100%') + } + + } +} + diff --git a/feature/worldclock/src/main/ets/pages/EditCities.ets b/feature/worldclock/src/main/ets/pages/EditCities.ets new file mode 100644 index 0000000..e077358 --- /dev/null +++ b/feature/worldclock/src/main/ets/pages/EditCities.ets @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import router from '@ohos.router'; +import resmgr from '@ohos.resourceManager'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import { + EventName, + EventReportUtil, + GlobalContext, + LogUtil, + TitleBar, + BreakPoint, +} from '@hmos/common'; +import { WorldCityCard } from '../components/WorldCityCard'; +import { WorldClockInfo } from '../manager/types'; +import { WorldClockManager } from '../manager'; +import { WorldClockUtil } from '../utils/WorldClockUtil'; +import curves from '@ohos.curves'; +import deviceInfo from '@ohos.deviceInfo' + +interface DragParam { + selectedIndex: number; + insertIndex: number; +} + +interface idInfo { + id: number | string +} + +@Extend(Image) +function appBarIconBtn() { + .width($r('app.float.appbar_icon_size')) + .height($r('app.float.appbar_icon_size')) + .responseRegion({ + width: $r('app.float.response_region_size'), + height: $r('app.float.response_region_size'), + }) +} + +const TAG = 'EditCities'; +const DEFAULT_SORT_ORDER = 9999; +const DRAG_LEAVE_TOP_INDEX = 0; +const DRAG_EXTRA_SPACE_RATE = '20%'; +const HEIGHT_WITH_DRAG_EXTRA_SPACE = '140%'; +const TITLE_FLEX_SHRINK = 1; // Keep the origin height of title in flex layout +const CONTENT_FLEX_GROW = 1; // Extend the content area as much as possible in flex layout +const topMargin: number = 30; + +/** + * Page: Edit world cities + * + * @since 2022-09-28 + */ +@Component +export struct EditCities { + @StorageProp('currentBreakpoint') currentBreakpoint: string = ''; + @State listPosition: number = 0; // 0代表滚动到List顶部,1代表中间值,2代表滚动到List底部。 + @State worldClockList: WorldClockInfo[] = []; + @State updateList: WorldClockInfo[] = []; + @State isDrag: boolean = false; + @State scaleIndex: string = ''; + @State dragItem: string = ''; + @State neighborIndex: string = ''; + @State deviceType: string = deviceInfo.deviceType; + @State neighborUpIndex: WorldClockInfo = { + sortOrder: DEFAULT_SORT_ORDER, + cityIndex: '', + timezone: '', + city: '', + offset: 0 + }; + @State neighborDownIndex: WorldClockInfo = { + sortOrder: DEFAULT_SORT_ORDER, + cityIndex: '', + timezone: '', + city: '', + offset: 0 + }; + @State offsetX: number = 0; + @State offsetY: number = 0; + @State neighborScale: number = -1; + @State dragCityInfo: WorldClockInfo = { + sortOrder: DEFAULT_SORT_ORDER, + cityIndex: '', + timezone: '', + city: '', + offset: 0 + }; + @State hoverBackgroundInfo: Resource = $r('app.color.color_hover_disabled'); + private ITEM_INTV: number = 80; + private dragRefOffset: number = 0; + private deleteList: Set = new Set(); + private deleteIdList: Set = new Set(); + private isChanged: boolean = false; + //private scrollUp: boolean = true; + private scroller: Scroller = new Scroller(); + + // private isDragToBottom: boolean = false; + + /** + * handleButtonHoverEvent + * + * @param isHover + */ + + scaleSelect(item: string): number { + if (this.scaleIndex == item) { + return 1.05; + } else if (this.neighborIndex == item) { + return this.neighborScale; + } else { + return 1; + } + } + + itemMove(index: number, newIndex: number): void { + let tmp = this.worldClockList.splice(index, 1); + this.worldClockList.splice(newIndex, 0, tmp[0]); + this.updateList = this.worldClockList.map((item, index) => { + let worldClockInfo: WorldClockInfo = { + sortOrder: index, + id: item.id, + cityIndex: item.cityIndex, + timezone: item.timezone, + city: item.city, + offset: item.offset, + tag: item.tag, + } + return worldClockInfo; + }); + } + + private handleImageHoverEvent(isHover: boolean): Resource { + if (isHover) { + return $r('app.color.color_hover'); + } else { + return $r('app.color.color_hover_disabled'); + } + }; + + async aboutToAppear() { + LogUtil.info(TAG, 'aboutToAppear: EditCities.'); + this.deviceType = deviceInfo.deviceType + this.isChanged = false; + let worldClockList = await WorldClockManager.getAllWorldClock(); + for (let worldClock of worldClockList) { + worldClock.tag = await WorldClockUtil.updateTimeZoneInfo(worldClock.offset, worldClock.timezone, + worldClock.cityIndex); + } + this.worldClockList = worldClockList; + } + + onDelete(clockInfo: WorldClockInfo) { + LogUtil.info(TAG, ' the delete worldClockId:' + clockInfo.id); + this.deleteList.add(clockInfo); + this.deleteIdList.add(clockInfo.id as string); + LogUtil.info(TAG, ' the deleteIdList size:' + this.deleteIdList.size); + this.worldClockList = this.worldClockList.filter(item =>!this.deleteIdList.has(item.id as string)); + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.WORLD_CLOCK_CITY_DEL) + } + + @Builder + pixelMapBuilder(cityInfo: WorldClockInfo) { + Column() { + WorldCityCard({ + cityInfo: cityInfo, + }) + } + } + + @Builder + buildConfirmOperation() { + Row() { + Image($r('app.media.ic_confirm')) + .id('id_cityConfirm_img') + .fillColor($r('sys.color.icon_primary')) + .appBarIconBtn() + .draggable(false) + .onClick(async () => { + let delList = Array.from(this.deleteList) + if (this.isChanged) { + for (let item of this.updateList) { + await WorldClockManager.updateWorldClock(item); + } + } + for (let item of delList) { + await WorldClockManager.removeWorldClock(item); + } + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.WORLD_CLOCK_EDIT_CONFIRM) + router.back(); + }) + } + .justifyContent(FlexAlign.Center) + // .padding($r('app.float.button_icon_hover_padding')) + .borderRadius($r('app.float.new_button_size')) + .backgroundColor($r('sys.color.comp_background_tertiary')) + .width($r('app.float.new_button_size')) + .height($r('app.float.new_button_size')) + .onHover((isHover) => { + this.hoverBackgroundInfo = this.handleImageHoverEvent(isHover as boolean); + }) + } + + private doCancel(): void { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.WORLD_CLOCK_EDIT_CANCEL) + router.back(); + } + + @Builder + buildPage() { + Flex({ direction: FlexDirection.Column }) { + Row() { + TitleBar({ + title: (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('sort_city_title_new'), + onIconClick: () => this.doCancel(), + iconResource: $r('app.media.ic_cancel'), + isHoverInfo: true, + operationArea: (): void => this.buildConfirmOperation(), + }) + } + .flexShrink(TITLE_FLEX_SHRINK) + + List({ initialIndex: 0, scroller: this.scroller }) { + ForEach(this.worldClockList, (item: WorldClockInfo) => { + ListItem() { + WorldCityCard({ + cityInfo: item, + onDelete: (): void => this.onDelete(item), + }) + .animation({ curve: Curve.Sharp, duration: 100 }) + } + .transition( + TransitionEffect.asymmetric( + TransitionEffect.IDENTITY, + TransitionEffect.scale({ x: 1, y: 1 }) + .animation({ curve: Curve.Friction, duration: 100 }) + .combine( + TransitionEffect.OPACITY.animation({ curve: Curve.Sharp, duration: 100 }) + ) + ) + ) + .scale({ x: this.scaleSelect(item.cityIndex), y: this.scaleSelect(item.cityIndex) }) + .zIndex(this.dragItem == item.cityIndex ? 1 : 0) + .translate(this.dragItem == item.cityIndex ? { y: this.offsetY } : { y: 0 }) + .editable(true) + .gesture( + // 以下组合手势为顺序识别,当长按手势事件未正常触发时则不会触发拖动手势事件 + GestureGroup(GestureMode.Sequence, + LongPressGesture({ repeat: true }) + .onAction((event?: GestureEvent) => { + animateTo({ curve: Curve.Friction, duration: 300 }, () => { + this.scaleIndex = item.cityIndex; + }) + }) + .onActionEnd(() => { + animateTo({ curve: Curve.Friction, duration: 300 }, () => { + this.scaleIndex = null; + }) + }), + PanGesture() + .onActionStart(() => { + this.dragItem = item.cityIndex; + this.dragRefOffset = 0; + }) + .onActionUpdate((event: GestureEvent) => { + this.offsetY = event.offsetY - this.dragRefOffset; + LogUtil.info(TAG, 'Y:' + this.offsetY.toString()); + this.neighborIndex = null; + let info = this.worldClockList.indexOf(item); + this.neighborUpIndex = this.worldClockList[info - 1]; + this.neighborDownIndex = this.worldClockList[info + 1]; + if (this.offsetY < 0 && this.neighborUpIndex != null) { + this.neighborIndex = this.neighborUpIndex.cityIndex; + this.neighborScale = 1 - (-this.offsetY) / (this.ITEM_INTV / 2) / 20; + LogUtil.info(TAG, 'neighborScaleup:' + this.neighborScale.toString()); + } else if (this.offsetY > 0 && this.neighborDownIndex != null) { + this.neighborIndex = this.neighborDownIndex.cityIndex; + this.neighborScale = 1 - (this.offsetY) / (this.ITEM_INTV / 2) / 20; + LogUtil.info(TAG, 'neighborScaledown:' + this.neighborScale.toString()); + } + if (this.offsetY > this.ITEM_INTV && this.neighborDownIndex != null) { + this.offsetY -= this.ITEM_INTV; + this.dragRefOffset += this.ITEM_INTV; + this.isChanged = true + this.itemMove(info, info + 1); + } else if (this.offsetY < -this.ITEM_INTV && this.neighborUpIndex != null) { + this.offsetY += this.ITEM_INTV; + this.dragRefOffset -= this.ITEM_INTV; + this.isChanged = true + this.itemMove(info, info - 1); + } + }) + .onActionEnd((event: GestureEvent) => { + animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { + this.dragItem = ''; + }) + animateTo({ + curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150 + }, () => { + this.scaleIndex = null; + }) + animateTo({ + curve: Curve.Sharp, duration: 300 + }, () => { + this.neighborIndex = null; + }) + }) + ) + .onCancel(() => { + animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { + this.dragItem = ''; + }) + animateTo({ + curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150 + }, () => { + this.scaleIndex = ''; + }) + }) + ) + }, (item: WorldClockInfo) => JSON.stringify(item)) + } + .padding(this.currentBreakpoint === BreakPoint.MD ? { + left: $r('app.float.word_padding_left'), + right: $r('app.float.word_padding_right') + } : {}) + .height('100%') + .backgroundColor($r('sys.color.ohos_id_background_secondary')) + .onScroll((offset: number) => { + this.dragRefOffset -= offset; + this.offsetY += offset; + }) + .onTouch((event: TouchEvent) => { + if (event.type == TouchType.Move) { + LogUtil.info(TAG, 'y:' + event.touches[0].y.toString()); + if (event.touches[0].y < 66) { + this.scroller.scrollTo({ + xOffset: 0, + yOffset: 0, + animation: { duration: 2000, curve: Curve.Linear } + }); + } else if (event.touches[0].y > 645) { + this.scroller.scrollTo({ + xOffset: 0, + yOffset: this.worldClockList.length * this.ITEM_INTV, + animation: { duration: 3000, curve: Curve.Linear } + }); + } + } + }) + } + + } + + build() { + if (this.deviceType == '2in1') { + NavDestination() { + this.buildPage() + } + .hideTitleBar(true) + .backgroundColor(Color.Transparent) + .height($r('app.float.nav_destination_height')) + .constraintSize({ + maxHeight: '90%' + }) + } else { + this.buildPage() + } + + } +} \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/pages/EditCitiesForPC.ets b/feature/worldclock/src/main/ets/pages/EditCitiesForPC.ets new file mode 100644 index 0000000..bc3dab5 --- /dev/null +++ b/feature/worldclock/src/main/ets/pages/EditCitiesForPC.ets @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import router from '@ohos.router'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import resmgr from '@ohos.resourceManager'; +import { + EventName, + EventReportUtil, + GlobalContext, + LogUtil, + TitleBar, + BreakPoint, + CommonGrid, + EVENT_ID_WORLDCLOCK_FORPC, + EVENT_ID_CHANGE_WORLD_CLOCK +} from '@hmos/common'; +import { WorldCityCard } from '../components/WorldCityCard'; +import { WorldClockInfo } from '../manager/types'; +import { WorldClockManager } from '../manager'; +import { WorldClockUtil } from '../utils/WorldClockUtil'; +import curves from '@ohos.curves'; +import deviceInfo from '@ohos.deviceInfo' +import emitter from '@ohos.events.emitter'; + +interface DragParam { + selectedIndex: number; + insertIndex: number; +} + +interface idInfo { + id: number | string +} + +@Extend(Image) +function appBarIconBtn() { + .width($r('app.float.appbar_icon_size')) + .height($r('app.float.appbar_icon_size')) + .responseRegion({ + width: $r('app.float.response_region_size'), + height: $r('app.float.response_region_size'), + }) +} + +const TAG = 'EditCities'; +const DEFAULT_SORT_ORDER = 9999; +const TITLE_FLEX_SHRINK = 1; // Keep the origin height of title in flex layout + + +/** + * Page: Edit world cities + * + * @since 2022-09-28 + */ +@Component +export struct EditCitiesForPC { + @StorageProp('currentBreakpoint') currentBreakpoint: string = ''; + @State listPosition: number = 0; // 0代表滚动到List顶部,1代表中间值,2代表滚动到List底部。 + @State worldClockList: WorldClockInfo[] = []; + @State updateList: WorldClockInfo[] = []; + @State isDrag: boolean = false; + @State scaleIndex: string = ''; + @State idString: string = '' + @State dragItem: string = ''; + @State neighborIndex: string = ''; + @State deviceType: string = deviceInfo.deviceType; + @Consume('pageInfos') pageInfos: NavPathStack; + @State neighborUpIndex: WorldClockInfo = { + sortOrder: DEFAULT_SORT_ORDER, + cityIndex: '', + timezone: '', + city: '', + offset: 0 + }; + @State neighborDownIndex: WorldClockInfo = { + sortOrder: DEFAULT_SORT_ORDER, + cityIndex: '', + timezone: '', + city: '', + offset: 0 + }; + @State offsetX: number = 0; + @State offsetY: number = 0; + @State neighborScale: number = -1; + @State dragCityInfo: WorldClockInfo = { + sortOrder: DEFAULT_SORT_ORDER, + cityIndex: '', + timezone: '', + city: '', + offset: 0 + }; + @State hoverBackgroundInfo: Resource = $r('sys.color.interactive_hover'); + @State listIndexCache: number = 0; + private ITEM_INTV: number = 80; + private dragRefOffset: number = 0; + private deleteList: Set = new Set(); + private deleteIdList: Set = new Set(); + private isChanged: boolean = false; + //private scrollUp: boolean = true; + private scroller: Scroller = new Scroller(); + + // private isDragToBottom: boolean = false; + + /** + * handleButtonHoverEvent + * + * @param isHover + */ + + scaleSelect(item: string): number { + if (this.scaleIndex == item) { + return 1.05; + } else if (this.neighborIndex == item) { + return this.neighborScale; + } else { + return 1; + } + } + + itemMove(index: number, newIndex: number): void { + let tmp = this.worldClockList.splice(index, 1); + this.worldClockList.splice(newIndex, 0, tmp[0]); + this.updateList = this.worldClockList.map((item, index) => { + let worldClockInfo: WorldClockInfo = { + sortOrder: index, + id: item.id, + cityIndex: item.cityIndex, + timezone: item.timezone, + city: item.city, + offset: item.offset, + tag: item.tag, + } + return worldClockInfo; + }); + this.idString = this.updateList.map(item => item.id).toString() + } + + async aboutToAppear() { + LogUtil.info(TAG, 'aboutToAppear: EditCities.'); + this.deviceType = deviceInfo.deviceType + this.isChanged = false; + let worldClockList = await WorldClockManager.getAllWorldClock(); + for (let worldClock of worldClockList) { + worldClock.tag = await WorldClockUtil.updateTimeZoneInfo(worldClock.offset, worldClock.timezone, + worldClock.cityIndex); + } + this.worldClockList = worldClockList; + this.idString = this.worldClockList.map(item => item.id).toString() + } + + onDelete(clockInfo: WorldClockInfo) { + LogUtil.info(TAG, ' the delete worldClockId:' + clockInfo.id); + this.deleteList.add(clockInfo); + this.deleteIdList.add(clockInfo.id as string); + LogUtil.info(TAG, ' the deleteIdList size:' + this.deleteIdList.size); + this.worldClockList = this.worldClockList.filter(item =>!this.deleteIdList.has(item.id as string)); + this.idString = this.worldClockList.map(item => item.id).toString() + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.WORLD_CLOCK_CITY_DEL) + } + + @Builder + pixelMapBuilder(cityInfo: WorldClockInfo) { + Column() { + WorldCityCard({ + cityInfo: cityInfo, + }) + } + } + + @Builder + buildConfirmOperation() { + Row() { + Image($r('app.media.ic_confirm')) + .fillColor($r('sys.color.icon_primary')) + .appBarIconBtn() + .draggable(false) + .focusable(true) + .onClick(async () => { + let delList = Array.from(this.deleteList) + if (this.isChanged) { + for (let item of this.updateList) { + await WorldClockManager.updateWorldClock(item); + } + } + for (let item of delList) { + await WorldClockManager.removeWorldClock(item); + } + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.WORLD_CLOCK_EDIT_CONFIRM) + if (this.deviceType == '2in1') { + emitter.emit({ + eventId: EVENT_ID_WORLDCLOCK_FORPC, + priority: emitter.EventPriority.IMMEDIATE, + }, { + data: { + idString: this.idString + } + }) + emitter.emit({ + eventId: EVENT_ID_CHANGE_WORLD_CLOCK, + priority: emitter.EventPriority.IMMEDIATE, + }) + this.pageInfos.pop(); + } else { + router.back() + } + }) + } + .justifyContent(FlexAlign.Center) + .borderRadius($r('app.float.dial_shadow_radius')) + .backgroundColor(this.hoverBackgroundInfo) + .width($r('app.float.pc_button_size')) + .height($r('app.float.pc_button_size')) + .onMouse((event) => { + if ((event?.action === MouseAction.Press) && (event?.button === MouseButton.Left)) { + this.hoverBackgroundInfo = $r('sys.color.interactive_click'); + } else if (event?.action === MouseAction.Release) { + this.hoverBackgroundInfo = $r('sys.color.interactive_hover'); + } + }) + } + + private doCancel(): void { + // EventReportUtil.write(EventName.WORLD_CLOCK_EDIT_CANCEL, hiAppEvent.EventType.BEHAVIOR, { + // 'result': EventResult.WORLD_CLOCK_EDIT_CANCEL }); + if (this.deviceType == '2in1') { + this.pageInfos.pop(); + } else { + router.back() + } + + } + + @Builder + buildPage() { + Flex({ direction: FlexDirection.Column }) { + Row() { + TitleBar({ + title: (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('sort_city_title_new'), + onIconClick: () => this.doCancel(), + iconResource: $r('app.media.ic_cancel'), + isHoverInfo: true, + isFocusAble: true, + operationArea: (): void => this.buildConfirmOperation(), + }) + } + .flexShrink(TITLE_FLEX_SHRINK) + + CommonGrid({ lg8: true }) { + List({ initialIndex: 0, scroller: this.scroller }) { + ForEach(this.worldClockList, (item: WorldClockInfo) => { + ListItem() { + WorldCityCard({ + cityInfo: item, + onDelete: (): void => this.onDelete(item), + isLarge: this.deviceType == '2in1' ? true : false, + isGrid: false, + isPCView: this.deviceType == '2in1' + }) + .animation({ curve: Curve.Sharp, duration: 100 }) + } + .transition( + TransitionEffect.asymmetric( + TransitionEffect.IDENTITY, + TransitionEffect.scale({ x: 1, y: 1 }) + .animation({ curve: Curve.Friction, duration: 100 }) + .combine( + TransitionEffect.OPACITY.animation({ curve: Curve.Sharp, duration: 100 }) + ) + ) + ) + .scale({ x: this.scaleSelect(item.cityIndex), y: this.scaleSelect(item.cityIndex) }) + .zIndex(this.dragItem == item.cityIndex ? 1 : 0) + .translate(this.dragItem == item.cityIndex ? { y: this.offsetY } : { y: 0 }) + .padding({ + left: $r('app.float.world_city_list_item_padding'), + right: $r('app.float.world_city_list_item_padding') + }) + .editable(true) + .gesture( + // 以下组合手势为顺序识别,当长按手势事件未正常触发时则不会触发拖动手势事件 + GestureGroup(GestureMode.Sequence, + LongPressGesture({ repeat: true }) + .onAction((event?: GestureEvent) => { + animateTo({ curve: Curve.Friction, duration: 300 }, () => { + this.scaleIndex = item.cityIndex; + }) + }) + .onActionEnd(() => { + animateTo({ curve: Curve.Friction, duration: 300 }, () => { + this.scaleIndex = null; + }) + }), + PanGesture() + .onActionStart(() => { + this.dragItem = item.cityIndex; + this.dragRefOffset = 0; + }) + .onActionUpdate((event: GestureEvent) => { + this.offsetY = event.offsetY - this.dragRefOffset; + LogUtil.info(TAG, 'Y:' + this.offsetY.toString()); + this.neighborIndex = null; + let info = this.worldClockList.indexOf(item); + this.neighborUpIndex = this.worldClockList[info - 1]; + this.neighborDownIndex = this.worldClockList[info + 1]; + if (this.offsetY < 0 && this.neighborUpIndex != null) { + this.neighborIndex = this.neighborUpIndex.cityIndex; + const scale = 1 - (-this.offsetY) / (this.ITEM_INTV / 2) / 20; + this.neighborScale = scale < 0.94 ? 0.94 : scale + LogUtil.info(TAG, 'neighborScaleup:' + this.neighborScale.toString()); + } else if (this.offsetY > 0 && this.neighborDownIndex != null) { + this.neighborIndex = this.neighborDownIndex.cityIndex; + const scale = 1 - (this.offsetY) / (this.ITEM_INTV / 2) / 20; + this.neighborScale = scale < 0.94 ? 0.94 : scale + LogUtil.info(TAG, 'neighborScaledown:' + this.neighborScale.toString()); + } + if (this.offsetY > this.ITEM_INTV && this.neighborDownIndex != null) { + this.offsetY -= this.ITEM_INTV; + this.dragRefOffset += this.ITEM_INTV; + this.isChanged = true + this.itemMove(info, info + 1); + } else if (this.offsetY < -this.ITEM_INTV && this.neighborUpIndex != null) { + this.offsetY += this.ITEM_INTV; + this.dragRefOffset -= this.ITEM_INTV; + this.isChanged = true + this.itemMove(info, info - 1); + } + }) + .onActionEnd((event: GestureEvent) => { + animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { + this.dragItem = ''; + }) + animateTo({ + curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150 + }, () => { + this.scaleIndex = null; + }) + animateTo({ + curve: Curve.Sharp, duration: 300 + }, () => { + this.neighborIndex = null; + }) + }) + ) + .onCancel(() => { + animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { + this.dragItem = ''; + }) + animateTo({ + curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150 + }, () => { + this.scaleIndex = ''; + }) + }) + ) + .onKeyEvent((e: KeyEvent) => { + if (e.keyText === 'KEYCODE_TAB' || e.keyText === 'KEYCODE_DPAD_DOWN' || e.keyText === 'KEYCODE_DPAD_UP') { + const listIndex = this.worldClockList.findIndex(i => item.id === i.id) + if (listIndex !== this.listIndexCache) { + this.listIndexCache = listIndex + // 48 视口区最后显示一半的列表向上滚动的距离,94.2每条item滚动高度 + this.scroller.scrollTo({ + xOffset: 0, + yOffset: listIndex > 5 ? (listIndex - 6) * 92 + 48 : 0, + animation: { duration: 100, curve: Curve.Linear } + }); + } + } + }) + }, (item: WorldClockInfo) => JSON.stringify(item)) + } + .padding(this.currentBreakpoint === BreakPoint.MD ? { + left: $r('app.float.word_padding_left'), + right: $r('app.float.word_padding_right') + } : {}) + .height('100%') + .backgroundColor($r('sys.color.background_secondary')) + .onScroll((offset: number) => { + this.dragRefOffset -= offset; + this.offsetY += offset; + }) + .onTouch((event: TouchEvent) => { + if (event.type == TouchType.Move) { + LogUtil.info(TAG, 'y:' + event.touches[0].y.toString()); + if (event.touches[0].y < 66) { + this.scroller.scrollTo({ + xOffset: 0, + yOffset: 0, + animation: { duration: 2000, curve: Curve.Linear } + }); + } else if (event.touches[0].y > 565) { + this.scroller.scrollTo({ + xOffset: 0, + yOffset: this.worldClockList.length * this.ITEM_INTV, + animation: { duration: 3000, curve: Curve.Linear } + }); + } + } + }) + } + } + .backgroundColor($r('sys.color.ohos_id_background_secondary')) + } + + build() { + if (this.deviceType == '2in1') { + NavDestination() { + this.buildPage() + } + .hideTitleBar(true) + .backgroundColor(Color.Transparent) + .margin({ bottom: $r('app.float.world_edit_bottom_margin') }) + .constraintSize({ + maxHeight: '90%' + }) + } else { + this.buildPage() + } + + } +} \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/pages/FaManagerCity.ets b/feature/worldclock/src/main/ets/pages/FaManagerCity.ets new file mode 100644 index 0000000..a17c8a7 --- /dev/null +++ b/feature/worldclock/src/main/ets/pages/FaManagerCity.ets @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import router from '@ohos.router'; +import { TitleBar, LogUtil, FormUtil, FormManager, FormInfo } from '@hmos/common'; +import i18n from '@ohos.i18n'; +import { FaClockCard } from '../components/FaClockCard' +import { FaCityInfo } from '../manager/types' +import { CityClockCardUtil } from '../utils/CityClockCardUtil' + + +const TAG: string = 'FaManagerCity'; +const CITY_CLOCK_CARD: string = 'CITY_CLOCK_CARD'; +const WORLD_CLOCK_CARD: string = 'WORLD_CLOCK_CARD'; + +@Component +export struct FaManagerCity { + @State titleName: Resource = {} as Resource; + @State faCityInfos: FaCityInfo[] = []; + formInfo: FormInfo = {} as FormInfo; + + async aboutToAppear(): Promise { + const requestFormId = AppStorage.Get('requestFormId'); + LogUtil.info(TAG, 'requestFormId:' + requestFormId); + if (requestFormId) { + this.formInfo = await FormManager.getFromInfoSpById(requestFormId!) as FormInfo; + LogUtil.info(TAG, 'formInfo:' + JSON.stringify(this.formInfo)); + AppStorage.Delete('requestFormId'); + } + if (this.formInfo.parameters && this.formInfo.formName === CITY_CLOCK_CARD) { + const cityDisplayName = i18n.TimeZone.getCityDisplayName(this.formInfo.parameters[0], + i18n.System.getSystemLanguage()); + const cityClockName = cityDisplayName.substr(0, cityDisplayName.indexOf('(')).trim(); + const faCityInfo: FaCityInfo = { + cityIndex: this.formInfo.parameters[0], + cityName: cityClockName, + } + this.faCityInfos.push(faCityInfo); + LogUtil.info(TAG, 'cityClockName:' + JSON.stringify(this.faCityInfos)); + this.titleName = $r('app.string.fa_single_clock_name'); + } + if (this.formInfo.formName === WORLD_CLOCK_CARD) { + this.titleName = $r('app.string.fa_three_clock_name'); + if (this.formInfo.parameters) { + this.formInfo.parameters.forEach((value, index) => { + const cityDisplayName = i18n.TimeZone.getCityDisplayName(value, i18n.System.getSystemLanguage()); + const cityClockName = cityDisplayName.substr(0, cityDisplayName.indexOf('(')).trim(); + const faCityInfo: FaCityInfo = { + cityIndex: value, + cityName: cityClockName, + } + this.faCityInfos.push(faCityInfo); + }); + } + LogUtil.info(TAG, 'worldClockNames:' + JSON.stringify(this.faCityInfos)); + } + + } + + private async doCancel(): Promise { + LogUtil.info(TAG, 'doCancel and faCityInfos:' + JSON.stringify(this.faCityInfos)) + let cityIndexs: string[] = []; + this.faCityInfos.forEach((item, index) => { + cityIndexs.push(item.cityIndex); + }); + const changedFormInfo: FormInfo = { + formId: this.formInfo.formId, + formName: this.formInfo.formName, + parameters: cityIndexs, + }; + await FormManager.saveFormToSp(changedFormInfo); + if (this.faCityInfos.length === 1) { + CityClockCardUtil.notifyCityClockCardUpdate(changedFormInfo.formId, cityIndexs[0]) + } else { + CityClockCardUtil.notifyWorldClockCardUpdate(changedFormInfo.formId, cityIndexs) + } + router.back(); + } + + @Builder + buildTitleBar(): void { + Row() { + TitleBar({ + title: '', + onIconClick: () => this.doCancel(), + iconResource: $r('app.media.ic_cancel'), + operationArea: () => { + }, + }) + } + } + + @Builder + buildTitle(): void { + Row() { + Text(this.titleName) + .fontSize($r('app.float.fa_card_title_font_size')) + .fontColor($r('app.color.fa_card_title_color')) + .textAlign(TextAlign.Center) + } + .margin({ top: $r('app.float.fa_card_title_margin_top') }) + .width('100%') + .justifyContent(FlexAlign.Center) + } + + @Builder + buildClockCard(): void { + Row() { + FaClockCard({ faCityInfoList: $faCityInfos }) + } + .margin({ top: $r('app.float.fa_card_margin_top') }) + } + + build() { + Column() { + this.buildTitleBar(); + this.buildTitle(); + Row() { + Text($r('app.string.app_name')) + .fontSize($r('app.float.fa_card_app_font_size')) + .fontColor($r('app.color.fa_card_title_color')) + } + .margin({ top: $r('app.float.fa_card_margin_top') }) + .width('100%') + .justifyContent(FlexAlign.Center) + + Row() { + Text($r('app.string.fa_select_city_title')) + .fontSize($r('app.float.fa_card_select_font_size')) + .fontColor($r('app.color.fa_card_title_color')) + .textAlign(TextAlign.Center) + } + .margin({ top: $r('app.float.fa_card_title_margin_top') }) + .padding({ left: $r('app.float.fa_card_title_margin_left') }) + .width('100%') + + this.buildClockCard(); + } + .height('100%') + } +} \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/pages/TimeZoneUtil.ets b/feature/worldclock/src/main/ets/pages/TimeZoneUtil.ets new file mode 100644 index 0000000..f4267aa --- /dev/null +++ b/feature/worldclock/src/main/ets/pages/TimeZoneUtil.ets @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import i18n from '@ohos.i18n'; +import intl from '@ohos.intl'; +import Settings from '@ohos.settings'; + +export interface cityInfo { + settingSummary?: string + zoneId: string, + cityId: string, + cityDisplayName: string, + offset: number, + zoneDisplayName: string, + rawOffset: number +} + +export interface Obj { + A?: number, + B?: number, + C?: number, + D?: number, + E?: number, + F?: number, + G?: number, + H?: number, + I?: number, + J?: number, + K?: number, + L?: number, + M?: number, + N?: number, + O?: number, + P?: number, + Q?: number, + R?: number, + S?: number, + T?: number, + U?: number, + V?: number, + W?: number, + X?: number, + Y?: number, + Z?: number, +} + +const RAW_OFFSET_HOUR_MOD: number = 3600000; +const RAW_OFFSET_MINUTE_MOD: number = 60000; + +/** + * 时区相关工具类 + * + * @since 2022-12-09 + */ +export class TimeZoneUtil { + private static readonly TAG: string = 'TimeZoneUtil : '; + private static readonly TIME_FORMAT_KEY: string = Settings.date.TIME_FORMAT; + + /** + * 获取系统支持的时区ID集合 + * + */ + static getAvailableTimeZoneIDs(): Array { + return i18n.TimeZone.getAvailableIDs(); + } + + /** + * 获取系统支持的时区城市ID集合 + * + */ + static getAvailableZoneCityIDs(): Array { + return i18n.TimeZone.getAvailableZoneCityIDs(); + } + + /** + * 获取某时区城市的本地化显示 + * + */ + static getCityLocalDisplayName(cityID: string): string { + let locale = new intl.Locale(i18n.System.getSystemLanguage()); + return i18n.TimeZone.getCityDisplayName(cityID, locale.toString()); + } + + static getSystemLanguage(): string { + let locale = new intl.Locale(i18n.System.getSystemLanguage()); + return locale.toString(); + } + + /** + * 获取某时区城市在locale下的本地化显示 + * + */ + static getCityDisplayName(cityID: string, locale: string): string { + return i18n.TimeZone.getCityDisplayName(cityID, locale); + } + + /** + * 创建某时区城市对应的时区对象 + * + */ + static getTimezoneFromCity(cityID: string): i18n.TimeZone { + return i18n.TimeZone.getTimezoneFromCity(cityID); + } +} + + +export class BasicDataSource implements IDataSource { + private listeners: DataChangeListener[] = []; + + public totalCount(): number { + return 0; + } + + public getData(index: number): cityInfo | null { + return null; + } + + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + this.listeners.push(listener); + } + } + + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + this.listeners.splice(pos, 1); + } + } + + notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded(); + }) + } + + notifyDataAdd(index: number): void { + this.listeners.forEach(listener => { + listener.onDataAdd(index); + }) + } + + notifyDataChange(index: number): void { + this.listeners.forEach(listener => { + listener.onDataChange(index); + }) + } + + notifyDataDelete(index: number): void { + this.listeners.forEach(listener => { + listener.onDataDelete(index); + }) + } + + notifyDataMove(from: number, to: number): void { + this.listeners.forEach(listener => { + listener.onDataMove(from, to); + }) + } +} + +export class MyDataSource extends BasicDataSource { + private dataArray: cityInfo[] = []; + + public totalCount(): number { + return this.dataArray.length; + } + + public getData(index: number): cityInfo | null { + return this.dataArray[index]; + } + + public addData(index: number, data: cityInfo): void { + this.dataArray.splice(index, 0, data); + this.notifyDataAdd(index); + } + + public refreshData(dataList: cityInfo[]) { + this.dataArray = dataList; + this.notifyDataReload(); + } + + public pushData(data: cityInfo): void { + this.dataArray.push(data); + this.notifyDataAdd(this.dataArray.length - 1); + } +} \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/pages/index.ets b/feature/worldclock/src/main/ets/pages/index.ets new file mode 100644 index 0000000..965a4e3 --- /dev/null +++ b/feature/worldclock/src/main/ets/pages/index.ets @@ -0,0 +1,902 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import commonEventManager from '@ohos.commonEventManager'; +import i18n from '@ohos.i18n'; +import Intl from '@ohos.intl'; +import router from '@ohos.router'; +import emitter from '@ohos.events.emitter'; +import { + AddButton, + Clock, + CommonGrid, + EVENT_ID_CHANGE_WORLD_CLOCK, + EventName, + EventReportUtil, + GlobalContext, + LogUtil, + TimeUtil, + BreakPoint, + ButtonSize, + EVENT_ID_WORLDCLOCK_FORPC +} from '@hmos/common'; +import { MANAGE_EDIT_WORLD_CLOCK_PC } from '../utils/index' +import { WorldClockCard } from '../components/WorldClockCard'; +import { WorldClockInfo } from '../manager/types'; +import { WorldClockManager } from '../manager'; +// import dataShare from '@ohos.data.dataShare'; +import settings from '@ohos.settings'; +import { BusinessError } from '@ohos.base'; +import deviceInfo from '@ohos.deviceInfo' +// import hiSysEvent from '@ohos.hiSysEvent'; + +interface ICustomMenuItem extends MenuItemOptions { + key: string; +} + +interface ItemInfo { + id: number, + sortOrder: number, + cityIndex: string, + timezone: string, + city: string, + offset: string, + tag?: string +} + +const TAG = 'WorldClock'; +const EDIT_CITIES_PAGE = 'pages/EditCities'; +const LONG_PRESS_DURATION = 500; +const TIME_SUBSCRIBE_INFO: CommonEventSubscribeInfo = { + events: [ + commonEventManager.Support.COMMON_EVENT_TIME_CHANGED, + commonEventManager.Support.COMMON_EVENT_TIMEZONE_CHANGED, + commonEventManager.Support.COMMON_EVENT_TIME_TICK, + ], +}; + +// This type is not exported from @ohos.commonEvent, so we get it by exported function. +type CommonEventSubscriber = commonEventManager.CommonEventSubscriber; +type CommonEventSubscribeInfo = commonEventManager.CommonEventSubscribeInfo; +const MAX_HEAD_ANGLE: number = 17; +const BODY_HEAD_CRITICAL_ANGLE: number = 12; +const MAX_BODY_ANGLE: number = 10; +const DELETE_SWIPE_HEAD_WIDTH: number = 56; +const DELETE_SWIPE_BODY_WIDTH: number = 123; +const ANGLE_RADIO: number = 10; + +/** + * Main page for world clock + * + * @since 2022-09-26 + */ +@Component +struct ButtonForAdd { + @State isLarge: Boolean = false + + @Styles + normalButtonStyle() { + .backgroundColor($r('sys.color.ohos_id_container_color_active')) + } + + @Styles + pressedButtonStyle() { + .backgroundColor($r('sys.color.interactive_click')) + } + + build() { + Button({ type: ButtonType.Circle, stateEffect: true }) { + Image($r('app.media.ic_add')) + .width($r('app.float.button_icon_size')) + .height($r('app.float.button_icon_size')) + .draggable(false) + } + .id('id_add_button') + .backgroundColor($r('app.color.add_button_color')) + .width($r('app.float.button_size')) + .height($r('app.float.button_size')) + .stateStyles({ + normal: this.normalButtonStyle, + pressed: this.pressedButtonStyle, + }) + } + + // .margin({ bottom: $r('app.float.appbar_icon_margin') }) + + +} + +@Component +export struct WorldClock { + @StorageProp('currentBreakpoint') currentBreakpoint: string = ''; + @StorageProp('currentAbleScreen') foldAbleScreen: number = 0; + @Link @Watch('refreshTimeInfo') worldClockList: WorldClockInfo[]; + @State isDateTypeClock: boolean = i18n.System.is24HourClock(); + @Consume('pageInfos') pageInfos: NavPathStack; + @StorageProp('setOrientaion') getOrientaion: number = 0; + @State itemContextMenuData: Array = [{ key: 'del', content: $r('app.string.alarm_tips_del') }, { + key: 'edit', + content: $r('app.string.alarm_tips_title') + }]; + @Prop isPortraitOrientation: boolean = true; //纵向 true 横向 false + @State isChanged: boolean = false; + @State isClockHide: boolean = false; + @State isHourSystemChanged: boolean = false; + @State clockDiameter: number = 0; + @State headAngle: number = 0; + @State bodyAngle: number = 0; + @State swipeScale: number = 1; + @State deleteWorldClockId?: string = ''; + @State deviceType: string = deviceInfo.deviceType; + @Prop isWorldClockVisible: boolean = true; + private timeSubscriber: CommonEventSubscriber = {} as CommonEventSubscriber; + @State private currentDateAndWeek: string = ''; + @State private timeZone: string = ''; + @State private keyEvent: string = ''; + @State private worldListLastId: string | undefined = ''; + @State listIndexCache: number = 0; + @State isAlarm: boolean = false; + private scrollerForList: Scroller = new Scroller(); + private scrollerForScroll: Scroller = new Scroller(); + // private dataShareHelper: dataShare.DataShareHelper = {} as dataShare.DataShareHelper; + private uri: string = 'datashare:///com.ohos.settingsdata/entry/settingsdata/SETTINGSDATA?Proxy=true&key=' + settings.date.TIME_FORMAT; + private isFoldCross: number = 2224; + private isFoldVertical: number = 2496; + private isTablePad: string = 'tablet' + + async aboutToAppear(): Promise { + LogUtil.info(TAG, 'about to appear'); + this.deviceType = deviceInfo.deviceType + await this.refreshTimeInfo() + this.subscribeEvents(); + this.subscribeDataBaseShare(); + this.isChanged = !this.isChanged; + emitter.on({ + eventId: EVENT_ID_WORLDCLOCK_FORPC, + }, (target) => { + let arr: WorldClockInfo[] = target.data?.idString !== '' ? this.getArrDiff(target.data?.idString.split(','), this.worldClockList) : []; + this.worldClockList = arr + }); + + } + + getArrDiff(arr1: number[], arr2: WorldClockInfo[]) { + let result: WorldClockInfo[] = []; + for (let i = 0; i < arr1.length; i++) { + let obj: WorldClockInfo = arr2.filter(item => item.id == arr1[i].toString())[0] + result.push(obj) + } + return result + } + + async aboutToDisappear(): Promise { + this.unSubscribeEvents(); + this.unSubscribeDataBaseShare(); + } + + /** + * refresh Time Info + */ + async refreshTimeInfo(): Promise { + const timeZoneId = i18n.getTimeZone().getID(); + const locale = new Intl.Locale(i18n.System.getSystemLanguage()); + this.timeZone = i18n.getTimeZone(timeZoneId).getDisplayName(locale.toString()); + this.currentDateAndWeek = TimeUtil.getCurrentFormattedDateWithWeek(); + this.worldListLastId = this.worldClockList.length > 0 ? this.worldClockList[this.worldClockList.length - 1].id : '' + } + + /** + * Subscribe events, used to refresh the page + */ + private async subscribeEvents(): Promise { + // Create a subscriber and subscribe to time-change event. + LogUtil.info(TAG, 'createSubscriber'); + this.timeSubscriber = await commonEventManager.createSubscriber(TIME_SUBSCRIBE_INFO); + if (!this.timeSubscriber) { + return; + } + commonEventManager.subscribe(this.timeSubscriber, (error, data) => { + if (error) { + LogUtil.error(TAG, 'time changed error:' + JSON.stringify(error)); + } else { + if (data && data.event.indexOf('usual.event.TIME_CHANGED') !== -1) { + LogUtil.info(TAG, 'data.event: ' + data.event) + this.isHourSystemChanged = !this.isHourSystemChanged; + this.refreshTimeInfo() + } else { + this.isChanged = !this.isChanged; + this.refreshTimeInfo() + } + } + }); + } + + /** + * Unsubscribe events + */ + private unSubscribeEvents(): void { + commonEventManager.unsubscribe(this.timeSubscriber, error => { + if (error) { + LogUtil.error(TAG, 'Unsubscribe to time-change event failed because: ', error.message); + return; + } + LogUtil.info(TAG, 'Unsubscribe to time-change event successfully!'); + }); + } + + /** + * Subscribe DataBaseShare, used to refresh the page + */ + private async subscribeDataBaseShare(): Promise { + LogUtil.info(TAG, 'subscribeDataBaseShare'); + // try { + // dataShare.createDataShareHelper((GlobalContext.getContext().getObject('clockContext') as Context), this.uri) + // .then((data: dataShare.DataShareHelper) => { + // LogUtil.info(TAG, 'createDataShareHelper succeed'); + // this.dataShareHelper = data; + // this.dataShareHelper.on('dataChange', this.uri, () => { + // this.isDateTypeClock = !this.isDateTypeClock + // this.isHourSystemChanged = !this.isHourSystemChanged; + // }); + // }) + // .catch((err: BusinessError) => { + // LogUtil.error(TAG, `createDataShareHelper error: code: ${err.code}, message: ${err.message} `); + // }); + // } catch (err) { + // LogUtil.error(TAG, `createDataShareHelper error: code: ${(err as BusinessError).code}, message: ${(err as BusinessError).message} `); + // } + } + + /** + * Unsubscribe DataBaseShare + */ + private unSubscribeDataBaseShare(): void { + // this.dataShareHelper.off('dataChange', this.uri, () => { + LogUtil.info(TAG, 'unSubscribeDataBaseShare'); + // }); + } + + @Builder + buildClock(): void { + Row() { + Clock({ + getOrientaion: this.getOrientaion, + isPortraitOrientation: this.isPortraitOrientation, + showBoth: this.deviceType == '2in1' ? true : false + }) + } + .transition(TransitionEffect.opacity(1)) + .justifyContent(FlexAlign.Center) + .width('100%') + .margin({ + top: this.deviceType == '2in1' ? '' : (this.isPortraitOrientation || this.foldAbleScreen === 1 ? $r('app.float.clock_shadow_above_space_32') : '10vp'), + bottom: this.deviceType == '2in1' ? '' : (this.isPortraitOrientation || this.foldAbleScreen === 1 ? $r('app.float.clock_margin_vertical') : $r('app.float.clock_landscape_no_height')), + }) + } + + @Builder + buildTimeInfo(): void { + Column() { + if (this.deviceType == '2in1') { + Row() { + if (this.worldClockList.length === 0) { + Text(this.timeZone) + .fontColor($r('sys.color.icon_secondary')) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + .margin({ right: 10 }) + Text(this.currentDateAndWeek) + .fontColor($r('sys.color.icon_secondary')) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + } else { + Column() { + Text(this.timeZone) + .fontColor($r('sys.color.icon_secondary')) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontWeight(FontWeight.Regular) + .margin({ right: 10, bottom: 5 }) + Text(this.currentDateAndWeek) + .fontColor($r('sys.color.icon_secondary')) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + } + } + } + } else { + Row() { + Text(this.timeZone) + .fontColor($r('sys.color.ohos_id_color_primary')) + .fontSize($r('app.float.text_20')) + .fontWeight(FontWeight.Medium) + } + .margin({ bottom: $r('app.float.timezone_margin_bottom') }) + + Row() { + Text(this.currentDateAndWeek) + .fontColor($r('sys.color.ohos_id_color_secondary')) + .fontSize($r('sys.float.ohos_id_text_size_body2')) + .fontWeight(FontWeight.Regular) + } + } + } + .alignItems(HorizontalAlign.Center) + .width('100%') + .padding({ + left: $r('sys.float.padding_level8'), + right: $r('sys.float.padding_level8'), + }) + .margin({ bottom: $r('app.float.date_info_margin_bottom') }) + } + + private async dealWorldClockDelete(worldClockInfo: WorldClockInfo): Promise { + LogUtil.info(TAG, 'start to dealWorldClockDelete id :' + worldClockInfo.id); + let delIndex = -1; + this.worldClockList.forEach((item: WorldClockInfo, index: number) => { + LogUtil.info(TAG, 'worldClockList-' + JSON.stringify(item)); + if (item.id === worldClockInfo.id) { + delIndex = index; + } + }) + LogUtil.info(TAG, 'dealWorldClockDelete for delIndex:' + delIndex); + if (delIndex !== -1) { + this.worldClockList.splice(delIndex, 1); + } + LogUtil.info(TAG, 'dealWorldClockDelete end'); + WorldClockManager.removeWorldClock(worldClockInfo).then((data) => { + emitter.emit({ + eventId: EVENT_ID_CHANGE_WORLD_CLOCK, + priority: emitter.EventPriority.IMMEDIATE, + }) + }); + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CITY_SWIPE_LEFT_DEL) + } + + @Styles + buttonStyle() { + .borderRadius($r('app.float.swipe_image_height_and_width')) + .height($r('app.float.swipe_button_height_and_width')) + .width($r('app.float.swipe_button_height_and_width')) + .margin($r('app.float.swipe_margin_size')) + } + + @Builder + renderSwipeAction(worldClockInfo: WorldClockInfo): void { + Row() { + Stack() { + Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Image($r('app.media.ic_delete_grey')) + .width($r('app.float.swipe_delete_head_width')) + .draggable(false) + .rotate({ + x: 0, + y: 0, + z: 1, + centerX: '100%', + centerY: '100%', + angle: this.headAngle, + }) + } + .width($r('app.float.swipe_button_height_and_width')) + .height($r('app.float.swipe_button_height_and_width')) + } + .buttonStyle() + .scale({ x: this.swipeScale, y: this.swipeScale }) + .backgroundColor($r('sys.color.ohos_id_color_warning')) + .onClick(() => { + setTimeout(() => { + if (worldClockInfo.id) { + this.deleteWorldClockId = worldClockInfo.id; + } + if (this.worldClockList.length > 3 && this.foldAbleScreen === 1) { + this.dealWorldClockDelete(worldClockInfo); + } else { + animateTo({ + duration: 350, + curve: Curve.Friction, + }, () => { + this.dealWorldClockDelete(worldClockInfo); + }) + } + }, 150) + }) + } + .id('id_deleteCity_img') + .padding(this.foldAbleScreen === 1 && this.worldClockList.length < 4 ? { + left: 0, + top: $r('app.float.swipe_margin_size'), + right: $r('app.float.swipe_padding_size'), + bottom: $r('app.float.swipe_margin_size') + } : $r('app.float.swipe_margin_size')) + .onAreaChange((oldValue: Area, newValue: Area) => { + let wid = newValue.width; + this.headAngle = (Number(wid) - DELETE_SWIPE_HEAD_WIDTH) / DELETE_SWIPE_HEAD_WIDTH * ANGLE_RADIO; + if (this.headAngle <= 0) { + this.headAngle = 0; + } + if (this.headAngle >= MAX_HEAD_ANGLE) { + this.headAngle = MAX_HEAD_ANGLE; + } + if (this.headAngle > BODY_HEAD_CRITICAL_ANGLE) { + this.swipeScale = 1.05; + this.bodyAngle = (Number(wid) - DELETE_SWIPE_BODY_WIDTH) / DELETE_SWIPE_BODY_WIDTH * ANGLE_RADIO; + } else { + this.bodyAngle = 0; + this.swipeScale = 1; + } + if (this.bodyAngle > MAX_BODY_ANGLE) { + this.bodyAngle = MAX_BODY_ANGLE; + this.swipeScale = 1.05; + } + this.bodyAngle = (-1) * this.bodyAngle; + }) + .justifyContent(FlexAlign.SpaceEvenly) + } + + pcRightControlMenuClickHandle(menuItem: ICustomMenuItem, worldItem: WorldClockInfo) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.ENTER_ALARM_EDIT_PAGE) + menuItem.key == 'del' && this.dealWorldClockDelete(worldItem); + menuItem.key == 'edit' && this.pageInfos.pushPath(new NavPathInfo(MANAGE_EDIT_WORLD_CLOCK_PC, 'unknow')); + } + + private onScrollParent(yOffset: number): void { + if (yOffset === 0 || yOffset > 0 && this.isClockHide || yOffset < 0 && !this.isClockHide) { + return; + } + const offset = this.scrollerForScroll.currentOffset(); + LogUtil.info(TAG, `yOffset=${yOffset} current=${offset.yOffset}`); + if (yOffset > 0) { + this.isClockHide = true; + } else { + this.isClockHide = false; + } + } + + private onScrollStopParent(): void { + const offset = this.scrollerForScroll.currentOffset(); + LogUtil.info(TAG, `onScrollStop current=${offset.yOffset} ${this.isClockHide}`); + if (this.isClockHide) { + this.scrollerForScroll.scrollEdge(Edge.End); + } else { + this.scrollerForScroll.scrollEdge(Edge.Start); + } + } + + @Builder + pcRightControlMenuBuilder(worldItem: WorldClockInfo) { + Menu() { + ForEach(this.itemContextMenuData, (item: ICustomMenuItem) => { + MenuItem({ content: item.content }) + .contentFont({ + size: $r('sys.float.Body_L') + }) + .onClick(() => this.pcRightControlMenuClickHandle(item, worldItem)) + .borderRadius($r('sys.float.corner_radius_level4')) + }, (item: MenuItemOptions, index: number) => String(index)) + } + .width('16.6%') + .shadow(ShadowStyle.OUTER_DEFAULT_MD) + } + + @Builder + listWold(): void { + ForEach(this.worldClockList, (item: WorldClockInfo) => { + ListItem() { + WorldClockCard({ + cityInfo: item, + isChanged: this.isChanged, + isHourSystemChanged: this.isHourSystemChanged, + isLarge: this.deviceType == '2in1' ? true : false, + cancelDoubleCardMargin: this.foldAbleScreen === 1 && this.worldClockList.length > 3 + }) + } + .transition( + TransitionEffect.asymmetric( + TransitionEffect.IDENTITY, + TransitionEffect.scale({ x: 1, y: 1 }) + .animation({ curve: Curve.Friction, duration: 0 }) + .combine( + TransitionEffect.OPACITY.animation({ curve: Curve.Sharp, duration: 0 }) + ) + ) + ) + .margin({ + bottom: (this.deviceType === '2in1' && item.id === this.worldListLastId) || this.worldClockList.length === 1 ? 0 : $r('app.float.card_list_margin') + }) + .clip(this.deviceType === '2in1' ? false : true) + .bindContextMenu(this.pcRightControlMenuBuilder(item), ResponseType.RightClick) + .zIndex(this.deleteWorldClockId === item.id ? 0 : 1) + .swipeAction( + { + edgeEffect: SwipeEdgeEffect.None, + end: { + builder: (): void => this.renderSwipeAction(item), + onAction: () => { + setTimeout(() => { + this.deleteWorldClockId = item.id; + if (this.worldClockList.length > 3 && this.foldAbleScreen === 1) { + this.dealWorldClockDelete(item); + } else { + animateTo({ + duration: 350, + curve: Curve.Friction, + }, () => { + this.dealWorldClockDelete(item); + }) + } + }, 350); + }, + onEnterActionArea: () => { + LogUtil.info(TAG, 'List Swipe EnterDeleteArea'); + }, + onExitActionArea: () => { + LogUtil.info(TAG, 'List Swipe ExitDeleteArea'); + } + }, + } + ) + + .editable(true) + .gesture(LongPressGesture({ duration: LONG_PRESS_DURATION }) + .onAction(() => { + if (this.deviceType !== '2in1') { + router.pushUrl({ url: EDIT_CITIES_PAGE }); + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.WORLD_CLOCK_LONG_PRESS_EDIT) + } + }) + ) + .onKeyEvent((e: KeyEvent) => { + this.keyEvent = e.keyText === 'KEYCODE_ENTER' || e.keyText === 'KEYCODE_SPACE' ? 'enter' : ''; + setTimeout(() => { + this.keyEvent = '' + }) + + if (e.keyText === 'KEYCODE_TAB' || e.keyText === 'KEYCODE_DPAD_DOWN' || e.keyText === 'KEYCODE_DPAD_UP') { + const listIndex = this.worldClockList.findIndex(i => item.id === i.id) + if (listIndex !== this.listIndexCache) { + this.listIndexCache = listIndex + // 34=38-4 视口区最后显示一半的列表向上滚动的距离,93每条item滚动高度 + this.scrollerForScroll.scrollTo({ + xOffset: 0, + yOffset: listIndex > 3 ? (listIndex - 4) * 93 + 33 : 0, + animation: { duration: 100, curve: Curve.Linear } + }); + } + } + }) + .onClick(() => { + if (this.keyEvent === 'enter') { + this.pageInfos.pushPath(new NavPathInfo(MANAGE_EDIT_WORLD_CLOCK_PC, 'unknow')) + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.WORLD_CLOCK_LONG_PRESS_EDIT) + } + }) + + }, (item: WorldClockInfo) => JSON.stringify(item)) + + if (this.deviceType !== '2in1') { + ListItem() { + }.height($r('app.float.empty_world_list_alarm')) + } + if (this.worldClockList.length > 3 && this.foldAbleScreen !== 1 && this.deviceType !== '2in1') { + ListItem() { + }.transition( + TransitionEffect.asymmetric( + TransitionEffect.IDENTITY, + TransitionEffect.scale({ x: 1, y: 1 }).animation({ curve: Curve.Friction, duration: 0 }) + .combine( + TransitionEffect.OPACITY.animation({ curve: Curve.Sharp, duration: 0 }) + ) + ) + ) + } + } + + @Builder + buildWorldClockList(): void { + List({ initialIndex: 0, scroller: this.scrollerForList }) { + this.listWold() + } + .padding(this.foldAbleScreen === 1 ? { + left: $r('app.float.word_padding_left'), + right: $r('app.float.word_padding_right') + } : {}) + .lanes(this.worldClockList.length > 3 && this.foldAbleScreen === 1 ? 2 : 1, $r('app.float.lans_list_item_10')) + .listDirection(Axis.Vertical) + .margin({ + left: $r('app.float.card_margin_start'), + right: $r('app.float.card_margin_start') + }) + .clip(false) + .edgeEffect(EdgeEffect.Spring) + .nestedScroll({ + scrollForward: NestedScrollMode.PARENT_FIRST, + scrollBackward: NestedScrollMode.SELF_FIRST + }) + + } + + @Builder + buildWorldClockPcList(): void { + List({ initialIndex: 0, scroller: this.scrollerForList }) { + this.listWold() + } + .height(this.worldClockList.length >= 5 ? '100%' : '') + .padding(this.deviceType == '2in1' ? { + right: $r('app.float.card_margin_start') + } : (this.currentBreakpoint === BreakPoint.MD ? this.isPortraitOrientation ? { + left: $r('app.float.word_padding_left'), + right: $r('app.float.word_padding_right') + } : this.foldAbleScreen === 1 ? + this.getOrientaion === this.isFoldCross ? { + left: $r('app.float.word_padding_left'), + right: $r('app.float.word_padding_right') + } : + { + left: $r('app.float.fold_cross_padding'), + right: $r('app.float.fold_cross_padding'), + } : {} : {})) + .listDirection(Axis.Vertical) + .margin(this.deviceType == '2in1' ? { left: $r('app.float.card_margin_start'), + right: 0 } : { + left: $r('app.float.card_margin_start'), + right: $r('app.float.card_margin_start') + }) + .clip(false) + } + + @Builder + buildTimeInfoAndWorldClockList(): void { + Stack({ alignContent: Alignment.Start }) { + Flex({ direction: FlexDirection.Column }) { + this.buildTimeInfo(); + } + + Flex({ direction: FlexDirection.Column }) { + CommonGrid() { + this.buildWorldClockList(); + } + } + .clip(true) + .margin({ top: $r('app.float.empty_world_list_alarm') }) + } + .width('100%') + .height('97%') + } + + @Builder + buildPortrait() { // 纵向 + Stack({ alignContent: Alignment.Bottom }) { + Scroll(this.scrollerForScroll) { + Column() { + this.buildClock(); + if (this.worldClockList.length === 0) { + this.buildTimeInfo(); + } else { + this.buildTimeInfoAndWorldClockList(); + } + } + } + .scrollBar(BarState.Off) + .onScroll((_: number, yOffset: number) => this.onScrollParent(yOffset)) + .onScrollStop(() => this.onScrollStopParent()) + .align(Alignment.Top) + .height('100%') + .width('100%') + + // AddButton({ + // alarmFlag: this.isAlarm, + // onButtonClick: () => router.pushUrl({ url: 'pages/AddCityNew' }) + // + // }) + // .transition(TransitionEffect.opacity(1)) + // .visibility(this.isWorldClockVisible ? Visibility.Visible : Visibility.Hidden) + } + .height('100%') + .width('100%') + } + + @Builder + buildLandscape() { // 横向 + if (this.deviceType == '2in1') { + Row() { + Column() { + this.buildClock(); + this.buildTimeInfo(); + } + .width(this.worldClockList.length == 0 ? 'calc(100% - 172vp)' : '40%') + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + + Column() { + Flex({ justifyContent: FlexAlign.Center, + alignItems: ItemAlign.Center }) { + if (this.worldClockList.length != 0) { + Row() { + Scroll(this.scrollerForScroll) { + this.buildWorldClockList(); + } + .height('100%') + .edgeEffect(EdgeEffect.Spring) + .scrollBar(BarState.Off) + .align(Alignment.Top) + } + } + Row() { + Button({ type: ButtonType.Circle, stateEffect: true }) { + Image($r('app.media.ic_add')) + .width($r('app.float.button_icon_size')) + .height($r('app.float.button_icon_size')) + .draggable(false) + } + .id('id_add_button') + .backgroundColor($r('app.color.color_fab_bg')) + .width($r('app.float.large_btn_width')) + .height($r('app.float.large_btn_height')) + .transition(TransitionEffect.opacity(1)) + .stateStyles({ + // pressed: this.pressedButtonStyle, + }) + .onClick(() => { + router.pushUrl({ url: 'pages/AddCityNew' }); + }) + } + .width($r('app.float.large_btn_wrapper_width')) + .justifyContent(FlexAlign.Center) + .alignItems(VerticalAlign.Center) + } + .width('100%') + .height('100%') + } + .width(this.worldClockList.length == 0 ? $r('app.float.large_btn_wrapper_width') : '60%') + .height('100%') + } + // .width('calc(100% - 96vp)') + .margin({ left: $r('app.float.large_row_margin_left') }) + + } else if (this.deviceType === this.isTablePad) { + Row() { + Column() { + this.buildClock(); + this.buildTimeInfo(); + } + .width('40%') + + Rect() + .fill($r('sys.color.comp_divider')) + .width('1px') + .height($r('app.float.line_height')) + .margin({ left: $r('app.float.rect_pad_margin_left') }) + + Stack({ alignContent: Alignment.Bottom }) { + Scroll(this.scrollerForScroll) { + this.buildWorldClockList(); + } + .height('100%') + .edgeEffect(EdgeEffect.Spring) + .align(Alignment.Top) + + AddButton({ + alarmFlag: this.isAlarm, + onButtonClick: () => router.pushUrl({ url: 'pages/AddCityNew' }) + }) + .transition(TransitionEffect.opacity(1)) + .visibility(this.isWorldClockVisible ? Visibility.Visible : Visibility.Hidden) + } + .width('calc(60% - 19vp)') + } + .width('100%') + } else { + Row() { + Column() { + this.buildClock(); + this.buildTimeInfo(); + } + .width('40%') + + Rect() + .fill($r('sys.color.ohos_id_color_list_separator')) + .width('1px') + .height($r('app.float.line_height')) + .margin({ left: $r('app.float.rect_margin_left') }) + + Stack({ alignContent: Alignment.Bottom }) { + Scroll(this.scrollerForScroll) { + this.buildWorldClockList(); + } + .height('100%') + .edgeEffect(EdgeEffect.Spring) + .scrollBar(BarState.Off) + .align(Alignment.Top) + + AddButton({ + alarmFlag: this.isAlarm, + onButtonClick: () => router.pushUrl({ url: 'pages/AddCityNew' }) + }) + .transition(TransitionEffect.opacity(1)) + .visibility(this.isWorldClockVisible ? Visibility.Visible : Visibility.Hidden) + } + .width('60%') + } + .width('100%') + } + } + + @Builder + buildPortraitForPC() { + Row() { + Column() { + this.buildClock(); + this.buildTimeInfo() + } + .width(this.worldClockList.length === 0 ? '86%' : '40%') + .padding({ + left: this.worldClockList.length === 0 ? $r('app.float.tool_bar_width_1') : $r('app.float.tool_bar_width'), + }) + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + if (this.worldClockList.length != 0) { + Scroll(this.scrollerForScroll) { + this.buildWorldClockPcList(); + } + .height(this.deviceType == '2in1' ? '76%' : '100%') + .align(this.deviceType == '2in1' ? Alignment.Center : Alignment.Top) + .edgeEffect(EdgeEffect.Spring) + .flexBasis(50) + .flexGrow(10) + .padding({ + top: $r('app.float.pc_list_scroll_padding'), + bottom: $r('app.float.pc_list_scroll_padding') + }) + } + Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row, justifyContent: FlexAlign.Center }) { + AddButton({ + alarmFlag: this.isAlarm, + buttonSize: ButtonSize.LARGER, + onButtonClick: (): void => { + router.push({ url: 'pages/AddCityNew' }); + }, + }) + .transition(TransitionEffect.opacity(1)) + .visibility(this.isWorldClockVisible ? Visibility.Visible : Visibility.Hidden) + } + .flexBasis(50) + .flexGrow(1) + .padding({ + left: $r('app.float.pc_padding'), + right: $r('app.float.pc_padding') + }) + } + .width(this.worldClockList.length === 0 ? '14%' : '60%') + .height('100%') + } + .width('100%') + .height('100%') + } + + build() { + if (this.deviceType == '2in1') { + this.buildPortraitForPC() + + } else { + if (this.isPortraitOrientation || this.foldAbleScreen === 1) { + this.buildPortrait() + } else { + this.buildLandscape(); + } + } + + } +} + diff --git a/feature/worldclock/src/main/ets/utils/CityClockCardUtil.ets b/feature/worldclock/src/main/ets/utils/CityClockCardUtil.ets new file mode 100644 index 0000000..34f0495 --- /dev/null +++ b/feature/worldclock/src/main/ets/utils/CityClockCardUtil.ets @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import i18n from '@ohos.i18n' +import { + CalendarFiled, + FormManager, + FormUtil, + LogUtil, + MILLIS_IN_SECOND, + MINUTE_IN_HOUR, + ResourceManager, + SECOND_IN_MINUTE, + TIME_TAG_ID_LIST_IN_ZH, + TimeUtil, +} from '@hmos/common'; +import { FaCityData } from '../manager/types' + +const TAG: string = 'ClockCardUtil'; + +export interface formDataInterface { + city: string, + date: string, + hoursWests?: number, +} + +export interface formDataInterfaceSecond { + cityFirst: string, + dateFirst: string, + hourswests1?: number, + citySecond: string, + dateSecond: string, + hourswests2?: number, + cityThird: string, + dateThird: string, + hourswests3?: number, +} + +/** + * City Clock Card Util + * + * @since 2023-06-16 + */ +export class CityClockCardUtil { + /** + * notify City Clock Card Content Update + * + * @param formId formId + * @param cityIndex cityIndex + * @returns + */ + public static async notifyCityClockCardUpdate(formId: string, cityIndex: string): Promise { + const formData = CityClockCardUtil.initCityClockCard(cityIndex); + FormUtil.notifyFormDataChanged(formId, formData); + } + + + /** + * notify World Clock Card Content Update + * + * @param formId formId + * @param cityIndexList cityIndexList + * @returns + */ + public static async notifyWorldClockCardUpdate(formId: string, cityIndexList: string[]): Promise { + const formData = CityClockCardUtil.initWorldClockCard(cityIndexList); + FormUtil.notifyFormDataChanged(formId, formData); + } + + /** + * initCityClockCard + * + * @param cityIndex cityIndex + * @returns + */ + public static initCityClockCard(cityIndex: string): object { + const faCityFirst: FaCityData = CityClockCardUtil.getCityNameAndDate(cityIndex); + let formData: formDataInterface = { + city: faCityFirst.cityName, + date: faCityFirst.cityDate, + hoursWests: faCityFirst.hoursWests, + }; + return formData; + } + + /** + * initWorldClockCard + * + * @param cityIndexList cityIndexList + * @returns + */ + public static initWorldClockCard(cityIndexList: string[]): object { + const faCityFirst: FaCityData = CityClockCardUtil.getCityNameAndDate(cityIndexList[0]); + const faCitySecond: FaCityData = CityClockCardUtil.getCityNameAndDate(cityIndexList[1]); + const faCityThird: FaCityData = CityClockCardUtil.getCityNameAndDate(cityIndexList[2]); + let formData: formDataInterfaceSecond = { + cityFirst: faCityFirst.cityName, + dateFirst: faCityFirst.cityDate, + hourswests1: faCityFirst.hoursWests, + citySecond: faCitySecond.cityName, + dateSecond: faCitySecond.cityDate, + hourswests2: faCitySecond.hoursWests, + cityThird: faCityThird.cityName, + dateThird: faCityThird.cityDate, + hourswests3: faCityThird.hoursWests, + }; + return formData; + } + + /** + * get CityName Date hoursWests + * + * @param cityIndex cityIndex + * @returns + */ + public static getCityNameAndDate(cityIndex: string): FaCityData { + const timezone: string = i18n.TimeZone.getTimezoneFromCity(cityIndex).getID(); + const hoursWests = CityClockCardUtil.getHourWest(timezone, cityIndex); + LogUtil.info(TAG, 'getCityNameAndDate cityIndex:' + cityIndex + ', hoursWests:' + hoursWests); + const cityDate: string = CityClockCardUtil.getCurrentFormattedDateWithWeek(timezone); + const cityDisplayName = i18n.TimeZone.getCityDisplayName(cityIndex, i18n.System.getSystemLanguage()); + const cityName = cityDisplayName.substr(0, cityDisplayName.indexOf('(')); + const faCity: FaCityData = { + cityName: cityName, + cityDate: cityDate, + hoursWests: hoursWests, + }; + return faCity; + } + + /** + * getHourWest + * + * @param timeZone timeZone + * @param cityIndex cityIndex + * @returns + */ + public static getHourWest(timeZone: string, cityIndex: string): number { + const calendar = i18n.getCalendar(i18n.System.getSystemLocale()); + calendar.setTimeZone(timeZone); + let year = calendar.get(CalendarFiled.Year) + let month = calendar.get(CalendarFiled.Month); + let date = calendar.get(CalendarFiled.Date) + let hour = calendar.get(CalendarFiled.Hour_OF_Day) + let minute = calendar.get(CalendarFiled.Minute) + const timeStamp = new Date(year, month, date, hour, minute).getTime(); + const currentOffset = i18n.TimeZone.getTimezoneFromCity(cityIndex).getOffset(timeStamp); + const hourWest = (currentOffset / MILLIS_IN_SECOND / SECOND_IN_MINUTE / MINUTE_IN_HOUR) * -1; + return hourWest; + } + + /** + * getCurrentFormattedDateWithWeek + * + * @param timeZone timeZone + * @returns + */ + public static getCurrentFormattedDateWithWeek(timeZone: string): string { + const calendar = i18n.getCalendar(i18n.System.getSystemLocale()); + calendar.setTimeZone(timeZone); + let year: number = calendar.get(CalendarFiled.Year); + let month: number = calendar.get(CalendarFiled.Month); + let date: number = calendar.get(CalendarFiled.Date); + let hour: number = calendar.get(CalendarFiled.Hour_OF_Day); + let minute: number = calendar.get(CalendarFiled.Minute); + const timeZoneDate = new Date(year, month, date, hour, minute); + const language: string = i18n.System.getSystemLanguage(); + return timeZoneDate.toLocaleDateString(language, { + weekday: 'long', + month: 'long', + day: 'numeric', + }); + } +} \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/utils/WorldClockTsUtil.ts b/feature/worldclock/src/main/ets/utils/WorldClockTsUtil.ts new file mode 100644 index 0000000..51e5bd9 --- /dev/null +++ b/feature/worldclock/src/main/ets/utils/WorldClockTsUtil.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class WordClockTsUtil { + public static transHandleScroll(tablet, k, obj, i): void { + if (k === tablet && !obj[tablet]) { + obj[tablet] = i; + } + } + + public static transIndexerSelect(objs, tablet): number { + return objs[tablet]; + } +} + +export default new WordClockTsUtil(); diff --git a/feature/worldclock/src/main/ets/utils/WorldClockUtil.ets b/feature/worldclock/src/main/ets/utils/WorldClockUtil.ets new file mode 100644 index 0000000..c769934 --- /dev/null +++ b/feature/worldclock/src/main/ets/utils/WorldClockUtil.ets @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import i18n from '@ohos.i18n'; +import resmgr from '@ohos.resourceManager'; +import { + MINUTE_IN_HOUR, + SECOND_IN_MINUTE, + MILLIS_IN_SECOND, + HOURS_IN_ONE_DAY, + FIRST_PARAM, + SECOND_PARAM, + CalendarFiled, + GlobalContext +} from '@hmos/common'; + +/** + * Word Clock Util + * + * @since 2022-12-08 + */ +export class WorldClockUtil { + /** + * update timezone information + * + * @param the offset of timezone + * @return the formatted timezone info + */ + static async updateTimeZoneInfo(rawOffset: number, timeZone: string, cityIndex: string): Promise { + const calendar = i18n.getCalendar(i18n.getSystemLocale()); + calendar.setTimeZone(timeZone); + let year = calendar.get(CalendarFiled.Year) + let month = calendar.get(CalendarFiled.Month); + let date = calendar.get(CalendarFiled.Date) + let hour = calendar.get(CalendarFiled.Hour_OF_Day) + let minute = calendar.get(CalendarFiled.Minute) + const timeStamp = new Date(year, month, date, hour, minute).getTime(); + const currentOffset = i18n.TimeZone.getTimezoneFromCity(cityIndex).getOffset(timeStamp); + const offset = i18n.getTimeZone().getRawOffset(); + const cityOffset = currentOffset - offset; + const hours = cityOffset / (MINUTE_IN_HOUR * SECOND_IN_MINUTE * MILLIS_IN_SECOND); + const minutes = Math.abs(cityOffset / (SECOND_IN_MINUTE * MILLIS_IN_SECOND)) % MINUTE_IN_HOUR; + + let minSeq: string; + let hourSeq: string; + if ((hours == 0) || minutes == 0) { + minSeq = (minutes == 0) ? '' : (await (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getPluralStringValue( + $r('app.plural.minute').id, minutes)); + hourSeq = (hours == 0) ? '' : (await (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getPluralStringValue( + $r('app.plural.hour').id, Math.abs(hours))); + } else { + minSeq = (await (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getPluralStringValue($r('app.plural.minute') + .id, minutes)); + hourSeq = (await (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getPluralStringValue( + $r('app.plural.hour').id, Math.abs(hours))) + ' '; + } + if (currentOffset > rawOffset) { + let cityTimeZoneInfo: string = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('text_with_separate') + .replace(FIRST_PARAM, WorldClockUtil.getDayString(hours, hourSeq, minSeq)) + .replace(SECOND_PARAM, (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('world_digital_dst_tv')); + return cityTimeZoneInfo; + } + return WorldClockUtil.getDayString(hours, hourSeq, minSeq); + } + + private static getDayString(hours: number, hourSeq: string, minSeq: string) { + let cityTimeZoneInfo: string = ''; + const date = new Date(); + const hour = date.getHours() + hours; + + let fast: string = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('fastGMT') + .replace(FIRST_PARAM, hourSeq) + .replace(SECOND_PARAM, minSeq); + let late: string = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('lateGMT') + .replace(FIRST_PARAM, hourSeq) + .replace(SECOND_PARAM, minSeq); + + if (hour >= 0 && hour <= HOURS_IN_ONE_DAY) { + let today: string = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('today'); + if (hour > date.getHours()) { + cityTimeZoneInfo = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('text_with_separate') + .replace(FIRST_PARAM, today) + .replace(SECOND_PARAM, fast); + } else if (hour < date.getHours()) { + cityTimeZoneInfo = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('text_with_separate') + .replace(FIRST_PARAM, today) + .replace(SECOND_PARAM, late); + } else { + cityTimeZoneInfo = today; + } + } else if (hour < 0) { + let yesterday: string = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('yesterday'); + cityTimeZoneInfo = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('text_with_separate') + .replace(FIRST_PARAM, yesterday) + .replace(SECOND_PARAM, late); + } else if (hour > HOURS_IN_ONE_DAY) { + let tomorrow: string = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('tomorrow'); + cityTimeZoneInfo = (GlobalContext.getContext() + .getObject('resourceManager') as resmgr.ResourceManager).getStringByNameSync('text_with_separate') + .replace(FIRST_PARAM, tomorrow) + .replace(SECOND_PARAM, fast); + } + return cityTimeZoneInfo; + + } +} \ No newline at end of file diff --git a/feature/worldclock/src/main/ets/utils/index.ets b/feature/worldclock/src/main/ets/utils/index.ets new file mode 100644 index 0000000..ca1c515 --- /dev/null +++ b/feature/worldclock/src/main/ets/utils/index.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const MANAGE_NEW_WORLD_CLOCK: string = 'ManageNewWorldClock'; + +export const MANAGE_EDIT_WORLD_CLOCK: string = 'ManageEditWorldClock'; + +export const MANAGE_EDIT_WORLD_CLOCK_PC: string = 'ManageEditWorldClockForPC'; diff --git a/feature/worldclock/src/main/module.json5 b/feature/worldclock/src/main/module.json5 new file mode 100644 index 0000000..8a7f67d --- /dev/null +++ b/feature/worldclock/src/main/module.json5 @@ -0,0 +1,10 @@ +{ + "module": { + "name": "worldclock", + "type": "har", + "deviceTypes": [ + "default", + "tablet" + ], + } +} diff --git a/feature/worldclock/src/main/resources/base/element/color.json b/feature/worldclock/src/main/resources/base/element/color.json new file mode 100644 index 0000000..da32db6 --- /dev/null +++ b/feature/worldclock/src/main/resources/base/element/color.json @@ -0,0 +1,24 @@ +{ + "color": [ + { + "name": "fa_card_title_color", + "value": "#ff151414" + }, + { + "name": "fa_select_tip_color", + "value": "#66ffffff" + }, + { + "name": "line_color", + "value": "#d3d3d3" + }, + { + "name": "pressed_state_icon_background_color", + "value": "#1A000000" + }, + { + "name": "add_button_color", + "value": "#0A59F7" + } + ] +} \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/base/element/float.json b/feature/worldclock/src/main/resources/base/element/float.json new file mode 100644 index 0000000..cb31c70 --- /dev/null +++ b/feature/worldclock/src/main/resources/base/element/float.json @@ -0,0 +1,233 @@ +{ + "float": [ + { + "name": "clock_margin_vertical", + "value": "16vp" + }, + { + "name": "clock_shadow_above_space_32", + "value": "32vp" + }, + { + "name": "timezone_margin_bottom", + "value": "2vp" + }, + { + "name": "date_info_margin_bottom", + "value": "16vp" + }, + { + "name": "alarmCard_24", + "value": "24dp" + }, + { + "name": "text_20", + "value": "20dp" + }, + { + "name": "city_margin_bottom", + "value": "2vp" + }, + { + "name": "text_margin_right", + "value": "4vp" + }, + { + "name": "appbar_icon_size", + "value": "24vp" + }, + { + "name": "response_region_size", + "value": "48vp" + }, + { + "name": "appbar_icon_margin", + "value": "16vp" + }, + { + "name": "title_font_size", + "value": "24vp" + }, + { + "name": "card_title_line_height", + "value": "22vp" + }, + { + "name": "card_content_line_height", + "value": "19vp" + }, + { + "name": "card_content_text_margin_top_4", + "value": "4vp" + }, + { + "name": "card_content_text_margin_top_11", + "value": "11vp" + }, + { + "name": "card_content_text_margin_top_8", + "value": "8vp" + }, + { + "name": "card_content_text_margin_top_24", + "value": "24vp" + }, + { + "name": "fa_card_title_font_size", + "value": "20fp" + }, + { + "name": "fa_card_app_font_size", + "value": "16fp" + }, + { + "name": "fa_card_select_font_size", + "value": "16fp" + }, + { + "name": "fa_card_margin_top", + "value": "8vp" + }, + { + "name": "fa_card_title_margin_top", + "value": "12vp" + }, + { + "name": "fa_card_title_margin_left", + "value": "30vp" + }, + { + "name": "fa_card_left_margin", + "value": "8vp" + }, + { + "name": "fa_card_right_margin", + "value": "8vp" + }, + { + "name": "fa_card_height", + "value": "50vp" + }, + { + "name": "empty_world_list_alarm", + "value": "62vp" + }, + { + "name": "line_height", + "value": "500vp" + }, + { + "name": "clock_landscape_height", + "value": "25vp" + }, + { + "name": "clock_landscape_no_height", + "value": "0vp" + }, + { + "name": "word_padding_left", + "value": "104.5vp" + }, + { + "name": "lans_list_item_10", + "value": "10vp" + }, + { + "name": "word_padding_right", + "value": "104.5vp" + }, + { + "name": "fold_cross_padding", + "value": "115vp" + }, + { + "name": "edit_padding_left", + "value": "104.5vp" + }, + { + "name": "edit_padding_right", + "value": "104.5vp" + }, + { + "name": "large_btn_width", + "value": "56vp" + }, + { + "name": "large_btn_height", + "value": "56vp" + }, + { + "name": "large_btn_wrapper_width", + "value": "172vp" + }, + { + "name": "large_row_margin_left", + "value": "96vp" + }, + { + "name": "swipe_image_height_and_width", + "value": "20vp" + }, + { + "name": "swipe_button_height_and_width", + "value": "40vp" + }, + { + "name": "swipe_margin_size", + "value": "4vp" + }, + { + "name": "swipe_padding_size", + "value": "20vp" + }, + { + "name": "swipe_delete_head_width", + "value": "18vp" + }, + { + "name": "swipe_delete_body_width", + "value": "15vp" + }, + { + "name": "flod_worldclock_padding", + "value": "24vp" + }, + { + "name": "card_list_margin", + "value": "12vp" + }, + { + "name": "card_content_height_pc", + "value": "72vp" + + }, + { + "name": "rect_margin_left", + "value": "30px" + }, + { + "name": "rect_pad_margin_left", + "value": "17px" + }, + { + "name": "world_edit_bottom_margin", + "value": "12vp" + }, + { + "name": "world_city_list_item_padding", + "value": "6vp" + }, + { + "name": "nav_destination_height", + "value": "560vp" + }, + { + "name": "add_city_title_height", + "value": "56vp" + }, + { + "name": "white_card_padding", + "value": "5vp" + } + ] +} \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/base/element/string.json b/feature/worldclock/src/main/resources/base/element/string.json new file mode 100644 index 0000000..2abd28d --- /dev/null +++ b/feature/worldclock/src/main/resources/base/element/string.json @@ -0,0 +1,88 @@ +{ + "string": [ + { + "name": "sort_city_title_new", + "value": "Edit cities" + }, + { + "name": "today", + "value": "Today" + }, + { + "name": "yesterday", + "value": "Yesterday" + }, + { + "name": "tomorrow", + "value": "Tomorrow" + }, + { + "name": "fastGMT", + "value": "%1$s%2$s ahead" + }, + { + "name": "lateGMT", + "value": "%1$s%2$s behind" + }, + { + "name": "world_clock", + "value": "World clock" + }, + { + "name": "worldclock_city_exist_Toast", + "value": "You've already added this city." + }, + { + "name": "world_digital_dst_tv", + "value": "DST" + }, + { + "name": "city_full_Toast", + "value": "City list full. Delete a city before adding a new one." + }, + { + "name": "fa_single_clock_name", + "value": "City clock" + }, + { + "name": "fa_three_clock_name", + "value": "World clock" + }, + { + "name": "fa_select_city_title", + "value": "Select where you want to see the local time for." + }, + { + "name": "fa_select_city", + "value": "City" + }, + { + "name": "fa_alarm_alert", + "value": "Alarm clock" + }, + { + "name": "fa_city_pre1", + "value": "Beijing" + }, + { + "name": "fa_city_pre2", + "value": "Paris" + }, + { + "name": "fa_city_pre3", + "value": "London" + }, + { + "name": "alarm_tips_title", + "value": "编辑列表" + }, + { + "name": "alarm_tips_del", + "value": "Delete" + }, + { + "name": "add_page_title", + "value": "Add city" + } + ] +} \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/base/media/ic_back_night.svg b/feature/worldclock/src/main/resources/base/media/ic_back_night.svg new file mode 100644 index 0000000..a5470ac --- /dev/null +++ b/feature/worldclock/src/main/resources/base/media/ic_back_night.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_back + + + + + + + + + + \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/base/media/ic_public_close_filled.svg b/feature/worldclock/src/main/resources/base/media/ic_public_close_filled.svg new file mode 100644 index 0000000..a75bc26 --- /dev/null +++ b/feature/worldclock/src/main/resources/base/media/ic_public_close_filled.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_close_filled + + + + + + + + + + \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/base/media/ic_public_drag_handle.svg b/feature/worldclock/src/main/resources/base/media/ic_public_drag_handle.svg new file mode 100644 index 0000000..75a784d --- /dev/null +++ b/feature/worldclock/src/main/resources/base/media/ic_public_drag_handle.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_drag_handle + + + + + + + + + + \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/bo_CN/element/string.json b/feature/worldclock/src/main/resources/bo_CN/element/string.json new file mode 100644 index 0000000..ccb85f6 --- /dev/null +++ b/feature/worldclock/src/main/resources/bo_CN/element/string.json @@ -0,0 +1,64 @@ +{ + "string":[ + { + "name":"sort_city_title_new", + "value":"གྲོང་ཁྱེར་བསྒྱུར་བཅོས།" + }, + { + "name":"lateGMT", + "value":"%1$s%2$s རྒྱབ་ཏུ།" + }, + { + "name":"world_digital_dst_tv", + "value":"DST" + }, + { + "name":"fa_select_city", + "value":"གྲོང་ཁྱེར།" + }, + { + "name":"worldclock_city_exist_Toast", + "value":"གྲོང་ཁྱེར་འདི་སྔར་ནས་ཡོད།" + }, + { + "name":"fa_three_clock_name", + "value":"འཛམ་གླིང་ཆུ་ཚོད།" + }, + { + "name":"add_page_title", + "value":"གྲོང་ཁྱེར་སྣོན་པ།" + }, + { + "name":"alarm_tips_title", + "value":"རེའུ་མིག་རྩོམ་སྒྲིག" + }, + { + "name":"fa_select_city_title", + "value":"གྲོང་ཁྱེར་བདམས་ནས་ལྟོས་ཟླའི་གྲོང་ཁྱེར་གྱི་དུས་ཚོད་མངོན་སྟོན་བྱེད།" + }, + { + "name":"fa_alarm_alert", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་ཀྱི་དྲན་སྐུལ།" + }, + { + "name":"fa_city_pre1", + "value":"པེ་ཅིན།" + }, + { + "name":"fa_city_pre2", + "value":"པ་ལི།" + }, + { + "name":"fa_city_pre3", + "value":"ལོན་ཏོན།" + }, + { + "name":"fastGMT", + "value":"%1$s%2$s སྔོན་དུ།" + }, + { + "name":"fa_single_clock_name", + "value":"གྲོང་ཁྱེར་གྱི་ཆུ་ཚོད།" + } + ] +} \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/dark/element/color.json b/feature/worldclock/src/main/resources/dark/element/color.json new file mode 100644 index 0000000..736a2c0 --- /dev/null +++ b/feature/worldclock/src/main/resources/dark/element/color.json @@ -0,0 +1,19 @@ +{ + "color": [ + { + "name": "fa_card_title_color", + "value": "#ff151414" + }, + { + "name": "fa_select_tip_color", + "value": "#66ffffff" + },{ + "name": "pressed_state_icon_background_color", + "value": "#1A000000" + }, + { + "name": "add_button_color", + "value": "#0A59F7" + } + ] +} \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/dark/element/float.json b/feature/worldclock/src/main/resources/dark/element/float.json new file mode 100644 index 0000000..4e5e9ef --- /dev/null +++ b/feature/worldclock/src/main/resources/dark/element/float.json @@ -0,0 +1,224 @@ +{ + "float": [ + { + "name": "clock_margin_vertical", + "value": "16vp" + }, + { + "name": "clock_shadow_above_space_32", + "value": "32vp" + }, + { + "name": "timezone_margin_bottom", + "value": "2vp" + }, + { + "name": "date_info_margin_bottom", + "value": "16vp" + }, + { + "name": "alarmCard_24", + "value": "24dp" + }, + { + "name": "text_20", + "value": "20dp" + }, + { + "name": "city_margin_bottom", + "value": "2vp" + }, + { + "name": "text_margin_right", + "value": "4vp" + }, + { + "name": "appbar_icon_size", + "value": "24vp" + }, + { + "name": "response_region_size", + "value": "48vp" + }, + { + "name": "appbar_icon_margin", + "value": "16vp" + }, + { + "name": "title_font_size", + "value": "24vp" + }, + { + "name": "card_title_line_height", + "value": "22vp" + }, + { + "name": "card_content_line_height", + "value": "19vp" + }, + { + "name": "card_content_text_margin_top_4", + "value": "4vp" + }, + { + "name": "card_content_text_margin_top_11", + "value": "11vp" + }, + { + "name": "card_content_text_margin_top_8", + "value": "8vp" + }, + { + "name": "card_content_text_margin_top_24", + "value": "24vp" + }, + { + "name": "fa_card_title_font_size", + "value": "20fp" + }, + { + "name": "fa_card_app_font_size", + "value": "16fp" + }, + { + "name": "fa_card_select_font_size", + "value": "16fp" + }, + { + "name": "fa_card_margin_top", + "value": "8vp" + }, + { + "name": "fa_card_title_margin_top", + "value": "12vp" + }, + { + "name": "fa_card_title_margin_left", + "value": "30vp" + }, + { + "name": "fa_card_left_margin", + "value": "8vp" + }, + { + "name": "fa_card_right_margin", + "value": "8vp" + }, + { + "name": "fa_card_height", + "value": "50vp" + }, + { + "name": "empty_world_list_alarm", + "value": "62vp" + }, + { + "name": "line_height", + "value": "500vp" + }, + { + "name": "clock_landscape_height", + "value": "25vp" + }, + { + "name": "clock_landscape_no_height", + "value": "0vp" + }, + { + "name": "word_padding_left", + "value": "104.5vp" + }, + { + "name": "lans_list_item_10", + "value": "10vp" + }, + { + "name": "word_padding_right", + "value": "104.5vp" + }, + { + "name": "fold_cross_padding", + "value": "115vp" + }, + { + "name": "edit_padding_left", + "value": "104.5vp" + }, + { + "name": "edit_padding_right", + "value": "104.5vp" + }, + { + "name": "large_btn_width", + "value": "56vp" + }, + { + "name": "large_btn_height", + "value": "56vp" + }, + { + "name": "large_btn_wrapper_width", + "value": "172vp" + }, + { + "name": "large_row_margin_left", + "value": "96vp" + }, + { + "name": "swipe_image_height_and_width", + "value": "20vp" + }, + { + "name": "swipe_button_height_and_width", + "value": "40vp" + }, + { + "name": "swipe_margin_size", + "value": "4vp" + }, + { + "name": "swipe_padding_size", + "value": "20vp" + }, + { + "name": "swipe_delete_head_width", + "value": "18vp" + }, + { + "name": "swipe_delete_body_width", + "value": "15vp" + }, + { + "name": "flod_worldclock_padding", + "value": "24vp" + }, + { + "name": "card_list_margin", + "value": "12vp" + }, + { + "name": "card_content_height_pc", + "value": "72vp" + }, + { + "name": "world_edit_bottom_margin", + "value": "12vp" + }, + { + "name": "world_city_list_item_padding", + "value": "6vp" + }, + { + "name": "nav_destination_height", + "value": "560vp" + }, + { + "name": "add_city_title_height", + "value": "56vp" + }, + { + "name": "white_card_padding", + "value": "5vp" + } + ] +} \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/dark/element/string.json b/feature/worldclock/src/main/resources/dark/element/string.json new file mode 100644 index 0000000..5c99099 --- /dev/null +++ b/feature/worldclock/src/main/resources/dark/element/string.json @@ -0,0 +1,84 @@ +{ + "string": [ + { + "name": "sort_city_title_new", + "value": "Edit cities" + }, + { + "name": "today", + "value": "Today" + }, + { + "name": "yesterday", + "value": "Yesterday" + }, + { + "name": "tomorrow", + "value": "Tomorrow" + }, + { + "name": "fastGMT", + "value": "%1$s%2$s ahead" + }, + { + "name": "lateGMT", + "value": "%1$s%2$s behind" + }, + { + "name": "world_clock", + "value": "World clock" + }, + { + "name": "worldclock_city_exist_Toast", + "value": "You've already added this city." + }, + { + "name": "world_digital_dst_tv", + "value": "DST" + }, + { + "name": "city_full_Toast", + "value": "City list full. Delete a city before adding a new one." + }, + { + "name": "fa_single_clock_name", + "value": "City clock" + }, + { + "name": "fa_three_clock_name", + "value": "World clock" + }, + { + "name": "fa_select_city_title", + "value": "Select where you want to see the local time for." + }, + { + "name": "fa_select_city", + "value": "City" + }, + { + "name": "fa_alarm_alert", + "value": "Alarm clock" + }, + { + "name": "fa_city_pre1", + "value": "Beijing" + }, + { + "name": "fa_city_pre2", + "value": "Paris" + }, + { + "name": "fa_city_pre3", + "value": "London" + }, + { + "name": "alarm_tips_title", + "value": "编辑列表" + }, + { + "name": "alarm_tips_del", + "value": "删除" + } + ] +} \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/dark/media/ic_delete_grey.svg b/feature/worldclock/src/main/resources/dark/media/ic_delete_grey.svg new file mode 100644 index 0000000..7a177f3 --- /dev/null +++ b/feature/worldclock/src/main/resources/dark/media/ic_delete_grey.svg @@ -0,0 +1,14 @@ + + + Public/ic_delete_grey + + + + + + + + + + + \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/en/element/string.json b/feature/worldclock/src/main/resources/en/element/string.json new file mode 100644 index 0000000..ca8c5e3 --- /dev/null +++ b/feature/worldclock/src/main/resources/en/element/string.json @@ -0,0 +1,84 @@ +{ + "string": [ + { + "name": "sort_city_title_new", + "value": "Edit cities" + }, + { + "name": "today", + "value": "Today" + }, + { + "name": "yesterday", + "value": "Yesterday" + }, + { + "name": "tomorrow", + "value": "Tomorrow" + }, + { + "name": "fastGMT", + "value": "%1$s%2$s ahead" + }, + { + "name": "lateGMT", + "value": "%1$s%2$s behind" + }, + { + "name": "world_clock", + "value": "World clock" + }, + { + "name": "worldclock_city_exist_Toast", + "value": "You've already added this city." + }, + { + "name": "world_digital_dst_tv", + "value": "DST" + }, + { + "name":"city_full_Toast", + "value":"City list full. Delete a city before adding a new one." + }, + { + "name": "fa_single_clock_name", + "value": "City clock" + }, + { + "name": "fa_three_clock_name", + "value": "World clock" + }, + { + "name":"fa_alarm_alert", + "value":"Alarm clock" + }, + { + "name": "fa_select_city_title", + "value": "Select where you want to see the local time for." + }, + { + "name": "fa_select_city", + "value": "City" + }, + { + "name": "fa_city_pre1", + "value": "Beijing" + }, + { + "name": "fa_city_pre2", + "value": "Paris" + }, + { + "name": "fa_city_pre3", + "value": "London" + }, + { + "name": "alarm_tips_title", + "value": "Edit list" + }, + { + "name": "alarm_tips_del", + "value": "Clear" + } + ] +} diff --git a/feature/worldclock/src/main/resources/ug/element/string.json b/feature/worldclock/src/main/resources/ug/element/string.json new file mode 100644 index 0000000..d60230b --- /dev/null +++ b/feature/worldclock/src/main/resources/ug/element/string.json @@ -0,0 +1,64 @@ +{ + "string":[ + { + "name":"sort_city_title_new", + "value":"شەھەرنى تەھرىرلەش" + }, + { + "name":"lateGMT", + "value":"%1$s%2$s كېيىن" + }, + { + "name":"world_digital_dst_tv", + "value":"يازلىق ۋاقىت" + }, + { + "name":"fa_select_city", + "value":"شەھەر" + }, + { + "name":"worldclock_city_exist_Toast", + "value":"بۇ شەھەر مەۋجۇت" + }, + { + "name":"fa_three_clock_name", + "value":"دۇنيا سائىتى" + }, + { + "name":"add_page_title", + "value":"شەھەر قوشۇش" + }, + { + "name":"alarm_tips_title", + "value":"تىزىملىك تەھرىرلەش" + }, + { + "name":"fa_select_city_title", + "value":"ۋاقىت كۆرسىتىدىغان شەھەر تاللاڭ." + }, + { + "name":"fa_alarm_alert", + "value":"قوڭغۇراق سائەت" + }, + { + "name":"fa_city_pre1", + "value":"بېيجىڭ" + }, + { + "name":"fa_city_pre2", + "value":"پارىژ" + }, + { + "name":"fa_city_pre3", + "value":"لوندون" + }, + { + "name":"fastGMT", + "value":"سەھەر %1$s%2$s" + }, + { + "name":"fa_single_clock_name", + "value":"شەھەر سائىتى" + } + ] +} \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/zh_CN/element/string.json b/feature/worldclock/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..7a80fae --- /dev/null +++ b/feature/worldclock/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,88 @@ +{ + "string": [ + { + "name": "sort_city_title_new", + "value": "编辑城市" + }, + { + "name": "today", + "value": "今天" + }, + { + "name": "yesterday", + "value": "昨天" + }, + { + "name": "tomorrow", + "value": "明天" + }, + { + "name": "fastGMT", + "value": "早 %1$s%2$s" + }, + { + "name": "lateGMT", + "value": "晚 %1$s%2$s" + }, + { + "name": "world_clock", + "value": "世界时钟" + }, + { + "name": "worldclock_city_exist_Toast", + "value": "该城市已存在" + }, + { + "name": "world_digital_dst_tv", + "value": "夏令时" + }, + { + "name": "city_full_Toast", + "value": "列表已满,请删除城市后再添加" + }, + { + "name": "fa_single_clock_name", + "value": "城市时钟" + }, + { + "name": "fa_three_clock_name", + "value": "世界时钟" + }, + { + "name": "fa_select_city_title", + "value": "选择城市以显示对应城市的时间" + }, + { + "name": "fa_select_city", + "value": "城市" + }, + { + "name": "fa_alarm_alert", + "value": "闹钟提醒" + }, + { + "name": "fa_city_pre1", + "value": "北京" + }, + { + "name": "fa_city_pre2", + "value": "巴黎" + }, + { + "name": "fa_city_pre3", + "value": "伦敦" + }, + { + "name": "alarm_tips_title", + "value": "编辑列表" + }, + { + "name": "alarm_tips_del", + "value": "删除" + }, + { + "name": "add_page_title", + "value": "添加城市" + } + ] +} diff --git a/feature/worldclock/src/main/resources/zh_HK/element/string.json b/feature/worldclock/src/main/resources/zh_HK/element/string.json new file mode 100644 index 0000000..9737664 --- /dev/null +++ b/feature/worldclock/src/main/resources/zh_HK/element/string.json @@ -0,0 +1,64 @@ +{ + "string":[ + { + "name":"sort_city_title_new", + "value":"編輯城市" + }, + { + "name":"lateGMT", + "value":"晚 %1$s%2$s" + }, + { + "name":"world_digital_dst_tv", + "value":"DST" + }, + { + "name":"fa_select_city", + "value":"城市" + }, + { + "name":"worldclock_city_exist_Toast", + "value":"該城市已存在" + }, + { + "name":"fa_three_clock_name", + "value":"世界時鐘" + }, + { + "name":"add_page_title", + "value":"新增城市" + }, + { + "name":"alarm_tips_title", + "value":"編輯清單" + }, + { + "name":"fa_select_city_title", + "value":"選擇城市,以顯示相應城市的時間" + }, + { + "name":"fa_alarm_alert", + "value":"鬧鐘提醒" + }, + { + "name":"fa_city_pre1", + "value":"北京" + }, + { + "name":"fa_city_pre2", + "value":"巴黎" + }, + { + "name":"fa_city_pre3", + "value":"倫敦" + }, + { + "name":"fastGMT", + "value":"早 %1$s%2$s" + }, + { + "name":"fa_single_clock_name", + "value":"城市時鐘" + } + ] +} \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/zh_TW/element/string.json b/feature/worldclock/src/main/resources/zh_TW/element/string.json new file mode 100644 index 0000000..5ca9d44 --- /dev/null +++ b/feature/worldclock/src/main/resources/zh_TW/element/string.json @@ -0,0 +1,64 @@ +{ + "string":[ + { + "name":"sort_city_title_new", + "value":"編輯城市" + }, + { + "name":"lateGMT", + "value":"晚 %1$s%2$s" + }, + { + "name":"world_digital_dst_tv", + "value":"DST" + }, + { + "name":"fa_select_city", + "value":"城市" + }, + { + "name":"worldclock_city_exist_Toast", + "value":"該城市已存在" + }, + { + "name":"fa_three_clock_name", + "value":"世界時鐘" + }, + { + "name":"add_page_title", + "value":"新增城市" + }, + { + "name":"alarm_tips_title", + "value":"編輯清單" + }, + { + "name":"fa_select_city_title", + "value":"選取城市以顯示對應城市的時間" + }, + { + "name":"fa_alarm_alert", + "value":"鬧鐘提醒" + }, + { + "name":"fa_city_pre1", + "value":"北京" + }, + { + "name":"fa_city_pre2", + "value":"巴黎" + }, + { + "name":"fa_city_pre3", + "value":"倫敦" + }, + { + "name":"fastGMT", + "value":"早 %1$s%2$s" + }, + { + "name":"fa_single_clock_name", + "value":"城市時鐘" + } + ] +} \ No newline at end of file diff --git a/feature/worldclock/src/main/resources/zz_ZX/element/string.json b/feature/worldclock/src/main/resources/zz_ZX/element/string.json new file mode 100644 index 0000000..a576f88 --- /dev/null +++ b/feature/worldclock/src/main/resources/zz_ZX/element/string.json @@ -0,0 +1,88 @@ +{ + "string": [ + { + "name": "world_clock", + "value": "[TS_794210]_World clock" + }, + { + "name": "today", + "value": "[TS_812906]_Today" + }, + { + "name": "tomorrow", + "value": "[TS_812903]_Tomorrow" + }, + { + "name": "yesterday", + "value": "[TS_812904]_Yesterday" + }, + { + "name": "worldclock_city_exist_Toast", + "value": "[TS_812905]_You've already added this city." + }, + { + "name": "world_digital_dst_tv", + "value": "[TS_812909]_DST" + }, + { + "name": "fastGMT", + "value": "[TS_812910]_%1$s%2$s ahead" + }, + { + "name": "lateGMT", + "value": "[TS_812907]_%1$s%2$s behind" + }, + { + "name": "sort_city_title_new", + "value": "[TS_812908]_Edit cities" + }, + { + "name": "city_full_Toast", + "value": "[TS_813906]_City list full. Delete a city before adding a new one." + }, + { + "name": "fa_three_clock_name", + "value": "[TS_843082]_World clock" + }, + { + "name": "fa_alarm_alert", + "value": "[TS_843080]_Alarm clock" + }, + { + "name": "fa_city_pre1", + "value": "[TS_843074]_Beijing" + }, + { + "name": "fa_city_pre2", + "value": "[TS_843087]_Paris" + }, + { + "name": "fa_city_pre3", + "value": "[TS_843081]_London" + }, + { + "name": "fa_single_clock_name", + "value": "[TS_843072]_City clock" + }, + { + "name": "fa_select_city", + "value": "[TS_846748]_City" + }, + { + "name": "add_page_title", + "value": "[TS_881030]_Add city" + }, + { + "name": "alarm_tips_title", + "value": "[TS_878045]_编辑列表" + }, + { + "name": "fa_select_city_title", + "value": "[TS_846747]_Select where you want to see the local time for." + }, + { + "name": "alarm_tips_del", + "value": "[TS_878046]_Delete" + } + ] +} \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000..1d30cdc --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,8 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + // "@ohos/hypium-plugin": "1.0.8-rc7", + // "fs-extra": "10.0.1", + // "execa": "5.1.1" + } +} \ No newline at end of file diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..e6d289b --- /dev/null +++ b/local.properties @@ -0,0 +1,8 @@ +# This file is automatically generated by DevEco Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file should *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# For customization when using a Version Control System, please read the header note. +sdk.dir=C:/Work/Software/arkui/HarmonyOSSDK_1018 \ No newline at end of file diff --git a/oh-package-lock.json5 b/oh-package-lock.json5 new file mode 100644 index 0000000..120748e --- /dev/null +++ b/oh-package-lock.json5 @@ -0,0 +1,20 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hypium@1.0.9": "@ohos/hypium@1.0.9" + }, + "packages": { + "@ohos/hypium@1.0.9": { + "name": "@ohos/hypium", + "version": "1.0.9", + "integrity": "sha512-qW5HE293s8+YRwh+6reJjR1Q08iuhay2RdPJ/JJWOI9/mjHn/zMW4i/CUfhvqlAD2w+PXUfwAYoXItc4OQpo6A==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.9.tgz", + "shasum": "c0b869cd51c7eb0764f90cb1a5b231d5512cef97", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000..7036f36 --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,19 @@ +{ + "modelVersion": "5.0.0", + "license": "ISC", + "devDependencies": {}, + "name": "ohosclock", + "ohos": { + "org": "huawei", + "directoryLevel": "project", + "buildTool": "hvigor" + }, + "description": "example description", + "repository": {}, + "version": "1.0.0", + "dependencies": { + "@ohos/hypium": "1.0.9", + // "@ohos/hvigor-ohos-plugin":"1.1.6", + // "@ohos/hvigor":"1.1.6" + } +} \ No newline at end of file diff --git a/package.json b/package.json deleted file mode 100644 index b84491d..0000000 --- a/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "license":"ISC", - "devDependencies":{}, - "name":"ohosclock", - "ohos":{ - "org":"huawei", - "directoryLevel":"project", - "buildTool":"hvigor" - }, - "description":"example description", - "repository":{}, - "version":"1.0.0", - "dependencies":{ - "@ohos/hypium":"1.0.1", - "@ohos/hvigor-ohos-plugin":"1.1.6", - "@ohos/hvigor":"1.1.6" - } -} \ No newline at end of file diff --git a/product/pc/build-profile.json5 b/product/pc/build-profile.json5 deleted file mode 100644 index f8f0340..0000000 --- a/product/pc/build-profile.json5 +++ /dev/null @@ -1,13 +0,0 @@ -{ - "apiType": 'stageMode', - "buildOption": { - }, - "targets": [ - { - "name": "default" - }, - { - "name": "ohosTest", - } - ] -} \ No newline at end of file diff --git a/product/pc/package-lock.json b/product/pc/package-lock.json deleted file mode 100644 index 58ca695..0000000 --- a/product/pc/package-lock.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "pc", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@ohos/common": { - "version": "file:../../common" - }, - "@ohos/countdown": { - "version": "file:../../feature/countdown", - "requires": { - "@ohos/common": "file:../../common" - }, - "dependencies": { - "@ohos/common": { - "version": "file:../../common" - } - } - }, - "@ohos/timer": { - "version": "file:../../feature/timer", - "requires": { - "@ohos/common": "file:../../common" - }, - "dependencies": { - "@ohos/common": { - "version": "file:../../common" - } - } - } - } -} diff --git a/product/pc/package.json b/product/pc/package.json deleted file mode 100644 index 7e73831..0000000 --- a/product/pc/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "license": "ISC", - "devDependencies": {}, - "name": "pc", - "ohos": { - "org": "huawei", - "directoryLevel": "module", - "buildTool": "hvigor" - }, - "description": "example description", - "repository": {}, - "version": "1.0.0", - "dependencies": { - "@ohos/countdown": "file:../../feature/countdown", - "@ohos/timer": "file:../../feature/timer", - "@ohos/common": "file:../../common" - } -} diff --git a/product/pc/src/main/ets/MainAbility/MainAbility.ts b/product/pc/src/main/ets/MainAbility/MainAbility.ts deleted file mode 100644 index ae6097e..0000000 --- a/product/pc/src/main/ets/MainAbility/MainAbility.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import Ability from '@ohos.application.Ability' - -export default class MainAbility extends Ability { - onCreate(want, launchParam) { - console.log("[Demo] MainAbility onCreate") - globalThis.abilityWant = want; - globalThis.abilityContext = this.context; - } - - onDestroy() { - console.log("[Demo] MainAbility onDestroy") - } - - onWindowStageCreate(windowStage) { - // Main window is created, set main page for this ability - console.log("[Demo] MainAbility onWindowStageCreate") - - windowStage.loadContent("pages/index", (err, data) => { - if (err.code) { - console.error('Failed to load the content. Cause:' + JSON.stringify(err)); - return; - } - console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data)) - }); - } - - onWindowStageDestroy() { - // Main window is destroyed, release UI related resources - console.log("[Demo] MainAbility onWindowStageDestroy") - } - - onForeground() { - // Ability has brought to foreground - console.log("[Demo] MainAbility onForeground") - } - - onBackground() { - // Ability has back to background - console.log("[Demo] MainAbility onBackground") - } -}; diff --git a/product/pc/src/main/ets/pages/Countdown.ets b/product/pc/src/main/ets/pages/Countdown.ets deleted file mode 100644 index 7fea84f..0000000 --- a/product/pc/src/main/ets/pages/Countdown.ets +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CountdownView, CountdownController, CountdownState, TimeSelectView } from '@ohos/countdown'; -import { ImageComponent, ConfigData } from '@ohos/common'; - -@Component -export struct CountDown { - private headName: ResourceStr = $r('app.string.timer_clock'); - private controller: CountdownController = new CountdownController(); - private viewSize: number = 232; - private originRadius: number = 5; - private scaleWidth: number = 2; - private scaleHeight: number = 10; - private scalePadding: number = 5; - private sca: number = 0.6; - private imgLength: number = 12; - @State totalTime: number = 0; - @State currentTime: number = 0; - @State state: CountdownState = CountdownState.IDLE; - @State hour: string = '00'; - @State minute: string = '00'; - @State second: string = '00'; - - build() { - Row() { - Column() { - Row() { - Text(this.headName) - .fontSize($r('app.float.font_30')) - .fontWeight(FontWeight.Regular) - .fontColor($r('app.color.text_color')) - .lineHeight($r('app.float.wh_value_21')); - }.width(ConfigData.WH_100_100) - .padding({ left: $r('app.float.wh_value_24') }) - .height($r('app.float.wh_value_28')); - - Column() { - CountdownView({ - viewSize: this.viewSize, - totalTime: this.totalTime, - currentTime: this.currentTime, - state: this.state, - originRadius: this.originRadius, - scaleWidth: this.scaleWidth, - scaleHeight: this.scaleHeight, - scalePadding: this.scalePadding - }); - }.margin({ top: $r('app.float.wh_value_38'), bottom: $r('app.float.wh_value_38') }); - - Column() { - Text(this.hour + ':' + this.minute + ':' + this.second) - .lineHeight($r('app.float.wh_value_16')) - .fontColor($r('app.color.text_color')) - .fontWeight(FontWeight.Regular) - .fontSize($r('app.float.font_20')); - }.width(ConfigData.WH_100_100); - } - .height(ConfigData.WH_100_100) - .width($r('app.float.wh_value_304')); - - Divider().vertical(true).width($r('app.float.wh_value_0_5')); - Column() { - Column() { - TimeSelectView({ - marginRight: $r('app.float.distance_19'), - dividerMargin: $r('app.float.distance_19'), - hour: $hour, - minute: $minute, - second: $second, - sca: this.sca, - callBack: this.timeSelectCallBack - }); - } - .justifyContent(FlexAlign.Center) - .visibility((this.state == CountdownState.IDLE || this.state == CountdownState.PREPARED) ? Visibility.Visible : Visibility.Hidden) - .padding($r('app.float.distance_12')) - .height($r('app.float.wh_value_324')) - .flexShrink(1); - - Row() { - Row() { - ImageComponent({ - color: $r('app.color.white'), - imageSrc: $r('app.media.ic_clock_refresh'), - imgLength: this.imgLength - }); - } - .opacity((this.state == CountdownState.IDLE || this.state == CountdownState.STOPPED) ? $r('app.float.opacity_4') : $r('app.float.opacity_full')) - .enabled((this.state == CountdownState.PREPARED || this.state == CountdownState.PAUSED || this.state == CountdownState.RUNNING)) - .onClick(() => { - this.controller.reset(); - }); - - Row() { - ImageComponent({ - color: $r('app.color.selected_text_color'), - imageSrc: this.state == CountdownState.RUNNING ? $r('app.media.ic_clock_suspend') : $r('app.media.ic_clock_play'), - imgLength: this.imgLength - }); - } - .opacity((this.state == CountdownState.IDLE || this.state == CountdownState.STOPPED) ? $r('app.float.opacity_4') : $r('app.float.opacity_full')) - .enabled((this.state == CountdownState.PREPARED || this.state == CountdownState.PAUSED || this.state == CountdownState.RUNNING)) - .onClick(() => { - if (this.state == CountdownState.PREPARED) { - this.controller.setTime(this.totalTime); - this.controller.start(); - } else if (this.state == CountdownState.PAUSED) { - this.controller.start(); - } else if (this.state == CountdownState.RUNNING) { - this.controller.pause(); - } - }); - - Row() { - ImageComponent({ - color: $r('app.color.white'), - imageSrc: $r('app.media.ic_clock_sound'), - imgLength: this.imgLength - }); - } - .onClick(() => { - }); - } - .height($r('app.float.wh_value_24')) - .width(ConfigData.WH_100_100) - .justifyContent(FlexAlign.SpaceBetween) - .margin({ bottom: $r('app.float.distance_10'), top: $r('app.float.distance_4') }) - .padding({ left: $r('app.float.distance_84'), right: $r('app.float.distance_84') }); - } - .height(ConfigData.WH_100_100) - .width($r('app.float.wh_value_275_5')); - } - } - - /** - * CallBack for TimeSelectView. - */ - private timeSelectCallBack: () => void = () => { - this.totalTime = (parseInt(this.hour) * 60 * 60 + parseInt(this.minute) * 60 + parseInt(this.second)) * 1000; - - this.controller.setTime(this.totalTime); - } - - aboutToAppear() { - this.controller.setStateUpdateListener((state) => { - this.state = state; - if (state == CountdownState.STOPPED) { - this.hour = this.fill(Math.floor(this.totalTime / ConfigData.ONE_HOUR_TIME).toString()); - this.minute = this.fill(Math.floor(this.totalTime % ConfigData.ONE_HOUR_TIME / ConfigData.ONE_MINUTE_TIME).toString()); - this.second = this.fill(Math.floor(this.totalTime % ConfigData.ONE_MINUTE_TIME / ConfigData.ONE_SECOND_TIME).toString()); - - this.controller.reStart(); - } - }) - this.controller.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - this.currentTime = currentTimeMs; - this.totalTime = totalTimeMs; - - this.hour = this.fill(Math.floor(this.currentTime / ConfigData.ONE_HOUR_TIME).toString()); - this.minute = this.fill(Math.floor(this.currentTime % ConfigData.ONE_HOUR_TIME / ConfigData.ONE_MINUTE_TIME).toString()); - this.second = this.fill(Math.floor(this.currentTime % ConfigData.ONE_MINUTE_TIME / ConfigData.ONE_SECOND_TIME).toString()); - }) - this.controller.getData(); - } - - /** - * Make up 0 if less than 10. - */ - fill(value) { - return (value > 9 ? '' : '0') + value; - } -} \ No newline at end of file diff --git a/product/pc/src/main/ets/pages/index.ets b/product/pc/src/main/ets/pages/index.ets deleted file mode 100644 index 8d7b21b..0000000 --- a/product/pc/src/main/ets/pages/index.ets +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ConfigData } from '@ohos/common'; -import { Timer } from './timer/Timer'; -import { CountDown } from './Countdown'; - -@Entry -@Component -struct Index { - private featureList: Array = [ - { - name: $r('app.string.alarm_clock'), - icon: $r('app.media.ic_clock_clock'), - selectedIcon: $r('app.media.ic_clock_clock_click') - }, - { - name: $r('app.string.stopwatch'), - icon: $r('app.media.ic_clock_timer'), - selectedIcon: $r('app.media.ic_clock_timer_click') - }, - { - name: $r('app.string.timer_clock'), - icon: $r('app.media.ic_clock_second_chronograph'), - selectedIcon: $r('app.media.ic_clock_second_chronograph_click') - }, - ]; - @State featureIndex: number = 1; - - build() { - Row() { - Column() { - ForEach(this.featureList.map((value, index) => { - return { i: index, data: value } - }), item => { - Column() { - Image(this.featureIndex == item.i ? item.data.selectedIcon : item.data.icon) - .width($r('app.float.wh_value_12')) - .height($r('app.float.wh_value_12')) - Text(item.data.name) - .fontColor(this.featureIndex == item.i ? $r('app.color.selected_text_color') : $r('app.color.text_color')) - .fontSize($r('app.float.font_10')) - .margin({ top: $r('app.float.distance_1') }) - } - .onClick(() => { - this.featureIndex = item.i; - }) - }, item => item.i) - - } - .backgroundColor($r('app.color.background_color_white')) - .padding({ top: $r('app.float.wh_value_118'), bottom: $r('app.float.wh_value_118') }) - .height(ConfigData.WH_100_100) - .justifyContent(FlexAlign.SpaceBetween) - .width($r('app.float.wh_value_60')) - - Row() { - Column() { - Timer() - }.visibility(this.featureIndex == 1 ? Visibility.Visible : Visibility.None) - - Column() { - CountDown() - }.visibility(this.featureIndex == 2 ? Visibility.Visible : Visibility.None) - } - .width(ConfigData.WH_100_100) - .height(ConfigData.WH_100_100) - .flexShrink(1) - }.backgroundColor($r('app.color.background_color')) - .width(ConfigData.WH_100_100) - .height(ConfigData.WH_100_100) - } -} \ No newline at end of file diff --git a/product/pc/src/main/ets/pages/timer/TimeOfRecord.ets b/product/pc/src/main/ets/pages/timer/TimeOfRecord.ets deleted file mode 100644 index 70d9439..0000000 --- a/product/pc/src/main/ets/pages/timer/TimeOfRecord.ets +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TimerUtil, ConfigData } from '@ohos/common'; - -@Component -export struct TimeOfRecord { - @State timeList: Array = []; - @Prop lastTimeMs: number; - - build() { - Column() { - List() { - ForEach(this.timeList.map((value, index) => { - return { i: index, data: this.timeList[this.timeList.length - index-1] } - }), (item) => { - ListItem() { - Row() { - Row() { - Text(TimerUtil.addZero(this.timeList.length - item.i)) - .fontColor($r('app.color.text_color')) - .fontSize($r('app.float.font_14')) - .fontWeight(500) - Text('+' + TimerUtil.timeFormat(item.data - (item.i < this.timeList.length - 1 ? this.timeList[this.timeList.length - 1 - item.i -1] : 0))) - .width($r('app.float.wh_value_60')) - .textAlign(TextAlign.Center) - .fontSize($r('app.float.font_14')) - .fontColor($r('app.color.text_color')) - .opacity($r('app.float.opacity_6')) - } - .width($r('app.float.wh_value_151')) - .justifyContent(FlexAlign.SpaceBetween) - - Text(TimerUtil.timeFormat(item.data)) - .fontSize($r('app.float.font_14')) - .fontColor($r('app.color.text_color')) - .fontWeight(500) - }.width(ConfigData.WH_100_100) - .height(ConfigData.WH_100_100) - .justifyContent(FlexAlign.SpaceBetween) - .padding({ left: $r('app.float.distance_12'), right: $r('app.float.distance_12') }) - } - .height($r('app.float.wh_value_28')) - .width(ConfigData.WH_100_100) - .clip(true) - }, item => item.i) - } - .backgroundColor($r('app.color.background_color_white')) - - .borderRadius($r('app.float.distance_12')) - .margin({ top: $r('app.float.distance_40') }) - - }.padding($r('app.float.distance_12')) - .height($r('app.float.wh_value_324')) - .flexShrink(1) - } -} \ No newline at end of file diff --git a/product/pc/src/main/ets/pages/timer/Timer.ets b/product/pc/src/main/ets/pages/timer/Timer.ets deleted file mode 100644 index 2a61730..0000000 --- a/product/pc/src/main/ets/pages/timer/Timer.ets +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TimerController } from '@ohos/timer'; -import { ConfigData } from '@ohos/common'; -import { TimerClock } from './TimerClock'; -import { CurrentTimeDisplay } from './CurrentTimeDisplay'; -import { TimeOfRecord } from './TimeOfRecord'; -import { TimerControl } from './TimerControl'; - -@Component -export struct Timer { - private timerController: TimerController = new TimerController(); - private timeList: Array = []; - @State lastTimeMs: number = 0; - - aboutToAppear() { - this.timerController.setTimeListUpdateListener((timeList: Array, lastTimeMs: number) => { - this.timeList = timeList; - this.lastTimeMs = lastTimeMs; - }) - setTimeout(() => { - this.timerController.reload(); - }, 100) - } - - build() { - Row() { - Column() { - Row() { - Text($r('app.string.stopwatch')) - .fontSize($r('app.float.font_30')) - .fontWeight(500) - .fontColor($r('app.color.text_color')) - .lineHeight($r('app.float.wh_value_21')) - }.width(ConfigData.WH_100_100) - .padding({ left: $r('app.float.wh_value_24') }) - .height($r('app.float.wh_value_28')) - - Column() { - TimerClock({ timerController: this.timerController }) - }.margin({ top: $r('app.float.wh_value_38'), bottom: $r('app.float.wh_value_38') }) - - CurrentTimeDisplay({ timerController: this.timerController, lastTimeMs: this.lastTimeMs }) - } - .height(ConfigData.WH_100_100) - .width($r('app.float.wh_value_304')) - - Divider().vertical(true).width($r('app.float.wh_value_0_5')).opacity($r('app.float.opacity_1')) - Column() { - TimeOfRecord({ timeList: this.timeList, lastTimeMs: this.lastTimeMs }) - TimerControl({ timerController: this.timerController, timeList: this.timeList }) - } - .height(ConfigData.WH_100_100) - .width($r('app.float.wh_value_275_5')) - } - } -} \ No newline at end of file diff --git a/product/pc/src/main/ets/pages/timer/TimerControl.ets b/product/pc/src/main/ets/pages/timer/TimerControl.ets deleted file mode 100644 index 0a84a5c..0000000 --- a/product/pc/src/main/ets/pages/timer/TimerControl.ets +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TimerController, TimerState } from '@ohos/timer'; -import { ImageComponent, ConfigData } from '@ohos/common'; - -const DEFAULT_ICON_SIZE: number = 12; - -@Component -export struct TimerControl { - private timerController: TimerController = new TimerController(); - private timeList: Array = []; - @State state: TimerState = TimerState.IDLE; - - aboutToAppear() { - this.timerController.setStateUpdateListener((state) => { - this.state = state; - }) - } - - build() { - Row() { - Row() { - ImageComponent({ - color: $r('app.color.white'), - imageSrc: $r('app.media.ic_clock_refresh'), - imgLength: DEFAULT_ICON_SIZE - }) - } - .opacity(this.state == TimerState.PAUSED ? $r('app.float.opacity_full') : $r('app.float.opacity_4')) - .enabled(this.state == TimerState.PAUSED) - .onClick(() => { - this.timerController.reset() - }) - - Row() { - ImageComponent({ - color: $r('app.color.selected_text_color'), - imageSrc: this.state == TimerState.RUNNING ? - $r('app.media.ic_clock_suspend') : - $r('app.media.ic_clock_play'), imgLength: DEFAULT_ICON_SIZE - }) - }.onClick(() => { - if (this.state == TimerState.IDLE) { - this.timerController.start(); - } else if (this.state == TimerState.RUNNING) { - this.timerController.pause(); - } else if (this.state == TimerState.PAUSED) { - this.timerController.start(); - } - }) - - Row() { - ImageComponent({ - color: $r('app.color.white'), - imageSrc: $r('app.media.ic_clock_timer2'), - imgLength: DEFAULT_ICON_SIZE - }) - } - .opacity(this.state == TimerState.RUNNING ? $r('app.float.opacity_full') : $r('app.float.opacity_4')) - .enabled(this.state == TimerState.RUNNING) - .onClick(() => { - this.timerController.writeTime(); - }) - } - .height($r('app.float.wh_value_24')) - .width(ConfigData.WH_100_100) - .justifyContent(FlexAlign.SpaceBetween) - .margin({ bottom: $r('app.float.distance_10'), top: $r('app.float.distance_4') }) - .padding({ left: $r('app.float.distance_84'), right: $r('app.float.distance_84') }) - .shadow({ radius: $r('app.float.wh_value_28'), offsetY: -60, color: $r('app.color.background_color') }) - } -} \ No newline at end of file diff --git a/product/pc/src/main/module.json5 b/product/pc/src/main/module.json5 deleted file mode 100644 index 0f92245..0000000 --- a/product/pc/src/main/module.json5 +++ /dev/null @@ -1,43 +0,0 @@ -{ - "module": { - "name": "pc", - "type": "entry", - "srcEntrance": "./ets/Application/MyAbilityStage.ts", - "description": "$string:entry_desc", - "mainElement": "MainAbility", - "deviceTypes": [ - "tablet" - ], - "deliveryWithInstall": true, - "installationFree": false, - "pages": "$profile:main_pages", - "uiSyntax": "ets", - "abilities": [ - { - "name": "MainAbility", - "srcEntrance": "./ets/MainAbility/MainAbility.ts", - "description": "$string:MainAbility_desc", - "icon": "$media:icon", - "label": "$string:MainAbility_label", - "startWindowIcon": "$media:icon", - "startWindowBackground": "$color:white", - "visible": true, - "skills": [ - { - "entities": [ - "entity.system.home" - ], - "actions": [ - "action.system.home" - ] - } - ] - } - ], - "requestPermissions": [ - { - "name": "ohos.permission.PUBLISH_AGENT_REMINDER" - } - ] - } -} \ No newline at end of file diff --git a/product/pc/src/main/resources/base/element/color.json b/product/pc/src/main/resources/base/element/color.json deleted file mode 100644 index ab58100..0000000 --- a/product/pc/src/main/resources/base/element/color.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "color": [ - { - "name": "white", - "value": "#FFFFFF" - }, - { - "name": "background_color", - "value": "#F1F3F5" - } - ] -} \ No newline at end of file diff --git a/product/pc/src/main/resources/base/element/float.json b/product/pc/src/main/resources/base/element/float.json deleted file mode 100644 index 110b59a..0000000 --- a/product/pc/src/main/resources/base/element/float.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "float": [ - { - "name": "wh_value_0_5", - "value": "0.5vp" - }, - { - "name": "wh_value_12", - "value": "12vp" - }, - { - "name": "wh_value_16", - "value": "16vp" - }, - { - "name": "wh_value_21", - "value": "21vp" - }, - { - "name": "wh_value_24", - "value": "24vp" - }, - { - "name": "wh_value_28", - "value": "28vp" - }, - { - "name": "wh_value_38", - "value": "38vp" - }, - { - "name": "wh_value_60", - "value": "60vp" - }, - { - "name": "wh_value_118", - "value": "118vp" - }, - { - "name": "wh_value_151", - "value": "151vp" - }, - { - "name": "wh_value_275_5", - "value": "275.5vp" - }, - { - "name": "wh_value_304", - "value": "304vp" - }, - { - "name": "wh_value_324", - "value": "324vp" - }, - { - "name": "font_10", - "value": "10px" - }, - { - "name": "font_14", - "value": "14px" - }, - { - "name": "font_16", - "value": "16px" - }, - { - "name": "font_20", - "value": "20px" - }, - { - "name": "font_30", - "value": "30px" - }, - { - "name": "distance_1", - "value": "1vp" - }, - { - "name": "distance_4", - "value": "4vp" - }, - { - "name": "distance_10", - "value": "10vp" - }, - { - "name": "distance_12", - "value": "12vp" - }, - { - "name": "distance_19", - "value": "19vp" - }, - { - "name": "distance_40", - "value": "40vp" - }, - { - "name": "distance_84", - "value": "84vp" - }, - { - "name": "opacity_1", - "value": "0.1" - }, - { - "name": "opacity_4", - "value": "0.4" - }, - { - "name": "opacity_6", - "value": "0.6" - }, - { - "name": "opacity_full", - "value": "1" - } - ] -} \ No newline at end of file diff --git a/product/pc/src/main/resources/base/element/string.json b/product/pc/src/main/resources/base/element/string.json deleted file mode 100644 index f0dc461..0000000 --- a/product/pc/src/main/resources/base/element/string.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "string": [ - { - "name": "entry_desc", - "value": "description" - }, - { - "name": "MainAbility_desc", - "value": "description" - }, - { - "name": "MainAbility_label", - "value": "Clock" - } - ] -} \ No newline at end of file diff --git a/product/pc/src/ohosTest/ets/TestAbility/TestAbility.ts b/product/pc/src/ohosTest/ets/TestAbility/TestAbility.ts deleted file mode 100644 index 9bb3e67..0000000 --- a/product/pc/src/ohosTest/ets/TestAbility/TestAbility.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import Ability from '@ohos.application.Ability' -import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry' -import { Hypium } from '@ohos/hypium' -import testsuite from '../test/List.test' - -export default class TestAbility extends Ability { - onCreate(want, launchParam) { - console.log('TestAbility onCreate') - var abilityDelegator: any - abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() - var abilityDelegatorArguments: any - abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() - console.info('start run testcase!!!') - Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) - } - - onDestroy() { - console.log('TestAbility onDestroy') - } - - onWindowStageCreate(windowStage) { - console.log('TestAbility onWindowStageCreate') - windowStage.loadContent("TestAbility/pages/index", (err, data) => { - if (err.code) { - console.error('Failed to load the content. Cause:' + JSON.stringify(err)); - return; - } - console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data)) - }); - - globalThis.abilityContext = this.context; - } - - onWindowStageDestroy() { - console.log('TestAbility onWindowStageDestroy') - } - - onForeground() { - console.log('TestAbility onForeground') - } - - onBackground() { - console.log('TestAbility onBackground') - } -}; \ No newline at end of file diff --git a/product/pc/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts b/product/pc/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts deleted file mode 100644 index e82df7a..0000000 --- a/product/pc/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import TestRunner from '@ohos.application.testRunner' -import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry' - -var abilityDelegator = undefined -var abilityDelegatorArguments = undefined - -function translateParamsToString(parameters) { - const keySet = new Set([ - '-s class', '-s notClass', '-s suite', '-s it', - '-s level', '-s testType', '-s size', '-s timeout', - '-s dryRun' - ]) - let targetParams = ''; - for (const key in parameters) { - if (keySet.has(key)) { - targetParams = `${targetParams} ${key} ${parameters[key]}` - } - } - return targetParams.trim() -} - -async function onAbilityCreateCallback() { - console.log("onAbilityCreateCallback"); -} - -async function addAbilityMonitorCallback(err: any) { - console.info("addAbilityMonitorCallback : " + JSON.stringify(err)) -} - -export default class OpenHarmonyTestRunner implements TestRunner { - constructor() { - } - - onPrepare() { - console.info("OpenHarmonyTestRunner OnPrepare ") - } - - async onRun() { - console.log('OpenHarmonyTestRunner onRun run') - abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() - abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() - var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' - let lMonitor = { - abilityName: testAbilityName, - onAbilityCreate: onAbilityCreateCallback, - }; - abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) - var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName - cmd += ' '+translateParamsToString(abilityDelegatorArguments.parameters) - var debug = abilityDelegatorArguments.parameters["-D"] - if (debug == 'true') - { - cmd += ' -D' - } - console.info('cmd : '+cmd) - abilityDelegator.executeShellCommand(cmd, - (err: any, d: any) => { - console.info('executeShellCommand : err : ' + JSON.stringify(err)); - console.info('executeShellCommand : data : ' + d.stdResult); - console.info('executeShellCommand : data : ' + d.exitCode); - }) - console.info('OpenHarmonyTestRunner onRun end') - } -}; \ No newline at end of file diff --git a/product/pc/src/ohosTest/ets/test/Ability.test.ets b/product/pc/src/ohosTest/ets/test/Ability.test.ets deleted file mode 100644 index 05f3a3d..0000000 --- a/product/pc/src/ohosTest/ets/test/Ability.test.ets +++ /dev/null @@ -1,523 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' -import { TimerController, TimerState } from '@ohos/timer'; -<<<<<<< HEAD -import { CountdownController, CountdownState } from '@ohos/countdown'; -======= ->>>>>>> 34e999d (add timer ohosTest) -import { TimerUtil } from '@ohos/common'; - -export default function abilityTest() { - describe('ActsAbilityTest', function () { - - it('startState', 0, function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(TimerState.RUNNING); - testState = false; - } - }); - testState = true; - mTimerController.start(); - }) - - it('startTime', 0, function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setTimeUpdateListener((time) => { - if (testState) { - expect(time > 0).assertTrue(); - testState = false; - } - }); - testState = true; - mTimerController.start(); - }) - - it('clockUpdate', 0, function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setClockUpdateListener((time) => { - if (testState) { - expect(time > 0).assertTrue(); - testState = false; - } - }); - testState = true; - mTimerController.start(); - }) - - it('pauseState', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(TimerState.PAUSED); - testState = false; - } - }); - await mTimerController.start(); - testState = true; - setTimeout(async () => { - await mTimerController.pause(); - }, 100); - }) - - it('pauseTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - let testTime = 0; - mTimerController.setTimeUpdateListener((time) => { - if (testState) { - expect(testTime).assertEqual(time); - testState = false; - } else { - testTime = time; - } - }); - await mTimerController.start(); - await mTimerController.pause(); - testState = true; - setTimeout(async () => { - await mTimerController.start(); - }, 100); - }) - - it('resetAfterStartState', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(TimerState.IDLE); - testState = false; - } - }); - await mTimerController.start(); - testState = true; - setTimeout(async () => { - await mTimerController.reset(); - }, 100); - }) - - it('resetAfterStartTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setTimeUpdateListener((time) => { - if (testState) { - expect(time).assertEqual(0); - testState = false; - } - }); - await mTimerController.start(); - testState = true; - setTimeout(async () => { - await mTimerController.reset(); - }, 100); - }) - - it('reStartState', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(TimerState.RUNNING); - testState = false; - } - }); - await mTimerController.start(); - setTimeout(async () => { - await mTimerController.pause(); - testState = true; - await mTimerController.start(); - }, 100); - }) - - it('reStartStateTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - let testTime = 0; - mTimerController.setTimeUpdateListener((time) => { - if (testTime > 0) { - expect(testTime < time).assertTrue(); - testState = false; - } else if (testState == true) { - testTime = time; - } - }); - await mTimerController.start(); - await mTimerController.pause(); - testState = true; - setTimeout(async () => { - await mTimerController.start(); - }, 100); - }) - - it('resetAfterPauseState', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(TimerState.IDLE); - testState = false; - } - }); - await mTimerController.start(); - setTimeout(async () => { - await mTimerController.pause(); - testState = true; - await mTimerController.reset(); - }, 100); - }) - - it('resetAfterPauseTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setTimeUpdateListener((time) => { - if (testState) { - expect(time).assertEqual(0); - testState = false; - } - }); - await mTimerController.start(); - setTimeout(async () => { - await mTimerController.pause(); - testState = true; - await mTimerController.reset(); - }, 100); - }) - - it('writeTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setTimeListUpdateListener((timeList) => { - if (testState) { - expect(timeList.length > 1).assertTrue(); - testState = false; - } - }); - await mTimerController.start(); - setTimeout(async () => { - testState = true; - await mTimerController.writeTime(); - }, 100); - }) - - it('resetWriteTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setTimeListUpdateListener((timeList) => { - if (testState) { - expect(timeList.length).assertEqual(0); - testState = false; - } - }); - await mTimerController.start(); - setTimeout(async () => { - await mTimerController.writeTime(); - testState = true; - mTimerController.reset(); - }, 100); - }) - - it('addZeroMin', 0, async function () { - let num = 0 - let res = TimerUtil.addZero(num) - expect(res).assertEqual('00') - }) - - it('addZeroMinus', 0, async function () { - let num = -1 - let res = TimerUtil.addZero(num) - expect(res).assertEqual('0-1') - }) - - it('addZeroMax', 0, async function () { - let num = 9 - let res = TimerUtil.addZero(num) - expect(res).assertEqual('09') - }) - - it('addZeroOver', 0, async function () { - let num = 10 - let res = TimerUtil.addZero(num) - expect(res).assertEqual('10') - }) - - it('addZeroNaN', 0, async function () { - let num = NaN - let res = TimerUtil.addZero(num) - expect(res).assertEqual('NaN') - }) - - it('timeFormatZero', 0, async function () { - let num = 0 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`00:00.00`) - }) - - it('timeFormatMinus', 0, async function () { - let num = -1 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`00:00.00`) - }) - - it('timeFormatMinMs', 0, async function () { - let num = 9 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`00:00.00`) - }) - - it('timeFormatMs', 0, async function () { - let num = 10 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`00:00.01`) - }) - - it('timeFormatSec', 0, async function () { - let num = 1000 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`00:01.00`) - }) - - it('timeFormatMin', 0, async function () { - let num = 1000 * 60 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`01:00.00`) - }) - - it('timeFormatHour', 0, async function () { - let num = 1000 * 60 * 60 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`01:00:00.00`) - }) - - it('timeFormatDay', 0, async function () { - let num = 1000 * 60 * 60 * 24 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`01:00:00:00.00`) - }) -<<<<<<< HEAD - }) - - describe('CountdownTest', function () { - it('setTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testState) { - expect(totalTimeMs).assertEqual(100); - testState = false; - } - }); - testState = true; - mCountdownController.setTime(100); - }) - - it('setTimeState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.PREPARED); - testState = false; - } - }); - testState = true; - mCountdownController.setTime(100); - }) - - it('startState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.RUNNING); - testState = false; - } - }); - mCountdownController.setTime(100); - testState = true; - mCountdownController.start(); - }) - - it('startTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testState) { - expect(currentTimeMs > 0).assertTrue(); - testState = false; - } - }); - mCountdownController.setTime(100); - testState = true; - mCountdownController.start(); - }) - - it('pauseState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.PAUSED); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - testState = true; - setTimeout(() => { - mCountdownController.pause(); - }, 100); - }) - - it('pauseTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - let testTime = 0; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testState) { - expect(testTime).assertEqual(totalTimeMs); - testState = false; - } else { - testTime = totalTimeMs; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - mCountdownController.pause(); - testState = true; - setTimeout(() => { - mCountdownController.start(); - }, 100); - }) - - it('resetAfterStartState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.IDLE); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - testState = true; - setTimeout(() => { - mCountdownController.reset(); - }, 100); - }) - - it('resetAfterStartTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testState) { - expect(totalTimeMs).assertEqual(0); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - testState = true; - setTimeout(() => { - mCountdownController.reset(); - }, 100); - }) - - it('reStartState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.RUNNING); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - setTimeout(() => { - mCountdownController.pause(); - testState = true; - mCountdownController.start(); - }, 100); - }) - - it('reStartStateTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - let testTime = 0; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testTime > 0) { - expect(testTime < totalTimeMs).assertTrue(); - testState = false; - } else if (testState == true) { - testTime = currentTimeMs; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - mCountdownController.pause(); - testState = true; - setTimeout(() => { - mCountdownController.start(); - }, 100); - }) - - it('resetAfterPauseState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.IDLE); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - setTimeout(() => { - mCountdownController.pause(); - testState = true; - mCountdownController.reset(); - }, 100); - }) - - it('resetAfterPauseTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testState) { - expect(totalTimeMs).assertEqual(0); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - setTimeout(() => { - mCountdownController.pause(); - testState = true; - mCountdownController.reset(); - }, 100); - }) -======= - ->>>>>>> 34e999d (add timer ohosTest) - }) -} \ No newline at end of file diff --git a/product/pc/src/ohosTest/module.json5 b/product/pc/src/ohosTest/module.json5 deleted file mode 100644 index 0f40197..0000000 --- a/product/pc/src/ohosTest/module.json5 +++ /dev/null @@ -1,39 +0,0 @@ -{ - "module": { - "name": "pc_test", - "type": "feature", - "srcEntrance": "./ets/Application/TestAbilityStage.ts", - "description": "$string:entry_test_desc", - "mainElement": "TestAbility", - "deviceTypes": [ - "default", - "tablet" - ], - "deliveryWithInstall": true, - "installationFree": false, - "pages": "$profile:test_pages", - "uiSyntax": "ets", - "abilities": [ - { - "name": "TestAbility", - "srcEntrance": "./ets/TestAbility/TestAbility.ts", - "description": "$string:TestAbility_desc", - "icon": "$media:icon", - "label": "$string:TestAbility_label", - "visible": true, - "startWindowIcon": "$media:icon", - "startWindowBackground": "$color:white", - "skills": [ - { - "actions": [ - "action.system.home" - ], - "entities": [ - "entity.system.home" - ] - } - ] - } - ] - } -} \ No newline at end of file diff --git a/product/pc/src/ohosTest/resources/base/element/string.json b/product/pc/src/ohosTest/resources/base/element/string.json deleted file mode 100644 index 36d4230..0000000 --- a/product/pc/src/ohosTest/resources/base/element/string.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "string": [ - { - "name": "entry_test_desc", - "value": "test ability description" - }, - { - "name": "TestAbility_desc", - "value": "the test ability" - }, - { - "name": "TestAbility_label", - "value": "test label" - } - ] -} \ No newline at end of file diff --git a/product/pc/src/ohosTest/resources/base/media/icon.png b/product/pc/src/ohosTest/resources/base/media/icon.png deleted file mode 100644 index ce307a8827bd75456441ceb57d530e4c8d45d36c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6790 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 { + LogUtil.info(TAG, 'read backup file', JSON.stringify(item)); + }) + await cloneManager.cloneHandle(); + } else { + LogUtil.info(TAG, 'cloneData not found', cloneData); + if (bundleVersion.name === '0.0.0.0') { // 双升单 + LogUtil.info(TAG, 'start OTA upgrade.'); + await backupManager.restoreHandle(); + } else { // 单到单克隆 + LogUtil.info(TAG, 'start single to single clone'); + const list = fs.listFileSync('/data/storage/el2/backup/restore/', { + recursion: true // 递归查目录 + }); + list.map(item => { + LogUtil.info(TAG, 'read backup file', JSON.stringify(item)); + }) + await cloneManager.cloneSingleDB(); + } + } + } catch (err) { + LogUtil.info(TAG, 'accessSync failed with error message: ' + err.message + ', error code: ' + err.code); + } + } +} diff --git a/product/phone/src/main/ets/BackupExtension/BackupMananger.ets b/product/phone/src/main/ets/BackupExtension/BackupMananger.ets new file mode 100644 index 0000000..0d226f2 --- /dev/null +++ b/product/phone/src/main/ets/BackupExtension/BackupMananger.ets @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GlobalContext, LogUtil } from '@hmos/common'; +import fsManager from '../DBBackup/FSMananger'; +import DBManager from '../DBBackup/DBManager'; +import contextConstant from '@ohos.app.ability.contextConstant'; + + +const TAG: string = 'BackupManager'; + +interface IBackupManager { + restoreHandle: () => Promise; + dbRestore: () => Promise; + copyDoubleDB: () => Promise; +} + +function getContext(): Context { + return GlobalContext.getContext().getObject('clockBackupContext') as Context +} + +const backupManager: IBackupManager = { + restoreHandle: async () => { + LogUtil.info(TAG, `clockOnRestore BackupManager.copyDoubleDB`); + const cpSuccess = await backupManager.copyDoubleDB(); + if (!cpSuccess) { + return; + } + LogUtil.info(TAG, `clockOnRestore BackupManager.dbRestore()`); + LogUtil.info(TAG, `clockOnRestore dbRestore`); + await backupManager.dbRestore(); + }, + dbRestore: async () => { + LogUtil.info(TAG, 'DB migration begin') + const isSuccess = await DBManager.doubleToSingle(); + if (isSuccess) { + LogUtil.info(TAG, 'double to single DB migration success') + } + return isSuccess; + }, + copyDoubleDB: async () => { + const context = getContext(); + const doubleDBFilename = 'alarms.db'; + // backup 沙箱路径 + const sourcePath = '/data/storage/el1/backup/restore/com.huawei.deskclock/de/databases'; + context.area = contextConstant.AreaMode.EL1; + const targetFilePath = `${context.databaseDir}/rdb/${doubleDBFilename}`; + const sourceFilePath = `${sourcePath}/${doubleDBFilename}`; + + LogUtil.info(TAG, 'move double DB begin'); + const r = await fsManager(getContext()).moveFile(sourceFilePath, targetFilePath); + LogUtil.info(TAG, 'move double DB end', String(r)); + return r; + } +} + +export default backupManager; \ No newline at end of file diff --git a/product/phone/src/main/ets/BackupExtension/CloneManager.ets b/product/phone/src/main/ets/BackupExtension/CloneManager.ets new file mode 100644 index 0000000..47f74d9 --- /dev/null +++ b/product/phone/src/main/ets/BackupExtension/CloneManager.ets @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from '@ohos.file.fs'; +import contextConstant from '@ohos.app.ability.contextConstant'; +import { GlobalContext, LogUtil } from '@hmos/common'; +import CloneFSManager from '../DBBackup/CloneFSManager'; +import cloneDBManager from '../DBBackup/CloneDBManager'; + + +const TAG: string = 'cloneManager'; + +interface ICloneManagerManager { + dbRestore: () => Promise; + cloneHandle: () => Promise; + cloneAlarmDB: () => Promise; + cloneWorldClockDB: () => Promise; + cloneSingleDB: () => Promise; + judgeDBExist: () => Promise; +}; + +interface DoubleDBExist { + alarmExist: boolean; + worldClockExist: boolean; +} + +function getContext(): Context { + return GlobalContext.getContext().getObject('clockBackupContext') as Context +} + +const cloneManager: ICloneManagerManager = { + cloneHandle: async () => { + const existData: DoubleDBExist = await cloneManager.judgeDBExist() + if (existData.alarmExist) { + const cloneSuccess = await cloneManager.cloneAlarmDB(); + if (!cloneSuccess) { + return; + } + } + if (existData.worldClockExist) { + const cloneSuccess = await cloneManager.cloneWorldClockDB(); + if (!cloneSuccess) { + return; + } + } + LogUtil.info(TAG, 'Exist:', JSON.stringify(existData)); + await cloneManager.dbRestore(); + }, + + dbRestore: async () => { + LogUtil.info(TAG, 'clone DB migration begin'); + const isSuccess = await cloneDBManager.doubleToSingle(); + if (isSuccess) { + LogUtil.info(TAG, 'clone DB migration success'); + } + return isSuccess; + }, + /** + * 将克隆得到的闹钟数据库移动到数据库沙箱 + * */ + cloneAlarmDB: async () => { + const context = getContext(); + context.area = contextConstant.AreaMode.EL1; + const sourceAlarmFileName = 'alarm.db'; + const sourcePath = '/data/storage/el2/backup/restore/'; + const targetAlarmFilePath = `${context.databaseDir}/rdb/${sourceAlarmFileName}`; + const sourceAlarmFilePath = `${sourcePath}${sourceAlarmFileName}`; + LogUtil.info(TAG, 'clone double DB begin' + targetAlarmFilePath); + const alarmSuccess = await CloneFSManager(getContext()).moveFile(sourceAlarmFilePath, targetAlarmFilePath); + LogUtil.info(TAG, 'copy double DB end'); + return alarmSuccess; + }, + /** + * 将克隆得到的世界时钟数据库移动到数据库沙箱 + * */ + cloneWorldClockDB: async () => { + const context = getContext(); + context.area = contextConstant.AreaMode.EL1; + const sourceClockFileName = 'clock.db'; + const sourcePath = '/data/storage/el2/backup/restore/'; + const targetClockFilePath = `${context.databaseDir}/rdb/worldClock.db` + const sourceClockFilePath = `${sourcePath}${sourceClockFileName}`; + const clockSuccess = await CloneFSManager(getContext()).moveFile(sourceClockFilePath, targetClockFilePath); + return clockSuccess; + }, + /** + * 单到单克隆数据库替换 + * */ + cloneSingleDB: async () => { + const context = getContext(); + context.area = contextConstant.AreaMode.EL1; + const sourceSingleDBName = 'Clock.db'; + const sourceSingleSHMName = 'Clock.db-shm'; + const sourceSingleWALName = 'Clock.db-wal'; + const sourcePath = '/data/storage/el2/backup/restore/data/storage/el1/database/entry/rdb/'; + const targetSingleDBPath = `${context.databaseDir}/rdb/cloneClock.db`; + const targetSingleSHMPath = `${context.databaseDir}/rdb/cloneClock.db-shm`; + const targetSingleWALPath = `${context.databaseDir}/rdb/cloneClock.db-wal`; + const sourceSingleDBPath = `${sourcePath}${sourceSingleDBName}`; + const sourceSingleSHMPath = `${sourcePath}${sourceSingleSHMName}`; + const sourceSingleWALPath = `${sourcePath}${sourceSingleWALName}`; + const dbSuccess = await CloneFSManager(getContext()).moveFile(sourceSingleDBPath, targetSingleDBPath); + const shmSuccess = await CloneFSManager(getContext()).moveFile(sourceSingleSHMPath, targetSingleSHMPath); + const walSuccess = await CloneFSManager(getContext()).moveFile(sourceSingleWALPath, targetSingleWALPath); + const backupSuccess = await cloneDBManager.restoreClock() + return backupSuccess; + }, + /** + * 针对克隆时选择的包为闹钟和世界时钟判断 + * */ + judgeDBExist: async () => { + const alarmData: string = '/data/storage/el2/backup/restore/alarm.db'; + const worldClockData: string = '/data/storage/el2/backup/restore/clock.db'; + let alarmExist = fs.accessSync(alarmData); + let worldClockExist = fs.accessSync(worldClockData); + let resault: DoubleDBExist = { alarmExist: alarmExist, worldClockExist: worldClockExist }; + return resault; + } +} + +export default cloneManager; \ No newline at end of file diff --git a/product/phone/src/main/ets/DBBackup/CloneDBHandle.ets b/product/phone/src/main/ets/DBBackup/CloneDBHandle.ets new file mode 100644 index 0000000..69de752 --- /dev/null +++ b/product/phone/src/main/ets/DBBackup/CloneDBHandle.ets @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dataRdb from '@ohos.data.relationalStore'; +import { ValuesBucket, ValueType } from '@ohos.data.ValuesBucket'; +import { AlarmInfo, LogUtil } from '@hmos/common'; + +type TQueryFn = (predicates: dataRdb.RdbPredicates) => void; +type TQueryReadFn = (resultSet: dataRdb.ResultSet) => void; + +const TAG: string = 'CloneDBHandle'; + +class CloneDBHandle { + public rdbStore: dataRdb.RdbStore = {} as dataRdb.RdbStore; + public dbConfig: IRelationalStoreConfig + + constructor(options: IRelationalStoreConfig) { + this.dbConfig = options + } + + async getRdbStore(context: Context, storeConfig: dataRdb.StoreConfig, createSql?: string): Promise { + LogUtil.info(TAG, 'getRdbStore', context.databaseDir, JSON.stringify(storeConfig)); + /* + * 获取关系型数据库 + * context 上下文 + * storeConfig 数据库配置 { name: 数据库名, securityLevel: 数据安全级别 } + * */ + // 获取关系型数据库 + try { + const rdbStore: dataRdb.RdbStore = await dataRdb.getRdbStore(context, storeConfig); + this.rdbStore = rdbStore; + LogUtil.info(TAG, 'create rdb success', JSON.stringify(this.rdbStore)); + if (createSql) { + await this.createTable(createSql); + } + } catch (error) { + LogUtil.info(TAG, 'create rdb fail', JSON.stringify(error)); + } + return this; + } + + async createTable(createSql: string) { + LogUtil.info(TAG, 'createSQL', JSON.stringify(createSql)) + try { + const r = await this.rdbStore.executeSql(createSql); + LogUtil.info(TAG, 'create rdb table success', String(r)); + } catch (error) { + LogUtil.info(TAG, 'create rdb table fail', JSON.stringify(error)); + } + } + + async dbQuery(tableName: string, fieldQueryFn: TQueryFn, readHandle?: TQueryReadFn): Promise { + const predicates: dataRdb.RdbPredicates = new dataRdb.RdbPredicates(tableName); + fieldQueryFn(predicates); + let result: dataRdb.ResultSet = {} as dataRdb.ResultSet; + try { + const resultSet: dataRdb.ResultSet = await this.rdbStore.query(predicates); + LogUtil.info(TAG, 'query data success', String(resultSet.rowCount)); + result = resultSet + } catch (error) { + LogUtil.info(TAG, 'query data fail', error); + } + return result + } + + async dbInsert(tableName: string, row: ValuesBucket) { + try { + const r: number = await this.rdbStore.insert(tableName, row); + LogUtil.info(TAG, 'insert data success', String(r)); + return r + } catch (error) { + LogUtil.info(TAG, 'insert data fail', error.code); + } + return -1; + } + + async dbBatchInsert(tableName: string, rows: Array) { + try { + const r: number = await this.rdbStore.batchInsert(tableName, rows); + LogUtil.info(TAG, 'insert data success'); + LogUtil.info(TAG, 'insert 0', tableName, JSON.stringify(rows)); + return r + } catch (error) { + LogUtil.info(TAG, 'insert data fail', error); + } + return -1; + } + + async dbClearTable(sql: string): Promise { + try { + const r = await this.rdbStore.executeSql(sql); + LogUtil.info(TAG, 'clear table success'); + return true; + } catch (error) { + LogUtil.info(TAG, 'clear table fail', JSON.stringify(error)); + } + return false; + } + + async deleteDoubleRdb(context: Context, rdbName: string) { + LogUtil.info(TAG, 'start delete double rdb: ', JSON.stringify(rdbName)); + try { + await dataRdb.deleteRdbStore(context, rdbName); + LogUtil.info(TAG, 'delete double rdb success'); + } catch (error) { + LogUtil.info(TAG, 'delete double rdb fail: ', JSON.stringify(error)); + } + } + + readResultSet(resultSet: dataRdb.ResultSet): Array { + let a: AlarmInfo[] = []; + while (resultSet.goToNextRow()) { + const daysOfWeekNative: Uint8Array = resultSet.getBlob(resultSet.getColumnIndex('DAYS_OF_WEEK')); + const daysOfWeek: number[] = []; + daysOfWeekNative.forEach(day => { + daysOfWeek.push(day); + }); + + let id: string = resultSet.getString(resultSet.getColumnIndex('ID')); + let hour: number = resultSet.getLong(resultSet.getColumnIndex('HOUR')); + let minute: number = resultSet.getLong(resultSet.getColumnIndex('MINUTE')); + let second: number = resultSet.getLong(resultSet.getColumnIndex('SECOND')); + let enabled: boolean = !!resultSet.getLong(resultSet.getColumnIndex('ENABLED')); + let alarmTime: number = resultSet.getLong(resultSet.getColumnIndex('ALARM_TIME')); + let title: string = resultSet.getString(resultSet.getColumnIndex('TITLE')); + let ringDuration: number = resultSet.getLong(resultSet.getColumnIndex('RING_DURATION')); + let snoozeDuration: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_DURATION')); + let snoozeTimes: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_TIMES')); + let snoozeCount: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_COUNT')); + resultSet.getBlob(resultSet.getColumnIndex('DAYS_OF_WEEK')); + + + a.push({ + id, + hour, + minute, + second, + enabled, + alarmTime, + title, + ringDuration, + snoozeDuration, + snoozeTimes, + snoozeCount, + daysOfWeek + }) + } + return a; + } +} + +export { CloneDBHandle } + +export type TReadDataSetHandle = (arg: dataRdb.ResultSet) => T + +export interface IRelationalStoreConfig { + context: Context + storeConfig: dataRdb.StoreConfig + createSql?: string + readDataSetHandle?: TReadDataSetHandle +} + +export default function (config: IRelationalStoreConfig): Promise { + const dbHandle = new CloneDBHandle(config); + return new Promise((resolve) => { + dbHandle.getRdbStore(config.context, config.storeConfig, config.createSql).then((rdbh) => { + resolve(rdbh); + }) + }) +} \ No newline at end of file diff --git a/product/phone/src/main/ets/DBBackup/CloneDBManager.ets b/product/phone/src/main/ets/DBBackup/CloneDBManager.ets new file mode 100644 index 0000000..1cf9681 --- /dev/null +++ b/product/phone/src/main/ets/DBBackup/CloneDBManager.ets @@ -0,0 +1,625 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import relationalStore from '@ohos.data.relationalStore'; +import { ValuesBucket, ValueType } from '@ohos.data.ValuesBucket'; +import contextConstant from '@ohos.app.ability.contextConstant'; +import i18n from '@ohos.i18n'; +import { BusinessError } from '@ohos.base'; +import { + TimeUtil, + GlobalContext, + ALARM_TIME_NEVER, + AlarmInfo, + AlarmManager, + ENABLED_FALSE, + ENABLED_TRUE, + INIT_SNOOZE_COUNT, + LogUtil, + ResourceManager, + TimerManager, + CommonUtil, + SQL_CREATE_TABLE +} from '@hmos/common'; +import { WorldClockInfo, WorldClockManager } from '@hmos/worldclock'; +import CloneDB, { CloneDBHandle, IRelationalStoreConfig } from './CloneDBHandle'; +import cloneManager from '../BackupExtension/CloneManager'; + + +interface IDoubleDataRow { + enabled: boolean, + ringduration: number, + volume: number, + alarmtime: number, + daysofweektype: number, + hour: number, + alert: string, + snoozetimes: number, + vibrate: number, + message: string, + daysofweek: number, + minutes: number, + snoozeduration: number, +} + +interface DoubleWorldDataRow { + _id: number, + timezone: string, + homecity: number, + sort_order: number, + city_index: string +} + +interface TDBManager { + getRDBH: (config: IRelationalStoreConfig) => Promise + singleReadData: () => Promise>, + singleReadResultSet: (resultSet: relationalStore.ResultSet) => Array, + doubleReadData: () => Promise>, + worldClockReadData: () => Promise>, + readRestoreAlarmData: () => Promise>, + readRestoreWorldData: () => Promise>, + doubleReadResultSet: (resultSet: relationalStore.ResultSet) => Array, + worldReadResultSet: (resultSet: relationalStore.ResultSet) => Array, + formatToSingle: (doubleData: Array) => Array, + doubleToSingle: () => Promise, + singleRowformatVB: (alarmInfo: AlarmInfo) => ValuesBucket, + singleInsertTest?: () => Promise, + transformDayOfWeek: (num: number) => Array, + clearSingleTable: (tableName: string) => Promise; + getWorldClockCityId: (cityIndex: string) => string; + initWorldClockInfo: (cityIndex: string) => WorldClockInfo; + updateTimer: () => Promise; + addWorldClockList: (worldClockList: Array) => Promise; + restoreClock: () => Promise; +} + +const TAG: string = 'CloneDBManager'; +const DEFAULT_SORT_ORDER = 9999; +const singleStoreConfig: relationalStore.StoreConfig = { name: 'Clock.db', securityLevel: 1 }; +const alarmStoreConfig: relationalStore.StoreConfig = { name: 'alarm.db', securityLevel: 1 }; +const worldStoreConfig: relationalStore.StoreConfig = { name: 'worldClock.db', securityLevel: 1 }; +const restoreConfig: relationalStore.StoreConfig = { name: 'cloneClock.db', securityLevel: 1 }; +const singleTableName: string = 'ALARM_CLOCK'; +const singleWorldTableName: string = 'WORLD_CLOCK'; +const alarmTableName: string = 'deskclock_alarm_tb'; +// 数据库的建表 SQL +const alarmCreateSql = ` + CREATE TABLE IF NOT EXISTS ${alarmTableName} ( + _ID INTEGER PRIMARY KEY AUTOINCREMENT, + enabled INTEGER, + ringduration INTEGER DEFAULT 5, + volume INTEGER, + alarmtime INTEGER, + daysofweektype INTEGER, + hour INTEGER, + alert TEXT, + snoozetimes INTEGER DEFAULT 3, + vibrate INTEGER, + message TEXT, + minutes INTEGER, + daysofweek INTEGER, + snoozeduration INTEGER DEFAULT 10) + `; +const worldTableName: string = 'comandroiddeskclockbackuplocation_items_tb'; +// 双框架世界时钟的建表 SQL +const worldCreateSql = ` + CREATE TABLE IF NOT EXISTS ${worldTableName} ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + homecity INTEGER, + sort_order INTEGER, + timezone TEXT, + city_index TEXT) + `; + +// 默认时钟标签 +let defaultTitle: string = ''; +let defaultDemoTitle: string = ''; +const DEFAULT_ALARM_TITLE_RESOURCES: Resource = $r('app.string.alarm_clock'); +const DEFAULT_ALARM_DEMO_TITLE_RESOURCES: Resource = $r('app.string.default_alarm_label'); + +export let singleRDBH: CloneDBHandle; + +export let doubleRDBH: CloneDBHandle; + +export let worldRDBH: CloneDBHandle; + +export let restoreRDBH: CloneDBHandle; + +function getContext(): Context { + let context: Context = GlobalContext.getContext().getObject('clockBackupContext') as Context + context.area = contextConstant.AreaMode.EL1; + return context; +} + +async function getSingleRDBH(): Promise { + if (singleRDBH) { + return singleRDBH; + } + singleRDBH = await cloneDBManager.getRDBH({ + context: getContext(), + storeConfig: singleStoreConfig, + createSql: SQL_CREATE_TABLE + }); + return singleRDBH; +} + +async function getAlarmRDBH(): Promise { + if (doubleRDBH) { + return singleRDBH; + } + doubleRDBH = await cloneDBManager.getRDBH({ + context: getContext(), + storeConfig: alarmStoreConfig, + createSql: alarmCreateSql + }); + return doubleRDBH; +} + +async function getWorldRDBH(): Promise { + worldRDBH = await cloneDBManager.getRDBH({ + context: getContext(), + storeConfig: worldStoreConfig, + createSql: worldCreateSql + }); + return worldRDBH; +} + +async function getRestoreRDBH(): Promise { + restoreRDBH = await cloneDBManager.getRDBH({ + context: getContext(), + storeConfig: restoreConfig + }); + return restoreRDBH; +} + +const cloneDBManager: TDBManager = { + getRDBH: async (config: IRelationalStoreConfig) => { + return await CloneDB({ context: getContext(), storeConfig: config.storeConfig, createSql: config.createSql }); + }, + + singleReadData: async (): Promise> => { + let rdbHandle = await getSingleRDBH(); + let resultSet = await rdbHandle.dbQuery(singleTableName, (predicates) => { + predicates.orderByAsc('HOUR'); + predicates.orderByAsc('MINUTE'); + }); + let result = await cloneDBManager.singleReadResultSet(resultSet); + resultSet.close(); + return result; + }, + + singleReadResultSet: (resultSet: relationalStore.ResultSet): Array => { + let a: AlarmInfo[] = []; + while (resultSet.goToNextRow()) { + const daysOfWeekNative: Uint8Array = resultSet.getBlob(resultSet.getColumnIndex('DAYS_OF_WEEK')); + const daysOfWeek: number[] = []; + daysOfWeekNative.forEach(day => { + daysOfWeek.push(day); + }); + + let id: string = resultSet.getString(resultSet.getColumnIndex('ID')); + let hour: number = resultSet.getLong(resultSet.getColumnIndex('HOUR')); + let minute: number = resultSet.getLong(resultSet.getColumnIndex('MINUTE')); + let second: number = resultSet.getLong(resultSet.getColumnIndex('SECOND')); + let enabled: boolean = !!resultSet.getLong(resultSet.getColumnIndex('ENABLED')); + let alarmTime: number = resultSet.getLong(resultSet.getColumnIndex('ALARM_TIME')); + let title: string = resultSet.getString(resultSet.getColumnIndex('TITLE')); + let ringDuration: number = resultSet.getLong(resultSet.getColumnIndex('RING_DURATION')); + let snoozeDuration: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_DURATION')); + let snoozeTimes: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_TIMES')); + let snoozeCount: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_COUNT')); + resultSet.getBlob(resultSet.getColumnIndex('DAYS_OF_WEEK')); + + a.push({ + id, + hour, + minute, + second, + enabled, + alarmTime, + title, + ringDuration, + snoozeDuration, + snoozeTimes, + snoozeCount, + daysOfWeek + }) + } + return a; + }, + + doubleReadData: async (): Promise> => { + let rdbHandle = doubleRDBH = await getAlarmRDBH(); + let resultSet = await rdbHandle.dbQuery(alarmTableName, (predicates) => { + predicates.orderByAsc('hour'); + predicates.orderByAsc('minutes'); + }); + + let result = await cloneDBManager.doubleReadResultSet(resultSet); + await resultSet.close(); + return result; + }, + + worldClockReadData: async (): Promise> => { + LogUtil.info(TAG, 'start to read world clock') + let rdbHandle = worldRDBH = await getWorldRDBH(); + LogUtil.info(TAG, 'get world rdbh success') + let resultSet = await rdbHandle.dbQuery(worldTableName, (predicates) => { + predicates.orderByAsc('sort_order'); + }); + let result = await cloneDBManager.worldReadResultSet(resultSet); + await resultSet.close(); + return result; + }, + + readRestoreAlarmData: async (): Promise> => { + LogUtil.info(TAG, 'start to read restore alarm clock'); + let rdbHandle = await getRestoreRDBH(); + LogUtil.info(TAG, 'get alarm restore rdbh success'); + let resultSet = await rdbHandle.dbQuery(singleTableName, (predicates) => { + predicates.orderByAsc('HOUR'); + predicates.orderByAsc('MINUTE'); + }); + let result = await AlarmManager.getAlarmListFromResultSet(resultSet); + await resultSet.close(); + LogUtil.info(TAG, JSON.stringify(result)); + return result; + }, + + readRestoreWorldData: async (): Promise> => { + LogUtil.info(TAG, 'start to read restore world clock') + let rdbHandle = await getRestoreRDBH(); + LogUtil.info(TAG, 'get restore world rdbh success') + let resultSet = await rdbHandle.dbQuery(singleWorldTableName, (predicates) => { + predicates.orderByAsc('ID'); + }); + let result = await WorldClockManager.getWorldClockListFromResultSet(resultSet); + await resultSet.close(); + LogUtil.info(TAG, JSON.stringify(result)); + return result; + }, + + doubleReadResultSet: (resultSet: relationalStore.ResultSet): Array => { + let a: IDoubleDataRow[] = []; + while (resultSet.goToNextRow()) { + let hour: number = resultSet.getLong(resultSet.getColumnIndex('hour')); + let minutes: number = resultSet.getLong(resultSet.getColumnIndex('minutes')); + let daysofweek = resultSet.getLong(resultSet.getColumnIndex('daysofweek')); + let alarmtime: number = resultSet.getLong(resultSet.getColumnIndex('alarmtime')); + let enabled: boolean = !!resultSet.getLong(resultSet.getColumnIndex('enabled')); + let vibrate: number = resultSet.getLong(resultSet.getColumnIndex('vibrate')); + let volume: number = resultSet.getLong(resultSet.getColumnIndex('volume')); + let message: string = resultSet.getString(resultSet.getColumnIndex('message')); + let alert: string = resultSet.getString(resultSet.getColumnIndex('alert')); + let daysofweektype: number = resultSet.getLong(resultSet.getColumnIndex('daysofweektype')); + let ringduration: number = resultSet.getLong(resultSet.getColumnIndex('ringduration')); + let snoozeduration: number = resultSet.getLong(resultSet.getColumnIndex('snoozeduration')); + let snoozetimes: number = resultSet.getLong(resultSet.getColumnIndex('snoozetimes')); + + + a.push({ + hour, + minutes, + daysofweek, + alarmtime, + enabled, + vibrate, + volume, + message, + alert, + daysofweektype, + ringduration, + snoozeduration, + snoozetimes + }) + } + return a; + }, + + worldReadResultSet: (resultSet: relationalStore.ResultSet): Array => { + let result: DoubleWorldDataRow[] = []; + while (resultSet.goToNextRow()) { + let _id: number = resultSet.getLong(resultSet.getColumnIndex('_id')); + let timezone: string = resultSet.getString(resultSet.getColumnIndex('timezone')); + let homecity: number = resultSet.getLong(resultSet.getColumnIndex('homecity')); + let sort_order: number = resultSet.getLong(resultSet.getColumnIndex('sort_order')); + let city_index: string = resultSet.getString(resultSet.getColumnIndex('city_index')); + + result.push({ + _id, + timezone, + homecity, + sort_order, + city_index + }) + } + return result + }, + + doubleToSingle: async () => { + const existData = await cloneManager.judgeDBExist(); + LogUtil.info(TAG, 'Exist:', JSON.stringify(existData)); + GlobalContext.getContext() + .setObject('clockContext', GlobalContext.getContext().getObject('clockBackupContext') as Context); + defaultTitle = await ResourceManager.getStringByIdAsync((DEFAULT_ALARM_TITLE_RESOURCES).id); + defaultDemoTitle = await ResourceManager.getStringByIdAsync((DEFAULT_ALARM_DEMO_TITLE_RESOURCES).id); + LogUtil.info(`${TAG}, defaultTitle is ${JSON.stringify(defaultTitle)}; defaultDemoTitle is ${JSON.stringify(defaultDemoTitle)}`); + if (existData.alarmExist) { + const data: IDoubleDataRow[] = await cloneDBManager.doubleReadData(); // 读取双框架闹钟数据 + LogUtil.info(TAG, 'get double data success'); + let formatData = await cloneDBManager.formatToSingle(data) + .map(item => cloneDBManager.singleRowformatVB(item)); // 转换为单框架数据格式 + LogUtil.info(TAG, 'format to single data success', JSON.stringify(formatData)); + if (!singleRDBH) { + LogUtil.info(TAG, 'singleRDBH is undefined'); + singleRDBH = await getSingleRDBH(); + } + LogUtil.info(TAG, 'clear single table start'); + const cr = await cloneDBManager.clearSingleTable(singleTableName); + if (cr) { + LogUtil.info(TAG, 'clear single table success'); + } + const r = await singleRDBH.dbBatchInsert(singleTableName, formatData); + if (r === -1) { + LogUtil.info(TAG, 'insert to single DB fail'); + return false; + } + ; + await cloneDBManager.updateTimer(); + LogUtil.info(TAG, 'insert to single DB success'); + await singleRDBH.deleteDoubleRdb(getContext(), 'alarm.db') + LogUtil.info(TAG, 'success over'); + } + if (existData.worldClockExist) { + LogUtil.info(TAG, 'start to handle world clock data') + const data: DoubleWorldDataRow[] = await cloneDBManager.worldClockReadData(); // 读取双框架世界时钟数据 + LogUtil.info(TAG, 'get double worldClock data success', JSON.stringify(data)); + const cr = await cloneDBManager.clearSingleTable(singleWorldTableName); + if (cr) { + LogUtil.info(TAG, 'clear world clock table success'); + } + await cloneDBManager.addWorldClockList(data); + await worldRDBH.deleteDoubleRdb(getContext(), 'worldClock.db') + } + return true; + }, + + formatToSingle(doubleData) { + LogUtil.info(TAG, 'doubleData is ', JSON.stringify(doubleData)); + let formatResult = doubleData.map((item) => { + let hour = item.hour; + let minute = item.minutes; + let second = 0; + let enabled = item.enabled; + let alarmTime = item.alarmtime; + let title = item.message ? item.message : defaultTitle; + let ringDuration = item.ringduration; + let snoozeDuration = item.snoozeduration; + let snoozeTimes = item.snoozetimes; + let snoozeCount = item.snoozetimes; + let daysOfWakeType = item.daysofweektype || 0; + if (item.daysofweektype === 1) { + daysOfWakeType = 3; + } + /** + * 针对双框架数据库字段 daysofweek 字段说明 + * 双框架 daysofweek 在数据库中存储为 10进制数字,例如:31, 96 需要转2进制进行星期识别 + * 31 转 2进制结果为 11111, 5个1 表示设置了 周五,周四,周三,周二,周一 + * 96 转 2进制结果为 1100000, 2个1和5个0 表示设置了 周日,周六; (周五,周四,周三,周二,周一 为0 表示未设置); + * 针对双框数据 转换为 [0,0,0,0,0,0,0] 表示 [周日,周一,周二,周三,周四,周五,周六] + * + * 单框架数据 0:周日,1:周一,2:周二,3:周三,4:周四,5:周五,6:周六 + * */ + let daysOfWeek = cloneDBManager.transformDayOfWeek(item.daysofweek); + + /** + * 双框架示例数据无标题问题 + * 判断是否为双的示例数据 + * */ + if (!item.message && Number(item.volume) === 3) { + LogUtil.info(TAG, `demo data ${defaultTitle}`); + title = defaultDemoTitle; + } + + return { + hour, + minute, + second, + enabled, + alarmTime, + title, + ringDuration, + snoozeDuration, + snoozeTimes, + snoozeCount, + daysOfWeek, + daysOfWakeType + } + }) + return formatResult; + }, + + transformDayOfWeek: (num) => { + let str: string = Number(num).toString(2).padStart(7, '0'); + if (str) { + let arr: string[] = str.split('').reverse(); + if (arr && arr.length > 0) { + arr!.unshift(arr.pop() as string); + } + let rst: number[] = arr.reduce>((r, it, i) => { + (Number(it) === 1) && r.push(Number(i)); + return r + }, []); + return rst; + } else { + return []; + } + }, + + clearSingleTable: async (tableName) => { + LogUtil.info(TAG, 'enter clearSingleTable'); + let rdbHandle = await getSingleRDBH(); + let CLEAR_TABLE_SQL = `DELETE FROM ${tableName}`; + let r = await rdbHandle.dbClearTable(CLEAR_TABLE_SQL); + return r; + }, + singleRowformatVB: (alarmInfo) => { + return { + 'TITLE': alarmInfo.title, + 'HOUR': alarmInfo.hour, + 'MINUTE': alarmInfo.minute, + 'SECOND': alarmInfo.second || 0, + 'DAYS_OF_WEEK': new Uint8Array(alarmInfo.daysOfWeek || []), + 'ALARM_TIME': alarmInfo.enabled ? TimeUtil.getAlarmTime(alarmInfo) : ALARM_TIME_NEVER, + 'ENABLED': alarmInfo.enabled ? ENABLED_TRUE : ENABLED_FALSE, + 'RING_DURATION': alarmInfo.ringDuration as ValueType, + 'SNOOZE_DURATION': alarmInfo.snoozeDuration, + 'SNOOZE_TIMES': alarmInfo.snoozeTimes, + 'SNOOZE_COUNT': alarmInfo.snoozeCount || INIT_SNOOZE_COUNT, + 'DAYS_OF_WAKE_TYPE': alarmInfo.daysOfWakeType as ValueType + }; + }, + + getWorldClockCityId(cityIndex) { + let lastDeliver: number = cityIndex.lastIndexOf('_'); + let secondDeliver: number = cityIndex.lastIndexOf('_', (lastDeliver - 1)); + const spaceReg: RegExp = new RegExp('\\s', 'g'); + const sliceReg: RegExp = new RegExp('\-', 'g'); + const transAReg: RegExp = new RegExp('\á', 'g'); + const transSAReg: RegExp = new RegExp('\ã', 'g'); + const transEReg: RegExp = new RegExp('\é', 'g'); + const transIReg: RegExp = new RegExp('\í', 'g'); + const transOReg: RegExp = new RegExp('\ó', 'g'); + const transNReg: RegExp = new RegExp('\ñ', 'g'); + const deliverReg: RegExp = new RegExp('\'', 'g'); + const pointReg: RegExp = new RegExp('\\.', 'g'); + const andReg: RegExp = new RegExp('\&', 'g'); + let doubleCityIndex: string = cityIndex.substring((secondDeliver + 1), lastDeliver); + let cityId: string = cityIndex.substring((secondDeliver + 1), lastDeliver) + .replace(spaceReg, '_') + .replace(sliceReg, '') + .replace(transAReg, 'a') + .replace(transSAReg, 'a') + .replace(transEReg, 'e') + .replace(transIReg, 'i') + .replace(transOReg, 'o') + .replace(transNReg, 'n') + .replace(deliverReg, '') + .replace(pointReg, '') + .replace(andReg, 'amp'); + switch (doubleCityIndex) { + case 'Kingston': + case 'Santa Clara': + case 'San Juan': + case 'San Jose': { + cityId = cityIndex.substring((secondDeliver + 1)).replace(spaceReg, '_'); + break; + } + case 'Basseterre': { + cityId = 'Basseterre_St_Kitts_amp_Nevis'; + break; + } + case 'Basse-terre': { + cityId = 'Basseterre_Guadeloupe'; + break; + } + case 'St. Johns': { + cityId = cityIndex.substring((secondDeliver + 1)).replace(spaceReg, '_').replace(pointReg, ''); + break; + } + case `St. John's`: { + cityId = cityIndex.substring((secondDeliver + 1)) + .replace(spaceReg, '_') + .replace(pointReg, '') + .replace(andReg, 'amp') + .replace(deliverReg, ''); + break; + } + } + LogUtil.info(TAG, 'cityId: ', JSON.stringify(cityId)); + return cityId; + }, + + initWorldClockInfo: (cityId) => { + const cityIndex = cityId; + const timezone = i18n.TimeZone.getTimezoneFromCity(cityId).getID(); + const city = i18n.TimeZone.getCityDisplayName(cityId, i18n.getSystemLocale()); + const offset = i18n.TimeZone.getTimezoneFromCity(cityId).getRawOffset(); + return { + sortOrder: DEFAULT_SORT_ORDER, + cityIndex, + timezone, + city, + offset, + }; + }, + + updateTimer: async () => { + const nearstAlarm = await AlarmManager.getNearestAlarm(); + if (!nearstAlarm) { + LogUtil.info(TAG, 'No Alarm Enabled'); + return; + } else { + await TimerManager.startAlarm(nearstAlarm); + const timeStamp = await TimerManager.getTriggerTime(); + CommonUtil.refreshNearestAlarmTime(timeStamp); + return; + } + }, + + addWorldClockList: async (worldClockList) => { + for (const worldClockInfo of worldClockList) { + let cityId: string = cloneDBManager.getWorldClockCityId(worldClockInfo.city_index); + await WorldClockManager.addWorldClock(cloneDBManager.initWorldClockInfo(cityId)); + } + }, + + restoreClock: async () => { + let alarmRestoreData: AlarmInfo[] = await cloneDBManager.readRestoreAlarmData(); + let worldRestoreData: WorldClockInfo[] = await cloneDBManager.readRestoreWorldData(); + GlobalContext.getContext() + .setObject('clockContext', GlobalContext.getContext().getObject('clockBackupContext') as Context); + const cleanAlarm = await cloneDBManager.clearSingleTable(singleTableName); + if (cleanAlarm) { + LogUtil.info(TAG, 'clear alarm table success'); + } + ; + const cleanWorld = await cloneDBManager.clearSingleTable(singleWorldTableName); + if (cleanWorld) { + LogUtil.info(TAG, 'clear world table success'); + } + for (const alarmInfo of alarmRestoreData) { + if (alarmInfo.title === '') { + try { + const PRE_ALARM_TITLE_LIST: number[] = [DEFAULT_ALARM_DEMO_TITLE_RESOURCES.id]; + await ResourceManager.preloadStringResources(PRE_ALARM_TITLE_LIST); + alarmInfo.title = await ResourceManager.getStringByIdAsync(DEFAULT_ALARM_DEMO_TITLE_RESOURCES.id); + } catch (error) { + LogUtil.error(TAG, 'getString error:', (error as BusinessError).message); + } + + } + await AlarmManager.addAlarm(alarmInfo, false, true); + } + for (const worldClockInfo of worldRestoreData) { + await WorldClockManager.addWorldClock(worldClockInfo); + } + await cloneDBManager.updateTimer(); + await restoreRDBH.deleteDoubleRdb(getContext(), 'cloneClock.db') + return true + } +} + +export default cloneDBManager; diff --git a/product/phone/src/main/ets/DBBackup/CloneFSManager.ets b/product/phone/src/main/ets/DBBackup/CloneFSManager.ets new file mode 100644 index 0000000..3b2f59c --- /dev/null +++ b/product/phone/src/main/ets/DBBackup/CloneFSManager.ets @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from '@ohos.file.fs'; +import { LogUtil } from '@hmos/common'; +import { Context } from '@ohos.abilityAccessCtrl'; + +const TAG = `CloneFSManager`; + + +interface ICloneFSManager { + context: Context; + + moveFile(sourceFilePath: string, targetFilePath: string): Promise; +} + + +class CloneFSManager implements ICloneFSManager { + public context = {} as Context; + + constructor(context: Context) { + this.context = context; + } + + async moveFile(sourceFilePath: string, targetFilePath: string): Promise { + try { + const exist = fs.accessSync(sourceFilePath); + LogUtil.info(TAG, 'sourceFilePath exist', JSON.stringify(exist)); + if (!exist) { + LogUtil.info(TAG, 'sourceFilePath not found', sourceFilePath); + return false; + } + const targetPath = targetFilePath.substring(0, targetFilePath.lastIndexOf('/')); + const existTargetFilePath = await fs.access(targetPath); + if (!existTargetFilePath) { + LogUtil.info(TAG, 'targetPath not found', targetPath); + fs.mkdirSync(targetPath); + LogUtil.info(TAG, 'create targetPath success', targetPath); + } + LogUtil.info(TAG, 'sourceFilePath exist', sourceFilePath); + const cpSuccess = await fs.copyFile(sourceFilePath, targetFilePath).then(() => true); + if (cpSuccess) { + LogUtil.info(TAG, 'copy file success'); + const list = fs.listFileSync(this.context.databaseDir, { + recursion: true // 递归查目录 + }); + list.map(item => { + LogUtil.info(TAG, 'read context.databaseDir file', JSON.stringify(item)); + }) + return true; + } + LogUtil.info(TAG, 'copy file fail'); + return false; + + } catch (error) { + LogUtil.error(TAG, JSON.stringify(error)); + } + return false; + } +} + +export default function (context: Context): CloneFSManager { + return new CloneFSManager(context); +} diff --git a/product/phone/src/main/ets/DBBackup/DBManager.ets b/product/phone/src/main/ets/DBBackup/DBManager.ets new file mode 100644 index 0000000..84f4b78 --- /dev/null +++ b/product/phone/src/main/ets/DBBackup/DBManager.ets @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import DB, { DBHandle, IRelationalStoreConfig } from './index'; +import relationalStore from '@ohos.data.relationalStore'; +import { ValuesBucket, ValueType } from '@ohos.data.ValuesBucket'; +import rdb from '@ohos.data.rdb'; +import { SQL_CREATE_TABLE } from '@hmos/common' +import { + TimeUtil, + GlobalContext, + ALARM_TIME_NEVER, + AlarmInfo, + ENABLED_FALSE, + ENABLED_TRUE, + INIT_SNOOZE_COUNT, + LogUtil +} from '@hmos/common'; +import cloneDBManager from './CloneDBManager'; + +interface IDoubleDataRow { + _id: string, + hour: number, + minutes: number, + daysofweek: number, + alarmtime: number, + enabled: boolean, + vibrate: number, + volume: number, + message: string, + alert: string, + daysofweektype: number, + daysofweekshow: string, + alarmtype: number, + ringduration: number, + snoozeduration: number, + snoozetimes: number, +} + +interface DoubleWorldDataRow { + _id: number, + timezone: string, + homecity: number, + sort_order: number, + city_index: string +} + +interface TDBManager { + getRDBH: (config: IRelationalStoreConfig) => Promise; + getResourcesTitle: (resource: Resource) => Promise; + singleReadData: () => Promise>; + singleReadResultSet: (resultSet: relationalStore.ResultSet) => Array; + doubleReadData: () => Promise>; + doubleReadWorldClockData: () => Promise>; + doubleReadResultSet: (resultSet: relationalStore.ResultSet) => Array; + doubleReadWorldClockResultSet: (resultSet: relationalStore.ResultSet) => Array; + formatToSingle: (doubleData: Array) => Array; + doubleToSingle: () => Promise; + singleRowformatVB: (alarmInfo: AlarmInfo) => ValuesBucket; + singleInsertTest?: () => Promise; + transformDayOfWeek: (num: number) => Array; + clearSingleTable: (tableName: string) => Promise; + intertBigData: (num: number) => Promise; +} + +const TAG: string = 'DBManager'; +const context = GlobalContext.getContext().getObject('clockBackupContext') as Context; +const singleStoreConfig: relationalStore.StoreConfig = { name: 'Clock.db', securityLevel: 1 }; +const doubleStoreConfig: relationalStore.StoreConfig = { name: 'alarms.db', securityLevel: 1 }; +const singleTableName: string = 'ALARM_CLOCK'; +const singleWorldClockTableName: string = 'WORLD_CLOCK'; +// 数据库的建表 SQL +const singleCreateSql: string = SQL_CREATE_TABLE; +const doubleTableName: string = 'alarms'; +const doubleWorldTableName: string = 'locations'; +// 数据库的建表 SQL +const doubleCreateSql = ` + CREATE TABLE IF NOT EXISTS ${doubleTableName} ( + _id INTEGER PRIMARY KEY, + hour INTEGER, + minutes INTEGER, + daysofweek INTEGER, + alarmtime INTEGER, + enabled INTEGER, + vibrate INTEGER, + volume INTEGER, + message TEXT, + alert TEXT, + daysofweektype INTEGER, + daysofweekshow TEXT, + alarmtype INTEGER DEFAULT 0, + ringduration INTEGER DEFAULT 5, + snoozeduration INTEGER DEFAULT 10, + snoozetimes INTEGER DEFAULT 3) + `; +// 双框架世界时钟的建表 SQL +const worldCreateSql = ` + CREATE TABLE IF NOT EXISTS ${doubleWorldTableName} ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + homecity INTEGER, + sort_order INTEGER, + timezone TEXT, + city_index TEXT) + `; + +// 默认时钟标签 +let defaultTitle: string = ''; +let defaultDemoTitle: string = ''; +const DEFAULT_ALARM_TITLE_RESOURCES: Resource = $r('app.string.alarm_clock'); +const DEFAULT_ALARM_DEMO_TITLE_RESOURCES: Resource = $r('app.string.default_alarm_label'); + +export let singleRDBH: DBHandle; + +export let doubleRDBH: DBHandle; + +export let doubleWorldRDBH: DBHandle; + +function getContext(): Context { + return GlobalContext.getContext().getObject('clockBackupContext') as Context; +} + +export async function getSingleRDBH(context?: Context): Promise { + if (singleRDBH) { + return singleRDBH; + } + const _context = context || getContext(); + singleRDBH = await dbManager.getRDBH({ + context: _context, + storeConfig: singleStoreConfig, + createSql: singleCreateSql + }); + return singleRDBH; +} + +export async function getDoubleRDBH(context?: Context): Promise { + if (doubleRDBH) { + return doubleRDBH; + } + const _context = context || getContext(); + doubleRDBH = await dbManager.getRDBH({ + context: _context, + storeConfig: doubleStoreConfig, + createSql: doubleCreateSql + }); + return doubleRDBH; +} + +export async function getDoubleWorldRDBH(context?: Context): Promise { + if (doubleWorldRDBH) { + return doubleWorldRDBH; + } + const _context = context || getContext(); + doubleWorldRDBH = await dbManager.getRDBH({ + context: _context, + storeConfig: doubleStoreConfig, + createSql: doubleCreateSql + }); + return doubleWorldRDBH; +} + +const dbManager: TDBManager = { + getRDBH: async (config: IRelationalStoreConfig) => { + return await DB({ context: config.context, storeConfig: config.storeConfig, createSql: config.createSql }); + }, + getResourcesTitle: async (resource) => { + let value: string = ''; + try { + const context = getContext(); + const resourceManager = context.resourceManager; + value = await resourceManager.getStringValue(resource); + // app.string.alarm_clock 'app.string.default_alarm_label' + LogUtil.info(TAG, `get getResourcesTitle success ${value}`); + } catch (error) { + LogUtil.error(TAG, `get getResourcesTitle fail. code: ${error.code}, message: ${error.message}`); + } + + return value; + }, + singleReadData: async (): Promise> => { + let rdbHandle = await getSingleRDBH(); + let resultSet = await rdbHandle.dbQuery(singleTableName, (predicates) => { + predicates.orderByAsc('HOUR'); + predicates.orderByAsc('MINUTE'); + }); + let result = dbManager.singleReadResultSet(resultSet); + resultSet.close(); + return result; + }, + singleReadResultSet: (resultSet: relationalStore.ResultSet): Array => { + let a: AlarmInfo[] = []; + while (resultSet.goToNextRow()) { + const daysOfWeekNative: Uint8Array = resultSet.getBlob(resultSet.getColumnIndex('DAYS_OF_WEEK')); + const daysOfWeek: number[] = []; + daysOfWeekNative.forEach(day => { + daysOfWeek.push(day); + }); + + let id: string = resultSet.getString(resultSet.getColumnIndex('ID')); + let hour: number = resultSet.getLong(resultSet.getColumnIndex('HOUR')); + let minute: number = resultSet.getLong(resultSet.getColumnIndex('MINUTE')); + let second: number = resultSet.getLong(resultSet.getColumnIndex('SECOND')); + let enabled: boolean = !!resultSet.getLong(resultSet.getColumnIndex('ENABLED')); + let alarmTime: number = resultSet.getLong(resultSet.getColumnIndex('ALARM_TIME')); + let title: string = resultSet.getString(resultSet.getColumnIndex('TITLE')); + let ringDuration: number = resultSet.getLong(resultSet.getColumnIndex('RING_DURATION')); + let snoozeDuration: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_DURATION')); + let snoozeTimes: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_TIMES')); + let snoozeCount: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_COUNT')); + resultSet.getBlob(resultSet.getColumnIndex('DAYS_OF_WEEK')); + + a.push({ + id, + hour, + minute, + second, + enabled, + alarmTime, + title, + ringDuration, + snoozeDuration, + snoozeTimes, + snoozeCount, + daysOfWeek + }) + } + return a; + }, + doubleReadData: async (): Promise> => { + let rdbHandle = doubleRDBH = await getDoubleRDBH(); + let resultSet = await rdbHandle.dbQuery(doubleTableName, (predicates) => { + predicates.orderByAsc('hour'); + predicates.orderByAsc('minutes'); + }); + + let result = dbManager.doubleReadResultSet(resultSet); + resultSet.close(); + return result; + }, + doubleReadWorldClockData: async (): Promise> => { + let rdbHandle = doubleWorldRDBH = await getDoubleWorldRDBH(); + let resultSet = await rdbHandle.dbQuery(doubleWorldTableName, (predicates) => { + predicates.orderByAsc('sort_order'); + }); + + let result = dbManager.doubleReadWorldClockResultSet(resultSet); + resultSet.close(); + return result; + }, + doubleReadResultSet: (resultSet: relationalStore.ResultSet): Array => { + let a: IDoubleDataRow[] = []; + while (resultSet.goToNextRow()) { + let _id: string = resultSet.getString(resultSet.getColumnIndex('_id')); + let hour: number = resultSet.getLong(resultSet.getColumnIndex('hour')); + let minutes: number = resultSet.getLong(resultSet.getColumnIndex('minutes')); + let daysofweek = resultSet.getLong(resultSet.getColumnIndex('daysofweek')); + let alarmtime: number = resultSet.getLong(resultSet.getColumnIndex('alarmtime')); + let enabled: boolean = !!resultSet.getLong(resultSet.getColumnIndex('enabled')); + let vibrate: number = resultSet.getLong(resultSet.getColumnIndex('vibrate')); + let volume: number = resultSet.getLong(resultSet.getColumnIndex('volume')); + let message: string = resultSet.getString(resultSet.getColumnIndex('message')); + let alert: string = resultSet.getString(resultSet.getColumnIndex('alert')); + let daysofweektype: number = resultSet.getLong(resultSet.getColumnIndex('daysofweektype')); + let daysofweekshow: string = resultSet.getString(resultSet.getColumnIndex('daysofweekshow')); + let alarmtype: number = resultSet.getLong(resultSet.getColumnIndex('alarmtype')); + let ringduration: number = resultSet.getLong(resultSet.getColumnIndex('ringduration')); + let snoozeduration: number = resultSet.getLong(resultSet.getColumnIndex('snoozeduration')); + let snoozetimes: number = resultSet.getLong(resultSet.getColumnIndex('snoozetimes')); + + + a.push({ + _id, + hour, + minutes, + daysofweek, + alarmtime, + enabled, + vibrate, + volume, + message, + alert, + daysofweektype, + daysofweekshow, + alarmtype, + ringduration, + snoozeduration, + snoozetimes + }) + } + return a; + }, + doubleReadWorldClockResultSet: (resultSet: relationalStore.ResultSet): Array => { + let result: DoubleWorldDataRow[] = []; + while (resultSet.goToNextRow()) { + let _id: number = resultSet.getLong(resultSet.getColumnIndex('_id')); + let timezone: string = resultSet.getString(resultSet.getColumnIndex('timezone')); + let homecity: number = resultSet.getLong(resultSet.getColumnIndex('homecity')); + let sort_order: number = resultSet.getLong(resultSet.getColumnIndex('sort_order')); + let city_index: string = resultSet.getString(resultSet.getColumnIndex('city_index')); + + result.push({ + _id, + timezone, + homecity, + sort_order, + city_index + }) + } + return result + }, + doubleToSingle: async () => { + GlobalContext.getContext() + .setObject('clockContext', GlobalContext.getContext().getObject('clockBackupContext') as Context); + const data: IDoubleDataRow[] = await dbManager.doubleReadData(); // 读取双框架闹钟数据 + LogUtil.info(TAG, 'get double clock data success'); + defaultTitle = await dbManager.getResourcesTitle(DEFAULT_ALARM_TITLE_RESOURCES); + defaultDemoTitle = await dbManager.getResourcesTitle(DEFAULT_ALARM_DEMO_TITLE_RESOURCES); + let formatData = dbManager.formatToSingle(data) + .map(item => dbManager.singleRowformatVB(item)); // 转换为单框架数据格式 + LogUtil.info(TAG, 'format to single clock data success', JSON.stringify(formatData)); + if (!singleRDBH) { + LogUtil.info(TAG, 'singleRDBH is undefined'); + await getSingleRDBH(); + } + LogUtil.info(TAG, 'clear single clock table start'); + const cr = await dbManager.clearSingleTable(singleTableName); + if (cr) { + LogUtil.info(TAG, 'clear single clock table success'); + } + const r = await singleRDBH.dbBatchInsert(singleTableName, formatData); + if (r === -1) { + LogUtil.info(TAG, 'insert to single DB fail'); + return false; + } + LogUtil.info(TAG, 'start to handle world clock data'); + const worldClockData: DoubleWorldDataRow[] = await dbManager.doubleReadWorldClockData(); // 读取双框架世界时钟数据 + LogUtil.info(TAG, 'get double worldClock data success', JSON.stringify(worldClockData)); + const isClear = await dbManager.clearSingleTable(singleWorldClockTableName); + if (isClear) { + LogUtil.info(TAG, 'clear world clock table success'); + } + await cloneDBManager.addWorldClockList(worldClockData); + LogUtil.info(TAG, 'insert to single DB success'); + LogUtil.info(TAG, 'success over'); + return true; + + }, + formatToSingle(doubleData) { + let formatResult = doubleData.map((item) => { + let id = item._id; + let hour = item.hour; + let minute = item.minutes; + let second = 0; + let enabled = item.enabled; + let alarmTime = item.alarmtime; + let title = item.message || defaultTitle; + let ringDuration = item.ringduration; + let snoozeDuration = item.snoozeduration; + let snoozeTimes = item.snoozetimes; + let snoozeCount = item.snoozetimes; + let daysOfWakeType = item.daysofweektype || 0; + + // 以下为双有的列,单目前没有的列 + let vibrate = item.vibrate; + let volume = item.volume; + let alert = item.alert; + let daysofweekshow = item.daysofweekshow; + let alarmtype = item.alarmtype; + + LogUtil.info(TAG, `title ${title}, ${title.length}`); + + /** + * 双框架示例数据无标题问题 + * 判断是否为双的示例数据 + * */ + if (!item.message && Number(item.volume) === 3) { + LogUtil.info(TAG, `demo data ${defaultTitle}`); + title = defaultDemoTitle; + } + + /** + * 针对双框架数据库字段 daysofweek 字段说明 + * 双框架 daysofweek 在数据库中存储为 10进制数字,例如:31, 96 需要转2进制进行星期识别 + * 31 转 2进制结果为 11111, 5个1 表示设置了 周五,周四,周三,周二,周一 + * 96 转 2进制结果为 1100000, 2个1和5个0 表示设置了 周日,周六; (周五,周四,周三,周二,周一 为0 表示未设置); + * 针对双框数据 转换为 [0,0,0,0,0,0,0] 表示 [周日,周一,周二,周三,周四,周五,周六] + * + * 单框架数据 0:周日,1:周一,2:周二,3:周三,4:周四,5:周五,6:周六 + * daysOfWeek = [2] + * */ + let daysOfWeek = dbManager.transformDayOfWeek(item.daysofweek); + return { + id, + hour, + minute, + second, + enabled, + alarmTime, + title, + ringDuration, + snoozeDuration, + snoozeTimes, + snoozeCount, + daysOfWeek, + daysOfWakeType + } + }) + return formatResult; + }, + transformDayOfWeek: (num) => { + let str: string = Number(num).toString(2).padStart(7, '0'); + if (str) { + let arr: string[] = str.split('').reverse(); + if (arr && arr.length > 0) { + arr!.unshift(arr.pop() as string); + } + let rst: number[] = arr.reduce>((r, it, i) => { + (Number(it) === 1) && r.push(Number(i)); + return r + }, []); + return rst; + } else { + return []; + } + }, + singleRowformatVB: (alarmInfo) => { + return { + 'TITLE': alarmInfo.title, + 'HOUR': alarmInfo.hour, + 'MINUTE': alarmInfo.minute, + 'SECOND': alarmInfo.second || 0, + 'DAYS_OF_WEEK': new Uint8Array(alarmInfo.daysOfWeek || []), + 'ALARM_TIME': alarmInfo.enabled ? TimeUtil.getAlarmTime(alarmInfo) : ALARM_TIME_NEVER, + 'ENABLED': alarmInfo.enabled ? ENABLED_TRUE : ENABLED_FALSE, + 'RING_DURATION': alarmInfo.ringDuration as ValueType, + 'SNOOZE_DURATION': alarmInfo.snoozeDuration, + 'SNOOZE_TIMES': alarmInfo.snoozeTimes, + 'SNOOZE_COUNT': alarmInfo.snoozeCount || INIT_SNOOZE_COUNT, + 'DAYS_OF_WAKE_TYPE': alarmInfo.daysOfWakeType as ValueType + }; + }, + clearSingleTable: async (tableName) => { + LogUtil.info(TAG, 'enter clearSingleTable'); + let rdbHandle = await getSingleRDBH(); + let CLEAR_TABLE_SQL = `DELETE FROM ${tableName}`; + let r = await rdbHandle.dbClearTable(CLEAR_TABLE_SQL); + return r; + }, + intertBigData: async (num = 100) => { + const arr: ValuesBucket[] = []; + + for (let i = 0; i < num; i++) { + let title = `clock_${i}`; + let hour = i % 23; + let minute = i % 59; + let second = i % 59; + let daysOfWeek = [6, 0]; + let enabled = true; + let ringDuration = 5; + let snoozeDuration = 10; + let snoozeTimes = 3; + let d = { + hour, + minute, + second, + enabled, + title, + ringDuration, + snoozeDuration, + snoozeTimes, + daysOfWeek + } as AlarmInfo; + arr.push(dbManager.singleRowformatVB(d)); + } + if (!singleRDBH) { + LogUtil.info(TAG, 'insert big data singleRDBH is undefined'); + await getSingleRDBH(); + } + const r = await singleRDBH.dbBatchInsert(singleTableName, arr); + LogUtil.info(TAG, `insert big data ${num}`); + return r; + } +} + +export default dbManager; diff --git a/product/phone/src/main/ets/DBBackup/FSMananger.ets b/product/phone/src/main/ets/DBBackup/FSMananger.ets new file mode 100644 index 0000000..818e112 --- /dev/null +++ b/product/phone/src/main/ets/DBBackup/FSMananger.ets @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs, { Filter } from '@ohos.file.fs'; +import { GlobalContext, LogUtil } from '@hmos/common'; +import DBManager, { singleRDBH } from './DBManager'; +import { Context } from '@ohos.abilityAccessCtrl'; + +const TAG = `FSManager`; + +interface IListFileOptions { + recursion?: boolean; + listNum?: number; + filter?: Filter; +} + +interface IFSManager { + context: Context; + + getFileList(path: string, options?: IListFileOptions): Promise; + + moveFile(sourceFilePath: string, targetFilePath: string): Promise; +} + + +class FSManager implements IFSManager { + public context = {} as Context; + + constructor(context: Context) { + this.context = context; + } + + async getFileList(path: string, options: IListFileOptions = { recursion: true }) { + try { + const list = await fs.listFile(path, options); + LogUtil.info(TAG, `read ${path} file success`); + list.map(item => { + LogUtil.info(TAG, 'file', JSON.stringify(item)); + }) + } catch (error) { + LogUtil.error(TAG, 'getFileList', path, JSON.stringify(error)) + } + } + + async moveFile(sourceFilePath: string, targetFilePath: string): Promise { + LogUtil.info(TAG, sourceFilePath, targetFilePath) + try { + const exist = await fs.access(sourceFilePath); + if (!exist) { + LogUtil.info(TAG, 'sourceFilePath not found', sourceFilePath); + return false; + } + const targetPath = targetFilePath.substring(0, targetFilePath.lastIndexOf('/')); + const existTargetFilePath = await fs.access(targetPath); + if (!existTargetFilePath) { + LogUtil.info(TAG, 'targetPath not found', targetPath); + fs.mkdirSync(targetPath); + LogUtil.info(TAG, 'create targetPath success', targetPath); + } + + LogUtil.info(TAG, 'sourceFilePath exist', sourceFilePath); + const cpSuccess = await fs.moveFile(sourceFilePath, targetFilePath).then(() => true); + if (cpSuccess) { + LogUtil.info(TAG, 'move file success'); + const list = await fs.listFile(this.context.databaseDir, { + recursion: true // 递归查目录 + }); + list.map(item => { + LogUtil.info(TAG, 'read context.databaseDir file', JSON.stringify(item)); + }) + return true; + } + LogUtil.info(TAG, 'move file fail'); + return false; + + } catch (error) { + LogUtil.error(TAG, JSON.stringify(error)); + } + + return false; + } +} + +export default function (context: Context): FSManager { + return new FSManager(context); +} + diff --git a/product/phone/src/main/ets/DBBackup/index.ets b/product/phone/src/main/ets/DBBackup/index.ets new file mode 100644 index 0000000..5635d34 --- /dev/null +++ b/product/phone/src/main/ets/DBBackup/index.ets @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import relationalStore from '@ohos.data.relationalStore'; +import { ValuesBucket, ValueType } from '@ohos.data.ValuesBucket'; +import { AlarmInfo, LogUtil } from '@hmos/common'; + +type TQueryFn = (predicates: relationalStore.RdbPredicates) => void; +type TQueryReadFn = (resultSet: relationalStore.ResultSet) => void; + +const TAG: string = 'DBHandle'; + +class DBHandle { + public rdbStore: relationalStore.RdbStore = {} as relationalStore.RdbStore; + public dbConfig: IRelationalStoreConfig + + constructor(options: IRelationalStoreConfig) { + this.dbConfig = options + } + + async getRdbStore(context: Context, storeConfig: relationalStore.StoreConfig, createSql: string): + Promise { + LogUtil.info(TAG, 'getRdbStore', context.databaseDir); + /* + * 获取关系型数据库 + * context 上下文 + * storeConfig 数据库配置 { name: 数据库名, securityLevel: 数据安全级别 } + * */ + // 获取关系型数据库 + try { + const rdbStore: relationalStore.RdbStore = await relationalStore.getRdbStore(context, storeConfig); + this.rdbStore = rdbStore; + LogUtil.info(TAG, 'create rdb success'); + await this.createTable(createSql); + } catch (error) { + LogUtil.info(TAG, 'create rdb fail', JSON.stringify(error)); + } + return this; + } + + async createTable(createSql: string) { + try { + const r = await this.rdbStore.executeSql(createSql); + LogUtil.info(TAG, 'create rdb table success', String(r)); + } catch (error) { + LogUtil.info(TAG, 'create rdb table fail', JSON.stringify(error)); + } + } + + async dbQuery(tableName: string, fieldQueryFn: TQueryFn, readHandle?: TQueryReadFn): + Promise { + const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(tableName); + fieldQueryFn(predicates); + let result: relationalStore.ResultSet = {} as relationalStore.ResultSet; + try { + const resultSet: relationalStore.ResultSet = await this.rdbStore.query(predicates); + LogUtil.info(TAG, 'query data success', String(resultSet.rowCount)); + result = resultSet + } catch (error) { + LogUtil.info(TAG, 'query data fail', error); + // return Error(error) + } + return result + } + + async dbInsert(tableName: string, row: ValuesBucket) { + try { + const r: number = await this.rdbStore.insert(tableName, row); + LogUtil.info(TAG, 'insert data success', String(r)); + return r + } catch (error) { + LogUtil.info(TAG, 'insert data fail', error.code); + } + return -1; + } + + async dbBatchInsert(tableName: string, rows: Array) { + try { + + const r: number = await this.rdbStore.batchInsert(tableName, rows); + LogUtil.info(TAG, 'insert', String(r), tableName, JSON.stringify(rows)); + return r + } catch (error) { + LogUtil.info(TAG, 'insert data fail', error); + } + return -1; + } + + async dbClearTable(sql: string): Promise { + try { + const r = await this.rdbStore.executeSql(sql); + LogUtil.info(TAG, 'clear table success'); + return true; + } catch (error) { + LogUtil.info(TAG, 'clear table fail', JSON.stringify(error)); + } + return false; + } + + readResultSet(resultSet: relationalStore.ResultSet): Array { + let a: AlarmInfo[] = []; + while (resultSet.goToNextRow()) { + const daysOfWeekNative: Uint8Array = resultSet.getBlob(resultSet.getColumnIndex('DAYS_OF_WEEK')); + const daysOfWeek: number[] = []; + daysOfWeekNative.forEach(day => { + daysOfWeek.push(day); + }); + + let id: string = resultSet.getString(resultSet.getColumnIndex('ID')); + let hour: number = resultSet.getLong(resultSet.getColumnIndex('HOUR')); + let minute: number = resultSet.getLong(resultSet.getColumnIndex('MINUTE')); + let second: number = resultSet.getLong(resultSet.getColumnIndex('SECOND')); + let enabled: boolean = !!resultSet.getLong(resultSet.getColumnIndex('ENABLED')); + let alarmTime: number = resultSet.getLong(resultSet.getColumnIndex('ALARM_TIME')); + let title: string = resultSet.getString(resultSet.getColumnIndex('TITLE')); + let ringDuration: number = resultSet.getLong(resultSet.getColumnIndex('RING_DURATION')); + let snoozeDuration: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_DURATION')); + let snoozeTimes: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_TIMES')); + let snoozeCount: number = resultSet.getLong(resultSet.getColumnIndex('SNOOZE_COUNT')); + resultSet.getBlob(resultSet.getColumnIndex('DAYS_OF_WEEK')); + + + a.push({ + id, + hour, + minute, + second, + enabled, + alarmTime, + title, + ringDuration, + snoozeDuration, + snoozeTimes, + snoozeCount, + daysOfWeek + }) + } + return a; + } +} + +export { DBHandle } + +export type TReadDataSetHandle = (arg: relationalStore.ResultSet) => T + +export interface IRelationalStoreConfig { + context: Context + storeConfig: relationalStore.StoreConfig + createSql: string + readDataSetHandle?: TReadDataSetHandle +} + +export default function (config: IRelationalStoreConfig): Promise { + const dbHandle = new DBHandle(config); + return new Promise((resolve) => { + dbHandle.getRdbStore(config.context, config.storeConfig, config.createSql).then((rdbh) => { + resolve(rdbh); + }) + }) +} \ No newline at end of file diff --git a/product/phone/src/main/ets/FaAbility/FaCityManagerAbility.ets b/product/phone/src/main/ets/FaAbility/FaCityManagerAbility.ets new file mode 100644 index 0000000..5221b25 --- /dev/null +++ b/product/phone/src/main/ets/FaAbility/FaCityManagerAbility.ets @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import UIAbility from '@ohos.app.ability.UIAbility'; +import contextConstant from '@ohos.app.ability.contextConstant'; +import { LogUtil, GlobalContext } from '@hmos/common'; +import window from '@ohos.window'; +import Want from '@ohos.app.ability.Want'; +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; + +const TAG: string = 'FaCityManagerAbility'; + +export default class FaCityManagerAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + this.context.area = contextConstant.AreaMode.EL1 + GlobalContext.getContext().setObject('resourceManager', this.context.resourceManager); + GlobalContext.getContext().setObject('clockContext', this.context); + GlobalContext.getContext().setObject('clockContextType', 'FaCityManagerAbility'); + LogUtil.info(TAG, 'FaCityManagerAbility onCreate:' + JSON.stringify(want)); + if (want.parameters) { + const formId = want?.parameters.formId; + LogUtil.info(TAG, 'FaCityManagerAbility formId:' + formId); + AppStorage.SetOrCreate('requestFormId', formId); + } + } + + onDestroy(): void { + LogUtil.info(TAG, 'FaCityManagerAbility onDestroy'); + } + + async onWindowStageCreate(windowStage: window.WindowStage): Promise { + // Main window is created, set main page for this ability + LogUtil.info(TAG, 'FaCityManagerAbility onWindowStageCreate'); + const mainWindow = await windowStage.getMainWindow(); + windowStage.loadContent('pages/FaManagerCity', (err, data) => { + if (err.code) { + LogUtil.error(TAG, 'Failed to load the content. Cause: ' + JSON.stringify(err)); + return; + } + LogUtil.info(TAG, 'Succeeded in loading the content. Data:' + JSON.stringify(data)); + }); + + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + LogUtil.info(TAG, 'FaCityManagerAbility onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + LogUtil.info(TAG, 'FaCityManagerAbility onForeground'); + } + + onBackground(): void { + // Ability has back to background + LogUtil.info(TAG, 'FaCityManagerAbility onBackground'); + } +} diff --git a/product/phone/src/main/ets/ForegroundAbility/ForegroundAbility.ets b/product/phone/src/main/ets/ForegroundAbility/ForegroundAbility.ets new file mode 100644 index 0000000..c9bcd13 --- /dev/null +++ b/product/phone/src/main/ets/ForegroundAbility/ForegroundAbility.ets @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Ability from '@ohos.app.ability.UIAbility'; +import contextConstant from '@ohos.app.ability.contextConstant'; +import emitter from '@ohos.events.emitter'; +import { LogUtil, EVENT_ID_FINISH_ABILITY } from '@hmos/common'; +import Want from '@ohos.app.ability.Want'; +import window from '@ohos.window'; + +const TAG = 'ForegroundAbility'; + +export default class ForegroundAbility extends Ability { + onCreate(want: Want): void { + LogUtil.info(TAG, 'ForegroundAbility onCreate'); + this.context.area = contextConstant.AreaMode.EL1 + } + + onDestroy(): void { + LogUtil.info(TAG, 'ForegroundAbility onDestroy'); + } + + async onWindowStageCreate(windowStage: window.WindowStage): Promise { + LogUtil.info(TAG, 'ForegroundAbility onWindowStageCreate'); + const mainWindow = await windowStage.getMainWindow() + mainWindow.setUIContent('pages/ForegroundPage', () => { + }); + emitter.on({ + eventId: EVENT_ID_FINISH_ABILITY + }, () => { + this.context.terminateSelf(error => { + LogUtil.info(TAG, `terminateSelf finish error: ${JSON.stringify(error)}`); + }); + }); + } + + onWindowStageDestroy(): void { + LogUtil.info(TAG, 'ForegroundAbility onWindowStageDestroy'); + emitter.off(EVENT_ID_FINISH_ABILITY); + } + + onForeground(): void { + LogUtil.info(TAG, 'ForegroundAbility onForeground'); + } + + onBackground(): void { + LogUtil.info(TAG, 'ForegroundAbility onBackground'); + } +} diff --git a/product/phone/src/main/ets/FullScreenAbility/FullScreenAbility.ets b/product/phone/src/main/ets/FullScreenAbility/FullScreenAbility.ets new file mode 100644 index 0000000..fa8762f --- /dev/null +++ b/product/phone/src/main/ets/FullScreenAbility/FullScreenAbility.ets @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import contextConstant from '@ohos.app.ability.contextConstant'; +import emitter from '@ohos.events.emitter'; +import { LogUtil, FullScreenType, GlobalContext, AlarmStateManager, AlarmManager, TIMER_NOTICE_ID } from '@hmos/common'; +import Want from '@ohos.app.ability.Want'; +import window from '@ohos.window'; +import UIAbility from '@ohos.app.ability.UIAbility'; +import { FULL_SCREEN_ALARM_PAGE } from '@hmos/alarmclock/src/main/ets/manager/types'; +import { EVENT_ID_FULL_SCREEN_ALARM, EVENT_ID_TIMER_FULL_SCREEN_CLOSE } from '@hmos/common/src/main/ets/manager/types'; +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; + +const TAG = 'FullScreenAbility'; + +export const FULL_SCREEN_TIMER_PAGE = 'pages/FullScreenTimer'; + +export default class FullScreenAbility extends UIAbility { + private fullScreenPage: string = FULL_SCREEN_ALARM_PAGE; + + onCreate(want: Want): void { + LogUtil.info(TAG, 'onCreate:' + JSON.stringify(want)); + this.context.area = contextConstant.AreaMode.EL1; + GlobalContext.getContext().setObject('FullScreenAbilityContext', this.context); + GlobalContext.getContext().setObject('terminateFullScreenAbilityContext', false); + GlobalContext.getContext().setObject('resourceManager', this.context.resourceManager); + GlobalContext.getContext().setObject('abilityWant', want); + if (want.parameters && want.parameters.fullScreenType == FullScreenType.Timer) { + this.fullScreenPage = FULL_SCREEN_TIMER_PAGE; + } else { + this.fullScreenPage = FULL_SCREEN_ALARM_PAGE; + } + } + + onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam) { + LogUtil.info(TAG, 'onNewWant:' + JSON.stringify(want) + JSON.stringify(launchParam)); + } + + onDestroy(): void { + LogUtil.info(TAG, 'onDestroy'); + GlobalContext.getContext().setObject('FullScreenAbilityContext', undefined); + GlobalContext.getContext().setObject('terminateFullScreenAbilityContext', true); + } + + async onWindowStageCreate(windowStage: window.WindowStage): Promise { + LogUtil.info(TAG, 'onWindowStageCreate ' + this.fullScreenPage); + const mainWindow = await windowStage.getMainWindow(); + mainWindow.setUIContent(this.fullScreenPage, () => { + }); + mainWindow.setWindowLayoutFullScreen(true); + mainWindow.setWindowKeepScreenOn(true); + // windowStage.setShowOnLockScreen(true); + } + + onWindowStageDestroy(): void { + LogUtil.info(TAG, 'onWindowStageDestroy'); + } + + onForeground(): void { + LogUtil.info(TAG, 'onForeground'); + } + + onBackground(): void { + this.processTerminate(); + } + + processTerminate(): void { + const isTerminated: boolean = GlobalContext.getContext().getObject('terminateFullScreenAbilityContext') as boolean; + LogUtil.info(TAG, 'onBackground isTerminated:' + isTerminated); + if (isTerminated) { + LogUtil.info(TAG, 'onBackground return'); + return; + } + GlobalContext.getContext().setObject('FullScreenAbilityContext', undefined); + GlobalContext.getContext().setObject('terminateFullScreenAbilityContext', true); + if (this.fullScreenPage === FULL_SCREEN_ALARM_PAGE) { + emitter.emit({ + eventId: EVENT_ID_FULL_SCREEN_ALARM, priority: emitter.EventPriority.IMMEDIATE + }); + } else { + emitter.emit({ + eventId: EVENT_ID_TIMER_FULL_SCREEN_CLOSE, priority: emitter.EventPriority.IMMEDIATE + }); + } + this.context.terminateSelf(error => { + LogUtil.info(TAG, `terminateSelf finish error: ${JSON.stringify(error)}`); + }); + } +} diff --git a/product/phone/src/main/ets/IntentAbility/CreateAlarm.ets b/product/phone/src/main/ets/IntentAbility/CreateAlarm.ets new file mode 100644 index 0000000..d06263d --- /dev/null +++ b/product/phone/src/main/ets/IntentAbility/CreateAlarm.ets @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import IntentExecutor from '@ohos.app.ability.InsightIntentExecutor'; +import { LogUtil, AlarmInfo } from '@hmos/common'; +import insightIntent from '@ohos.app.ability.insightIntent'; +import AlarmManager from '@hmos/common/src/main/ets/manager/AlarmManager' + +const LOG_TAG: string = 'testTag-IntentExecutor-CreateAlarm'; + +export default class CreateAlarmIntentExecutorImpl extends IntentExecutor { + async onExecuteInUIAbilityBackgroundMode(name: string, param: Record): + Promise { + LogUtil.info(LOG_TAG, 'onExecuteInUIAbilityBackgroundMode start , name is ', name, 'param is ', JSON.stringify(param)); + let result: insightIntent.ExecuteResult; + if (name !== 'CreateAlarm') { + LogUtil.warn(LOG_TAG, 'Unsupported intentName %{public}s', name); + result = { + code: 10030002, + result: { + message: 'Not Support intent.', + } + }; + LogUtil.info(LOG_TAG, 'CreateAlarm failed,Not Support intent', JSON.stringify(result)); + return result; + } + + // 开始创建 + // 至少得有一个参数 + if (Object.keys(param).length < 1) { + result = { + code: 10030006, + result: { + message: 'no params', + } + }; + LogUtil.info(LOG_TAG, ' CreateAlarm failed , at least need one param', JSON.stringify(result)); + return result + } + + result = await CreateAlarm(param); + LogUtil.info(LOG_TAG, 'CreateAlarmResult Execute UIAbility in foreground mode finished, result %{public}s', JSON.stringify(result)); + return result; + } +} + +//创建闹钟 +export async function CreateAlarm(param: Record): Promise { + let result: insightIntent.ExecuteResult + LogUtil.info(LOG_TAG, 'CreateAlarm param: ', JSON.stringify(param)); + + let newDate = new Date(param.alarmTime as string) + + const alarmInfo: AlarmInfo = { + id: '', + title: param.alarmTitle ? param.alarmTitle as string : '闹钟', + hour: newDate.getHours(), + minute: newDate.getMinutes(), + second: newDate.getSeconds(), + daysOfWeek: [], // 设置每个星期哪一天重复提醒,范围从 1 到 7 + alarmTime: param.alarmTime as number, // 最近一次响铃时间,使用 Date 的 getTime 方法获取 + enabled: param.alarmState as boolean, // 是否打开 + ringPath: '', + ringDuration: param.alarmRingDuration ? param.alarmRingDuration as number : 5, // 响铃时间 + snoozeDuration: param.alarmSnoozeDuration ? param.alarmSnoozeDuration as number : 10, // 再响间隔 + snoozeTimes: param.alarmSnoozeTotal ? param.alarmSnoozeTotal as number : 3, // 再响次数 + snoozeCount: 0, // 某次响铃中,已再响的次数,与 snoozeTimes 相等时,不再重新响铃 + alarmTimeIntent: param.alarmTime as number, + } + + const currentTime = new Date().getTime(); + if (alarmInfo.alarmTime && alarmInfo.alarmTime < currentTime) { + result = { + code: 10030009, + result: { + message: 'CreateAlarm failed,the alarmTime need greater than the currentTime', + entityName: 'Alarm', + } + } + LogUtil.info(LOG_TAG, ' CreateAlarm failed, result: ', JSON.stringify(result)); + return result + } + LogUtil.info(LOG_TAG, 'before addAlarm alarmInfo: ', JSON.stringify(alarmInfo)); + await AlarmManager.addAlarm(alarmInfo, true); + //排序 + let sortAlarmList = await AlarmManager.sortAlarmListById(); + LogUtil.info(LOG_TAG, 'addAlarm sortAlarmList: ', JSON.stringify(sortAlarmList)); + if (sortAlarmList) { + const resultAlarm = sortAlarmList[sortAlarmList.length - 1] + LogUtil.info(LOG_TAG, 'addAlarm resultAlarm: ', JSON.stringify(resultAlarm)); + result = { + code: 0, + result: { + entityName: 'Alarm', + entityId: resultAlarm.id!, + alarmTime: resultAlarm.alarmTimeIntent ? resultAlarm.alarmTimeIntent : -1, + alarmTitle: resultAlarm.title, + alarmState: resultAlarm.enabled ? 1 : 0, + alarmSnoozeDuration: resultAlarm.snoozeDuration, + alarmSnoozeTotal: resultAlarm.snoozeTimes, + alarmRingDuration: resultAlarm.ringDuration + } + } + LogUtil.info(LOG_TAG, 'addAlarm success result: ', JSON.stringify(result)) + return result + } + result = { + code: -100, + result: { + message: 'CreateAlarm failed,because can not find the alarm list or the latest alarm', + entityName: 'Alarm', + } + } + LogUtil.info(LOG_TAG, ' CreateAlarm failed, result: ', JSON.stringify(result)); + return result + +} \ No newline at end of file diff --git a/product/phone/src/main/ets/IntentAbility/DelayRingIntent.ets b/product/phone/src/main/ets/IntentAbility/DelayRingIntent.ets new file mode 100644 index 0000000..e95aa36 --- /dev/null +++ b/product/phone/src/main/ets/IntentAbility/DelayRingIntent.ets @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import IntentExecutor from '@ohos.app.ability.InsightIntentExecutor'; +import { LogUtil, AlarmStateManager, TimeUtil } from '@hmos/common'; +import insightIntent from '@ohos.app.ability.insightIntent'; +import { AlarmServiceManager } from '@hmos/alarmclock'; + +const TEST_ABILITY: string = 'testTag-IntentExecutor-DelayRing'; + + +export default class DelayRingIntentExecutorImpl extends IntentExecutor { + async onExecuteInUIAbilityBackgroundMode(name: string): Promise { + LogUtil.info(TEST_ABILITY, 'onExecuteInUIAbilityBackgroundMode start , name is ', name); + let result: insightIntent.ExecuteResult; + if (name !== 'DelayAlarmRing') { + LogUtil.warn(TEST_ABILITY, 'Unsupported intentName ', name); + result = { + code: 10030002, + result: { + message: 'Not Support intent.', + entityName: 'Alarm' + } + }; + return result; + } + result = await delayRing() + LogUtil.info(TEST_ABILITY, 'ExecuteInUIAbilityBackgroundMode finished, result is', JSON.stringify(result)); + return result + } +} + + +async function delayRing(): Promise { + LogUtil.info(TEST_ABILITY, ' delayRing start') + const isFiring = await AlarmStateManager.isFiring(); //boolean + if (isFiring) { + let ringingAlarm = await AlarmServiceManager.delayAlarmAndReturn() + if (ringingAlarm) { + LogUtil.info(TEST_ABILITY, 'delayRing success , delay alarm is ' + JSON.stringify(ringingAlarm)); + return new Promise((resolve, reject) => { + let result: insightIntent.ExecuteResult = { + code: 0, + result: { + entityName: 'Alarm', + entityId: ringingAlarm?.id ? ringingAlarm.id : -1, + alarmTime: ringingAlarm?.alarmTimeIntent ? ringingAlarm.alarmTimeIntent : -1, + alarmTitle: ringingAlarm?.title ? ringingAlarm.title : -1, + alarmState: ringingAlarm?.enabled ? 1 : 0, + alarmSnoozeDuration: ringingAlarm?.snoozeDuration ? ringingAlarm.snoozeDuration : -1, + alarmSnoozeTotal: ringingAlarm?.snoozeTimes ? ringingAlarm.snoozeTimes : -1, + alarmRingDuration: ringingAlarm?.ringDuration ? ringingAlarm.ringDuration : -1 + } + } + resolve(result) + }) + } + } + let result: insightIntent.ExecuteResult = { + code: 10030005, + result: {} + } + LogUtil.info(TEST_ABILITY, ' delayRing failed,no ringing alarm'); + return result + +} \ No newline at end of file diff --git a/product/phone/src/main/ets/IntentAbility/DeleteAlarmIntent.ets b/product/phone/src/main/ets/IntentAbility/DeleteAlarmIntent.ets new file mode 100644 index 0000000..61fd87f --- /dev/null +++ b/product/phone/src/main/ets/IntentAbility/DeleteAlarmIntent.ets @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogUtil } from '@hmos/common'; +import IntentExecutor from '@ohos.app.ability.InsightIntentExecutor'; +import insightIntent from '@ohos.app.ability.insightIntent'; +import AlarmManager from '@hmos/common/src/main/ets/manager/AlarmManager' + +const TEST_ABILITY: string = 'testTag-IntentExecutor-DeleteAlarm'; + +export default class DeleteAlarmIntentExecutorImpl extends IntentExecutor { + async onExecuteInUIAbilityBackgroundMode(name: string, param: Record): + Promise { + + LogUtil.info(TEST_ABILITY, 'onExecuteInUIAbilityBackgroundMode start , name is ', name, 'param is ', JSON.stringify(param)); + + let result: insightIntent.ExecuteResult; + if (name !== 'DeleteAlarm') { + LogUtil.warn(TEST_ABILITY, 'Unsupported intentName ', name); + result = { + code: 10030002, + result: { + message: 'Not Support intent.', + entityName: 'Alarm' + } + }; + LogUtil.info(TEST_ABILITY, 'DeleteAlarm failed,Not Support intent', JSON.stringify(result)); + return result; + } + result = await deleteAlarm(param) + LogUtil.info(TEST_ABILITY, 'ExecuteInUIAbilityBackgroundMode finished, result is', JSON.stringify(result)); + return result + } +} + + +// 删除闹钟 +// id:string +async function deleteAlarm(param: Record): Promise { + + LogUtil.info(TEST_ABILITY, 'DeleteAlarm start', JSON.stringify(param)) + + if (!param.entityId) { + let result: insightIntent.ExecuteResult = { + code: 10030003, + result: { + message: 'DeleteAlarm failed,No alarmId', + entityName: 'Alarm' + } + } + LogUtil.info(TEST_ABILITY, 'DeleteAlarm failed ,No alarmId') + return result + } + let stringResult = await AlarmManager.removeAlarmById(param.entityId as string); + LogUtil.info(TEST_ABILITY, 'DeleteAlarm ending ' + JSON.stringify(stringResult)) + if (stringResult == 'success') { + return new Promise((resolve, reject) => { + let result: insightIntent.ExecuteResult = { + code: 0, + result: { + message: 'DeleteAlarm success,id is ' + param.entityId, + entityName: 'Alarm' + } + } + resolve(result) + }) + } else { + let result: insightIntent.ExecuteResult = { + code: 10030004, + result: { + message: 'DeleteAlarm failed,No this alarmId,id is ' + param.entityId, + entityName: 'Alarm' + } + } + return result + } + +} diff --git a/product/phone/src/main/ets/IntentAbility/EntryAbility.ets b/product/phone/src/main/ets/IntentAbility/EntryAbility.ets new file mode 100644 index 0000000..055f663 --- /dev/null +++ b/product/phone/src/main/ets/IntentAbility/EntryAbility.ets @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; +import UIAbility from '@ohos.app.ability.UIAbility'; +import Want from '@ohos.app.ability.Want'; +import window from '@ohos.window'; +import { LogUtil } from '@hmos/common/src/main/ets/utils/LogUtil'; +import contextConstant from '@ohos.app.ability.contextConstant'; +import { GlobalContext, } from '@hmos/common'; + +const LOG_TAG: string = 'testTag-IntentExecutor-EntryAbility'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { + this.context.area = contextConstant.AreaMode.EL1 + GlobalContext.getContext().setObject('abilityWant', want); + GlobalContext.getContext().setObject('clockContext', this.context); + GlobalContext.getContext().setObject('clockContextType', 'EntryAbility'); + LogUtil.info(LOG_TAG, 'Ability onCreate'); + LogUtil.info(LOG_TAG, JSON.stringify(this.context)); + LogUtil.info(LOG_TAG, JSON.stringify(this.context.stageMode)); + } + + async onDestroy() { + LogUtil.info(LOG_TAG, '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + // Main window is created, set main page for this ability + LogUtil.info(LOG_TAG, '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/TestIntentIndex', (err, data) => { + if (err.code) { + LogUtil.info(LOG_TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + LogUtil.info(LOG_TAG, 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data)); + }); + } + + onWindowStageDestroy() { + // Main window is destroyed, release UI related resources + LogUtil.info(LOG_TAG, '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground() { + // Ability has brought to foreground + LogUtil.info(LOG_TAG, '%{public}s', 'Ability onForeground'); + } + + onBackground() { + // Ability has back to background + LogUtil.info(LOG_TAG, '%{public}s', 'Ability onBackground'); + } +} diff --git a/product/phone/src/main/ets/IntentAbility/ModifyAlarmIntent.ets b/product/phone/src/main/ets/IntentAbility/ModifyAlarmIntent.ets new file mode 100644 index 0000000..e00637f --- /dev/null +++ b/product/phone/src/main/ets/IntentAbility/ModifyAlarmIntent.ets @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogUtil, TimeUtil } from '@hmos/common'; +import insightIntent from '@ohos.app.ability.insightIntent'; +import AlarmManager from '@hmos/common/src/main/ets/manager/AlarmManager' +import { AlarmInfo } from '@hmos/common/src/main/ets/manager/types' +import IntentExecutor from '@ohos.app.ability.InsightIntentExecutor'; +import { AlarmServiceManager } from '@hmos/alarmclock'; + +const TEST_ABILITY: string = 'testTag-IntentExecutor-modifyAlarm'; + + +export default class ModifyAlarmIntentExecutorImpl extends IntentExecutor { + async onExecuteInUIAbilityBackgroundMode(name: string, param: Record): + Promise { + LogUtil.info(TEST_ABILITY, 'onExecuteInUIAbilityBackgroundMode start , name is ', name, 'param is ', JSON.stringify(param)); + let result: insightIntent.ExecuteResult; + if (name !== 'ModifyAlarm') { + LogUtil.warn(TEST_ABILITY, 'Unsupported intentName ', name); + result = { + code: 10030002, + result: { + message: 'Not Support intent.', + entityName: 'Alarm' + } + }; + LogUtil.info(TEST_ABILITY, 'modifyAlarm failed,Not Support intent', JSON.stringify(result)); + return result; + } + const currentTime = new Date().getTime(); + if (param.alarmTime && param.alarmTime < currentTime) { + result = { + code: 10030009, + result: { + message: 'Create/modify Alarm failed,the alarmTime need greater than the currentTime', + entityName: 'Alarm', + } + } + LogUtil.info(TEST_ABILITY, ' CreateAlarm failed, result: ', JSON.stringify(result)) + return result + } + result = await modifyAlarm(param) + LogUtil.info(TEST_ABILITY, 'ExecuteInUIAbilityBackgroundMode finished, result is', JSON.stringify(result)); + return result + + } +} + + +// 修改闹钟 +async function modifyAlarm(param: Record): Promise { + LogUtil.info(TEST_ABILITY, 'modifyAlarm start , id is ' + param.entityId) + if (!param.entityId) { + let result: insightIntent.ExecuteResult = { + code: 10030003, + result: { + message: 'modifyAlarm failed,No alarmId', + entityName: 'Alarm' + } + } + LogUtil.info(TEST_ABILITY, 'modifyAlarm failed ,No alarmId') + return result + } + + let alarmInfoList: AlarmInfo[] | undefined = [] + + alarmInfoList = await AlarmManager.getAlarmById(param.entityId as string) + + if (alarmInfoList && alarmInfoList.length != 0) { + LogUtil.info(TEST_ABILITY, 'this alarm will be modified: ', JSON.stringify(alarmInfoList[0])) + alarmInfoList[0].title = param.alarmTitle ? param.alarmTitle as string : alarmInfoList[0].title + alarmInfoList[0].alarmTime = param.alarmTime ? param.alarmTime as number : alarmInfoList[0].alarmTime + alarmInfoList[0].alarmTimeIntent = param.alarmTime ? param.alarmTime as number : alarmInfoList[0].alarmTimeIntent + alarmInfoList[0].enabled = param.alarmState === 1 ? true : param.alarmState === 0 ? false : true + alarmInfoList[0].snoozeDuration = param.alarmSnoozeDuration ? param.alarmSnoozeDuration as number : + alarmInfoList[0].snoozeDuration + alarmInfoList[0].snoozeTimes = param.alarmSnoozeTotal ? param.alarmSnoozeTotal as number : + alarmInfoList[0].snoozeTimes + alarmInfoList[0].ringDuration = param.alarmRingDuration ? param.alarmRingDuration as number : + alarmInfoList[0].ringDuration + alarmInfoList[0].daysOfWeek = [] + alarmInfoList[0].daysOfWakeType = 1 + if (param.alarmTime || param.alarmTime === 0) { + let newDate = new Date(param.alarmTime as string) + alarmInfoList[0].hour = newDate.getHours() + alarmInfoList[0].minute = newDate.getMinutes() + alarmInfoList[0].second = newDate.getSeconds() + } + + LogUtil.info(TEST_ABILITY, 'before update ' + JSON.stringify(alarmInfoList[0])) + // 更新数据 + await AlarmManager.updateAlarm(alarmInfoList[0], true); + // 存储默认名称 + await AlarmManager.saveDefaultAlarmTitle(alarmInfoList[0].id!, alarmInfoList[0].title!); + // 更新还有xxx时间响铃 + await AlarmServiceManager.refreshNextAlertTime(false, true) + + let resultAlarmList: AlarmInfo[] | undefined = [] + resultAlarmList = await AlarmManager.getAlarmById(param.entityId as string) + if (resultAlarmList && resultAlarmList != undefined && resultAlarmList.length != 0) { + LogUtil.info(TEST_ABILITY, 'this alarm has be modified , the modified result is ', JSON.stringify(resultAlarmList![0])) + return new Promise((resolve, reject) => { + let result: insightIntent.ExecuteResult = { + code: 0, + result: { + entityType: 'Alarm', + entityId: resultAlarmList![0].id!, + alarmTime: resultAlarmList![0].alarmTimeIntent as number, + alarmTitle: resultAlarmList![0]?.title, + alarmState: resultAlarmList![0]?.enabled ? 1 : 0, + alarmSnoozeDuration: resultAlarmList![0]?.snoozeDuration, + alarmSnoozeTotal: resultAlarmList![0]?.snoozeTimes, + alarmRingDuration: resultAlarmList![0]?.ringDuration, + } + } + resolve(result) + }) + } + } + let result: insightIntent.ExecuteResult = { + code: 10030004, + result: { + message: 'modifyAlarm failed , no alarmInfo', + entityName: 'Alarm', + } + } + LogUtil.info(TEST_ABILITY, ' modifyAlarm failed,no alarmInfo', JSON.stringify(result)) + return result +} diff --git a/product/phone/src/main/ets/IntentAbility/QueryAlarmRing.ets b/product/phone/src/main/ets/IntentAbility/QueryAlarmRing.ets new file mode 100644 index 0000000..bbe90f7 --- /dev/null +++ b/product/phone/src/main/ets/IntentAbility/QueryAlarmRing.ets @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import IntentExecutor from '@ohos.app.ability.InsightIntentExecutor'; +import { LogUtil, AlarmStateManager } from '@hmos/common'; +import insightIntent from '@ohos.app.ability.insightIntent'; +import AlarmManager from '@hmos/common/src/main/ets/manager/AlarmManager' + +const LOG_TAG: string = 'testTag-QueryAlarmRing'; + + +export default class QueryAlarmRingIntentExecutorImpl extends IntentExecutor { + async onExecuteInUIAbilityBackgroundMode(name: string, param: Record): + Promise { + + LogUtil.info(LOG_TAG, 'onExecuteInUIAbilityBackgroundMode start , name is ', name); + + let result: insightIntent.ExecuteResult; + if (name !== 'QueryAlarmRing') { + LogUtil.warn(LOG_TAG, 'Unsupported intentName %{public}s', name); + result = { + code: 10030002, + result: { + message: 'Not Support intent.', + } + }; + LogUtil.info(LOG_TAG, 'QueryAlarmRing failed,Not Support intent', JSON.stringify(result)); + return result; + } + + result = await QueryAlarmRing(); + LogUtil.info(LOG_TAG, 'CreateAlarmResult Execute UIAbility in foreground mode finished, result %{public}s', JSON.stringify(result)); + return result; + } +} + + +//查询当前是否有正在响铃的闹钟 +export async function QueryAlarmRing(): Promise { + let res: insightIntent.ExecuteResult + const isFiring = await AlarmStateManager.isFiring(); //boolean + //需要询问是何时调用的此方法来确定是否是调用时机影响 + LogUtil.info(LOG_TAG, ' isFiring: ' + isFiring) + try { + if (isFiring) { + const firingAlarmId = await AlarmStateManager.getAlarmId(); + LogUtil.info(LOG_TAG, `current is firing: firing alarm id is: ${firingAlarmId}`); + + const alarmClock = await AlarmManager.getLatestAlarmFromDb(firingAlarmId); + LogUtil.info(LOG_TAG, 'alarmClockList: ', JSON.stringify(alarmClock)); + + if (alarmClock) { + LogUtil.info(LOG_TAG, 'QueryAlarmRing success '); + return new Promise((resolve, reject) => { + let result: insightIntent.ExecuteResult = { + code: 0, + result: { + entityName: 'Alarm', + entityId: alarmClock?.id ? alarmClock.id : -1, + alarmTime: alarmClock?.alarmTimeIntent ? alarmClock.alarmTimeIntent : -1, + alarmTitle: alarmClock?.title ? alarmClock.title : -1, + alarmState: alarmClock?.enabled ? 1 : 0, + alarmSnoozeDuration: alarmClock?.snoozeDuration ? alarmClock.snoozeDuration : -1, + alarmSnoozeTotal: alarmClock?.snoozeTimes ? alarmClock.snoozeTimes : -1, + alarmRingDuration: alarmClock?.ringDuration ? alarmClock.ringDuration : -1 + } + } + LogUtil.info(LOG_TAG, 'QueryAlarmRing success , the ringing alarm result: ' + JSON.stringify(result)); + resolve(result) + }) + } + + } + + res = { + code: 0, + result: {} + } + LogUtil.info(LOG_TAG, ' QueryAlarmRing success,no ringing alarm', JSON.stringify(res)); + return res + } catch (error) { + LogUtil.error(LOG_TAG, `QueryAlarmRing error: ${error}`); + res = { + code: 10030010, + result: {} + } + return res + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/IntentAbility/SearchAlarmIntent.ets b/product/phone/src/main/ets/IntentAbility/SearchAlarmIntent.ets new file mode 100644 index 0000000..cc755ba --- /dev/null +++ b/product/phone/src/main/ets/IntentAbility/SearchAlarmIntent.ets @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogUtil, TimeUtil } from '@hmos/common'; +import IntentExecutor from '@ohos.app.ability.InsightIntentExecutor'; +import insightIntent from '@ohos.app.ability.insightIntent'; +import AlarmManager from '@hmos/common/src/main/ets/manager/AlarmManager' +import { AlarmInfo } from '@hmos/common/src/main/ets/manager/types' + +// 查询闹钟 +const TEST_ABILITY: string = 'testTag-IntentExecutor-SearchAlarm'; + +interface objExecute { + entityName: string, + entityId: string, + alarmTime: number, + alarmTitle: string, + alarmState: number, + alarmSnoozeDuration: number, + alarmSnoozeTotal: number, + alarmRingDuration: number, +} + +export default class SearchAlarmIntentExecutorImpl extends IntentExecutor { + async onExecuteInUIAbilityBackgroundMode(name: string, param: Record): + Promise { + + LogUtil.info(TEST_ABILITY, 'onExecuteInUIAbilityBackgroundMode start , name is ', name, 'param is ', JSON.stringify(param)); + + let result: insightIntent.ExecuteResult; + if (name !== 'SearchAlarm') { + result = { + code: 10030002, + result: { + message: 'Not Support intent.', + entityName: 'Alarm' + } + }; + LogUtil.info(TEST_ABILITY, 'SearchAlarm failed,Not Support intent', JSON.stringify(result)); + return result; + } + + // 开始查询 + // 判断参数是否满足要求: 至少得有一个参数 且若有timeInterval timeInterval的长度不能超过2 + // 至少得有一个参数 + if (Object.keys(param).length < 1) { + result = { + code: 10030006, + result: { + message: 'no params', + entityName: 'Alarm' + } + }; + LogUtil.info(TEST_ABILITY, ' SearchAlarm failed , at least need one param', JSON.stringify(result)); + return result + } + // 传的没timeInterval + if (!param.timeInterval) { + result = await searchAlarm(param) + return result + } else if (param.timeInterval && (param.timeInterval as number[]).length < 2) { + // 如果传的有timeInterval 但是数组不符合条件 暂不处理 + LogUtil.info(TEST_ABILITY, ' SearchAlarm failed,timeInterval length error' + JSON.stringify(param.timeInterval)); + result = { + code: 10030007, + result: { + message: 'SearchAlarm failed,timeInterval length error', + entityName: 'Alarm' + } + }; + LogUtil.info(TEST_ABILITY, ' SearchAlarm failed,timeInterval length error', JSON.stringify(result)); + return result + } + // 传的有timeInterval 数组符合条件 放行 + LogUtil.info(TEST_ABILITY, ' SearchAlarm , has timeInterval'); + result = await searchAlarm(param) + return result + } +} + +// 查询闹钟 +// rangeType 单独查 alarmTimeIntent单独查 // 其他几个条件并列查 + +async function searchAlarm(param: Record): Promise { + let result: insightIntent.ExecuteResult + LogUtil.info(TEST_ABILITY, ' SearchAlarm start') + if (param.rangeType) { + result = await searchRangeType(param.rangeType as string) + return result + } else if (param.alarmTime) { + result = await searchAlarmTime(param.alarmTime as number) + return result + } else { + result = await searchUnion(param) + return result + } +} + +async function searchRangeType(rangeType: string) { + let result: insightIntent.ExecuteResult + switch (rangeType) { + case 'all': { + LogUtil.info(TEST_ABILITY, ' searchAlarm rangeType is all') + let resultAlarmList = await AlarmManager.getAllAlarmsToShow(); + let stringifyArr: string[] = [] + resultAlarmList.forEach((alarm) => { + stringifyArr.push(stringifyObject(alarm)) + }) + result = { code: 0, result: { items: stringifyArr, } } + LogUtil.info(TEST_ABILITY, ' searchAlarm rangeType:all success , list length is ' + resultAlarmList.length + ' result is' + JSON.stringify(result)); + return result + } + case 'next': { + LogUtil.info(TEST_ABILITY, ' searchAlarm rangeType is next') + let resultAlarm = await AlarmManager.getNearestAlarm(); + if (resultAlarm) { + let stringifyArr: string[] = [] + stringifyArr.push(stringifyObject(resultAlarm)) + result = { code: 0, result: { items: stringifyArr, } } + LogUtil.info(TEST_ABILITY, ' SearchAlarm rangeType:next success ', JSON.stringify(result)); + return result + } + result = { code: 0, result: {} } + LogUtil.info(TEST_ABILITY, ' SearchAlarm rangeType:next success,no next alarm will ring'); + return result + } + case 'current': { + LogUtil.info(TEST_ABILITY, ' searchAlarm rangeType is current') + let sortAlarmList = await AlarmManager.sortAlarmListById() + let resultAlarm = await AlarmManager.getNearestAlarm(); + if (sortAlarmList) { + resultAlarm = sortAlarmList[sortAlarmList.length - 1] + let stringifyArr: string[] = [] + stringifyArr.push(stringifyObject(resultAlarm)) + result = { code: 0, result: { items: stringifyArr } } + LogUtil.info(TEST_ABILITY, ' SearchAlarm rangeType:current success ', JSON.stringify(result)); + return result + } + } + default: { + result = { code: -1, result: {} } + return result + } + } +} + + +async function searchAlarmTime(alarmTime: number) { + let result: insightIntent.ExecuteResult + LogUtil.info(TEST_ABILITY + ' search by alarmTimeIntent now '); + let resultAlarmList = await AlarmManager.getAlarmByAlarmTimeIntent(alarmTime as number) + LogUtil.info(TEST_ABILITY + ' result alarm list is ' + JSON.stringify(resultAlarmList)); + let stringifyArr: string[] = [] + if (resultAlarmList && resultAlarmList.length != 0) { + resultAlarmList.forEach((alarm) => { + stringifyArr.push(stringifyObject(alarm)) + }) + result = { code: 0, result: { items: stringifyArr } } + LogUtil.info(TEST_ABILITY, ' SearchAlarm success by alarmTime success , list length is ' + JSON.stringify(resultAlarmList.length) + ', result is ' + JSON.stringify(result)); + return result + } + result = { code: 0, result: {} } + LogUtil.info(TEST_ABILITY, 'search success , no such clock', JSON.stringify(resultAlarmList)); + return result +} + + +async function searchUnion(param: Record) { + let result: insightIntent.ExecuteResult + let resultAlarmList: AlarmInfo[] | undefined = [] + resultAlarmList = await AlarmManager.searchAlarmInfo(param.entityId as string, param.alarmTitle as + string, param.alarmState as boolean, + param.timeInterval as string[]); + let stringifyArr: string[] = [] + if (resultAlarmList && resultAlarmList.length != 0) { + resultAlarmList.forEach((alarm) => { + stringifyArr.push(stringifyObject(alarm)) + }) + result = { code: 0, result: { items: stringifyArr } } + LogUtil.info(TEST_ABILITY, ' SearchAlarm success , list length is ' + JSON.stringify(resultAlarmList.length) + ', result is ' + JSON.stringify(result)); + return result + } + result = { code: 0, result: {} } + LogUtil.info(TEST_ABILITY, 'search success , no such clock', JSON.stringify(resultAlarmList)); + return result +} + +function stringifyObject(alarm: AlarmInfo) { + let addObject: objExecute = { + entityName: 'Alarm', + entityId: alarm.id as string, + alarmTime: TimeUtil.getAlarmTime(alarm), + alarmTitle: alarm.title, + alarmState: alarm.enabled ? 1 : 0, + alarmSnoozeDuration: alarm.snoozeDuration, + alarmSnoozeTotal: alarm.snoozeTimes, + alarmRingDuration: alarm.ringDuration + } + return JSON.stringify(addObject) +} \ No newline at end of file diff --git a/product/phone/src/main/ets/IntentAbility/StopRingIntent.ets b/product/phone/src/main/ets/IntentAbility/StopRingIntent.ets new file mode 100644 index 0000000..37758d3 --- /dev/null +++ b/product/phone/src/main/ets/IntentAbility/StopRingIntent.ets @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import IntentExecutor from '@ohos.app.ability.InsightIntentExecutor'; +import { LogUtil, AlarmStateManager, WantAgentUtil } from '@hmos/common'; +import insightIntent from '@ohos.app.ability.insightIntent'; +import AlarmManager from '@hmos/common/src/main/ets/manager/AlarmManager' + +const TEST_ABILITY: string = 'testTag-IntentExecutor-StopRing'; + +export default class StopAlarmRingIntentExecutorImpl extends IntentExecutor { + async onExecuteInUIAbilityBackgroundMode(name: string): Promise { + + LogUtil.info(TEST_ABILITY, 'onExecuteInUIAbilityBackgroundMode start , name is ', name); + + let result: insightIntent.ExecuteResult; + if (name !== 'StopAlarmRing') { + LogUtil.warn(TEST_ABILITY, 'Unsupported intentName ', name); + result = { + code: 10030002, + result: { + message: 'Not Support intent.', + entityName: 'Alarm' + } + }; + LogUtil.info(TEST_ABILITY, 'StopAlarmRing failed,Not Support intent', JSON.stringify(result)); + return result; + } + result = await stopAlarmRing() + LogUtil.info(TEST_ABILITY, 'ExecuteInUIAbilityBackgroundMode finished, result is', JSON.stringify(result)); + return result + } +} + + +async function stopAlarmRing(): Promise { + + LogUtil.info(TEST_ABILITY, 'StopRing start') + + const isFiring = await AlarmStateManager.isFiring(); //boolean + if (isFiring) { + const firingAlarmId = await AlarmStateManager.getAlarmId(); + const firingAlarm = await AlarmManager.getLatestAlarmFromDb(firingAlarmId); + if (firingAlarm) { + WantAgentUtil.triggerCloseAlarm(firingAlarm); + LogUtil.info(TEST_ABILITY, 'StopRing success , stop alarm is' + JSON.stringify(firingAlarm)) + return new Promise((resolve, reject) => { + let result: insightIntent.ExecuteResult = { + code: 0, + result: { + entityName: 'Alarm', + entityId: firingAlarm.id!, + alarmTitle: firingAlarm.title, + alarmTime: firingAlarm.alarmTimeIntent ? firingAlarm.alarmTimeIntent : -1, + alarmState: firingAlarm.enabled ? 1 : 0, + alarmSnoozeDuration: firingAlarm.snoozeDuration, + alarmSnoozeTotal: firingAlarm.snoozeTimes, + alarmRingDuration: firingAlarm.ringDuration + } + } + resolve(result) + }) + } + } + + let result: insightIntent.ExecuteResult = { + code: 10030008, + result: {} + } + LogUtil.info(TEST_ABILITY, 'StopRing failed,no ringing alarm'); + return result + +} \ No newline at end of file diff --git a/product/phone/src/main/ets/IntentAbility/ViewAlarm.ets b/product/phone/src/main/ets/IntentAbility/ViewAlarm.ets new file mode 100644 index 0000000..870a755 --- /dev/null +++ b/product/phone/src/main/ets/IntentAbility/ViewAlarm.ets @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import IntentExecutor from '@ohos.app.ability.InsightIntentExecutor'; +import { AGENT_REQUEST_CODE_NORMAL, LogUtil, WantAgentUtil } from '@hmos/common'; +import insightIntent from '@ohos.app.ability.insightIntent'; +import AlarmManager from '@hmos/common/src/main/ets/manager/AlarmManager' +import window from '@ohos.window'; +import wantAgent from '@ohos.app.ability.wantAgent' +import Want from '@ohos.app.ability.Want'; +import common from '@ohos.app.ability.common'; + +const LOG_TAG: string = 'testTag-ViewAlarm'; + + +export default class ViewAlarmIntentExecutorImpl extends IntentExecutor { + async onExecuteInUIAbilityForegroundMode(name: string, param: Record, + pageLoader?: window.WindowStage): + Promise { + LogUtil.info(LOG_TAG, 'onExecuteInUIAbilityForegroundMode start , name is ', JSON.stringify(param)); + + let result: insightIntent.ExecuteResult; + if (name !== 'ViewAlarm') { + LogUtil.warn(LOG_TAG, 'Unsupported intentName %{public}s', name); + result = { + code: 10030002, + result: { + message: 'Not Support intent.', + } + }; + LogUtil.info(LOG_TAG, 'ViewAlarm failed,Not Support intent', JSON.stringify(result)); + return result; + } + + const triggerWantAgent = await WantAgentUtil.getMainPageWantAgent(); + if (triggerWantAgent) { + wantAgent.trigger(triggerWantAgent, { + code: AGENT_REQUEST_CODE_NORMAL, + }, data => { + LogUtil.info(LOG_TAG, `trigger response: ${JSON.stringify(data)}`); + }); + } + + result = await viewAlarm(param); + LogUtil.info(LOG_TAG, 'Execute UIAbility in foreground mode finished, result %{public}s', JSON.stringify(result)); + return result; + } +} + + +//查看闹钟 +async function viewAlarm(param: Record): Promise { + let res: insightIntent.ExecuteResult + LogUtil.info(LOG_TAG, 'ViewAlarm param : ' + param.entityId); + + try { + if (!param.entityId) { + LogUtil.info(LOG_TAG, 'ViewAlarm failed ,No alarmId') + let result: insightIntent.ExecuteResult = { + code: 0, + result: { + message: 'ViewAlarm success ,but No Id ' + } + } + return result + + } else { + const alarmFromList = await AlarmManager.getIDAlarmFrom(param.entityId as string); + LogUtil.info(LOG_TAG, ' getIDAlarmFrom success, alarmFromList: ', JSON.stringify(alarmFromList)) + let result: insightIntent.ExecuteResult = { + code: 0, + result: { + message: 'ViewAlarm success' + } + } + LogUtil.info(LOG_TAG, ' ViewAlarm success, result: ', JSON.stringify(result)) + return result + } + } catch (error) { + LogUtil.error(LOG_TAG, `ViewAlarm error: ${error}`); + res = { + code: 10030011, + result: { + message: 'ViewAlarm failed', + entityName: 'Alarm', + } + } + return res + } + +} diff --git a/product/phone/src/main/ets/MainAbility/MainAbility.ets b/product/phone/src/main/ets/MainAbility/MainAbility.ets new file mode 100644 index 0000000..1a414a3 --- /dev/null +++ b/product/phone/src/main/ets/MainAbility/MainAbility.ets @@ -0,0 +1,344 @@ +// import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +// import { hilog } from '@kit.PerformanceAnalysisKit'; +// import { window } from '@kit.ArkUI'; +// +// export default class EntryAbility extends UIAbility { +// onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { +// hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); +// } +// +// onDestroy(): void { +// hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); +// } +// +// onWindowStageCreate(windowStage: window.WindowStage): void { +// // Main window is created, set main page for this ability +// hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); +// +// windowStage.loadContent('pages/index', (err) => { +// if (err.code) { +// hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); +// return; +// } +// hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); +// }); +// } +// +// onWindowStageDestroy(): void { +// // Main window is destroyed, release UI related resources +// hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); +// } +// +// onForeground(): void { +// // Ability has brought to foreground +// hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); +// } +// +// onBackground(): void { +// // Ability has back to background +// hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); +// } +// } + +//-------------------------以上仅打开index-------------------------------------------------------------- + + +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Ability from '@ohos.app.ability.UIAbility'; +import contextConstant from '@ohos.app.ability.contextConstant'; +import Want from '@ohos.app.ability.Want'; +import window from '@ohos.window'; +import Window from '@ohos.window'; +import router from '@ohos.router'; +import { + BreakpointManager, + GlobalContext, + LogUtil, + TsUtilManager, + PageStateManager, + CommonUtil, + SoundPool, + TIMER_NOTICE_ID, + TOTAL_TIME_TIMER_PREFERENCES_CLOCK, + LEFT_TIME_TIMER_PREFERENCES_CLOCK, + PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK, + START_DATE_TIMER_PREFERENCES_CLOCK, + TimerSystemTimer, +} from '@hmos/common'; +import { NotificationUtil } from '@hmos/alarmclock'; +import { BusinessError } from '@ohos.base'; +import EnvironmentCallback from '@ohos.app.ability.EnvironmentCallback'; +import { TimerNotificationUtil } from '@hmos/timer'; +import { timerPreferencesManagerUtil } from '@hmos/timer/src/main/ets/common/preferencesUtil'; +import TimerAudioPlayer from '@hmos/timer/src/main/ets/manager/timerAudioPlayer'; + +const TAG = 'MainAbility'; +const ALARM_CLOCK_TAB = 'ALARM_CLOCK_TAB'; +const TIMER_TAB = 'TIMER_TAB'; +const CALLER_BUNDLE_NAME = 'ohos.aafwk.param.callerBundleName'; +const TIMER_BEEP: string = 'Timer_Beep.ogg'; + +export interface initializingProperties { + externalWindowStage?: window.WindowStage; + windowStage?: window.WindowStage; +} + +export default class MainAbility extends Ability { + public environmentCallbackId: number = -1; + + onBackPressed() { + LogUtil.info(TAG, 'onBackPressed'); + return true; + } + + async onCreate(want: Want): Promise { + LogUtil.info(TAG, 'MainAbility onCreate'); + this.context.area = contextConstant.AreaMode.EL1; + AppStorage.setOrCreate('currentColorMode', this.context.config.colorMode); + GlobalContext.getContext().setObject('abilityWant', want); + GlobalContext.getContext().setObject('clockContext', this.context); + GlobalContext.getContext().setObject('clockContextType', 'MainAbility'); + GlobalContext.getContext().setObject('resourceManager', this.context.resourceManager); + LogUtil.info(TAG, 'MainAbility onCreate' + JSON.stringify(want)); + CommonUtil.requestTimerResources(); + this.registerEnvironmentChanged(); + //处理卡片跳转时钟默认显示闹钟列表 + if (want.parameters && want.parameters.tab_active == ALARM_CLOCK_TAB) { + AppStorage.setOrCreate('ALARM_CLOCK_TAB', true); + await PageStateManager.changeTabsToIndex(0); + } else if (want.parameters && want.parameters?.tab_active == TIMER_TAB) { + LogUtil.info(TAG, `changeTabsToIndex=>onForeground${want.parameters?.tab_active}}`) + await PageStateManager.changeTabsToIndex(3); + } else if (TsUtilManager.hasItsProperty(want.parameters, CALLER_BUNDLE_NAME) && + TsUtilManager.getItsProperty(want.parameters, CALLER_BUNDLE_NAME) === '') { + await PageStateManager.changeTabsToIndex(0); + } + } + + private registerEnvironmentChanged(): void { + let envCallback: EnvironmentCallback = { + onConfigurationUpdated(config): void { + let mainWindow = AppStorage.Get('mainWindow') + AppStorage.setOrCreate('currentColorMode', config.colorMode); + if (config.colorMode === 0) { + try { + let mainWindow = AppStorage.Get('mainWindow') + LogUtil.info(TAG, 'setting window backGroundColor'); + mainWindow?.setWindowBackgroundColor('#000000'); + } catch (error) { + LogUtil.error(TAG, 'set windowBGColor error:' + JSON.stringify(error)); + } + + let sysBarProps: window.SystemBarProperties = { + // 以下两个属性从API Version 8开始支持 + statusBarContentColor: '#ffffff', + navigationBarContentColor: '#ffffff' + }; + mainWindow?.setWindowSystemBarProperties(sysBarProps, (err: BusinessError) => { + let errCode: number = err.code; + if (errCode) { + LogUtil.error(TAG, 'Failed to set the system bar properties. Cause: ' + JSON.stringify(err)); + return; + } + LogUtil.info(TAG, 'Succeeded in setting the system bar properties.'); + }); + } else if (config.colorMode === 1) { + try { + let context = GlobalContext.getContext().getObject('clockContext') as Context + let backgroundColor: number = + context.resourceManager.getColorSync($r('sys.color.ohos_id_background_secondary').id) + let mainWindow = AppStorage.Get('mainWindow') + mainWindow?.setWindowBackgroundColor('#' + backgroundColor.toString(16)); + } catch (err) { + LogUtil.error(TAG, `onConfigurationUpdated Failed: ${JSON.stringify(err)}`) + } + + let isLayoutFullScreen = true; + mainWindow?.setWindowLayoutFullScreen(isLayoutFullScreen, (err: BusinessError) => { + let errCode: number = err.code; + if (errCode) { + LogUtil.error(TAG, 'Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(err)); + return; + } + LogUtil.info(TAG, 'Succeeded in setting the window layout to full-screen mode.'); + }); + let sysBarProps: window.SystemBarProperties = { + // 以下两个属性从API Version 8开始支持 + statusBarContentColor: '#000000', + navigationBarContentColor: '#000000' + }; + mainWindow?.setWindowSystemBarProperties(sysBarProps, (err: BusinessError) => { + let errCode: number = err.code; + if (errCode) { + LogUtil.error(TAG, 'Failed to set the system bar properties. Cause: ' + JSON.stringify(err)); + return; + } + LogUtil.info(TAG, 'Succeeded in setting the system bar properties.'); + }); + } else { + try { + let context = GlobalContext.getContext().getObject('clockContext') as Context; + let backgroundColor: number = + context.resourceManager.getColorSync($r('sys.color.ohos_id_background_secondary').id); + let mainWindow = AppStorage.Get('mainWindow'); + mainWindow?.setWindowBackgroundColor('#' + backgroundColor.toString(16)); + } catch (err) { + LogUtil.error(TAG, `onConfigurationUpdated Failed: ${JSON.stringify(err)}`); + } + } + }, + onMemoryLevel(level): void { + LogUtil.info(TAG, `onMemoryLevel success: ${level}`); + } + }; + try { + this.environmentCallbackId = this.context.getApplicationContext().on('environment', envCallback); + LogUtil.info(TAG, `callbackId: ${this.environmentCallbackId}`); + } catch (paramError) { + LogUtil.info(TAG, `error on environment ${(paramError as BusinessError).code}`); + } + } + + async onDestroy(): Promise { + await SoundPool.releaseAll() + AppStorage.setOrCreate('currentColorMode', this.context.config.colorMode); + LogUtil.info(TAG, 'MainAbility onDestroy'); + } + + async onWindowStageCreate(windowStage: window.WindowStage): Promise { + windowStage?.loadContent('pages/index') + // Main window is created, set main page for this ability + let windowClass: window.Window | null = null; + LogUtil.info(TAG, 'MainAbility onWindowStageCreate'); + const mainWindow = await windowStage.getMainWindow(); + AppStorage.setOrCreate('mainWindow', mainWindow); + const windowSize = (await mainWindow?.getProperties())?.windowRect || { + width: 0, height: 0 + }; + AppStorage.setOrCreate('windowStandardHeight', windowSize.height); + await BreakpointManager.initBreakpointFromWidth(windowSize.width); + const curColorMode = AppStorage.get('currentColorMode'); + let localStorage: LocalStorage = new LocalStorage({ + windowStage: windowStage, + } as initializingProperties); + windowStage?.loadContent('pages/index', localStorage).then(() => { + windowStage.getMainWindow().then((data) => { + windowClass = data; + AppStorage.setOrCreate('isMainPageReport', true); + const isMainPageReport = AppStorage.get('isMainPageReport'); + LogUtil.info(TAG, 'onMainStageCreate...' + isMainPageReport); + + if (curColorMode === 0) { + let isLayoutFullScreen = true; + windowClass?.setWindowLayoutFullScreen(isLayoutFullScreen, (err: BusinessError) => { + let errCode: number = err.code; + if (errCode) { + LogUtil.error(TAG, 'Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(err)); + return; + } + LogUtil.info(TAG, 'Succeeded in setting the window layout to full-screen mode.'); + }); + let sysBarProps: window.SystemBarProperties = { + // 以下两个属性从API Version 8开始支持 + statusBarContentColor: '#ffffff', + navigationBarContentColor: '#ffffff' + }; + windowClass.setWindowSystemBarProperties(sysBarProps, (err: BusinessError) => { + let errCode: number = err.code; + if (errCode) { + LogUtil.error(TAG, 'Failed to set the system bar properties. Cause: ' + JSON.stringify(err)); + return; + } + LogUtil.info(TAG, 'Succeeded in setting the system bar properties.'); + }); + } else { + let backgroundColor: number = + this.context.resourceManager.getColorSync($r('sys.color.ohos_id_background_secondary').id) + data.setWindowBackgroundColor('#' + backgroundColor.toString(16)); + AppStorage.SetOrCreate('mainWindow', data); + } + }).catch((err: BusinessError) => { + LogUtil.error(TAG, `GetMainWindow Failed: ${JSON.stringify(err)}`); + }); + }); + } + + async onWindowStageDestroy(): Promise { + // Main window is destroyed, release UI related resources + const curColorMode = AppStorage.get('currentColorMode'); + AppStorage.setOrCreate('currentColorMode', curColorMode); + LogUtil.info(TAG, 'MainAbility onWindowStageDestroy'); + //删除闹钟卡片进入标识 + AppStorage.setOrCreate('ALARM_CLOCK_TAB', false); + await TimerNotificationUtil.cancel(TIMER_NOTICE_ID) + timerPreferencesManagerUtil.del(TOTAL_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(LEFT_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(PASSED_TIME_TIME_TIMER_PREFERENCES_CLOCK); + timerPreferencesManagerUtil.del(START_DATE_TIMER_PREFERENCES_CLOCK); + TimerAudioPlayer.stopTick(TIMER_BEEP) + await TimerSystemTimer.destory(); + } + + async onForeground(): Promise { + // Ability has brought to foreground + GlobalContext.getContext().setObject('isBackground', false); + LogUtil.info(TAG, 'MainAbility onForeground'); + } + + async onBackground(): Promise { + // Ability has back to background + GlobalContext.getContext().setObject('isBackground', true); + LogUtil.info(TAG, 'MainAbility onBackground'); + } + + async onNewWant(want: Want): Promise { + LogUtil.info(TAG, 'onNewWant Want:' + JSON.stringify(want)); + GlobalContext.getContext().setObject('clockContext', this.context); + GlobalContext.getContext().setObject('clockContextType', 'MainAbility'); + if (!GlobalContext.getContext().getObject('isBackground')) { + if (TsUtilManager.hasItsProperty(want?.parameters, 'cityId')) { + try { + LogUtil.info(TAG, 'cityId Property exist'); + const cityId = want?.parameters?.cityId; + LogUtil.info(TAG, 'cityId is: ', cityId as string); + AppStorage.setOrCreate('cityId', cityId as string); + router.back(); + } catch (error) { + LogUtil.error(TAG, 'onNewWant error:' + JSON.stringify(error)); + } + } else { + LogUtil.info(TAG, 'cityId Property not exist'); + } + } + //处理卡片跳转时钟默认显示闹钟列表 + if (want.parameters && want.parameters.tab_active == ALARM_CLOCK_TAB) { + AppStorage.setOrCreate('ALARM_CLOCK_TAB', true); + await PageStateManager.changeTabsToIndex(0); + } else if (want.parameters && want.parameters?.tab_active == TIMER_TAB) { + LogUtil.info(TAG, `changeTabsToIndex=>onBackground${want.parameters?.tab_active}}`) + await PageStateManager.changeTabsToIndex(3); + } else if (TsUtilManager.hasItsProperty(want.parameters, CALLER_BUNDLE_NAME) && + TsUtilManager.getItsProperty(want.parameters, CALLER_BUNDLE_NAME) === '') { + await PageStateManager.changeTabsToIndex(0); + } + + if (want.parameters && want.parameters.alarmId && want.parameters.isMissed) { + const alarmId = want?.parameters?.alarmId + NotificationUtil.cancel(alarmId as string) + } + } +} diff --git a/product/phone/src/main/ets/MainAbility/MainAbility.ts b/product/phone/src/main/ets/MainAbility/MainAbility.ts deleted file mode 100644 index ae6097e..0000000 --- a/product/phone/src/main/ets/MainAbility/MainAbility.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import Ability from '@ohos.application.Ability' - -export default class MainAbility extends Ability { - onCreate(want, launchParam) { - console.log("[Demo] MainAbility onCreate") - globalThis.abilityWant = want; - globalThis.abilityContext = this.context; - } - - onDestroy() { - console.log("[Demo] MainAbility onDestroy") - } - - onWindowStageCreate(windowStage) { - // Main window is created, set main page for this ability - console.log("[Demo] MainAbility onWindowStageCreate") - - windowStage.loadContent("pages/index", (err, data) => { - if (err.code) { - console.error('Failed to load the content. Cause:' + JSON.stringify(err)); - return; - } - console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data)) - }); - } - - onWindowStageDestroy() { - // Main window is destroyed, release UI related resources - console.log("[Demo] MainAbility onWindowStageDestroy") - } - - onForeground() { - // Ability has brought to foreground - console.log("[Demo] MainAbility onForeground") - } - - onBackground() { - // Ability has back to background - console.log("[Demo] MainAbility onBackground") - } -}; diff --git a/product/phone/src/main/ets/ServiceExtAbility/AlarmService.ets b/product/phone/src/main/ets/ServiceExtAbility/AlarmService.ets new file mode 100644 index 0000000..d955736 --- /dev/null +++ b/product/phone/src/main/ets/ServiceExtAbility/AlarmService.ets @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import commonEventManager from '@ohos.commonEventManager'; +import contextConstant from '@ohos.app.ability.contextConstant'; +// import ServiceExtensionAbility from '@ohos.app.ability.ServiceExtensionAbility'; +import { AlarmServiceManager, AlarmCardUtil, NotificationUtil } from '@hmos/alarmclock'; +import { + AlarmInfo, + CommonUtil, + EVENT_ID_KILL_ALARM_TIMEOUT_MESSAGE, + FullScreenType, + WantAgentUtil +} from '@hmos/common'; +import { + LogUtil, + ResourceManager, + AlarmStateManager, + TIME_TAG_ID_LIST_IN_ZH, + AlarmServiceType, + MILLIS_IN_SECOND, + SECOND_IN_MINUTE, + GlobalContext, +} from '@hmos/common'; +import Want from '@ohos.app.ability.Want'; +import deviceInfo from '@ohos.deviceInfo' +import SnoozeManager from '@hmos/common/src/main/ets/manager/SnoozeManager'; +import notificationManager from '@ohos.notificationManager'; +import emitter from '@ohos.events.emitter'; + +const deviceType: string = deviceInfo.deviceType; +const TIME_SUBSCRIBE_INFO: CommonEventSubscribeInfo = { + events: [ + commonEventManager.Support.COMMON_EVENT_SCREEN_OFF, + ], +}; + +const TAG = 'AlarmService'; + +type CommonEventSubscriber = commonEventManager.CommonEventSubscriber; +type CommonEventSubscribeInfo = commonEventManager.CommonEventSubscribeInfo; + +/** + * Service ability for alarm notification + * + * @since 2022-08-18 + */ +export default class AlarmService { + private killAlarmMessageId: number = 0; + private commonEventSubscriber: CommonEventSubscriber = {} as CommonEventSubscriber; + private static instance: AlarmService; + private static isNotificationSubscribe = false; + + public static getContext(): AlarmService { + if (!AlarmService.instance) { + AlarmService.instance = new AlarmService(); + } + return AlarmService.instance; + } + + private setInitInfo(want: Want): void { + // this.context.area = contextConstant.AreaMode.EL1; + // GlobalContext.getContext().setObject('clockContext', this.context); + GlobalContext.getContext().setObject('clockContextType', 'AlarmService'); + // GlobalContext.getContext().setObject('AlarmServiceContext', this.context); + // GlobalContext.getContext().setObject('resourceManager', this.context.resourceManager); + GlobalContext.getContext().setObject('abilityWant', want); + } + + async onCreate(want: Want): Promise { + LogUtil.info(TAG, 'onCreate'); + this.setInitInfo(want); + await AlarmServiceManager.createAudioWorker(true); + await ResourceManager.preloadStringResources(TIME_TAG_ID_LIST_IN_ZH); + this.subscribeCommentEvent(); + if (!AlarmService.isNotificationSubscribe) { + await NotificationUtil.notificationSubscribeSystemSubscriber(); + AlarmService.isNotificationSubscribe = true; + } + CommonUtil.requestCPUResources(); + await CommonUtil.getWallpaper(); + } + + private serviceType(want?: Want) { + return want?.parameters?.serviceType + } + + private isSnooze(want?: Want) { + return want?.parameters?.isSnooze + } + + async onRequest(want: Want): Promise { + LogUtil.info(TAG, `onRequest, want is: ${JSON.stringify(want)}`); + const serviceType = this.serviceType(want); + const alarmInfo: AlarmInfo = want?.parameters?.alarmInfo as AlarmInfo; + const isNotificationCloseButton = want?.parameters?.isNotificationCloseButton; + const isSnooze = this.isSnooze(want); + this.setInitInfo(want); + if (serviceType && serviceType === AlarmServiceType.ReStart) { + await AlarmServiceManager.dealAlarmWithServiceType(alarmInfo, serviceType as AlarmServiceType); + return; + } + if (serviceType && serviceType === AlarmServiceType.ToggleFullScreen) { + if (deviceType !== '2in1') { + await WantAgentUtil.triggerFullScreenAbility(FullScreenType.Alarm); + } + return; + } + if (serviceType && serviceType === AlarmServiceType.Update) { + await AlarmServiceManager.refreshNextAlertTime(want?.parameters?.forceRefreshTimer as boolean); + return; + } + if (!serviceType || !alarmInfo) { + LogUtil.info(TAG, 'Some parameter is not passed:' + serviceType + ',alarmInfo:' + JSON.stringify(alarmInfo)); + return; + } + const isFiring = await AlarmStateManager.isFiring(); + if (isFiring && serviceType === AlarmServiceType.Start) { + LogUtil.info(TAG, 'one alarm has Firing, so need to clearKillAlarmMessage:' + this.killAlarmMessageId); + this.clearKillAlarmMessage(this.killAlarmMessageId); + } + await AlarmServiceManager.dealAlarmWithServiceType(alarmInfo, serviceType as AlarmServiceType, + isNotificationCloseButton as boolean, isSnooze as boolean); + await this.dealKillAlarmMessage(alarmInfo, serviceType as AlarmServiceType); + } + + async dealKillAlarmMessage(alarmInfo: AlarmInfo | undefined, serviceType: AlarmServiceType): Promise { + const isFiring = await AlarmStateManager.isFiring(); + const snoozeIds = await SnoozeManager.getSnoozedAlarmId(); + const notificationCnt = await notificationManager.getActiveNotificationCount(); + LogUtil.info(TAG, `dealKillAlarmMessage isFiring:${isFiring} cnt:${notificationCnt} snoozeIds:${JSON.stringify(snoozeIds)} `); + if (serviceType === AlarmServiceType.Start) { + if (isFiring && alarmInfo) { + LogUtil.info(TAG, 'alarm isFiring, so need to sendDelayedKillAlarmMessage'); + this.killAlarmMessageId = this.sendDelayedKillAlarmMessage(alarmInfo); + AlarmCardUtil.notifyAlarmCardRingUpdate(); + } else { + AlarmServiceManager.setScreenOff(); + LogUtil.info(TAG, 'no alarm isFiring, so not need to sendDelayedKillAlarmMessage'); + } + } else { + if (!isFiring) { + AlarmServiceManager.setScreenOff(); + this.clearKillAlarmMessage(this.killAlarmMessageId); + await CommonUtil.terminateAlarmService(); + } + } + } + + onDestroy(): void { + LogUtil.info(TAG, 'onDestroy'); + try { + GlobalContext.getContext().setObject('AlarmServiceContext', undefined); + this.clearKillAlarmMessage(this.killAlarmMessageId); + CommonUtil.terminateFullScreenAbility(); + AlarmServiceManager.setScreenOff(); + AlarmServiceManager.cancelSnoozeInput(); + AlarmServiceManager.stopAudioWorker(); + CommonUtil.releaseCPUResources(); + this.unSubscribeCommentEvent(); + } catch (error) { + LogUtil.error(TAG, 'onDestroy failed: ', JSON.stringify(error)); + } + + } + + /** + * Send a delay message after the alarm starts to automatically snooze the alarm. + */ + private sendDelayedKillAlarmMessage(alarmInfo: AlarmInfo): number { + this.clearKillAlarmMessage(this.killAlarmMessageId); + LogUtil.info(TAG, `send auto snooze message alarm id: ${JSON.stringify(alarmInfo.id)}, time: ${alarmInfo.ringDuration! * SECOND_IN_MINUTE * MILLIS_IN_SECOND}`); + return setTimeout(async (alarmInfo: AlarmInfo) => { + LogUtil.info(TAG, 'start to execute delayAlarm for auto snooze'); + const firingAlarmId = await AlarmStateManager.getAlarmId(); + LogUtil.info(TAG, `auto snooze alarmInfo id: ${JSON.stringify(alarmInfo.id)}, firingAlarmId: ${firingAlarmId}`); + if (alarmInfo.id !== String(firingAlarmId)) { + return; + } + await AlarmServiceManager.delayAlarm(alarmInfo, true); + this.killAlarmMessageId = 0; + const isFiring = await AlarmStateManager.isFiring(); + if (!isFiring) { + AlarmServiceManager.setScreenOff(); + await CommonUtil.terminateAlarmService(); + } + }, alarmInfo.ringDuration! * SECOND_IN_MINUTE * MILLIS_IN_SECOND, alarmInfo); + } + + private clearKillAlarmMessage(messageId?: number): void { + try { + if (messageId) { + LogUtil.info(TAG, `clearKillAlarmMessage: ${messageId}`); + clearTimeout(messageId); + this.killAlarmMessageId = 0; + } + } catch (error) { + LogUtil.error(TAG, 'clearKillAlarmMessage failed: ', JSON.stringify(error)); + } + } + + private async subscribeCommentEvent(): Promise { + this.commonEventSubscriber = await commonEventManager.createSubscriber(TIME_SUBSCRIBE_INFO); + if (!this.commonEventSubscriber) { + return; + } + commonEventManager.subscribe(this.commonEventSubscriber, async () => { + LogUtil.info(TAG, 'receive screen off event'); + await AlarmServiceManager.delayFiringAlarm(); + }); + + emitter.on({ + eventId: EVENT_ID_KILL_ALARM_TIMEOUT_MESSAGE + }, async (eventData) => { + LogUtil.info(TAG, `receve EVENT_ID_KILL_ALARM_TIMEOUT_MESSAGE: ${JSON.stringify(eventData)}`); + await this.dealKillAlarmMessage(undefined, AlarmServiceType.Delay); + }) + } + + private unSubscribeCommentEvent(): void { + commonEventManager.unsubscribe(this.commonEventSubscriber, error => { + if (error) { + LogUtil.error(TAG, `Unsubscribe to common event failed because: ${JSON.stringify(error)}`); + return; + } + LogUtil.info(TAG, 'Unsubscribe to common event event successfully!'); + }); + emitter.off(EVENT_ID_KILL_ALARM_TIMEOUT_MESSAGE); + } +}; \ No newline at end of file diff --git a/product/phone/src/main/ets/ServiceExtAbility/TimerService.ets b/product/phone/src/main/ets/ServiceExtAbility/TimerService.ets new file mode 100644 index 0000000..d821fea --- /dev/null +++ b/product/phone/src/main/ets/ServiceExtAbility/TimerService.ets @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import contextConstant from '@ohos.app.ability.contextConstant'; +// import ServiceExtensionAbility from '@ohos.app.ability.ServiceExtensionAbility'; +import { TimerNotificationUtil } from '@hmos/timer'; +import { + LogUtil, + TimerServiceType, + TimerStateManager, + TIMER_NOTICE_ID, + WantAgentUtil, + TIMEOUT_FULL_SCREEN_ID, + TIMEOUT_FULL_SCREEN_CLOSE_ID, + TIMEOUT_FLAG_ID, + CommonUtil, + FullScreenType, + GlobalContext, +} from '@hmos/common'; +import Want from '@ohos.app.ability.Want'; +import screenLock from '@ohos.screenLock'; +import power from '@ohos.power'; +import TimerAudioPlayer from '@hmos/timer/src/main/ets/manager/timerAudioPlayer'; +import { AlarmServiceManager } from '@hmos/alarmclock'; +import emitter from '@ohos.events.emitter'; +import deviceInfo from '@ohos.deviceInfo'; + +const TAG = 'TimerService'; +const TIMER_BEEP: string = 'Timer_Beep.ogg'; +const UPDATE_INTERVAL: number = 150; +let timeoutDuration: number = 0; +let timeOutFlag = false + +/** + * Service ability for alarm notification + * + * @since 2022-08-18 + */ +export default class TimerService { + private static instance: TimerService; + private static isNotificationSubscribe = false; + + public static getContext(): TimerService { + if (!TimerService.instance) { + TimerService.instance = new TimerService(); + } + return TimerService.instance; + } + + async onCreate(want: Want): Promise { + LogUtil.info(TAG, 'onCreate'); + // GlobalContext.getContext().setObject('clockContext', this.context); + // GlobalContext.getContext().setObject('clockContextType', 'TimerService'); + if (!TimerService.isNotificationSubscribe) { + await TimerNotificationUtil.notificationSubscribeSystemSubscriber(); + TimerService.isNotificationSubscribe = true; + } + CommonUtil.requestCPUResources(); + await CommonUtil.getWallpaper(); + } + + forwardTiming(localTime: number) { + const now = new Date().getTime(); // 获取当前本地时间 + const timeGap = now - localTime; // 计算时间差 + //下一次计时器应该触发的时间 + const nextTickTime = UPDATE_INTERVAL - (timeGap % UPDATE_INTERVAL); + let timerTimeout = setTimeout(() => { + if (timeOutFlag) { + timeoutDuration += UPDATE_INTERVAL; + emitter.emit({ + eventId: TIMEOUT_FULL_SCREEN_ID, + priority: emitter.EventPriority.IMMEDIATE + }, { + data: { + timeoutDuration: timeoutDuration + } + }); + this.forwardTiming(localTime); + } else { + clearTimeout(timerTimeout); + } + }, nextTickTime); + } + + async onRequestTimeOutFiring(timerId: string, alarmAlertTime: number): Promise { + if (deviceInfo.deviceType !== '2in1') { + timeoutDuration = 0; + timeOutFlag = true; + const isScreenLocked: boolean = await screenLock.isScreenLocked(); + LogUtil.info(TAG, 'isScreenLocked is ' + isScreenLocked); + await AlarmServiceManager.delayFiringAlarm(true); + await TimerStateManager.setStateFiring(TIMER_NOTICE_ID); + TimerAudioPlayer.playTick(TIMER_BEEP); + this.forwardTiming(new Date().getTime()); + if (!isScreenLocked) { + TimerNotificationUtil.timeoutPublishAlarmMissed(timerId, alarmAlertTime); + } else { + // power.wakeup('timer_alarm'); + TimerNotificationUtil.cancel(timerId); + await WantAgentUtil.triggerFullScreenAbility(FullScreenType.Timer); + } + emitter.emit({ + eventId: TIMEOUT_FLAG_ID, priority: emitter.EventPriority.IMMEDIATE + }); + } else { + timeoutDuration = 0 + timeOutFlag = true + const isScreenLocked: boolean = await screenLock.isScreenLocked(); + LogUtil.info(TAG, 'isScreenLockedpc is ' + isScreenLocked); + await AlarmServiceManager.delayFiringAlarm(true); + await TimerStateManager.setStateFiring(TIMER_NOTICE_ID); + if (!isScreenLocked) { + LogUtil.info('startPublishAlarmMissedPC0 notification success'); + await TimerNotificationUtil.timeoutPublishAlarmMissedPC(timerId, alarmAlertTime) + } + TimerAudioPlayer.playTick(TIMER_BEEP) + this.forwardTiming(new Date().getTime()) + } + } + + private onRequestTimeOut(info: string): void { + LogUtil.info(TAG, info); + timeoutDuration = 0; + timeOutFlag = false; + } + + private async terminateTimerService(): Promise { + LogUtil.info(TAG, `terminateTimerService begin`); + // try { + // LogUtil.info(TAG, `terminateTimerService begin`); + // // await this.context.terminateSelf(); + // } catch (error) { + // LogUtil.error(TAG, 'terminateTimerService failed: ', JSON.stringify(error)); + // } + } + + async onRequest(want: Want): Promise { + LogUtil.info(TAG, `onRequest, want is: ${JSON.stringify(want)}`); + const serviceType = want?.parameters?.serviceType; + const timerId = want?.parameters?.timerId as string; + const alarmAlertTime = want?.parameters?.alarmAlertTime as number; + + if (serviceType && serviceType === TimerServiceType.Start) { + this.onRequestTimeOut(`timeOut_Start`); + TimerNotificationUtil.startPublishAlarmMissed(timerId, alarmAlertTime) + return; + } else if (serviceType && serviceType === TimerServiceType.Pause) { + this.onRequestTimeOut(`timeOut_Pause`); + TimerNotificationUtil.pausePublishAlarmMissed(timerId, alarmAlertTime) + return; + } else if (serviceType && serviceType === TimerServiceType.Close) { + await TimerStateManager.setStateStop(timerId.toString()); + this.onRequestTimeOut(`timeOut_Close`); + TimerAudioPlayer.stopTick(TIMER_BEEP); + if (deviceInfo.deviceType === '2in1') { + emitter.emit({ eventId: TIMEOUT_FULL_SCREEN_CLOSE_ID, priority: emitter.EventPriority.IMMEDIATE }); + } + TimerNotificationUtil.cancel(timerId); + CommonUtil.terminateFullScreenAbility(); + await this.terminateTimerService(); + return; + } else if (serviceType && serviceType === TimerServiceType.Firing) { + LogUtil.info(TAG, `timeOut_Firing`); + await this.onRequestTimeOutFiring(timerId, alarmAlertTime); + return; + } else if (serviceType && serviceType === TimerServiceType.ToggleFullScreen) { + LogUtil.info(TAG, `timeOut_ToggleFullScreen`); + await WantAgentUtil.triggerFullScreenAbility(FullScreenType.Timer); + } else { + LogUtil.info(TAG, 'onRequest else'); + } + } + + onDestroy(): void { + LogUtil.info(TAG, 'onDestroy'); + try { + CommonUtil.terminateFullScreenAbility(); + CommonUtil.releaseCPUResources(); + } catch (error) { + LogUtil.error(TAG, 'onDestroy failed: ', JSON.stringify(error)); + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/entryformability/EntryFormAbility.ets b/product/phone/src/main/ets/entryformability/EntryFormAbility.ets new file mode 100644 index 0000000..bbc69b9 --- /dev/null +++ b/product/phone/src/main/ets/entryformability/EntryFormAbility.ets @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import formInfo from '@ohos.app.form.formInfo'; +import formBindingData from '@ohos.app.form.formBindingData'; +import contextConstant from '@ohos.app.ability.contextConstant'; +import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility'; +import formProvider from '@ohos.app.form.formProvider'; +import { Configuration } from '@ohos.app.ability.Configuration'; +import Want from '@ohos.app.ability.Want'; +import Base from '@ohos.base'; +import { FormManager, LogUtil, GlobalContext, } from '@hmos/common'; +import { FormInfo } from '@hmos/common'; +import { AlarmCardUtil } from '@hmos/alarmclock'; + +const TAG: string = 'EntryFormAbility'; +const CITY_CLOCK_CARD: string = 'CITY_CLOCK_CARD'; + +const WORLD_CLOCK_CARD: string = 'WORLD_CLOCK_CARD'; + +interface formData { + nextAlarmTime: string, + noAlarms: boolean, + isRinging: boolean, + remainingTime: string, +} + +export default class EntryFormAbility extends FormExtensionAbility { + onAddForm(want: Want) { + LogUtil.info(TAG, 'onAddForm'); + this.context.area = contextConstant.AreaMode.EL1 + GlobalContext.getContext().setObject('clockContext', this.context); + GlobalContext.getContext().setObject('clockContextType', 'EntryFormAbility'); + GlobalContext.getContext().setObject('resourceManager', this.context.resourceManager); + + // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类 + let obj: Record = { + 'nextAlarmTime': $r('app.string.fa_alarm_alert'), + 'noAlarms': false, + 'isRinging': false, + 'remainingTime': '', + 'ringingTimestamp': 0 + }; + let formData = formBindingData.createFormBindingData(obj); + if (want.parameters) { + const formName = want.parameters['ohos.extra.param.key.form_name'].toString(); + const formId = want.parameters['ohos.extra.param.key.form_identity'].toString(); + this.initAlarmClockCard(formId, formName) + } + return formData; + + + } + + private async saveFormInfo(formId: string, formName: string, parameters?: string[]): Promise { + LogUtil.info(TAG, 'SaveFormInfo to Sp -> formId:' + formId + ',formName:' + formName); + const formInfo: FormInfo = { + formId: formId, + formName: formName, + parameters: parameters, + }; + LogUtil.info(TAG, 'saveFormInfo formInfo:' + JSON.stringify(formInfo)); + await FormManager.saveFormToSp(formInfo); + } + + private async initAlarmClockCard(formId: string, formName: string): Promise { + LogUtil.info(TAG, 'initAlarmClockCard formId:' + formId + formName); + this.context.area = contextConstant.AreaMode.EL1 + GlobalContext.getContext().setObject('clockContext', this.context); + GlobalContext.getContext().setObject('clockContextType', 'EntryFormAbility'); + GlobalContext.getContext().setObject('resourceManager', this.context.resourceManager); + await this.saveFormInfo(formId, formName); + AlarmCardUtil.notifyAlarmCardTimeUpdate(); + } + + onCastToNormalForm(formId: string) { + // Called when the form provider is notified that a temporary form is successfully + // converted to a normal form. + // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理 + LogUtil.info(TAG, `onCastToNormalForm, formId: ${formId}`); + } + + onUpdateForm(formId: string) { + // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新 + LogUtil.info(TAG, 'onAcquireFormState:' + formId); + } + + onChangeFormVisibility(newStatus: Record) { + // Called when the form provider receives form events from the system. + // 需要配置formVisibleNotify为true,且为系统应用才会回调 + LogUtil.info(TAG, 'onChangeFormVisibility'); + } + + onFormEvent(formId: string, message: string) { + // Called when a specified message event defined by the form provider is triggered. + // 若卡片支持触发事件,则需要重写该方法并实现对事件的触发 + LogUtil.info(TAG, 'onFormEvent'); + } + + onRemoveForm(formId: string) { + // Called to notify the form provider that a specified form has been destroyed. + // 当对应的卡片删除时触发的回调,入参是被删除的卡片ID + LogUtil.info(TAG, 'EntryFormAbility onRemoveForm:' + formId); + GlobalContext.getContext().setObject('clockContext', this.context); + GlobalContext.getContext().setObject('clockContextType', 'EntryFormAbility'); + GlobalContext.getContext().setObject('resourceManager', this.context.resourceManager); + this.deleteFromInfo(formId); + } + + async deleteFromInfo(formId: string): Promise { + await FormManager.deleteFormToSp(formId); + } + + onConfigurationUpdate(config: Configuration) { + // 当系统配置信息更新时触发的回调 + LogUtil.info(TAG, 'onConfigurationUpdate:' + JSON.stringify(config)); + } + + onAcquireFormState(want: Want) { + // Called to return a {@link FormState} object. + LogUtil.info(TAG, 'EntryFormAbility onAcquireFormState:'); + // 卡片提供方接收查询卡片状态通知接口,默认返回卡片初始状态。 + return formInfo.FormState.READY; + } +} diff --git a/product/phone/src/main/ets/pages/AddCity.ets b/product/phone/src/main/ets/pages/AddCity.ets new file mode 100644 index 0000000..dce2f3a --- /dev/null +++ b/product/phone/src/main/ets/pages/AddCity.ets @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AddCity } from '@hmos/worldclock'; + +/** + * World clock: add city page + * + * @since 2023-01-06 + */ +@Entry +@Component +struct AddCityPage { + build() { + Column() { + AddCity() + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/AddCityNew.ets b/product/phone/src/main/ets/pages/AddCityNew.ets new file mode 100644 index 0000000..b0bc7cf --- /dev/null +++ b/product/phone/src/main/ets/pages/AddCityNew.ets @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AddWorldClock } from '@hmos/worldclock'; + +/** + * World clock: add city page + * + * @since 2023-01-06 + */ +@Entry +@Component +struct AddCityPage { + build() { + Column() { + AddWorldClock() + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/BannerAlarm.ets b/product/phone/src/main/ets/pages/BannerAlarm.ets new file mode 100644 index 0000000..d9b933a --- /dev/null +++ b/product/phone/src/main/ets/pages/BannerAlarm.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BannerAlarm } from '@hmos/alarmclock'; + +/** + * One-pixel page for raising priorities + * + * @since 2022-11-24 + */ +@Entry +@Component +struct BannerAlarmPage { + build() { + Column() { + BannerAlarm() + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/Countdown.ets b/product/phone/src/main/ets/pages/Countdown.ets deleted file mode 100644 index 327a450..0000000 --- a/product/phone/src/main/ets/pages/Countdown.ets +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CountdownView, CountdownController, CountdownState, TimeSelectView } from '@ohos/countdown'; -import { ImageComponent, ConfigData } from '@ohos/common'; - -@Component -export struct CountDown { - private headName: ResourceStr = $r('app.string.timer_clock'); - private controller: CountdownController = new CountdownController(); - private viewSize: number = 373.5; - @State totalTime: number = 0; - @State currentTime: number = 0; - @State state: CountdownState = CountdownState.IDLE; - @State hour: string = '00'; - @State minute: string = '00'; - @State second: string = '00'; - - build() { - Column() { - Row() { - Text(this.headName) - .fontSize($r('app.float.font_48')) - .fontColor($r('app.color.text_color')) - .lineHeight($r('app.float.wh_value_44')) - .fontWeight(FontWeight.Regular) - .textOverflow({ overflow: TextOverflow.Ellipsis }) - .textAlign(TextAlign.Center) - .margin({ top: $r('app.float.distance_18_5'), bottom: $r('app.float.distance_12') }); - } - .width(ConfigData.WH_100_100); - - Scroll() { - Column() { - Column() { - CountdownView({ - viewSize: this.viewSize, - totalTime: this.totalTime, - currentTime: this.currentTime, - state: this.state - }); - } - .margin({ bottom: $r('app.float.distance_16'), top: $r('app.float.distance_16') }); - - Text(this.hour + ':' + this.minute + ':' + this.second) - .fontSize($r('app.float.font_40')) - .fontColor($r('app.color.text_color')) - .lineHeight($r('app.float.wh_value_32')) - .fontWeight(FontWeight.Regular); - - Column() { - TimeSelectView({ - hour: $hour, - minute: $minute, - second: $second, - callBack: this.timeSelectCallBack - }); - } - .margin({ top: $r('app.float.distance_42'), bottom: $r('app.float.distance_24') }) - .visibility((this.state == CountdownState.IDLE || this.state == CountdownState.PREPARED) ? Visibility.Visible : Visibility.None); - } - } - .height($r('app.float.wh_value_516')) - .width(ConfigData.WH_100_100) - .scrollBar(BarState.Off); - - Row() { - Row() { - ImageComponent({ color: $r('app.color.white'), imageSrc: $r('app.media.ic_clock_refresh') }); - } - .opacity((this.state == CountdownState.IDLE || this.state == CountdownState.STOPPED) ? $r('app.float.opacity_4') : $r('app.float.opacity_full')) - .enabled((this.state == CountdownState.PREPARED || this.state == CountdownState.PAUSED || this.state == CountdownState.RUNNING)) - .onClick(() => { - this.controller.reset(); - }); - - Row() { - ImageComponent({ - color: $r('app.color.selected_text_color'), - imageSrc: this.state == CountdownState.RUNNING ? $r('app.media.ic_clock_suspend') : $r('app.media.ic_clock_play') - }); - } - .margin({ left: $r('app.float.distance_48'), right: $r('app.float.distance_48') }) - .opacity((this.state == CountdownState.IDLE || this.state == CountdownState.STOPPED) ? $r('app.float.opacity_4') : $r('app.float.opacity_full')) - .enabled((this.state == CountdownState.PREPARED || this.state == CountdownState.PAUSED || this.state == CountdownState.RUNNING)) - .onClick(() => { - if (this.state == CountdownState.PREPARED) { - this.controller.setTime(this.totalTime); - this.controller.start(); - } else if (this.state == CountdownState.PAUSED) { - this.controller.start(); - } else if (this.state == CountdownState.RUNNING) { - this.controller.pause(); - } - }) - - Row() { - ImageComponent({ color: $r('app.color.white'), imageSrc: $r('app.media.ic_clock_sound') }); - } - .onClick(() => { - }); - }.margin({ top: $r('app.float.distance_16'), bottom: $r('app.float.distance_16') }); - } - .width(ConfigData.WH_100_100) - .height(ConfigData.WH_100_100) - .padding({ left: $r('app.float.distance_32'), right: $r('app.float.distance_32') }); - } - - /** - * CallBack for TimeSelectView. - */ - private timeSelectCallBack: () => void = () => { - this.totalTime = (parseInt(this.hour) * 60 * 60 + parseInt(this.minute) * 60 + parseInt(this.second)) * 1000; - - this.controller.setTime(this.totalTime); - } - - aboutToAppear() { - this.controller.setStateUpdateListener((state) => { - this.state = state; - if (state == CountdownState.STOPPED) { - this.hour = this.fill(Math.floor(this.totalTime / ConfigData.ONE_HOUR_TIME).toString()); - this.minute = this.fill(Math.floor(this.totalTime % ConfigData.ONE_HOUR_TIME / ConfigData.ONE_MINUTE_TIME).toString()); - this.second = this.fill(Math.floor(this.totalTime % ConfigData.ONE_MINUTE_TIME / ConfigData.ONE_SECOND_TIME).toString()); - - this.controller.reStart(); - } - }) - this.controller.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - this.currentTime = currentTimeMs; - this.totalTime = totalTimeMs; - - this.hour = this.fill(Math.floor(this.currentTime / ConfigData.ONE_HOUR_TIME).toString()); - this.minute = this.fill(Math.floor(this.currentTime % ConfigData.ONE_HOUR_TIME / ConfigData.ONE_MINUTE_TIME).toString()); - this.second = this.fill(Math.floor(this.currentTime % ConfigData.ONE_MINUTE_TIME / ConfigData.ONE_SECOND_TIME).toString()); - }) - this.controller.getData(); - } - - /** - * Make up 0 if less than 10. - */ - fill(value) { - return (value > 9 ? '' : '0') + value; - } -} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/EditCities.ets b/product/phone/src/main/ets/pages/EditCities.ets new file mode 100644 index 0000000..10bb44b --- /dev/null +++ b/product/phone/src/main/ets/pages/EditCities.ets @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EditCities } from '@hmos/worldclock'; + +/** + * World clock: edit cities page + * + * @since 2022-09-26 + */ +@Entry +@Component +struct EditCitiesPage { + build() { + Column() { + EditCities() + } + .width('100%') + .height('100%') + } +} diff --git a/feature/countdown/index.ets b/product/phone/src/main/ets/pages/EditCitiesForPC.ets similarity index 64% rename from feature/countdown/index.ets rename to product/phone/src/main/ets/pages/EditCitiesForPC.ets index 5327a82..493a7ff 100644 --- a/feature/countdown/index.ets +++ b/product/phone/src/main/ets/pages/EditCitiesForPC.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,8 +13,21 @@ * limitations under the License. */ -export { CountdownView } from './src/main/ets/components/CountdownView'; - -export { TimeSelectView } from './src/main/ets/components/TimeSelectView'; +import { EditCitiesForPC } from '@hmos/worldclock'; -export { CountdownController, CountdownState } from './src/main/ets/controller/CountdownController'; \ No newline at end of file +/** + * World clock: edit cities page + * + * @since 2022-09-26 + */ +@Entry +@Component +struct EditCitiesForPCPage { + build() { + Column() { + EditCitiesForPC() + } + .width('100%') + .height('100%') + } +} diff --git a/product/phone/src/main/ets/pages/FaManagerCity.ets b/product/phone/src/main/ets/pages/FaManagerCity.ets new file mode 100644 index 0000000..05fd750 --- /dev/null +++ b/product/phone/src/main/ets/pages/FaManagerCity.ets @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FaManagerCity } from '@hmos/worldclock'; + +@Entry +@Component +struct Page { + build() { + Column() { + FaManagerCity() + } + .width('100%') + .height('100%') + .backgroundColor($r('sys.color.ohos_id_background_secondary')) + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/ForegroundPage.ets b/product/phone/src/main/ets/pages/ForegroundPage.ets new file mode 100644 index 0000000..a17b9aa --- /dev/null +++ b/product/phone/src/main/ets/pages/ForegroundPage.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ForegroundView } from '@hmos/alarmclock'; + +/** + * Used to increase the process priority during full-screen startup. + * + * @since 2023-1-5 + */ +@Entry +@Component +struct ForegroundPage { + build() { + Column() { + ForegroundView() + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/FullScreenAlarm.ets b/product/phone/src/main/ets/pages/FullScreenAlarm.ets new file mode 100644 index 0000000..547dbac --- /dev/null +++ b/product/phone/src/main/ets/pages/FullScreenAlarm.ets @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FullScreenAlarm } from '@hmos/alarmclock'; +import { LogUtil, EVENT_ID_FULL_SCREEN_ALARM } from '@hmos/common'; +import emitter from '@ohos.events.emitter'; + +const TAG = 'FullScreenAlarmPage'; + +/** + * Full Screen Alarm Page + * + * @since 2022-08-18 + */ +@Entry +@Component +struct FullScreenAlarmPage { + @State isBackImageShown: boolean = false; + + onBackPress() { + emitter.emit({ eventId: EVENT_ID_FULL_SCREEN_ALARM }); + LogUtil.info(TAG, 'onBackPress'); + return false; + } + + build() { + Column() { + FullScreenAlarm({ isBackImageShown: $isBackImageShown }); + } + .visibility(this.isBackImageShown ? Visibility.Visible : Visibility.Hidden) + } +} \ No newline at end of file diff --git a/product/pc/src/ohosTest/ets/TestAbility/pages/index.ets b/product/phone/src/main/ets/pages/FullScreenTimer.ets similarity index 41% rename from product/pc/src/ohosTest/ets/TestAbility/pages/index.ets rename to product/phone/src/main/ets/pages/FullScreenTimer.ets index b95547c..922e083 100644 --- a/product/pc/src/ohosTest/ets/TestAbility/pages/index.ets +++ b/product/phone/src/main/ets/pages/FullScreenTimer.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -12,38 +12,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import router from '@ohos.router'; +import { FullScreenTimer } from '@hmos/timer'; +import { EVENT_ID_TIMER_FULL_SCREEN_CLOSE, LogUtil, } from '@hmos/common'; +import emitter from '@ohos.events.emitter'; + +const TAG = 'FullScreenTimerPage'; + +/** + * Full Screen Alarm Page + * + * @since 2022-08-18 + */ @Entry @Component -struct Index { - aboutToAppear() { - console.info('TestAbility index aboutToAppear') +struct FullScreenTimerPage { + @State isBackImageShown: boolean = false; + @State timeoutDuration: number = 0; + + onBackPress() { + LogUtil.info(TAG, `onBackPress`) + emitter.emit({ + eventId: EVENT_ID_TIMER_FULL_SCREEN_CLOSE, priority: emitter.EventPriority.IMMEDIATE + }); + return false; + } + + build() { + Column() { + FullScreenTimer({ isBackImageShown: $isBackImageShown, timeoutDuration: $timeoutDuration }); + } + .visibility(this.isBackImageShown ? Visibility.Visible : Visibility.Hidden) } - @State message: string = 'Hello World' - build() { - Row() { - Column() { - Text(this.message) - .fontSize(50) - .fontWeight(FontWeight.Bold) - Button() { - Text('next page') - .fontSize(20) - .fontWeight(FontWeight.Bold) - }.type(ButtonType.Capsule) - .margin({ - top: 20 - }) - .backgroundColor('#0D9FFB') - .width('35%') - .height('5%') - .onClick(()=>{ - }) - } - .width('100%') - } - .height('100%') - } - } \ No newline at end of file +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/ManageAlarmClock.ets b/product/phone/src/main/ets/pages/ManageAlarmClock.ets new file mode 100644 index 0000000..5199730 --- /dev/null +++ b/product/phone/src/main/ets/pages/ManageAlarmClock.ets @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ManageAlarmClock } from '@hmos/alarmclock'; + +/** + * Entry of the alarm management page + * + * @since 2022-07-22 + */ +@Entry +@Component +struct ManageAlarmClockPage { + build() { + Column() { + ManageAlarmClock() + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/index.ets b/product/phone/src/main/ets/pages/index.ets index 89e915d..e2dabd5 100644 --- a/product/phone/src/main/ets/pages/index.ets +++ b/product/phone/src/main/ets/pages/index.ets @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,71 +13,1029 @@ * limitations under the License. */ -import { ConfigData } from '@ohos/common'; -import { Timer } from './timer/Timer'; -import { CountDown } from './Countdown'; +import emitter from '@ohos.events.emitter'; +import router from '@ohos.router'; +import i18n from '@ohos.i18n'; +import promptAction from '@ohos.promptAction' +import resmgr from '@ohos.resourceManager'; +import display from '@ohos.display'; +import { Callback, BusinessError } from '@ohos.base'; +import deviceInfo from '@ohos.deviceInfo' +import commonEventManager from '@ohos.commonEventManager'; +import { + ResourceManager, + AlarmManager, + AlarmInfoToShow, + TimeUtil, + TIME_DESC_STRING_ID_LIST, + LogUtil, + EVENT_ID_ALARM_RING, + EVENT_ID_CHANGE_ALARM, + EVENT_ID_DELETE_ALARM, + EVENT_ID_WINDOW_SHADOW, + EVENT_ID_SWITCH_PAGE, + PROMPT_TIME, + LESS_THAN_ONE_MINUTE_FLAG, + TitleBar, + FIRST_PARAM, + BreakpointManager, + BreakPoint, + EventReportUtil, + EventName, + EVENT_ID_CHANGE_WORLD_CLOCK, + PageStateManager, + GlobalContext, + AlarmInfo, + AlarmStateManager, + TimerManager, + CommonUtil, + SoundPool, + EVENT_ID_WORLDCLOCK_FORPC, + TIMER_Toggle_FRONT_AND_BACKEND, +} from '@hmos/common'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import { + AlarmClock, + ManageAlarmClock, + MANAGE_NEW_ALARM, + MANAGE_EDIT_ALARM, + ObservedAlarmInfo, + DELETE_ALARM_CLOCK, + AlarmCardUtil +} from '@hmos/alarmclock'; +import { + PRESET_CITY_BEIJING, + PRESET_CITY_LONDON, + PRESET_CITY_PARIS +} from '@hmos/common/src/main/ets/utils/types' +import { + WorldClock, + WorldClockInfo, + WorldClockManager, + WorldClockUtil, + MANAGE_NEW_WORLD_CLOCK, + MANAGE_EDIT_WORLD_CLOCK, + MANAGE_EDIT_WORLD_CLOCK_PC, + AddCity, + EditCities, + EditCitiesForPC +} from '@hmos/worldclock'; +import device, { DeviceResponse } from '@system.device' +import { Stopwatch } from '@hmos/stopwatch'; +import { Timer } from '@hmos/timer'; +import mediaquery from '@ohos.mediaquery'; +import Window from '@ohos.window'; + +const TAG = 'ClockEntry'; +const MAX_CITY_NUM = 24; +const DEFAULT_SORT_ORDER = 9999; +const SMALL_SCREEN_CONTENT_SPAN = 12; +const SMALL_SCREEN_CONTENT_OFFSET = 0; +const BIG_SCREEN_CONTENT_SPAN = 10; +const BIG_SCREEN_CONTENT_OFFSET = 2; +const TABLE_PAD = 2560; +const TIME_OUT_MILLIS = 600; + +enum MenuIndex { + ALARM = 0, + WORLD_CLOCK = 1, + STOPWATCH = 2, + TIMER = 3 +}; + +interface MenuItem { + icon: Resource, + iconActivated: Resource, + label: Resource, + index: number, + defaultBgColor: ResourceColor, + hoverBgColor: ResourceColor, + pressBgColor: ResourceColor, +}; + +const MENU_CONFIG: MenuItem[] = [ + { + icon: $r('app.media.ic_alarm'), + iconActivated: $r('app.media.ic_alarm_activated'), + label: $r('app.string.alarm_clock'), + index: MenuIndex.ALARM, + defaultBgColor: Color.Transparent, + hoverBgColor: $r('sys.color.ohos_id_color_hover'), + pressBgColor: $r('sys.color.ohos_id_color_click_effect') + }, + { + icon: $r('app.media.ic_world_clock'), + iconActivated: $r('app.media.ic_world_clock_activated'), + label: $r('app.string.world_clock'), + index: MenuIndex.WORLD_CLOCK, + defaultBgColor: Color.Transparent, + hoverBgColor: $r('sys.color.ohos_id_color_hover'), + pressBgColor: $r('sys.color.ohos_id_color_click_effect') + }, + { + icon: $r('app.media.ic_stopwatch'), + iconActivated: $r('app.media.ic_stopwatch_activated'), + label: $r('app.string.stopwatch'), + index: MenuIndex.STOPWATCH, + defaultBgColor: Color.Transparent, + hoverBgColor: $r('sys.color.ohos_id_color_hover'), + pressBgColor: $r('sys.color.ohos_id_color_click_effect') + }, + { + icon: $r('app.media.ic_timer'), + iconActivated: $r('app.media.ic_timer_activated'), + label: $r('app.string.timer'), + index: MenuIndex.TIMER, + defaultBgColor: Color.Transparent, + hoverBgColor: $r('sys.color.ohos_id_color_hover'), + pressBgColor: $r('sys.color.ohos_id_color_click_effect') + } +]; +type CommonEventSubscriber = commonEventManager.CommonEventSubscriber; +type CommonEventSubscribeInfo = commonEventManager.CommonEventSubscribeInfo; +const SCREEN_LOCK_INFO: CommonEventSubscribeInfo = { + events: [ + commonEventManager.Support.COMMON_EVENT_SCREEN_OFF, + ], +}; + +interface ITabBarBackgroundEvent { + onHover: (isHover: boolean, event: HoverEvent) => void; + onMouse: (event: MouseEvent) => void; +} + +interface ITabBarActionOptions { + index: MenuIndex | undefined; + status: string | undefined; +} + +function computedTabBarBackground(options: MenuItem, activeOptions: ITabBarActionOptions): ResourceColor { + if (options.index === activeOptions.index) { + if (activeOptions.status === 'hover') { + return options.hoverBgColor; + } else if (activeOptions.status === 'press') { + return options.pressBgColor; + } + } + return Color.Transparent +} +@Extend(Column) +function tabBarBackground(options: MenuItem, events: ITabBarBackgroundEvent, activeOptions: ITabBarActionOptions) { + .backgroundColor(computedTabBarBackground(options, activeOptions)) + .onHover(events.onHover) + .onMouse(events.onMouse) +} + + +/** + * 手机产品的时钟入口页面 + * + * @since 2022-07-19 + */ @Entry @Component +@Preview struct Index { - private featureList: Array = [ - { - name: $r('app.string.alarm_clock'), - icon: $r('app.media.ic_clock_clock'), - selectedIcon: $r('app.media.ic_clock_clock_click') - }, - { - name: $r('app.string.stopwatch'), - icon: $r('app.media.ic_clock_timer'), - selectedIcon: $r('app.media.ic_clock_timer_click') - }, - { - name: $r('app.string.timer_clock'), - icon: $r('app.media.ic_clock_second_chronograph'), - selectedIcon: $r('app.media.ic_clock_second_chronograph_click') - }, - ]; - @State featureIndex: number = 1; + @StorageProp('currentAbleScreen') @Watch('listenFoldScreen') foldAbleScreen: number = 0; + @StorageProp('currentBreakpoint') currentBreakpoint: string = ''; + @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack(); + @StorageProp('setOrientaion') getOrientaion: number = 0; + @State currentIndex: number = -1; // 当前中的工具栏菜单下标 + @Provide('currentIndex') currentIndexProvider: number = -1; + @State currentIndexCache: number = -1; + @State alarmClockList: AlarmInfoToShow[] = []; // 闹钟列表 + @State leftTimeToRingTime: string = ''; // 到下次响铃的间隔时间 + @StorageLink('count') count: number = 0; + @State isClockHide: boolean = false; + @State worldClockList: WorldClockInfo[] = []; + @State @Watch('changeNavbarStatus') isPortraitOrientation: boolean = true; + @State isPortrait: boolean = true + @StorageProp('isTabChange') @Watch('tabChangeProcess') isTabChange: boolean = false; + @State isForeground: boolean = false; + @StorageLink('EditBindSheet') isEditShow: boolean = false; + @StorageLink('NewBindSheet') isNewShow: boolean = false; + @State isPageHide: boolean = true; + @State isScreenLock: boolean = false; + @State isScreenEntry: boolean = false + @State currentActiveTab: ITabBarActionOptions = { index: undefined, status: undefined }; + @State isStopInit: boolean = false; + @State isTimerInit: boolean = false; + @State isAlarmInit: boolean = false; + @State isWorldClockInit: boolean = false; + @State isAlarmShown: boolean = false; + @State isWorldClockShown: boolean = false; + @State isWorldClockVisible: boolean = false; + @State isAlarmClockVisible: boolean = true; + @State isStopWatchVisible: boolean = true; + @State isTimerVisible: boolean = true; + private cityList: Set = new Set(); + private alarmRingTime: string = ''; // Next ringing time of a new or edited alarm + private tabController: TabsController = new TabsController(); + private orientationListener: mediaquery.MediaQueryListener | undefined = undefined; + private isFoldCross: number = 2224; + private isFoldVertical: number = 2496; + private isTablePad: string = 'tablet' + private commonEventSubscriberAsLock: CommonEventSubscriber = {} as CommonEventSubscriber; + private deviceType: string = deviceInfo.deviceType; - build() { + private async subscribeCommentAsLockEvent(): Promise { + this.commonEventSubscriberAsLock = await commonEventManager.createSubscriber(SCREEN_LOCK_INFO); + if (!this.commonEventSubscriberAsLock) { + return; + } + commonEventManager.subscribe(this.commonEventSubscriberAsLock, async () => { + this.isScreenLock = true + SoundPool.releaseAll(); + + }); + } + + private listenFoldScreen() { + this.getScreen() + } + + private unSubscribeCommentAsLockEvent(): void { + commonEventManager.unsubscribe(this.commonEventSubscriberAsLock, error => { + if (error) { + LogUtil.error(TAG, `Unsubscribe to common event failed because: ${JSON.stringify(error)}`); + return; + } + LogUtil.info(TAG, 'Unsubscribe to Lock event successfully!'); + }); + } + + private isPCLg() { + return this.deviceType === '2in1' + } + + async tabChangeProcess(): Promise { + if (GlobalContext.getContext().getObject('dialogController')) { + LogUtil.info(TAG, 'tabChangeProcess dialogController close'); + (GlobalContext.getContext().getObject('dialogController') as CustomDialogController).close() + } + GlobalContext.getContext().setObject('textInput', undefined); + + if (this.pageInfos.size() > 0) { + LogUtil.info(TAG, 'tabChangeProcess pageInfos' + JSON.stringify(this.pageInfos)); + this.pageInfos.pop(); + } else { + this.isEditShow = false; + this.isNewShow = false; + AppStorage.setOrCreate('EditShowItem', false); + AppStorage.setOrCreate('NewShowItem', false); + const routerState = router.getState(); + LogUtil.info(TAG, 'tabChangeProcess routerState:' + JSON.stringify(routerState)); + if (routerState && routerState.name !== 'index') { + router.back(); + } + } + await this.initContent(); + } + + async aboutToAppear(): Promise { + LogUtil.info(TAG, 'about to appear'); + if (this.deviceType === this.isTablePad) { + this.isPortraitOrientation = false; + } + LogUtil.info(TAG, 'about to appear1'); + + await this.initContent(); + LogUtil.info(TAG, 'about to appear2'); + + ResourceManager.preloadStringResources(TIME_DESC_STRING_ID_LIST); + LogUtil.info(TAG, 'about to appear3'); + if (this.currentIndex === MenuIndex.ALARM) { + await this.refreshAlarmData(); + } else { + this.refreshAlarmData(); + } + if (this.currentIndex === MenuIndex.WORLD_CLOCK) { + await this.refreshWorldClockData(); + } else { + this.refreshWorldClockData(); + } + LogUtil.info(TAG, 'about to appear4'); + + await SoundPool.create('aboutToAppear'); + SoundPool.loadResource(0, this.currentIndex); + LogUtil.info(TAG, 'about to appear5'); + + let lastWindow = await Window.getLastWindow(GlobalContext.getContext().getObject('clockContext') as Context); + LogUtil.info(TAG, 'about to appear5.1'); + + if (this.deviceType === this.isTablePad) { + LogUtil.info(TAG, 'about to appear5.2'); + + device.getInfo({ + success: (ret: DeviceResponse) => { + lastWindow.setPreferredOrientation(Window.Orientation.AUTO_ROTATION_RESTRICTED); + if (ret.windowWidth === TABLE_PAD) { + this.isPortraitOrientation = false; + } else { + this.isPortraitOrientation = true; + } + } + }) + } else { + LogUtil.info(TAG, 'about to appear5.3'); + + lastWindow.setPreferredOrientation(Window.Orientation.PORTRAIT); + } + LogUtil.info(TAG, 'about to appear6'); + + this.getScreen() + this.getOrientationOnce() + this.getOrientation(true) + LogUtil.info(TAG, 'about to appear7'); + + this.orientationListener = mediaquery.matchMediaSync('(orientation: landscape)'); + this.orientationListener.on('change', (result) => { + emitter.emit({ + eventId: EVENT_ID_WINDOW_SHADOW, + priority: emitter.EventPriority.IMMEDIATE, + }, { + data: { + isPortraitOrientation: result.matches ? true : false, + } + }) + LogUtil.info(TAG, 'Orientation change, result: ' + result + result.matches) + if (result.matches) { + this.isPortraitOrientation = false; + LogUtil.info(TAG, 'Screen orientation changed to landscape'); + } else { + this.isPortraitOrientation = true; + LogUtil.info(TAG, 'Screen orientation changed to portrait'); + } + }) + LogUtil.info(TAG, 'about to appear8'); + + this.subscribeCommentAsLockEvent(); + await this.getDefaultCity(); + this.subscribeEvents(); + BreakpointManager.register(); + LogUtil.info(TAG, 'about to appear9'); + + setTimeout(() => { + if (!this.isAlarmInit || !this.isWorldClockInit || !this.isStopInit || this.isTimerInit) { + this.isAlarmInit = true; + this.isWorldClockInit = true; + this.isStopInit = true; + this.isTimerInit = true; + } + }, TIME_OUT_MILLIS); + LogUtil.info(TAG, 'about to appear10'); + + } + + private async getDefaultCity() { + let worldClockList = await WorldClockManager.getAllWorldClock(); + const result = await AlarmManager.getFirstEnterApp(worldClockList?.length); + if (result) { + await this.initPresetCity(PRESET_CITY_BEIJING); + await this.initPresetCity(PRESET_CITY_LONDON); + await this.initPresetCity(PRESET_CITY_PARIS); + } + } + + async aboutToDisappear(): Promise { + this.unSubscribeCommentAsLockEvent(); + this.unSubscribeEvents(); + BreakpointManager.unregister(); + } + + private getScreen() { + LogUtil.info(TAG, 'Get screen start'); + let callback: Callback = (data: display.FoldStatus) => { + AppStorage.SetOrCreate('currentAbleScreen', data === 3 ? 1 : data); + }; + try { + const getfoldAbleScreen = display.getFoldStatus(); + AppStorage.SetOrCreate('currentAbleScreen', getfoldAbleScreen === 3 ? 1 : getfoldAbleScreen); + display.on('foldStatusChange', callback); + LogUtil.info(TAG, 'get screen success' + JSON.stringify(getfoldAbleScreen)); + } catch (exception) { + LogUtil.error('get screen result' + JSON.stringify(exception)); + } + } + + // reslove Orientation problem + private getOrientationOnce() { + LogUtil.info(TAG, 'Get orientation once start'); + display.getAllDisplay((err: BusinessError, data: Array) => { + const errCode: number = err.code; + if (errCode) { + LogUtil.error(TAG, 'Failed to obtain all the display objects. Code: ' + JSON.stringify(err)); + return; + } + if (this.deviceType === this.isTablePad) { + LogUtil.info(TAG, 'Now device is tablet'); + if (data[0].rotation === 0 || data[0].rotation === 2) { + this.isPortraitOrientation = false + } else if (data[0].rotation === 1 || data[0].rotation === 3) { + this.isPortraitOrientation = true + } + } else { + LogUtil.info(TAG, 'Now device is default'); + if (data[0].rotation === 0 || data[0].rotation === 2) { + this.isPortraitOrientation = true + } else if (data[0].rotation === 1 || data[0].rotation === 3) { + this.isPortraitOrientation = true + } + } + }); + } + + async onPageShow(): Promise { + LogUtil.info(TAG, 'on page show'); + await this.getScreen() + LogUtil.info(TAG, 'on page show'); + const cityId = AppStorage.Get('cityId'); + AppStorage.Delete('cityId'); + this.UpdateCityList() + cityId && await this.addNewWorldClock(cityId) + this.refreshAlarmData() + this.refreshWorldClockData(); + this.emitSwitchPageHandle(true); + if (this.isScreenLock) { + await SoundPool.loadResource(1, this.currentIndex) + SoundPool.soundPool.on('loadComplete', (soundId_: number) => { + this.isPageHide = false + }) + this.isScreenLock = false + this.isScreenEntry = true + } else { + this.isPageHide = false + this.isScreenEntry = false + } + + } + + async onPageHide(): Promise { + this.isPageHide = true + this.emitSwitchPageHandle(false); + } + + async initContent(): Promise { + this.currentIndex = await PageStateManager.getTabIndex(); + this.currentIndexProvider = this.currentIndex; + this.changeTabs(); + } + + changeTabs(): void { + if (this.currentIndex === MenuIndex.ALARM) { + if (this.isAlarmInit) { + this.isAlarmShown = true; + } + this.isAlarmInit = true; + this.isAlarmClockVisible = true; + this.isWorldClockVisible = false; + this.isStopWatchVisible = false; + this.isTimerVisible = false; + } else if (this.currentIndex === MenuIndex.WORLD_CLOCK) { + if (this.isWorldClockInit) { + this.isWorldClockShown = true; + } + this.isWorldClockInit = true; + this.isAlarmClockVisible = false; + this.isWorldClockVisible = true; + this.isStopWatchVisible = false; + this.isTimerVisible = false; + } else if (this.currentIndex === MenuIndex.STOPWATCH) { + this.isStopInit = true; + this.isAlarmClockVisible = false; + this.isWorldClockVisible = false; + this.isStopWatchVisible = true; + this.isTimerVisible = false; + } else if (this.currentIndex === MenuIndex.TIMER) { + this.isTimerInit = true; + this.isAlarmClockVisible = false; + this.isWorldClockVisible = false; + this.isStopWatchVisible = false; + this.isTimerVisible = true; + } + } + + /** + * 查询/刷新闹钟页面数据 + * 在第一次进入应用和跳转到别的应用或页面有,再返回当前页面时调用 + */ + async refreshAlarmData(): Promise { + LogUtil.info(TAG, 'refreshAlarmData'); + this.alarmClockList = await AlarmManager.getAllAlarmsToShow(); + LogUtil.info(TAG, 'alarmClockList:' + this.alarmClockList.length); + this.leftTimeToRingTime = await TimeUtil.getLeftTimeToRingTime(false); + const isFiring: boolean = await AlarmStateManager.isFiring(); + LogUtil.info(TAG, 'alarm isFiring' + isFiring); + if (!isFiring) { + AlarmCardUtil.notifyAlarmCardTimeUpdate(); + } + + await this.addOpsLogs(); + } + + /** + * addOpsLogs to diagnosis beta problems + */ + async addOpsLogs(): Promise { + for (const value of this.alarmClockList) { + if (value.enabled) { + LogUtil.info(TAG, `opsLogs has enabled alarm id: ${value.id}`); + break; + } + } + const timerId = await TimerManager.getTimerId(); + const triggerTime = await TimerManager.getTriggerTime(); + const triggerAlarmId = await TimerManager.getTriggerTimeID(); + LogUtil.info(TAG, `opsLogs triggerAlarmId: ${triggerAlarmId} triggerTime: ${triggerTime} timerId: ${timerId} `); + + const nearestAlarmTime: string = CommonUtil.getNearestAlarmTime(); + LogUtil.info(TAG, `opsLogs nearestAlarmTime: ${nearestAlarmTime} tips: ${this.leftTimeToRingTime}`); + } + + /** + * 刷新最近一次闹钟的状态 + */ + async refreshNearestAlarm(): Promise { + LogUtil.info(TAG, 'refreshNearestAlarm'); + let nearClock: AlarmInfo | undefined = await AlarmManager.getNearestAlarm(); + LogUtil.info(TAG, `nearClock: ${JSON.stringify(nearClock)}`); + if (nearClock) { + AlarmManager.updateAlarm(nearClock) + this.leftTimeToRingTime = await TimeUtil.getLeftTimeToRingTime(true); + const isFiring: boolean = await AlarmStateManager.isFiring(); + LogUtil.info(TAG, 'alarm isFiring' + isFiring); + if (!isFiring) { + AlarmCardUtil.notifyAlarmCardTimeUpdate(); + } + } + } + + /** + * refresh world clock list. + */ + async refreshWorldClockData(): Promise { + LogUtil.info(TAG, 'refreshWorldClockData'); + let worldClockList = await WorldClockManager.getAllWorldClock(); + this.cityList.clear(); + for (let worldClock of worldClockList) { + this.cityList.add(worldClock.cityIndex); + worldClock.tag = await WorldClockUtil.updateTimeZoneInfo(worldClock.offset, + worldClock.timezone, worldClock.cityIndex); + } + this.worldClockList = worldClockList; +1 } + + private emitSwitchPageHandle(isShow: boolean): void { + emitter.emit({ + eventId: EVENT_ID_SWITCH_PAGE, + priority: emitter.EventPriority.IMMEDIATE, + }, { + data: { + isPageShow: isShow, + } + }) + if (isShow) { + emitter.emit({ eventId: TIMER_Toggle_FRONT_AND_BACKEND, priority: emitter.EventPriority.IMMEDIATE }, { + data: { pageShow: true } + }) + } + } + + /** + * Do not modify the folding method + */ + private getOrientation(isUp?: boolean) { + LogUtil.info(TAG, 'get orientation start, isUp:' + JSON.stringify(isUp)); + display.getAllDisplay((err: BusinessError, data: Array) => { + const errCode: number = err.code; + if (errCode) { + LogUtil.error(TAG, 'Failed to obtain all the display objects. Code: ' + JSON.stringify(err)); + return; + } + let result = 0 + if (data[0].rotation === 0 || data[0].rotation === 2 && isUp) { + result = this.isFoldCross + } else if (data[0].rotation === 1 || data[0].rotation === 3 && isUp) { + result = this.isFoldVertical + } else { + result = data[0].width + } + AppStorage.SetOrCreate('setOrientaion', result); + }); + } + + private async changeNavbarStatus() { + if (this.foldAbleScreen === 1) { + this.getOrientation() + } + } + + private async showToastWhenAlarmChanges(): Promise { + if (this.alarmRingTime) { + const string_less_than_one_minute = await ResourceManager.getStringByIdAsync($r('app.string.less_than_one_minute') + .id); + const STRING_RING_IN = await ResourceManager.getStringByIdAsync($r('app.string.ring_in').id); + const remainingRingTime = this.alarmRingTime === LESS_THAN_ONE_MINUTE_FLAG + ? string_less_than_one_minute : STRING_RING_IN.replace(FIRST_PARAM, this.alarmRingTime); + promptAction.showToast({ message: remainingRingTime, duration: PROMPT_TIME }); + this.alarmRingTime = ''; + } + } + + /** + * Subscription event, used to refresh the page + */ + private subscribeEvents(): void { + // 监听处理闹钟启闹 + emitter.on({ + eventId: EVENT_ID_ALARM_RING + }, async () => { + LogUtil.info(TAG, 'receive EVENT_ID_ALARM_RING') + await AlarmManager.updateTimer(); + this.refreshAlarmData(); + }); + // 监听处理闹钟编辑和创建 + emitter.on({ + eventId: EVENT_ID_CHANGE_ALARM, + }, (eventData) => { + LogUtil.info(TAG, 'receive EVENT_ID_CHANGE_ALARM') + this.alarmRingTime = eventData.data ? eventData.data.KEY_ALARM_RING_TIME : ''; + LogUtil.info(TAG, 'receive alarmRingTime:' + this.alarmRingTime); + this.showToastWhenAlarmChanges(); + this.refreshAlarmData(); + }); + // 监听处理闹钟批量删除 + emitter.on({ + eventId: EVENT_ID_DELETE_ALARM, + }, (eventData) => { + LogUtil.info(TAG, 'receive EVENT_ID_DELETE_ALARM') + this.showToastWhenAlarmChanges(); + this.refreshAlarmData(); + }); + // 监听处理世界时钟左滑删除 + emitter.on({ + eventId: EVENT_ID_CHANGE_WORLD_CLOCK, + }, () => { + LogUtil.info(TAG, 'receive WORLD_CLOCK_CHANGE'); + this.UpdateCityList() + }); + } + + private UpdateCityList(): void { + this.cityList.clear(); + for (let worldClock of this.worldClockList) { + this.cityList.add(worldClock.cityIndex); + } + } + + /** + * Unsubscribe from events + */ + private unSubscribeEvents(): void { + emitter.off(EVENT_ID_ALARM_RING); + emitter.off(EVENT_ID_CHANGE_ALARM); + emitter.off(EVENT_ID_DELETE_ALARM); + emitter.off(EVENT_ID_CHANGE_WORLD_CLOCK); + emitter.off(EVENT_ID_WORLDCLOCK_FORPC); + } + + private async addNewWorldClock(cityId: string): Promise { + if (cityId === '') { + LogUtil.info(TAG, 'select no city'); + return; + } + if (this.cityList.has(cityId)) { + promptAction.showToast({ message: $r('app.string.worldclock_city_exist_Toast'), duration: PROMPT_TIME }); + return; + } + if (this.cityList.size === MAX_CITY_NUM) { + promptAction.showToast({ message: $r('app.string.city_full_Toast'), duration: PROMPT_TIME }); + return; + } + await WorldClockManager.addWorldClock(this.initWorldClockInfo(cityId)); + } + + private initWorldClockInfo(cityId: string): WorldClockInfo { + const cityIndex = cityId; + const timezone = i18n.TimeZone.getTimezoneFromCity(cityId).getID(); + const city = i18n.TimeZone.getCityDisplayName(cityId, i18n.getSystemLocale()); + const offset = i18n.TimeZone.getTimezoneFromCity(cityId).getRawOffset(); + return { + sortOrder: DEFAULT_SORT_ORDER, + cityIndex, + timezone, + city, + offset, + }; + } + + private reportTabEvent(index: number): void { + // if (index === MenuIndex.ALARM) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_ALARM_CLOCK_TAB) + // } + // if (index === MenuIndex.WORLD_CLOCK) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_WORLD_CLOCK_TAB) + // } + // if (index === MenuIndex.STOPWATCH) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_STOPWATCH_TAB) + // } + // if (index === MenuIndex.TIMER) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_TIMER_TAB) + // } + } + + private isPageVisible(index: MenuIndex): boolean { + return this.currentIndex == index; + } + + @Builder + buildTitleBar(id: number): void { + // TitleBar({ + // title: (GlobalContext.getContext().getObject('resourceManager') as resmgr.ResourceManager).getStringSync(id), + // operationArea: () => { + // }, + // }) + // .margin(this.isPCLg() ? { left: $r('app.float.snooze_button_border_radius') } : (this.isPortraitOrientation ? { + // left: this.foldAbleScreen === 1 ? $r('app.float.change_vertical') : $r('app.float.change_cross'), + // right: this.foldAbleScreen === 1 ? $r('app.float.change_vertical') : $r('app.float.change_cross') + // } : { left: $r('app.float.change_vertical'), right: $r('app.float.change_vertical') })) + // .transition({ type: TransitionType.All, opacity: 0 }) + // .shadow({ + // radius: $r('app.float.button_shadow_radius'), + // color: $r('sys.color.ohos_id_background_secondary'), + // offsetX: $r('app.float.button_shadow_x'), + // offsetY: $r('app.float.button_shadow_y'), + // }) + } + + @Builder + buildTitle(): void { + Flex({ alignItems: ItemAlign.Start }) { + GridRow() { + GridCol({ + span: { xs: SMALL_SCREEN_CONTENT_SPAN, md: SMALL_SCREEN_CONTENT_SPAN, lg: BIG_SCREEN_CONTENT_SPAN }, + offset: { xs: SMALL_SCREEN_CONTENT_OFFSET, md: SMALL_SCREEN_CONTENT_OFFSET, lg: BIG_SCREEN_CONTENT_OFFSET }, + }) { + Row() { + if (this.currentIndex === MenuIndex.ALARM) { + this.buildTitleBar(MENU_CONFIG[this.currentIndex].label.id); + } else if (this.currentIndex === MenuIndex.WORLD_CLOCK) { + this.buildTitleBar(MENU_CONFIG[this.currentIndex].label.id); + } else if (this.currentIndex === MenuIndex.STOPWATCH) { + this.buildTitleBar(MENU_CONFIG[this.currentIndex].label.id); + } else if (this.currentIndex === MenuIndex.TIMER) { + this.buildTitleBar(MENU_CONFIG[this.currentIndex].label.id); + } + } + .width('100%') + .margin(this.isPCLg() ? { left: '-192vp' } : '0') + .height($r('app.float.title_bar_height')) + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Start) + .backgroundColor($r('sys.color.ohos_id_background_secondary')) + .shadow({ + radius: $r('app.float.title_bar_shadow_radius'), + color: $r('sys.color.ohos_id_background_secondary'), + offsetX: $r('app.float.title_bar_shadow_x'), + offsetY: $r('app.float.title_bar_shadow_y') + }) + } + .backgroundColor($r('app.color.transparent')) + .height($r('app.float.title_bar_height')) + } + .height('100%') + .width(this.isPortraitOrientation || this.foldAbleScreen === 1 ? '100%' : this.currentBreakpoint !== BreakPoint.LG ? '39%' : '100%') + } + .renderGroup(true) + .hitTestBehavior(HitTestMode.Transparent) + .height('100%') + .width('100%') + } + + @Builder + buildTabItem(item: MenuItem): void { Column() { - Column() { - Column() { - Timer() - }.visibility(this.featureIndex == 1 ? Visibility.Visible : Visibility.None) - - Column() { - CountDown() - }.visibility(this.featureIndex == 2 ? Visibility.Visible : Visibility.None) - } - .width(ConfigData.WH_100_100) - .height(ConfigData.WH_100_100) - .flexShrink(1) - - Row() { - ForEach(this.featureList.map((value, index) => { - return { i: index, data: value } - }), item => { - Column() { - Image(this.featureIndex == item.i ? item.data.selectedIcon : item.data.icon) - .width($r('app.float.wh_value_32')) - .height($r('app.float.wh_value_32')) - Text(item.data.name) - .fontColor(this.featureIndex == item.i ? $r('app.color.selected_text_color') : $r('app.color.text_color')) - .fontSize($r('app.float.font_24')) - .margin({ top: $r('app.float.distance_3') }) - }.width(ConfigData.WH_33_100) - .onClick(() => { - this.featureIndex = item.i; + if (this.currentIndex === item.index) { + Stack() { + Image(item.iconActivated) + .width($r('app.float.appbar_icon_size')) + .height($r('app.float.appbar_icon_size')) + .draggable(false) + } + + Text(item.label) + .margin({ top: $r('app.float.main_tab_text_margin') }) + .fontSize($r('app.float.main_tab_text_size')) + .fontWeight(FontWeight.Medium) + .lineHeight($r('app.float.main_tab_line_height')) + .fontColor($r('sys.color.ohos_id_icon_color_active')) + } else { + Image(item.icon) + .fillColor($r('sys.color.ohos_fa_icon_secondary')) + .width($r('app.float.appbar_icon_size')) + .height($r('app.float.appbar_icon_size')) + .draggable(false) + Text(item.label) + .margin({ top: $r('app.float.main_tab_text_margin') }) + .fontSize($r('app.float.main_tab_text_size')) + .fontWeight(FontWeight.Medium) + .lineHeight($r('app.float.main_tab_line_height')) + .fontColor($r('sys.color.ohos_id_color_bottom_tab_text_off')) + } + } + .tabBarBackground(item, { + onHover: (isHover) => { + const active: ITabBarActionOptions = { index: undefined, status: undefined }; + if (isHover) { + active.index = item.index; + active.status = 'hover'; + } else { + active.index = undefined; + active.status = undefined; + } + this.currentActiveTab = active; + }, + onMouse: (event) => { + if ((event.action === MouseAction.Press) && (event.button === MouseButton.Left)) { + const active: ITabBarActionOptions = { index: undefined, status: undefined }; + active.index = item.index; + active.status = 'press'; + this.currentActiveTab = active; + } else if (event.action === MouseAction.Release) { + const active: ITabBarActionOptions = { index: undefined, status: undefined }; + active.index = item.index; + active.status = 'hover'; + this.currentActiveTab = active; + } + } + }, this.currentActiveTab) + .borderRadius($r('sys.float.ohos_id_corner_radius_default_s')) + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .onClick(() => { + animateTo({ duration: 1, curve: Curve.Friction }, () => { + this.currentIndex = item.index; + this.currentIndexProvider = this.currentIndex; + PageStateManager.saveTabIndexToSp(this.currentIndex); + this.changeTabs(); + }); + this.reportTabEvent(this.currentIndex) + }) + } + + @Builder + buildTabs(): void { + if (this.currentBreakpoint !== BreakPoint.LG) { + this.buildTabsLandSpace() + } else { + this.buildContTabs() + } + } + + @Builder + buildTabsLandSpace(): void { + Flex({ justifyContent: FlexAlign.End }) { + this.buildContTabs() + } + .renderGroup(true) + } + + @Builder + buildContTabs(): void { + Tabs({ + index: this.currentIndex, + controller: this.tabController, + barPosition: this.currentBreakpoint === BreakPoint.LG ? BarPosition.Start : BarPosition.End, + }) { + ForEach(MENU_CONFIG, (item: MenuItem) => { + TabContent().tabBar(this.buildTabItem(item)).width(0) + }) + } + .renderGroup(true) + .barWidth(this.isPCLg() ? '100%' : this.currentBreakpoint === BreakPoint.LG ? $r('app.float.tool_bar_width') : '100%') + .barHeight(this.isPCLg() ? '50%' : this.currentBreakpoint === BreakPoint.LG ? '60%' : $r('app.float.title_bar_height')) + .vertical(this.currentBreakpoint === BreakPoint.LG) + .onChange((index: number) => { + this.currentIndexCache = index + }) + .onKeyEvent((event: KeyEvent) => { + if (event.keyText === 'KEYCODE_ENTER' || event.keyText === 'KEYCODE_SPACE') { + animateTo({ duration: 0, curve: Curve.Friction }, () => { + this.currentIndex = this.currentIndexCache; + this.currentIndexProvider = this.currentIndex; + PageStateManager.saveTabIndexToSp(this.currentIndex); + this.changeTabs(); + }); + this.reportTabEvent(this.currentIndex); + } + }) + .animationDuration(0) + .scrollable(false) + .backgroundColor($r('sys.color.ohos_id_background_secondary')) + .width(this.currentBreakpoint === BreakPoint.LG ? $r('app.float.tool_bar_width') : this.isPortraitOrientation || this.foldAbleScreen === 1 ? '100%' : '60%') + .height(this.currentBreakpoint === BreakPoint.LG ? '100%' : $r('app.float.title_bar_height')) + .margin(this.isPortraitOrientation ? {} : { right: $r('app.float.tab_bar_margin_right') }) + } + + @Builder + buildContent(): void { + Flex({ direction: FlexDirection.Column }) { + Stack() { + if (this.isAlarmInit) { + AlarmClock({ + alarmClockListFromEntry: $alarmClockList, + leftTimeToRingTime: $leftTimeToRingTime, + isAlarmClockVisible: this.isAlarmClockVisible, + isPortraitOrientation: this.isPortraitOrientation, + isBigView: this.currentBreakpoint, + }) + .transition(TransitionEffect.opacity(this.isAlarmShown ? 1 : 0.99)) + .visibility(this.isPageVisible(MenuIndex.ALARM) ? Visibility.Visible : Visibility.Hidden) + } + if (this.isWorldClockInit) { + WorldClock({ + worldClockList: $worldClockList, + isWorldClockVisible: this.isWorldClockVisible, + isPortraitOrientation: this.isPortraitOrientation, + }) + .transition(TransitionEffect.opacity(this.isWorldClockShown ? 1 : 0.99)) + .visibility(this.isPageVisible(MenuIndex.WORLD_CLOCK) ? Visibility.Visible : Visibility.Hidden) + } + if (this.isStopInit) { + Stopwatch({ + isStopwatchVisible: this.isStopWatchVisible, + isPortraitOrientation: this.isPortraitOrientation, + isClockHide: $isClockHide, + isBigView: this.currentBreakpoint, + isPageHide: this.isPageHide, + currentIndex: this.currentIndex }) - }, item => item.i) + .transition(TransitionEffect.opacity(0.99)) + .visibility(this.isPageVisible(MenuIndex.STOPWATCH) ? Visibility.Visible : Visibility.Hidden) + } + if (this.isTimerInit) { + Timer({ + isTimerVisible: this.isTimerVisible, + isPortraitOrientation: this.isPortraitOrientation, + isBigView: this.currentBreakpoint, + isPageHide: this.isPageHide, + currentIndex: this.currentIndex, + isScreenEntry: this.isScreenEntry + }) + .transition(TransitionEffect.opacity(0.99)) + .visibility(this.isPageVisible(MenuIndex.TIMER) ? Visibility.Visible : Visibility.Hidden) + } + } + .width('100%') + } + .padding({ top: $r('app.float.content_offset'), bottom: $r('app.float.title_bar_height') }) + .width('100%') + .height('100%') + } + + @Builder + routerMap(name: string, param: ObservedAlarmInfo | object): void { + if (name === MANAGE_NEW_ALARM) { + ManageAlarmClock(); + } else if (name === MANAGE_EDIT_ALARM) { + ManageAlarmClock({ alarmInfoParam: param as ObservedAlarmInfo }); + } else if (name === MANAGE_NEW_WORLD_CLOCK) { + AddCity(); + } else if (name === MANAGE_EDIT_WORLD_CLOCK) { + EditCities(); + } else if (name === MANAGE_EDIT_WORLD_CLOCK_PC) { + EditCitiesForPC() + } + } + + build() { + Navigation(this.pageInfos) { + Stack({ alignContent: this.currentBreakpoint === BreakPoint.LG ? Alignment.Start : Alignment.Bottom }) { + this.buildContent(); + this.buildTitle(); + this.buildTabs(); } - .height($r('app.float.wh_value_75')) - .width(ConfigData.WH_100_100) - .justifyContent(FlexAlign.Center) + .width('100%') + .height('100%') + .backgroundColor(this.currentBreakpoint === BreakPoint.LG ? $r('sys.color.ohos_id_color_sub_background') : $r('sys.color.ohos_id_background_secondary')) + Text('111111') + .backgroundColor(Color.Red) + } + .hideTitleBar(true) + .hideToolBar(true) + .hideBackButton(true) + .navDestination(this.routerMap) + .mode(NavigationMode.Stack) + .expandSafeArea([SafeAreaType.KEYBOARD]) + } + + async initPresetCity(cityId: string) { + LogUtil.info(TAG, 'initPresetCity cityId: ' + cityId); + if (cityId) { + cityId && await WorldClockManager.addWorldClock(this.initWorldClockInfo(cityId)); + this.refreshWorldClockData(); } - .width(ConfigData.WH_100_100) - .height(ConfigData.WH_100_100) - .backgroundColor($r('app.color.background_color')) } } \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/index11.ets b/product/phone/src/main/ets/pages/index11.ets new file mode 100644 index 0000000..bd3c76b --- /dev/null +++ b/product/phone/src/main/ets/pages/index11.ets @@ -0,0 +1,1041 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// import emitter from '@ohos.events.emitter'; +// import router from '@ohos.router'; +// import i18n from '@ohos.i18n'; +// import promptAction from '@ohos.promptAction' +// import resmgr from '@ohos.resourceManager'; +// import display from '@ohos.display'; +// import { Callback, BusinessError } from '@ohos.base'; +import deviceInfo from '@ohos.deviceInfo' +import commonEventManager from '@ohos.commonEventManager'; +import { + ResourceManager, + AlarmManager, + AlarmInfoToShow, + TimeUtil, + TIME_DESC_STRING_ID_LIST, + LogUtil, + EVENT_ID_ALARM_RING, + EVENT_ID_CHANGE_ALARM, + EVENT_ID_DELETE_ALARM, + EVENT_ID_WINDOW_SHADOW, + EVENT_ID_SWITCH_PAGE, + PROMPT_TIME, + LESS_THAN_ONE_MINUTE_FLAG, + TitleBar, + FIRST_PARAM, + BreakpointManager, + BreakPoint, + EventReportUtil, + EventName, + EVENT_ID_CHANGE_WORLD_CLOCK, + PageStateManager, + GlobalContext, + AlarmInfo, + AlarmStateManager, + TimerManager, + CommonUtil, + SoundPool, + EVENT_ID_WORLDCLOCK_FORPC, + TIMER_Toggle_FRONT_AND_BACKEND, +} from '@hmos/common'; +// import hiSysEvent from '@ohos.hiSysEvent'; +import { + AlarmClock, + ManageAlarmClock, + MANAGE_NEW_ALARM, + MANAGE_EDIT_ALARM, + ObservedAlarmInfo, + DELETE_ALARM_CLOCK, + AlarmCardUtil +} from '@hmos/alarmclock'; +import { + PRESET_CITY_BEIJING, + PRESET_CITY_LONDON, + PRESET_CITY_PARIS +} from '@hmos/common/src/main/ets/utils/types' +import { + WorldClock, + WorldClockInfo, + WorldClockManager, + WorldClockUtil, + MANAGE_NEW_WORLD_CLOCK, + MANAGE_EDIT_WORLD_CLOCK, + MANAGE_EDIT_WORLD_CLOCK_PC, + AddCity, + EditCities, + EditCitiesForPC +} from '@hmos/worldclock'; +import device, { DeviceResponse } from '@system.device' +import { Stopwatch } from '@hmos/stopwatch'; +import { Timer } from '@hmos/timer'; +import mediaquery from '@ohos.mediaquery'; +import Window from '@ohos.window'; + +const TAG = 'ClockEntry'; +const MAX_CITY_NUM = 24; +const DEFAULT_SORT_ORDER = 9999; +const SMALL_SCREEN_CONTENT_SPAN = 12; +const SMALL_SCREEN_CONTENT_OFFSET = 0; +const BIG_SCREEN_CONTENT_SPAN = 10; +const BIG_SCREEN_CONTENT_OFFSET = 2; +const TABLE_PAD = 2560; +const TIME_OUT_MILLIS = 600; + +enum MenuIndex { + ALARM = 0, + WORLD_CLOCK = 1, + STOPWATCH = 2, + TIMER = 3 +}; + +interface MenuItem { + icon: Resource, + iconActivated: Resource, + label: Resource, + index: number, + defaultBgColor: ResourceColor, + hoverBgColor: ResourceColor, + pressBgColor: ResourceColor, +}; + +const MENU_CONFIG: MenuItem[] = [ + { + icon: $r('app.media.ic_alarm'), + iconActivated: $r('app.media.ic_alarm_activated'), + label: $r('app.string.alarm_clock'), + index: MenuIndex.ALARM, + defaultBgColor: Color.Transparent, + hoverBgColor: $r('sys.color.ohos_id_color_hover'), + pressBgColor: $r('sys.color.ohos_id_color_click_effect') + }, + { + icon: $r('app.media.ic_world_clock'), + iconActivated: $r('app.media.ic_world_clock_activated'), + label: $r('app.string.world_clock'), + index: MenuIndex.WORLD_CLOCK, + defaultBgColor: Color.Transparent, + hoverBgColor: $r('sys.color.ohos_id_color_hover'), + pressBgColor: $r('sys.color.ohos_id_color_click_effect') + }, + { + icon: $r('app.media.ic_stopwatch'), + iconActivated: $r('app.media.ic_stopwatch_activated'), + label: $r('app.string.stopwatch'), + index: MenuIndex.STOPWATCH, + defaultBgColor: Color.Transparent, + hoverBgColor: $r('sys.color.ohos_id_color_hover'), + pressBgColor: $r('sys.color.ohos_id_color_click_effect') + }, + { + icon: $r('app.media.ic_timer'), + iconActivated: $r('app.media.ic_timer_activated'), + label: $r('app.string.timer'), + index: MenuIndex.TIMER, + defaultBgColor: Color.Transparent, + hoverBgColor: $r('sys.color.ohos_id_color_hover'), + pressBgColor: $r('sys.color.ohos_id_color_click_effect') + } +]; +type CommonEventSubscriber = commonEventManager.CommonEventSubscriber; +type CommonEventSubscribeInfo = commonEventManager.CommonEventSubscribeInfo; +const SCREEN_LOCK_INFO: CommonEventSubscribeInfo = { + events: [ + commonEventManager.Support.COMMON_EVENT_SCREEN_OFF, + ], +}; + +interface ITabBarBackgroundEvent { + onHover: (isHover: boolean, event: HoverEvent) => void; + onMouse: (event: MouseEvent) => void; +} + +interface ITabBarActionOptions { + index: MenuIndex | undefined; + status: string | undefined; +} + +function computedTabBarBackground(options: MenuItem, activeOptions: ITabBarActionOptions): ResourceColor { + if (options.index === activeOptions.index) { + if (activeOptions.status === 'hover') { + return options.hoverBgColor; + } else if (activeOptions.status === 'press') { + return options.pressBgColor; + } + } + return Color.Transparent +} + +@Extend(Column) +function tabBarBackground(options: MenuItem, events: ITabBarBackgroundEvent, activeOptions: ITabBarActionOptions) { + .backgroundColor(computedTabBarBackground(options, activeOptions)) + .onHover(events.onHover) + .onMouse(events.onMouse) +} + + +/** + * 手机产品的时钟入口页面 + * + * @since 2022-07-19 + */ +@Entry +@Component +@Preview +struct Index { + @StorageProp('currentAbleScreen') @Watch('listenFoldScreen') foldAbleScreen: number = 0; + @StorageProp('currentBreakpoint') currentBreakpoint: string = ''; + @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack(); + @StorageProp('setOrientaion') getOrientaion: number = 0; + @State currentIndex: number = -1; // 当前中的工具栏菜单下标 + @Provide('currentIndex') currentIndexProvider: number = -1; + @State currentIndexCache: number = -1; + @State alarmClockList: AlarmInfoToShow[] = []; // 闹钟列表 + @State leftTimeToRingTime: string = ''; // 到下次响铃的间隔时间 + @StorageLink('count') count: number = 0; + @State isClockHide: boolean = false; + @State worldClockList: WorldClockInfo[] = []; + @State @Watch('changeNavbarStatus') isPortraitOrientation: boolean = true; + @State isPortrait: boolean = true + @StorageProp('isTabChange') @Watch('tabChangeProcess') isTabChange: boolean = false; + @State isForeground: boolean = false; + @StorageLink('EditBindSheet') isEditShow: boolean = false; + @StorageLink('NewBindSheet') isNewShow: boolean = false; + @State isPageHide: boolean = true; + @State isScreenLock: boolean = false; + @State isScreenEntry: boolean = false + @State currentActiveTab: ITabBarActionOptions = { index: undefined, status: undefined }; + @State isStopInit: boolean = false; + @State isTimerInit: boolean = false; + @State isAlarmInit: boolean = false; + @State isWorldClockInit: boolean = false; + @State isAlarmShown: boolean = false; + @State isWorldClockShown: boolean = false; + @State isWorldClockVisible: boolean = false; + @State isAlarmClockVisible: boolean = true; + @State isStopWatchVisible: boolean = true; + @State isTimerVisible: boolean = true; + private cityList: Set = new Set(); + private alarmRingTime: string = ''; // Next ringing time of a new or edited alarm + private tabController: TabsController = new TabsController(); + private orientationListener: mediaquery.MediaQueryListener | undefined = undefined; + private isFoldCross: number = 2224; + private isFoldVertical: number = 2496; + private isTablePad: string = 'tablet' + private commonEventSubscriberAsLock: CommonEventSubscriber = {} as CommonEventSubscriber; + private deviceType: string = deviceInfo.deviceType; + + private async subscribeCommentAsLockEvent(): Promise { + this.commonEventSubscriberAsLock = await commonEventManager.createSubscriber(SCREEN_LOCK_INFO); + if (!this.commonEventSubscriberAsLock) { + return; + } + commonEventManager.subscribe(this.commonEventSubscriberAsLock, async () => { + this.isScreenLock = true + // SoundPool.releaseAll(); + + }); + } + + private listenFoldScreen() { + // this.getScreen() + } + + private unSubscribeCommentAsLockEvent(): void { + // commonEventManager.unsubscribe(this.commonEventSubscriberAsLock, error => { + // if (error) { + // LogUtil.error(TAG, `Unsubscribe to common event failed because: ${JSON.stringify(error)}`); + // return; + // } + // LogUtil.info(TAG, 'Unsubscribe to Lock event successfully!'); + // }); + } + + private isPCLg() { + return this.deviceType === '2in1' + } + + async tabChangeProcess(): Promise { + // if (GlobalContext.getContext().getObject('dialogController')) { + // LogUtil.info(TAG, 'tabChangeProcess dialogController close'); + // (GlobalContext.getContext().getObject('dialogController') as CustomDialogController).close() + // } + // GlobalContext.getContext().setObject('textInput', undefined); + // + // if (this.pageInfos.size() > 0) { + // LogUtil.info(TAG, 'tabChangeProcess pageInfos' + JSON.stringify(this.pageInfos)); + // this.pageInfos.pop(); + // } else { + // this.isEditShow = false; + // this.isNewShow = false; + // AppStorage.setOrCreate('EditShowItem', false); + // AppStorage.setOrCreate('NewShowItem', false); + // const routerState = router.getState(); + // LogUtil.info(TAG, 'tabChangeProcess routerState:' + JSON.stringify(routerState)); + // if (routerState && routerState.name !== 'index') { + // router.back(); + // } + // } + // await this.initContent(); + } + + async aboutToAppear(): Promise { + // LogUtil.info(TAG, 'about to appear'); + // if (this.deviceType === this.isTablePad) { + // this.isPortraitOrientation = false; + // } + // LogUtil.info(TAG, 'about to appear1'); + // + // await this.initContent(); + // LogUtil.info(TAG, 'about to appear2'); + // + // ResourceManager.preloadStringResources(TIME_DESC_STRING_ID_LIST); + // LogUtil.info(TAG, 'about to appear3'); + // if (this.currentIndex === MenuIndex.ALARM) { + // await this.refreshAlarmData(); + // } else { + // this.refreshAlarmData(); + // } + // if (this.currentIndex === MenuIndex.WORLD_CLOCK) { + // await this.refreshWorldClockData(); + // } else { + // this.refreshWorldClockData(); + // } + // LogUtil.info(TAG, 'about to appear4'); + // + // // await SoundPool.create('aboutToAppear'); + // // SoundPool.loadResource(0, this.currentIndex); + // LogUtil.info(TAG, 'about to appear5'); + // + // // let lastWindow = await Window.getLastWindow(GlobalContext.getContext().getObject('clockContext') as Context); + // LogUtil.info(TAG, 'about to appear5.1'); + // + // if (this.deviceType === this.isTablePad) { + // LogUtil.info(TAG, 'about to appear5.2'); + // + // // device.getInfo({ + // // success: (ret: DeviceResponse) => { + // // lastWindow.setPreferredOrientation(Window.Orientation.AUTO_ROTATION_RESTRICTED); + // // if (ret.windowWidth === TABLE_PAD) { + // // this.isPortraitOrientation = false; + // // } else { + // // this.isPortraitOrientation = true; + // // } + // // } + // // }) + // } else { + // LogUtil.info(TAG, 'about to appear5.3'); + // + // // lastWindow.setPreferredOrientation(Window.Orientation.PORTRAIT); + // } + // LogUtil.info(TAG, 'about to appear6'); + // + // this.getScreen() + // this.getOrientationOnce() + // this.getOrientation(true) + // LogUtil.info(TAG, 'about to appear7'); + // + // this.orientationListener = mediaquery.matchMediaSync('(orientation: landscape)'); + // this.orientationListener.on('change', (result) => { + // emitter.emit({ + // eventId: EVENT_ID_WINDOW_SHADOW, + // priority: emitter.EventPriority.IMMEDIATE, + // }, { + // data: { + // isPortraitOrientation: result.matches ? true : false, + // } + // }) + // LogUtil.info(TAG, 'Orientation change, result: ' + result + result.matches) + // if (result.matches) { + // this.isPortraitOrientation = false; + // LogUtil.info(TAG, 'Screen orientation changed to landscape'); + // } else { + // this.isPortraitOrientation = true; + // LogUtil.info(TAG, 'Screen orientation changed to portrait'); + // } + // }) + // LogUtil.info(TAG, 'about to appear8'); + // + // this.subscribeCommentAsLockEvent(); + // await this.getDefaultCity(); + // this.subscribeEvents(); + // BreakpointManager.register(); + // LogUtil.info(TAG, 'about to appear9'); + // + // setTimeout(() => { + // if (!this.isAlarmInit || !this.isWorldClockInit || !this.isStopInit || this.isTimerInit) { + // this.isAlarmInit = true; + // this.isWorldClockInit = true; + // this.isStopInit = true; + // this.isTimerInit = true; + // } + // }, TIME_OUT_MILLIS); + // LogUtil.info(TAG, 'about to appear10'); + + } + + private async getDefaultCity() { + // let worldClockList = await WorldClockManager.getAllWorldClock(); + // const result = await AlarmManager.getFirstEnterApp(worldClockList?.length); + // if (result) { + // await this.initPresetCity(PRESET_CITY_BEIJING); + // await this.initPresetCity(PRESET_CITY_LONDON); + // await this.initPresetCity(PRESET_CITY_PARIS); + // } + } + + async aboutToDisappear(): Promise { + // this.unSubscribeCommentAsLockEvent(); + // this.unSubscribeEvents(); + // BreakpointManager.unregister(); + } + + private getScreen() { + // LogUtil.info(TAG, 'Get screen start'); + // let callback: Callback = (data: display.FoldStatus) => { + // AppStorage.SetOrCreate('currentAbleScreen', data === 3 ? 1 : data); + // }; + // try { + // const getfoldAbleScreen = display.getFoldStatus(); + // AppStorage.SetOrCreate('currentAbleScreen', getfoldAbleScreen === 3 ? 1 : getfoldAbleScreen); + // display.on('foldStatusChange', callback); + // LogUtil.info(TAG, 'get screen success' + JSON.stringify(getfoldAbleScreen)); + // } catch (exception) { + // LogUtil.error('get screen result' + JSON.stringify(exception)); + // } + } + + // reslove Orientation problem + private getOrientationOnce() { + // LogUtil.info(TAG, 'Get orientation once start'); + // display.getAllDisplay((err: BusinessError, data: Array) => { + // const errCode: number = err.code; + // if (errCode) { + // LogUtil.error(TAG, 'Failed to obtain all the display objects. Code: ' + JSON.stringify(err)); + // return; + // } + // if (this.deviceType === this.isTablePad) { + // LogUtil.info(TAG, 'Now device is tablet'); + // if (data[0].rotation === 0 || data[0].rotation === 2) { + // this.isPortraitOrientation = false + // } else if (data[0].rotation === 1 || data[0].rotation === 3) { + // this.isPortraitOrientation = true + // } + // } else { + // LogUtil.info(TAG, 'Now device is default'); + // if (data[0].rotation === 0 || data[0].rotation === 2) { + // this.isPortraitOrientation = true + // } else if (data[0].rotation === 1 || data[0].rotation === 3) { + // this.isPortraitOrientation = true + // } + // } + // }); + } + + async onPageShow(): Promise { + // LogUtil.info(TAG, 'on page show'); + // await this.getScreen() + // LogUtil.info(TAG, 'on page show'); + // const cityId = AppStorage.Get('cityId'); + // AppStorage.Delete('cityId'); + // this.UpdateCityList() + // cityId && await this.addNewWorldClock(cityId) + // this.refreshAlarmData() + // this.refreshWorldClockData(); + // this.emitSwitchPageHandle(true); + // if (this.isScreenLock) { + // // await SoundPool.loadResource(1, this.currentIndex) + // // SoundPool.soundPool.on('loadComplete', (soundId_: number) => { + // // this.isPageHide = false + // // }) + // this.isScreenLock = false + // this.isScreenEntry = true + // } else { + // this.isPageHide = false + // this.isScreenEntry = false + // } + + } + + async onPageHide(): Promise { + // this.isPageHide = true + // this.emitSwitchPageHandle(false); + } + + async initContent(): Promise { + // this.currentIndex = await PageStateManager.getTabIndex(); + // this.currentIndexProvider = this.currentIndex; + // this.changeTabs(); + } + + changeTabs(): void { + // if (this.currentIndex === MenuIndex.ALARM) { + // if (this.isAlarmInit) { + // this.isAlarmShown = true; + // } + // this.isAlarmInit = true; + // this.isAlarmClockVisible = true; + // this.isWorldClockVisible = false; + // this.isStopWatchVisible = false; + // this.isTimerVisible = false; + // } else if (this.currentIndex === MenuIndex.WORLD_CLOCK) { + // if (this.isWorldClockInit) { + // this.isWorldClockShown = true; + // } + // this.isWorldClockInit = true; + // this.isAlarmClockVisible = false; + // this.isWorldClockVisible = true; + // this.isStopWatchVisible = false; + // this.isTimerVisible = false; + // } else if (this.currentIndex === MenuIndex.STOPWATCH) { + // this.isStopInit = true; + // this.isAlarmClockVisible = false; + // this.isWorldClockVisible = false; + // this.isStopWatchVisible = true; + // this.isTimerVisible = false; + // } else if (this.currentIndex === MenuIndex.TIMER) { + // this.isTimerInit = true; + // this.isAlarmClockVisible = false; + // this.isWorldClockVisible = false; + // this.isStopWatchVisible = false; + // this.isTimerVisible = true; + // } + } + + /** + * 查询/刷新闹钟页面数据 + * 在第一次进入应用和跳转到别的应用或页面有,再返回当前页面时调用 + */ + async refreshAlarmData(): Promise { + // LogUtil.info(TAG, 'refreshAlarmData'); + // this.alarmClockList = await AlarmManager.getAllAlarmsToShow(); + // LogUtil.info(TAG, 'alarmClockList:' + this.alarmClockList.length); + // this.leftTimeToRingTime = await TimeUtil.getLeftTimeToRingTime(false); + // const isFiring: boolean = await AlarmStateManager.isFiring(); + // LogUtil.info(TAG, 'alarm isFiring' + isFiring); + // if (!isFiring) { + // AlarmCardUtil.notifyAlarmCardTimeUpdate(); + // } + // + // await this.addOpsLogs(); + } + + /** + * addOpsLogs to diagnosis beta problems + */ + async addOpsLogs(): Promise { + // for (const value of this.alarmClockList) { + // if (value.enabled) { + // LogUtil.info(TAG, `opsLogs has enabled alarm id: ${value.id}`); + // break; + // } + // } + // const timerId = await TimerManager.getTimerId(); + // const triggerTime = await TimerManager.getTriggerTime(); + // const triggerAlarmId = await TimerManager.getTriggerTimeID(); + // LogUtil.info(TAG, `opsLogs triggerAlarmId: ${triggerAlarmId} triggerTime: ${triggerTime} timerId: ${timerId} `); + // + // const nearestAlarmTime: string = CommonUtil.getNearestAlarmTime(); + // LogUtil.info(TAG, `opsLogs nearestAlarmTime: ${nearestAlarmTime} tips: ${this.leftTimeToRingTime}`); + } + + /** + * 刷新最近一次闹钟的状态 + */ + async refreshNearestAlarm(): Promise { + // LogUtil.info(TAG, 'refreshNearestAlarm'); + // let nearClock: AlarmInfo | undefined = await AlarmManager.getNearestAlarm(); + // LogUtil.info(TAG, `nearClock: ${JSON.stringify(nearClock)}`); + // if (nearClock) { + // AlarmManager.updateAlarm(nearClock) + // this.leftTimeToRingTime = await TimeUtil.getLeftTimeToRingTime(true); + // const isFiring: boolean = await AlarmStateManager.isFiring(); + // LogUtil.info(TAG, 'alarm isFiring' + isFiring); + // if (!isFiring) { + // AlarmCardUtil.notifyAlarmCardTimeUpdate(); + // } + // } + } + + /** + * refresh world clock list. + */ + async refreshWorldClockData(): Promise { + // LogUtil.info(TAG, 'refreshWorldClockData'); + // let worldClockList = await WorldClockManager.getAllWorldClock(); + // this.cityList.clear(); + // for (let worldClock of worldClockList) { + // this.cityList.add(worldClock.cityIndex); + // worldClock.tag = await WorldClockUtil.updateTimeZoneInfo(worldClock.offset, + // worldClock.timezone, worldClock.cityIndex); + // } + // this.worldClockList = worldClockList; + } + + private emitSwitchPageHandle(isShow: boolean): void { + // emitter.emit({ + // eventId: EVENT_ID_SWITCH_PAGE, + // priority: emitter.EventPriority.IMMEDIATE, + // }, { + // data: { + // isPageShow: isShow, + // } + // }) + // if (isShow) { + // emitter.emit({ eventId: TIMER_Toggle_FRONT_AND_BACKEND, priority: emitter.EventPriority.IMMEDIATE }, { + // data: { pageShow: true } + // }) + // } + } + + /** + * Do not modify the folding method + */ + private getOrientation(isUp?: boolean) { + // LogUtil.info(TAG, 'get orientation start, isUp:' + JSON.stringify(isUp)); + // display.getAllDisplay((err: BusinessError, data: Array) => { + // const errCode: number = err.code; + // if (errCode) { + // LogUtil.error(TAG, 'Failed to obtain all the display objects. Code: ' + JSON.stringify(err)); + // return; + // } + // let result = 0 + // if (data[0].rotation === 0 || data[0].rotation === 2 && isUp) { + // result = this.isFoldCross + // } else if (data[0].rotation === 1 || data[0].rotation === 3 && isUp) { + // result = this.isFoldVertical + // } else { + // result = data[0].width + // } + // AppStorage.SetOrCreate('setOrientaion', result); + // }); + } + + private async changeNavbarStatus() { + // if (this.foldAbleScreen === 1) { + // this.getOrientation() + // } + } + + private async showToastWhenAlarmChanges(): Promise { + // if (this.alarmRingTime) { + // const string_less_than_one_minute = await ResourceManager.getStringByIdAsync($r('app.string.less_than_one_minute') + // .id); + // const STRING_RING_IN = await ResourceManager.getStringByIdAsync($r('app.string.ring_in').id); + // const remainingRingTime = this.alarmRingTime === LESS_THAN_ONE_MINUTE_FLAG + // ? string_less_than_one_minute : STRING_RING_IN.replace(FIRST_PARAM, this.alarmRingTime); + // promptAction.showToast({ message: remainingRingTime, duration: PROMPT_TIME }); + // this.alarmRingTime = ''; + // } + } + + /** + * Subscription event, used to refresh the page + */ + private subscribeEvents(): void { + // // 监听处理闹钟启闹 + // emitter.on({ + // eventId: EVENT_ID_ALARM_RING + // }, async () => { + // LogUtil.info(TAG, 'receive EVENT_ID_ALARM_RING') + // await AlarmManager.updateTimer(); + // this.refreshAlarmData(); + // }); + // // 监听处理闹钟编辑和创建 + // emitter.on({ + // eventId: EVENT_ID_CHANGE_ALARM, + // }, (eventData) => { + // LogUtil.info(TAG, 'receive EVENT_ID_CHANGE_ALARM') + // this.alarmRingTime = eventData.data ? eventData.data.KEY_ALARM_RING_TIME : ''; + // LogUtil.info(TAG, 'receive alarmRingTime:' + this.alarmRingTime); + // this.showToastWhenAlarmChanges(); + // this.refreshAlarmData(); + // }); + // // 监听处理闹钟批量删除 + // emitter.on({ + // eventId: EVENT_ID_DELETE_ALARM, + // }, (eventData) => { + // LogUtil.info(TAG, 'receive EVENT_ID_DELETE_ALARM') + // this.showToastWhenAlarmChanges(); + // this.refreshAlarmData(); + // }); + // // 监听处理世界时钟左滑删除 + // emitter.on({ + // eventId: EVENT_ID_CHANGE_WORLD_CLOCK, + // }, () => { + // LogUtil.info(TAG, 'receive WORLD_CLOCK_CHANGE'); + // this.UpdateCityList() + // }); + } + + private UpdateCityList(): void { + // this.cityList.clear(); + // for (let worldClock of this.worldClockList) { + // this.cityList.add(worldClock.cityIndex); + // } + } + + /** + * Unsubscribe from events + */ + private unSubscribeEvents(): void { + // emitter.off(EVENT_ID_ALARM_RING); + // emitter.off(EVENT_ID_CHANGE_ALARM); + // emitter.off(EVENT_ID_DELETE_ALARM); + // emitter.off(EVENT_ID_CHANGE_WORLD_CLOCK); + // emitter.off(EVENT_ID_WORLDCLOCK_FORPC); + } + + private async addNewWorldClock(cityId: string): Promise { + // if (cityId === '') { + // LogUtil.info(TAG, 'select no city'); + // return; + // } + // if (this.cityList.has(cityId)) { + // promptAction.showToast({ message: $r('app.string.worldclock_city_exist_Toast'), duration: PROMPT_TIME }); + // return; + // } + // if (this.cityList.size === MAX_CITY_NUM) { + // promptAction.showToast({ message: $r('app.string.city_full_Toast'), duration: PROMPT_TIME }); + // return; + // } + // await WorldClockManager.addWorldClock(this.initWorldClockInfo(cityId)); + } + + // private initWorldClockInfo(cityId: string): WorldClockInfo { + // const cityIndex = cityId; + // const timezone = i18n.TimeZone.getTimezoneFromCity(cityId).getID(); + // const city = i18n.TimeZone.getCityDisplayName(cityId, i18n.getSystemLocale()); + // const offset = i18n.TimeZone.getTimezoneFromCity(cityId).getRawOffset(); + // return { + // sortOrder: DEFAULT_SORT_ORDER, + // cityIndex, + // timezone, + // city, + // offset, + // }; + // } + + private reportTabEvent(index: number): void { + // if (index === MenuIndex.ALARM) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_ALARM_CLOCK_TAB) + // } + // if (index === MenuIndex.WORLD_CLOCK) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_WORLD_CLOCK_TAB) + // } + // if (index === MenuIndex.STOPWATCH) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_STOPWATCH_TAB) + // } + // if (index === MenuIndex.TIMER) { + // EventReportUtil.reportEvent(hiSysEvent.EventType.BEHAVIOR, EventName.CLICK_TIMER_TAB) + // } + } + + // private isPageVisible(index: MenuIndex): boolean { + // return this.currentIndex == index; + // } + + @Builder + buildTitleBar(id: number): void { + // TitleBar({ + // title: (GlobalContext.getContext().getObject('resourceManager') as resmgr.ResourceManager).getStringSync(id), + // operationArea: () => { + // }, + // }) + // .margin(this.isPCLg() ? { left: $r('app.float.snooze_button_border_radius') } : (this.isPortraitOrientation ? { + // left: this.foldAbleScreen === 1 ? $r('app.float.change_vertical') : $r('app.float.change_cross'), + // right: this.foldAbleScreen === 1 ? $r('app.float.change_vertical') : $r('app.float.change_cross') + // } : { left: $r('app.float.change_vertical'), right: $r('app.float.change_vertical') })) + // .transition({ type: TransitionType.All, opacity: 0 }) + // .shadow({ + // radius: $r('app.float.button_shadow_radius'), + // color: $r('sys.color.ohos_id_background_secondary'), + // offsetX: $r('app.float.button_shadow_x'), + // offsetY: $r('app.float.button_shadow_y'), + // }) + } + + @Builder + buildTitle(): void { + Flex({ alignItems: ItemAlign.Start }) { + GridRow() { + GridCol({ + span: { xs: SMALL_SCREEN_CONTENT_SPAN, md: SMALL_SCREEN_CONTENT_SPAN, lg: BIG_SCREEN_CONTENT_SPAN }, + offset: { xs: SMALL_SCREEN_CONTENT_OFFSET, md: SMALL_SCREEN_CONTENT_OFFSET, lg: BIG_SCREEN_CONTENT_OFFSET }, + }) { + Row() { + if (this.currentIndex === MenuIndex.ALARM) { + this.buildTitleBar(MENU_CONFIG[this.currentIndex].label.id); + } else if (this.currentIndex === MenuIndex.WORLD_CLOCK) { + this.buildTitleBar(MENU_CONFIG[this.currentIndex].label.id); + } else if (this.currentIndex === MenuIndex.STOPWATCH) { + this.buildTitleBar(MENU_CONFIG[this.currentIndex].label.id); + } else if (this.currentIndex === MenuIndex.TIMER) { + this.buildTitleBar(MENU_CONFIG[this.currentIndex].label.id); + } + } + .width('100%') + .margin(this.isPCLg() ? { left: '-192vp' } : '0') + .height($r('app.float.title_bar_height')) + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Start) + .backgroundColor($r('sys.color.ohos_id_background_secondary')) + .shadow({ + radius: $r('app.float.title_bar_shadow_radius'), + color: $r('sys.color.ohos_id_background_secondary'), + offsetX: $r('app.float.title_bar_shadow_x'), + offsetY: $r('app.float.title_bar_shadow_y') + }) + } + .backgroundColor($r('app.color.transparent')) + .height($r('app.float.title_bar_height')) + } + .height('100%') + .width(this.isPortraitOrientation || this.foldAbleScreen === 1 ? '100%' : this.currentBreakpoint !== BreakPoint.LG ? '39%' : '100%') + } + .renderGroup(true) + .hitTestBehavior(HitTestMode.Transparent) + .height('100%') + .width('100%') + } + + @Builder + buildTabItem(item: MenuItem): void { + Column() { + if (this.currentIndex === item.index) { + Stack() { + Image(item.iconActivated) + .width($r('app.float.appbar_icon_size')) + .height($r('app.float.appbar_icon_size')) + .draggable(false) + } + + Text(item.label) + .margin({ top: $r('app.float.main_tab_text_margin') }) + .fontSize($r('app.float.main_tab_text_size')) + .fontWeight(FontWeight.Medium) + .lineHeight($r('app.float.main_tab_line_height')) + .fontColor($r('sys.color.ohos_id_icon_color_active')) + } else { + Image(item.icon) + .fillColor($r('sys.color.ohos_fa_icon_secondary')) + .width($r('app.float.appbar_icon_size')) + .height($r('app.float.appbar_icon_size')) + .draggable(false) + Text(item.label) + .margin({ top: $r('app.float.main_tab_text_margin') }) + .fontSize($r('app.float.main_tab_text_size')) + .fontWeight(FontWeight.Medium) + .lineHeight($r('app.float.main_tab_line_height')) + .fontColor($r('sys.color.ohos_id_color_bottom_tab_text_off')) + } + } + .tabBarBackground(item, { + onHover: (isHover) => { + const active: ITabBarActionOptions = { index: undefined, status: undefined }; + if (isHover) { + active.index = item.index; + active.status = 'hover'; + } else { + active.index = undefined; + active.status = undefined; + } + this.currentActiveTab = active; + }, + onMouse: (event) => { + if ((event.action === MouseAction.Press) && (event.button === MouseButton.Left)) { + const active: ITabBarActionOptions = { index: undefined, status: undefined }; + active.index = item.index; + active.status = 'press'; + this.currentActiveTab = active; + } else if (event.action === MouseAction.Release) { + const active: ITabBarActionOptions = { index: undefined, status: undefined }; + active.index = item.index; + active.status = 'hover'; + this.currentActiveTab = active; + } + } + }, this.currentActiveTab) + .borderRadius($r('sys.float.ohos_id_corner_radius_default_s')) + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .onClick(() => { + animateTo({ duration: 1, curve: Curve.Friction }, () => { + this.currentIndex = item.index; + this.currentIndexProvider = this.currentIndex; + PageStateManager.saveTabIndexToSp(this.currentIndex); + this.changeTabs(); + }); + this.reportTabEvent(this.currentIndex) + }) + } + + @Builder + buildTabs(): void { + if (this.currentBreakpoint !== BreakPoint.LG) { + this.buildTabsLandSpace() + } else { + this.buildContTabs() + } + } + + @Builder + buildTabsLandSpace(): void { + Flex({ justifyContent: FlexAlign.End }) { + this.buildContTabs() + } + .renderGroup(true) + } + + @Builder + buildContTabs(): void { + Tabs({ + index: this.currentIndex, + controller: this.tabController, + barPosition: this.currentBreakpoint === BreakPoint.LG ? BarPosition.Start : BarPosition.End, + }) { + ForEach(MENU_CONFIG, (item: MenuItem) => { + TabContent().tabBar(this.buildTabItem(item)).width(0) + }) + } + .renderGroup(true) + .barWidth(this.isPCLg() ? '100%' : this.currentBreakpoint === BreakPoint.LG ? $r('app.float.tool_bar_width') : '100%') + .barHeight(this.isPCLg() ? '50%' : this.currentBreakpoint === BreakPoint.LG ? '60%' : $r('app.float.title_bar_height')) + .vertical(this.currentBreakpoint === BreakPoint.LG) + .onChange((index: number) => { + this.currentIndexCache = index + }) + .onKeyEvent((event: KeyEvent) => { + if (event.keyText === 'KEYCODE_ENTER' || event.keyText === 'KEYCODE_SPACE') { + animateTo({ duration: 0, curve: Curve.Friction }, () => { + this.currentIndex = this.currentIndexCache; + this.currentIndexProvider = this.currentIndex; + PageStateManager.saveTabIndexToSp(this.currentIndex); + this.changeTabs(); + }); + this.reportTabEvent(this.currentIndex); + } + }) + .animationDuration(0) + .scrollable(false) + .backgroundColor($r('sys.color.ohos_id_background_secondary')) + .width(this.currentBreakpoint === BreakPoint.LG ? $r('app.float.tool_bar_width') : this.isPortraitOrientation || this.foldAbleScreen === 1 ? '100%' : '60%') + .height(this.currentBreakpoint === BreakPoint.LG ? '100%' : $r('app.float.title_bar_height')) + .margin(this.isPortraitOrientation ? {} : { right: $r('app.float.tab_bar_margin_right') }) + } + + @Builder + buildContent(): void { + Flex({ direction: FlexDirection.Column }) { + Stack() { + if (this.isAlarmInit) { + AlarmClock({ + alarmClockListFromEntry: $alarmClockList, + leftTimeToRingTime: $leftTimeToRingTime, + isAlarmClockVisible: this.isAlarmClockVisible, + isPortraitOrientation: this.isPortraitOrientation, + isBigView: this.currentBreakpoint, + }) + .transition(TransitionEffect.opacity(this.isAlarmShown ? 1 : 0.99)) + // .visibility(this.isPageVisible(MenuIndex.ALARM) ? Visibility.Visible : Visibility.Hidden) + } + if (this.isWorldClockInit) { + WorldClock({ + worldClockList: $worldClockList, + isWorldClockVisible: this.isWorldClockVisible, + isPortraitOrientation: this.isPortraitOrientation, + }) + .transition(TransitionEffect.opacity(this.isWorldClockShown ? 1 : 0.99)) + // .visibility(this.isPageVisible(MenuIndex.WORLD_CLOCK) ? Visibility.Visible : Visibility.Hidden) + } + if (this.isStopInit) { + Stopwatch({ + isStopwatchVisible: this.isStopWatchVisible, + isPortraitOrientation: this.isPortraitOrientation, + isClockHide: $isClockHide, + isBigView: this.currentBreakpoint, + isPageHide: this.isPageHide, + currentIndex: this.currentIndex + }) + .transition(TransitionEffect.opacity(0.99)) + // .visibility(this.isPageVisible(MenuIndex.STOPWATCH) ? Visibility.Visible : Visibility.Hidden) + } + if (this.isTimerInit) { + Timer({ + isTimerVisible: this.isTimerVisible, + isPortraitOrientation: this.isPortraitOrientation, + isBigView: this.currentBreakpoint, + isPageHide: this.isPageHide, + currentIndex: this.currentIndex, + isScreenEntry: this.isScreenEntry + }) + .transition(TransitionEffect.opacity(0.99)) + // .visibility(this.isPageVisible(MenuIndex.TIMER) ? Visibility.Visible : Visibility.Hidden) + } + } + .width('100%') + } + .padding({ top: $r('app.float.content_offset'), bottom: $r('app.float.title_bar_height') }) + .width('100%') + .height('100%') + } + + @Builder + routerMap(name: string, param: ObservedAlarmInfo | object): void { + if (name === MANAGE_NEW_ALARM) { + ManageAlarmClock(); + } else if (name === MANAGE_EDIT_ALARM) { + ManageAlarmClock({ alarmInfoParam: param as ObservedAlarmInfo }); + } else if (name === MANAGE_NEW_WORLD_CLOCK) { + AddCity(); + } else if (name === MANAGE_EDIT_WORLD_CLOCK) { + EditCities(); + } else if (name === MANAGE_EDIT_WORLD_CLOCK_PC) { + EditCitiesForPC() + } + } + + build() { + Navigation(this.pageInfos) { + // Stack({ alignContent: this.currentBreakpoint === BreakPoint.LG ? Alignment.Start : Alignment.Bottom }) { + // this.buildContent(); + // this.buildTitle(); + // this.buildTabs(); + // } + // .width('100%') + // .height('100%') + // .backgroundColor(this.currentBreakpoint === BreakPoint.LG ? $r('sys.color.ohos_id_color_sub_background') : $r('sys.color.ohos_id_background_secondary')) + Text('111111') + .backgroundColor(Color.Red) + } + .hideTitleBar(true) + .hideToolBar(true) + .hideBackButton(true) + .navDestination(this.routerMap) + .mode(NavigationMode.Stack) + .expandSafeArea([SafeAreaType.KEYBOARD]) + } + + async initPresetCity(cityId: string) { + // LogUtil.info(TAG, 'initPresetCity cityId: ' + cityId); + // if (cityId) { + // cityId && await WorldClockManager.addWorldClock(this.initWorldClockInfo(cityId)); + // this.refreshWorldClockData(); + // } + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/timer/TimeOfRecord.ets b/product/phone/src/main/ets/pages/timer/TimeOfRecord.ets deleted file mode 100644 index 206318d..0000000 --- a/product/phone/src/main/ets/pages/timer/TimeOfRecord.ets +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TimerUtil, ConfigData } from '@ohos/common'; - -@Component -export struct TimeOfRecord { - @State timeList: Array = []; - @Prop lastTimeMs: number; - - build() { - Column() { - List() { - ForEach(this.timeList.map((value, index) => { - return { i: index, data: this.timeList[this.timeList.length - index-1] } - }), (item) => { - ListItem() { - Row() { - Row() { - Text(TimerUtil.addZero(this.timeList.length - item.i)) - .fontSize($r('app.float.font_28')) - .fontColor($r('app.color.text_color')) - .fontWeight(500) - Text('+' + TimerUtil.timeFormat(item.data - (item.i < this.timeList.length - 1 ? this.timeList[this.timeList.length - 1 - item.i - 1] : 0))) - .width($r('app.float.wh_value_150')) - .textAlign(TextAlign.Center) - .fontSize($r('app.float.font_28')) - .fontColor($r('app.color.text_color')) - .opacity($r('app.float.opacity_6')) - } - .width($r('app.float.wh_value_280')) - .justifyContent(FlexAlign.SpaceBetween) - - Text(TimerUtil.timeFormat(item.data)) - .fontSize($r('app.float.font_28')) - .fontColor($r('app.color.text_color')) - .fontWeight(500) - }.width(ConfigData.WH_100_100) - .height(ConfigData.WH_100_100) - .justifyContent(FlexAlign.SpaceBetween) - .padding({ left: $r('app.float.distance_16'), right: $r('app.float.distance_16') }) - } - .height($r('app.float.wh_value_75')) - .width(ConfigData.WH_100_100) - .clip(true) - }, item => item.i) - } - .backgroundColor($r('app.color.background_color_white')) - .borderRadius($r('app.float.distance_32')) - .padding({ top: $r('app.float.distance_6'), bottom: $r('app.float.distance_6') }) - } - .padding({ - left: $r('app.float.distance_16'), - right: $r('app.float.distance_16'), - top: $r('app.float.distance_8'), - bottom: $r('app.float.distance_8') - }) - .visibility(this.timeList.length > 0 ? Visibility.Visible : Visibility.None) - } -} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/timer/Timer.ets b/product/phone/src/main/ets/pages/timer/Timer.ets deleted file mode 100644 index 1fc4261..0000000 --- a/product/phone/src/main/ets/pages/timer/Timer.ets +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TimerController } from '@ohos/timer'; -import { ConfigData } from '@ohos/common'; -import { TimerClock } from './TimerClock'; -import { CurrentTimeDisplay } from './CurrentTimeDisplay'; -import { TimeOfRecord } from './TimeOfRecord'; -import { TimerControl } from './TimerControl'; - -@Component -export struct Timer { - private timerController: TimerController = new TimerController(); - private timeList: Array = []; - @State lastTimeMs: number = 0; - - aboutToAppear() { - this.timerController.setTimeListUpdateListener((timeList: Array, lastTimeMs: number) => { - this.timeList = timeList; - this.lastTimeMs = lastTimeMs; - }) - setTimeout(() => { - this.timerController.reload(); - }, 100) - } - - build() { - Column() { - Row() { - Text($r('app.string.stopwatch')) - .fontSize($r('app.float.font_48')) - .fontColor($r('app.color.text_color')) - .lineHeight($r('app.float.wh_value_44')) - .fontWeight(500) - .textOverflow({ overflow: TextOverflow.Ellipsis }) - .textAlign(TextAlign.Center) - .margin({ top: $r('app.float.distance_18_5'), bottom: $r('app.float.distance_12') }) - } - .padding({ left: $r('app.float.wh_value_32') }) - .width(ConfigData.WH_100_100) - - List() { - ListItem() { - TimerClock({ timerController: this.timerController }) - }.margin({ bottom: $r('app.float.distance_16'), top: $r('app.float.distance_16') }) - - ListItem() { - CurrentTimeDisplay({ timerController: this.timerController, lastTimeMs: this.lastTimeMs }) - } - .sticky(Sticky.Normal) - .backgroundColor($r('app.color.background_color')) - .shadow({ radius: $r('app.float.wh_value_20'), offsetY: 30, color: $r('app.color.background_color') }) - .margin({ bottom: $r('app.float.distance_8') }) - - ListItem() { - TimeOfRecord({ timeList: this.timeList, lastTimeMs: this.lastTimeMs }) - } - } - .width(ConfigData.WH_100_100) - .height($r('app.float.wh_value_526')) - .scrollBar(BarState.Off) - .edgeEffect(EdgeEffect.None) - - TimerControl({ timerController: this.timerController, timeList: this.timeList }) - } - } -} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/timer/TimerControl.ets b/product/phone/src/main/ets/pages/timer/TimerControl.ets deleted file mode 100644 index 2194ff7..0000000 --- a/product/phone/src/main/ets/pages/timer/TimerControl.ets +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { TimerController, TimerState } from '@ohos/timer'; -import { ImageComponent, ConfigData } from '@ohos/common'; - -@Component -export struct TimerControl { - private timerController: TimerController = new TimerController(); - private timeList: Array = []; - @State state: TimerState = TimerState.IDLE; - - aboutToAppear() { - this.timerController.setStateUpdateListener((state) => { - this.state = state; - }) - } - - build() { - Row() { - Row() { - ImageComponent({ color: $r('app.color.white'), imageSrc: $r('app.media.ic_clock_refresh') }) - } - .opacity(this.state == TimerState.PAUSED ? $r('app.float.opacity_full') : $r('app.float.opacity_4')) - .enabled(this.state == TimerState.PAUSED) - .onClick(() => { - this.timerController.reset(); - }) - - Row() { - ImageComponent({ - color: $r('app.color.selected_text_color'), - imageSrc: this.state == TimerState.RUNNING ? - $r('app.media.ic_clock_suspend') : - $r('app.media.ic_clock_play') - }) - }.onClick(() => { - if (this.state == TimerState.IDLE) { - this.timerController.start(); - } else if (this.state == TimerState.RUNNING) { - this.timerController.pause(); - } else if (this.state == TimerState.PAUSED) { - this.timerController.start(); - } - }) - - Row() { - ImageComponent({ color: $r('app.color.white'), imageSrc: $r('app.media.ic_clock_timer2') }) - } - .opacity(this.state == TimerState.RUNNING ? $r('app.float.opacity_full') : $r('app.float.opacity_4')) - .enabled(this.state == TimerState.RUNNING) - .onClick(() => { - this.timerController.writeTime(); - }) - } - .height($r('app.float.wh_value_64')) - .width(ConfigData.WH_100_100) - .justifyContent(FlexAlign.SpaceBetween) - .margin({ bottom: $r('app.float.distance_16'), top: $r('app.float.distance_6') }) - .padding({ left: $r('app.float.distance_96'), right: $r('app.float.distance_96') }) - .shadow({ radius: $r('app.float.wh_value_20'), offsetY: -40, color: $r('app.color.background_color') }) - } -} \ No newline at end of file diff --git a/product/phone/src/main/ets/subscriber/AlarmInitSubscriber.ets b/product/phone/src/main/ets/subscriber/AlarmInitSubscriber.ets new file mode 100644 index 0000000..dbcb1de --- /dev/null +++ b/product/phone/src/main/ets/subscriber/AlarmInitSubscriber.ets @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// import StaticSubscriberExtensionAbility from '@ohos.application.StaticSubscriberExtensionAbility'; +import wantAgent from '@ohos.app.ability.wantAgent'; +import commonEventManager from '@ohos.commonEventManager'; +import { LogUtil, WantAgentUtil, AGENT_REQUEST_CODE_NORMAL } from '@hmos/common'; + + +const TAG = 'AlarmInitSubscriber'; + +const EVENT_TIME_CHANGED = 'usual.event.TIME_CHANGED'; + +const EVENT_TIMEZONE_CHANGED = 'usual.event.TIMEZONE_CHANGED'; + +const EVENT_BOOT_COMPLETED = 'usual.event.BOOT_COMPLETED'; + +const EVENT_LOCKED_BOOT_COMPLETED = 'usual.event.LOCKED_BOOT_COMPLETED'; + +type CommonEventData = commonEventManager.CommonEventData; + +/** + * Subscriber used to receive common events + * + * @since 2022-11-11 + */ +export default class AlarmInitSubscriber { + async onReceiveEvent(event: CommonEventData): Promise { + LogUtil.info(TAG, `receive event: ${JSON.stringify(event)}`); + if (!event?.event) { + LogUtil.error(TAG, 'onReceiveEvent occurs error'); + return; + } + if (event.event === EVENT_TIME_CHANGED) { + const triggerWantAgent = await WantAgentUtil.getCommonEventWantAgent(); + if (triggerWantAgent) { + wantAgent.trigger(triggerWantAgent, { + code: AGENT_REQUEST_CODE_NORMAL, + }, data => { + LogUtil.info(TAG, `trigger response: ${JSON.stringify(data)}`); + }); + } + } else if (event.event === EVENT_BOOT_COMPLETED || event.event === EVENT_LOCKED_BOOT_COMPLETED || + event.event === EVENT_TIMEZONE_CHANGED) { + const triggerWantAgent = await WantAgentUtil.getCommonEventWantAgent(true); + if (triggerWantAgent) { + wantAgent.trigger(triggerWantAgent, { + code: AGENT_REQUEST_CODE_NORMAL, + }, data => { + LogUtil.info(TAG, `trigger response: ${JSON.stringify(data)}`); + }); + } + } + } +}; \ No newline at end of file diff --git a/product/phone/src/main/ets/widget/pages/WidgetCard.ets b/product/phone/src/main/ets/widget/pages/WidgetCard.ets new file mode 100644 index 0000000..11072ed --- /dev/null +++ b/product/phone/src/main/ets/widget/pages/WidgetCard.ets @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@Entry +@Component +struct WidgetCard { + @LocalStorageProp('nextAlarmTime') nextAlarmTime: ResourceStr = $r('app.string.fa_alarm_alert'); + @LocalStorageProp('remainingTime') remainingTime: string = ''; + @LocalStorageProp('isRinging') isRinging: boolean = false; + @LocalStorageProp('ringingTimestamp') ringingTimestamp: number = 0; + @LocalStorageProp('noAlarms') noAlarms: boolean = true; + @State time: number = -1; + @State six: number = 6; + @State ten: number = 10; + @State XVIII: number = 18; + @State twentyTwo: number = 22; + private timeCache: number = -1; + private pictures: Resource = $r('app.media.Stateless') + private blackWhite: Resource = $r('app.media.icon_clock_black') + /* + * The title. + */ + readonly TITLE: string = 'Hello World'; + /* + * The action type. + */ + readonly ACTION_TYPE: string = 'router'; + /* + * The ability name. + */ + readonly ABILITY_NAME: string = 'com.ohos.clock.phone'; + /* + * The message. + */ + readonly MESSAGE: string = 'add detail'; + /* + * The width percentage setting. + */ + readonly FULL_WIDTH_PERCENT: string = '100%'; + /* + * The height percentage setting. + */ + readonly FULL_HEIGHT_PERCENT: string = '100%'; + + private ShowsSetInterval(): Resource { + this.pictures = $r('app.media.Stateless') + return this.pictures + } + + private blackAndWhite(): Resource { + if (this.noAlarms) { + return $r('app.media.icon_clock_black'); + } + if (this.time >= this.six && this.time < this.XVIII || + this.time == -1 && (this.timeCache >= this.six && this.timeCache < this.XVIII)) { + this.blackWhite = $r('app.media.icon_clock_black') + } else if (this.time >= this.XVIII || this.time < this.six || + (this.time == -1 && (this.timeCache >= this.XVIII || this.timeCache < this.six))) { + this.blackWhite = $r('app.media.icon_clock_white') + } + return this.blackWhite + } + + @Builder + CardRipples(): void { + Image(this.ShowsSetInterval()) + .width(this.FULL_WIDTH_PERCENT) + .height(this.FULL_HEIGHT_PERCENT) + .syncLoad(true) + } + + @Builder + buildClockDetail(): void { + Stack() { + this.CardRipples() + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Start }) { + Text(this.nextAlarmTime) + .fontSize($r('app.float.main_tab_line_height')) + .fontColor($r('app.color.card_text_color_title')) + .lineHeight($r('app.float.slide_to_turn_off_font_size')) + .margin({ left: $r('app.float.divider_margin_offset_left') }) + .fontWeight(FontWeight.Medium) + if (this.remainingTime) { + Text(this.remainingTime) + .fontSize($r('app.float.select_all_text_size')) + .fontColor($r('app.color.card_text_color_Description')) + .lineHeight($r('app.float.ohos_id_card_margin_middle')) + .letterSpacing(0) + .textAlign(TextAlign.Start) + .opacity(0.6) + .margin({ top: $r('app.float.card_title_line_margin'), left: $r('app.float.divider_margin_offset_left') }) + .fontWeight(FontWeight.Regular) + } + } + + Image(this.blackAndWhite()) + .width($r('app.float.appbar_icon_size')) + .height($r('app.float.appbar_icon_size')) + .margin({ right: $r('app.float.divider_margin_offset_left') }) + } + } + } + + build() { + Row() { + this.buildClockDetail(); + } + .width(this.FULL_WIDTH_PERCENT) + .height(this.FULL_HEIGHT_PERCENT) + .onClick(() => { + postCardAction(this, { + 'action': this.ACTION_TYPE, + 'abilityName': this.ABILITY_NAME, + 'params': { + 'tab_active': 'ALARM_CLOCK_TAB' + } + }); + }) + } +} \ No newline at end of file diff --git a/product/pc/src/main/ets/pages/timer/CurrentTimeDisplay.ets b/product/phone/src/main/ets/workers/AudioWorker.ts similarity index 31% rename from product/pc/src/main/ets/pages/timer/CurrentTimeDisplay.ets rename to product/phone/src/main/ets/workers/AudioWorker.ts index a9c99d5..24e0523 100644 --- a/product/pc/src/main/ets/pages/timer/CurrentTimeDisplay.ets +++ b/product/phone/src/main/ets/workers/AudioWorker.ts @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2022 HiHope Open Source Organization. +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,34 +13,42 @@ * limitations under the License. */ -import { TimerController } from '@ohos/timer'; -import { TimerUtil, ConfigData } from '@ohos/common'; +import worker, { ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker'; +import { AudioPlayer } from '../../../../../../feature/alarmclock/src/main/ets/manager/AudioPlayer'; -@Component -export struct CurrentTimeDisplay { - @State currentTime: number = 0; - @State timerController: TimerController = new TimerController(); - @Prop lastTimeMs: number; +const workerPort: ThreadWorkerGlobalScope = worker.workerPort; +const TAG: string = 'AudioWorker'; - aboutToAppear() { - this.timerController.setTimeUpdateListener((currentTimeMs: number) => { - this.currentTime = currentTimeMs; - }) - } - build() { - Column() { - Text(TimerUtil.timeFormat(this.currentTime)) - .lineHeight($r('app.float.wh_value_16')) - .fontColor($r('app.color.text_color')) - .fontWeight(500) - .fontSize($r('app.float.font_20')) - Text(TimerUtil.timeFormat(this.currentTime - this.lastTimeMs)) - .lineHeight($r('app.float.wh_value_12')) - .fontColor($r('app.color.text_color')) - .opacity($r('app.float.opacity_6')) - .fontSize($r('app.float.font_14')) - .visibility(this.lastTimeMs > 0 ? Visibility.Visible : Visibility.Hidden) - }.width(ConfigData.WH_100_100) +/** + * Defines the event handler to be called when the worker thread receives a message sent by the host thread. + * The event handler is executed in the worker thread. + * + * @param e message data + */ +workerPort.onmessage = function (e: MessageEvents): void { + if (e.data === 'stopAudio') { + new AudioPlayer().stopAudio(); + } else { + new AudioPlayer().playAudio(e.data); } -} \ No newline at end of file +}; + +/** + * Defines the event handler to be called when the worker receives a message that cannot be deserialized. + * The event handler is executed in the worker thread. + * + * @param e message data + */ +workerPort.onmessageerror = function (e: MessageEvents): void { + +}; + +/** + * Defines the event handler to be called when an exception occurs during worker execution. + * The event handler is executed in the worker thread. + * + * @param e error message + */ +workerPort.onerror = function (e: ErrorEvent): void { +}; \ No newline at end of file diff --git a/product/phone/src/main/module.json5 b/product/phone/src/main/module.json5 index c928eb4..3cc2625 100644 --- a/product/phone/src/main/module.json5 +++ b/product/phone/src/main/module.json5 @@ -2,26 +2,27 @@ "module": { "name": "phone", "type": "entry", - "srcEntrance": "./ets/Application/MyAbilityStage.ts", + // "srcEntry": "./ets/Application/AbilityStage.ets", "description": "$string:phone_desc", - "mainElement": "MainAbility", + "mainElement": "com.example.ohosclock.phone", "deviceTypes": [ - "default" + "default", + "tablet", + "2in1" ], "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:main_pages", - "uiSyntax": "ets", "abilities": [ { - "name": "MainAbility", - "srcEntrance": "./ets/MainAbility/MainAbility.ts", + "name": "com.example.ohosclock.phone", + "srcEntry": "./ets/MainAbility/MainAbility.ets", "description": "$string:MainAbility_desc", - "icon": "$media:icon", - "label": "$string:MainAbility_label", - "startWindowIcon": "$media:icon", - "startWindowBackground": "$color:white", - "visible": true, + "icon": "$media:logo", + "label": "$string:app_name", + "startWindowIcon": "$media:starting_window", + "startWindowBackground": "$color:start_window_background", + "exported": true, "skills": [ { "entities": [ @@ -32,7 +33,149 @@ ] } ] - } + }, + // { + // "name": "com.huawei.hmos.clock.ForegroundAbility", + // "srcEntry": "./ets/ForegroundAbility/ForegroundAbility.ets", + // "description": "$string:MainAbility_desc", + // "icon": "$media:app_icon", + // "label": "$string:app_name", + // "startWindowIcon": "$media:starting_window", + // "startWindowBackground": "$color:start_window_background", + // "visible": false, + // "removeMissionAfterTerminate": true, + // "excludeFromMissions": true + // }, + // { + // "name": "com.huawei.hmos.clock.FullScreenAbility", + // "srcEntry": "./ets/FullScreenAbility/FullScreenAbility.ets", + // "description": "$string:MainAbility_desc", + // "icon": "$media:app_icon", + // "label": "$string:app_name", + // "startWindowIcon": "$media:starting_window", + // "startWindowBackground": "$color:transparent", + // "removeMissionAfterTerminate": true, + // "excludeFromMissions": true, + // "supportWindowMode": [ + // "fullscreen" + // ] + // }, + // { + // "name": "EntryAbility", + // "srcEntry": "./ets/IntentAbility/EntryAbility.ets", + // "description": "$string:dawn", + // "icon": "$media:icon", + // "label": "$string:dawn", + // "startWindowIcon": "$media:icon", + // "startWindowBackground": "$color:start_window_background", + // "removeMissionAfterTerminate": true, + // "visible": false, + // } + // ], + // "extensionAbilities": [ + // { + // "description": "$string:ServiceExtAbility", + // "icon": "$media:icon", + // "name": "BackupExtensionAbility", + // "srcEntry": "./ets/BackupExtension/BackupExtension.ets", + // "type": "backup", + // "visible": true, + // "metadata": [ + // { + // "name": "ohos.extension.backup", + // "resource": "$profile:backup_config" + // }, + // ] + // }, + // { + // "name": "com.huawei.hmos.clock.AlarmService", + // "visible": false, + // "icon": "$media:app_icon", + // "description": "$string:MainAbility_desc", + // "label": "$string:app_name", + // "srcEntry": "./ets/ServiceExtAbility/AlarmService.ets", + // "type": "service", + // "skills": [ + // { + // "entities": [ + // "entity.system.home" + // ], + // "actions": [ + // "action.system.home" + // ] + // } + // ] + // }, + // { + // "name": "com.huawei.hmos.clock.TimerService", + // "visible": false, + // "icon": "$media:app_icon", + // "description": "$string:MainAbility_desc", + // "label": "$string:app_name", + // "srcEntry": "./ets/ServiceExtAbility/TimerService.ets", + // "type": "service", + // "skills": [ + // { + // "entities": [ + // "entity.system.home" + // ], + // "actions": [ + // "action.system.home" + // ] + // } + // ] + // }, + // { + // "metadata": [ + // { + // "name": "ohos.extension.staticSubscriber", + // "resource": "$profile:static_subscriber_config" + // } + // ], + // "name": "com.huawei.hmos.clock.AlarmInitSubscriber", + // "srcEntry": "./ets/subscriber/AlarmInitSubscriber.ets", + // "type": "staticSubscriber", + // "visible": false + // }, + // { + // "name": "EntryFormAbility", + // "srcEntry": "./ets/entryformability/EntryFormAbility.ets", + // "label": "$string:EntryFormAbility_label", + // "description": "$string:EntryFormAbility_desc", + // "type": "form", + // "metadata": [ + // { + // "name": "ohos.extension.form", + // "resource": "$profile:form_config" + // } + // ] + // } + ], + "requestPermissions": [ + // { + // "name": "ohos.permission.RUNNING_LOCK" + // }, + // { + // "name": "ohos.permission.MANAGE_SECURE_SETTINGS" + // }, + // { + // "name": "ohos.permission.INTERNET", + // }, + // { + // "name": "ohos.permission.GET_NETWORK_INFO", + // }, + // { + // "name": "ohos.permission.VIBRATE" + // }, + // { + // "name": "ohos.permission.SET_UNREMOVABLE_NOTIFICATION" + // }, + // { + // "name": "ohos.permission.GET_WALLPAPER" + // }, + // { + // "name": "ohos.permission.START_ABILITIES_FROM_BACKGROUND" + // } ] } } \ No newline at end of file diff --git a/product/phone/src/main/resources/base/element/color.json b/product/phone/src/main/resources/base/element/color.json new file mode 100644 index 0000000..d527b77 --- /dev/null +++ b/product/phone/src/main/resources/base/element/color.json @@ -0,0 +1,40 @@ +{ + "color": [ + { + "name": "white", + "value": "#FFFFFF" + }, + { + "name": "main_background_color", + "value": "#F1F3F5" + }, + { + "name": "main_tab_selected_text_clock", + "value": "#0A59F7" + }, + { + "name": "main_tab_unselected_text_clock", + "value": "#606162" + }, + { + "name": "fa_background_color", + "value": "#F1F3F5" + }, + { + "name": "fa_clock_digit_color", + "value": "#E5D41919" + }, + { + "name": "fa_clock_digit_color_night", + "value": "#99FFFFFF" + }, + { + "name": "start_window_background", + "value": "#FFFFFF" + }, + { + "name": "item_title_font", + "value": "#000000" + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/base/element/float.json b/product/phone/src/main/resources/base/element/float.json index 4975c10..e164cc4 100644 --- a/product/phone/src/main/resources/base/element/float.json +++ b/product/phone/src/main/resources/base/element/float.json @@ -1,129 +1,504 @@ { "float": [ { - "name": "wh_value_20", - "value": "20vp" + "name": "tool_bar_width", + "value": "96vp" }, { - "name": "wh_value_32", - "value": "32vp" + "name": "tool_bar_width_1", + "value": "144vp" }, { - "name": "wh_value_44", - "value": "44vp" + "name": "title_bar_height", + "value": "56vp" }, { - "name": "wh_value_64", - "value": "64vp" + "name": "butt_ban_distance", + "value": "14vp" }, { - "name": "wh_value_75", - "value": "75vp" + "name": "title_bar_padding", + "value": "24vp" }, { - "name": "wh_value_96", - "value": "96vp" + "name": "content_offset", + "value": "40vp" + }, + { + "name": "clock_shadow_above_space", + "value": "35vp" + }, + { + "name": "main_tab_text_margin", + "value": "4vp" + }, + { + "name": "main_tab_text_size", + "value": "10vp" + }, + { + "name": "dial_top_position", + "value": "0" + }, + { + "name": "dial_center_position", + "value": "0.38" + }, + { + "name": "dial_bottom_position", + "value": "1" + }, + { + "name": "dial_shadow_radius", + "value": "28vp" + }, + { + "name": "dial_shadow_inside_x", + "value": "0vp" + }, + { + "name": "dial_shadow_inside_y", + "value": "-20vp" + }, + { + "name": "dial_shadow_outside_x", + "value": "0vp" + }, + { + "name": "dial_shadow_outside_y", + "value": "23vp" + }, + { + "name": "scale_top_position", + "value": "0" }, { - "name": "wh_value_150", - "value": "150vp" + "name": "scale_center_position", + "value": "0.5" }, { - "name": "wh_value_280", - "value": "280vp" + "name": "scale_bottom_position", + "value": "1" + }, + { + "name": "card_padding_vertical", + "value": "4vp" + }, + { + "name": "card_padding_horizontal", + "value": "4vp" + }, + { + "name": "card_inner_padding_horizontal", + "value": "8vp" }, { - "name": "wh_value_516", - "value": "516vp" + "name": "timer_pick_padding_vertical", + "value": "16vp" + }, + { + "name": "card_margin_start", + "value": "12vp" }, { - "name": "wh_value_526", - "value": "526vp" + "name": "card_margin_end", + "value": "12vp" }, { - "name": "font_24", - "value": "24px" + "name": "default_corner_radius_l", + "value": "16vp" }, { - "name": "font_28", - "value": "28px" + "name": "card_content_height", + "value": "64vp" }, { - "name": "font_40", - "value": "40px" + "name": "card_content_padding_height", + "value": "10vp" }, { - "name": "font_48", - "value": "48px" + "name": "card_tag_margin_end", + "value": "4vp" }, { - "name": "distance_3", + "name": "card_title_line_margin", + "value": "2vp" + }, + { + "name": "hour_shadow_offset_x", + "value": "5vp" + }, + { + "name": "hour_shadow_offset_y", + "value": "1vp" + }, + { + "name": "minute_shadow_offset_x", + "value": "0vp" + }, + { + "name": "minute_shadow_offset_y", "value": "3vp" }, { - "name": "distance_6", + "name": "second_shadow_offset_x", + "value": "-4vp" + }, + { + "name": "second_shadow_offset_y", + "value": "-3vp" + }, + { + "name": "text_size_headline3", + "value": "60fp" + }, + { + "name": "right_icon_height", + "value": "24vp" + }, + { + "name": "right_icon_width", + "value": "12vp" + }, + { + "name": "header_footer_height", + "value": "56vp" + }, + { + "name": "dialog_padding_horizontal", + "value": "24vp" + }, + { + "name": "dialog_button_height", + "value": "40vp" + }, + { + "name": "dialog_content_padding_vertical", + "value": "8vp" + }, + { + "name": "dialog_button_divider_margin", + "value": "4vp" + }, + { + "name": "dialog_button_divider_height", + "value": "24vp" + }, + { + "name": "dialog_button_divider_width", + "value": "2vp" + }, + { + "name": "dialog_margin_bottom", + "value": "-16vp" + }, + { + "name": "input_height", + "value": "48vp" + }, + { + "name": "button_size", + "value": "48vp" + }, + { + "name": "button_shadow_radius", + "value": "8vp" + }, + { + "name": "button_shadow_x", + "value": "0vp" + }, + { + "name": "button_shadow_y", "value": "6vp" }, { - "name": "distance_8", + "name": "button_icon_size", + "value": "24vp" + }, + { + "name": "digital_clock_text_size_normal", + "value": "18vp" + }, + { + "name": "ohos_id_text_size_headline3", + "value": "60vp" + }, + { + "name": "digital_clock_text_margin", + "value": "4vp" + }, + { + "name": "confirm_dialog_padding_horizontal", + "value": "16vp" + }, + { + "name": "confirm_dialog_content_padding_top", + "value": "24vp" + }, + { + "name": "confirm_dialog_content_padding_bottom", + "value": "8vp" + }, + { + "name": "dialog_button_divider_margin_vertical", "value": "8vp" }, { - "name": "distance_12", + "name": "confirm_dialog_border", + "value": "20vp" + }, + { + "name": "confirm_dialog_divider_height", + "value": "24vp" + }, + { + "name": "confirm_dialog_divider_width", + "value": "1vp" + }, + { + "name": "set_alarm_card_height", + "value": "48vp" + }, + { + "name": "title_font_size", + "value": "20vp" + }, + { + "name": "appbar_icon_margin", + "value": "16vp" + }, + { + "name": "appbar_icon_size", + "value": "24vp" + }, + { + "name": "response_region_size", + "value": "48vp" + }, + { + "name": "snooze_setting_margin_vertical_max", + "value": "16vp" + }, + { + "name": "snooze_setting_margin_vertical_min", + "value": "4vp" + }, + { + "name": "slider_margin_horizontal", + "value": "-16.5vp" + }, + { + "name": "slider_margin_offset_left", + "value": "6.75vp" + }, + { + "name": "divider_margin_horizontal", + "value": "-24vp" + }, + { + "name": "divider_margin_offset_left", "value": "12vp" }, { - "name": "distance_16", + "name": "slider_label_opacity", + "value": "0.6" + }, + { + "name": "slider_label_size", + "value": "14fp" + }, + { + "name": "slider_step_label_size", + "value": "10fp" + }, + { + "name": "clock_margin_vertical", "value": "16vp" }, { - "name": "distance_18_5", - "value": "18.5vp" + "name": "left_time_tips_margin_bottom", + "value": "32vp" + }, + { + "name": "current_time_margin_top", + "value": "68vp" + }, + { + "name": "current_date_margin_top", + "value": "5vp" + }, + { + "name": "snooze_btn_margin_top", + "value": "16vp" + }, + { + "name": "snooze_btn_opacity", + "value": "0.7" + }, + { + "name": "slide_to_turn_off_font_size", + "value": "16fp" + }, + { + "name": "slide_to_turn_off_margin_bottom", + "value": "38.5vp" + }, + { + "name": "slide_to_turn_off_button_size", + "value": "50vp" }, - { - "name": "distance_24", + "name": "slide_to_turn_off_button_border_width", + "value": "2vp" + }, + { + "name": "check_box_width", "value": "24vp" }, { - "name": "distance_26_5", - "value": "26.5vp" + "name": "switch_width", + "value": "60vp" }, { - "name": "distance_32", - "value": "32vp" + "name": "switch_height", + "value": "20vp" + }, + { + "name": "slide_to_turn_off_margin_top", + "value": "14vp" + }, + { + "name": "snooze_button_padding_horizontal", + "value": "45vp" }, { - "name": "distance_37_5", - "value": "37.5vp" + "name": "snooze_button_padding_vertical", + "value": "10vp" }, { - "name": "distance_42", - "value": "42vp" + "name": "snooze_button_border_width", + "value": "1vp" + }, + { + "name": "snooze_button_border_radius", + "value": "46vp" + }, + { + "name": "select_all_text_size", + "value": "10vp" + }, + { + "name": "select_all_text_margin", + "value": "3vp" }, { - "name": "distance_48", + "name": "ohos_id_card_margin_middle", + "value": "12vp" + }, + { + "name": "ohos_id_text_size_sub_title1", + "value": "18fp" + }, + { + "name": "ohos_id_elements_margin_horizontal_l", + "value": "14vp" + }, + { + "name": "ohos_id_corner_radius_dialog", + "value": "24vp" + }, + { + "name": "ohos_id_dialog_margin_start", + "value": "12vp" + }, + { + "name": "ohos_id_dialog_margin_end", + "value": "12vp" + }, + { + "name": "ohos_id_dialog_margin_bottom", + "value": "16vp" + }, + { + "name": "ohos_id_text_size_headline2", + "value": "72vp" + }, + { + "name": "ohos_id_max_padding_start", + "value": "24vp" + }, + { + "name": "ohos_id_max_padding_end", + "value": "24vp" + }, + { + "name": "ohos_id_text_size_headline6", + "value": "30vp" + }, + { + "name": "ohos_id_default_padding_end", + "value": "12vp" + }, + { + "name": "ohos_id_elements_margin_vertical_l", + "value": "16vp" + }, + { + "name": "list_item_height", "value": "48vp" }, { - "name": "distance_64", - "value": "64vp" + "name": "main_tab_line_height", + "value": "14vp" }, { - "name": "distance_96", - "value": "96vp" + "name": "left_time_line_height", + "value": "28vp" }, { - "name": "opacity_4", - "value": "0.4" + "name": "timer_picker_height", + "value": "200vp" }, { - "name": "opacity_6", - "value": "0.6" + "name": "timer_picker_width", + "value": "312vp" }, { - "name": "opacity_full", - "value": "1" + "name": "selected_all_padding", + "value": "24vp" + }, + { + "name": "selected_all_line_height", + "value": "13vp" + }, + { + "name": "radio_size", + "value": "20vp" + }, + { + "name": "title_bar_shadow_x", + "value": "0vp" + }, + { + "name": "title_bar_shadow_y", + "value": "6vp" + }, + { + "name": "title_bar_shadow_radius", + "value": "6vp" + }, + { + "name": "title_width", + "value": "300vp" + }, + { + "name": "change_cross", + "value": "16vp" + }, + { + "name": "change_vertical", + "value": "24vp" } ] } \ No newline at end of file diff --git a/product/phone/src/main/resources/base/element/string.json b/product/phone/src/main/resources/base/element/string.json index b816130..1f73fef 100644 --- a/product/phone/src/main/resources/base/element/string.json +++ b/product/phone/src/main/resources/base/element/string.json @@ -2,15 +2,43 @@ "string": [ { "name": "phone_desc", - "value": "description" + "value": "phone product" }, { "name": "MainAbility_desc", - "value": "description" + "value": "phone product ability" }, { "name": "MainAbility_label", - "value": "Clock" + "value": "clock" + }, + { + "name": "EntryFormAbility_desc", + "value": "form_description" + }, + { + "name": "EntryFormAbility_label", + "value": "form_label" + }, + { + "name": "FaCityManagerAbility_desc", + "value": "description" + }, + { + "name": "FaCityManagerAbility_label", + "value": "label" + }, + { + "name": "timer", + "value": "Timer" + }, + { + "name": "ServiceExtAbility", + "value": "back up exntension" + }, + { + "name": "fa_alarm_alert", + "value": "Alarm clock" } ] } \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/Stateless.svg b/product/phone/src/main/resources/base/media/Stateless.svg new file mode 100644 index 0000000..f125f44 --- /dev/null +++ b/product/phone/src/main/resources/base/media/Stateless.svg @@ -0,0 +1,21 @@ + + + 画板备份 19 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/product/pc/src/main/resources/base/media/icon.png b/product/phone/src/main/resources/base/media/app_icon.png similarity index 100% rename from product/pc/src/main/resources/base/media/icon.png rename to product/phone/src/main/resources/base/media/app_icon.png diff --git a/product/phone/src/main/resources/base/media/background.png b/product/phone/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..0ae8816b4cb7a1fc6528e536a93b2c35220d4927 GIT binary patch literal 19351 zcmYIwWmp?s7cCTbN{U;dc+nOs7Nit+cQ5X)p*U@!#iaz7;_ePc*|?gR+Q&HLTw zKKI9DCS#d1Gv}Z)=CxKy|(C@2I9@*g!(P*7k0H(|d(K|%So`C%S8L-o{@ zlSZi;qd7!=`DmlB@L5F#g#)>aje;6&kAnW+F35=rIiaAS<)WgXA?K+7t;>D!|F)vO z&PDtG%m3{d6NA-^f+C5c@KH+J5A`(oFqP?pD>z`R1M%x2n#*8q$FpNFAnwuxh4{Pt zXIt<2smxD7*L;#BC@<5A1wnBH>&mWO@jV2%D2RIh9M^p*;&sPZCn6qnH7>{_twI(f*o67K=)p?8wCsvmG4>! zdd)giR+*Qy_FntC(7?)FG1S@itux=6iQTb8dkB5=`RDayJI;*33a>VOiHAe0MmH9C zdL+Ol9Aq26o3k^XlP>|r_DXdy82BEd4X6x1(EhfHHT{XG6_mS0dm(=Sr%=}w_&dr$ zTl{othih|P@acO>?D9Rtbp{hOwXe591Q_{p!}Q@)y9j@?1-Dq{!Ddf<-FXC~IBg5t z;kV(R?4Pyx<$4)huUSJ1X3TMRun;^uC|4W9S1S9!?Cg+hG~X4 zn_85ia-9;DZJSoUo|kC0F%t-P;-?A*#hBV1dQCOLZy5%-%d#b`u-@*2+L>^&tl#Tj2-rIug~ zQJoN!!wAIrqC@9^iO5%EuMqU7gd->W&5mq(n9di=oRs*+|)LjJ}wSEMR=6 zMt!TJbDC9bg_BhF$jvP9w@k4c<*eYy8`mEQ`dU|GhSS?m!e0%W%0|EHa52z0DVq%$ zI53Od)>kH1h<#=F)UodH-J?HxW48`no99$iiv_jRgsB5PVYNiT=#r}cS;zG%R`|CBibwf^7JAi9Qg8e!rBjnBCm}g$ed`r zP2T?bb+^idjy28j7m^B%S(3gh{{F__b~i$QYRC$kmv#M^m0_;CJsU(qhXwIYUYfe+fhi4QPl(VeB&O-Zu#igYCohsTnVyCIyR0kf0C-?i@ki@eX$3BSb zD=ZLTk9>PP=L1jN7%}zXF)5coK%W=hfhg;}wPc-+bX4DT;i-r?Lv!J73*Mht-~|N? zHA0GbAmk++N^krwpNmr1-u-rUyHI9!2bTKN$&|LpXSS5HwrbdU1x9D?2j$NAO+LR@ z!WzT@364}K?c-+1mT>@dPx@gj>v;)dfV5!2A9wn}p$F{)3i~r_ddVR(Ou7EIh2oF) z-|sWj=}|h_Acr^BqL-dzEiKgrA(rD$8?I47#GtvCm-3LNK=px=kE7}3*@X^3%rXHE zL3CHCOux+iEALgxy5}7&nSu?0OX@EGUFxQA5MAaKTm#liNX9>a?w9c1tn(zc)xW`8 z&Sf`Pwqq&k+o?|`bWJrp)thvj-Tx6~o??n9X(p)d4gy+h#02rk&_@3^qTQrty&aO) zUj7!xReCr6e(cK_l(eWtHS+v4WVM5p&dDUQk8C+x3;@D%yAX07%&6Y}5#s#oT?L?R ze44$bYTdhyi+2BP@^*gZt%XC$k;ovK?)xXDg~X22w$-MA39EWQno0Mu*K%yPIgiB! z2mpc^70Gk_!ibNCNV+nk$s34HB~&=yZ&Ua?wG(Gnoy{{~whdW13uQtH{zaW`@O<_T z33&&)NDQaO_;d=GMiXh2YqB0&P_PxtDeP=L8d5+k<#Io|c$Im9*Fu(b{u{bj;ZMol zSy-U$>m-2#XLm&{^&%MDV$|AKi^KKA^okSbm7^chuXw zw|DN<5?Zd`O3qWcqRwomT}1#O#!}oaK>LYX=a|Ie)9}N|rbA`er-toCg&;`H==_EnSwc_-p`pc7)Jxn6|2Tv*mg7LH z%L=QL=#{s}+cLJLRzwkwBT>J*$tr#55N zD|%F%OFHF6%@#}pA0&^SvnG{A_Uf*VC!nt$deFOyhDidtQE6e`jA(D0z$Nkzt*}ic zLZVe))=>@Xp;Qzs5_&+vDN|tlEAp|oj!J!JAOnu!rAw9&)gsCF#^??gthm$zGe0pd zF5yzYmvH2D-aa!Sx}T>J(0Ll?Rket&O5A@t?V9)gSN+>r*?=`}BA5xnB=5)Uvf8!} zIB6v~8-{FnwbksU*yfzKst0n%YrL)TH;jGKi#Tyu=HG_lJtIC{JsY!$ATtKTfua4E zoII>l8#nj8cYbuNJCO0i595a$u4G`6EAa;{(S?^cV+TURTxLxXK?T3KzGQ4Z)VH76 zZk^ld)4|Z9@1)O?9Ikx0+dZ;u)ql62_>lX1CZVdpYCoFx9L*B6^qM961K}0uZnPKUwktThsYObXhif*ntN(Z{Wt$o~^16~{_ zBxfN3N|AbDf#rtM&29gXhXISVEYjTWr;Ru|NLGwyN!n&j;K$Ydgg&wTItw{V?T+6G z1mr^Nnab_Wr00FDyi+E;*En*v#2S4O0Fd1Fzz$%n4D9>^y@5KfZO4!)h7F%A< zPFa=|3k;Lwn|L4B>*Zkjg0GL5$r}_m1<9N#@p#Zr-ZAR=^oAiR+85Zy`T^5k5GiQR zmbZqChS^tEOHbwYjzN)N=umM?U7tQj5v^-#ptoUvh&?Kx)6s0K=;Bv%64%pPUaTYr z9Vb9{p0I^56zkvG7=5SgPTjzXhk=olX7j3JQfme7O2Z2Ii!v8?1@g}yhDml>W?l>T zYiuRT7E4p7ed2p1r{ldv50lCzQP@E$5i zJk&b23-?$a(3(`|hc`)oM8zLL5Mrg6h!r%6Bp?hwDp@kN&?Dc|uOyx=P%-_lt%3E; zypPXOUD$BB>SCMTuAs}_d)hp5RNzDg6yry)u0n$*uO&AYLT!6ogz+rG)cUl2pm;j| zTZxaer>k%i?K6LVmRHkVlfE*nn*Ns%{xa=@wg6GpP1?^Ed2|65R~=i?6i0cU=3{;m+$XyxL`vzL-rnzCcHz zPfrjv;w8pefX9II<_9Rbv!Y)D0*3!N|69;U3cYoOCLA~1(vu{nB>9f?)QK4`z4)bF zX*bbNT4Ru&nx7>SXM(AuJARJiHLM{o*n%t@sbI@tQx6X~ut}m!0m|ia~{X($XS%Guqq|Iz=4S%4YL*_*Atbm-Aa9&~PGS>lU{G9cjS>_$5pu z%-;?FZ>}(JaoHLh$1p4^`mr(|(hsP)NwQ1GJ#8|qn18(awcn;9$8yqX%yk{`P6lsw z!n=piZfhJo?DmpTO}#gzZv$Zud&uY}VoBZ4;+m2>$y`sVA1HJ_S}v1GwdOW(qXYZ= zQKfo6!T?@+gJkG}sI>dN2ovbFEiLQG7&9+V-sU)Zk2k9b*v(@BPsSzGcgs-fuf#}8 zVaQpCxPLmz8bp}MS1FaIN9St?KifIgWpB%r2g6e=JvMP{Z%zf&9Ny49xzc6oWqeCH z@m2m(Cq=+4phW&MBWGe)`~84+;|7!e=U-M~jK^%Em#l^12;Y|bSeQSIa^w|J{yq9e z9r=g#A==6e=EiF$oC0Sq-O*J1r?VL!F54^SoFo}{Uw*zbT$inT5n-s=eYn~_5#Yq1 z5lqta&zdvUl*Gg0cS%4U-YiEuvrM3rw8zXOU;Sg)_~$@5_jxeZp~r|tQj ztvBytHM{ht&WPrkw-=f{dRhY0V?h@}Tp%gMO(o*KHA15KM~=Q z0x9D+rCV6B%i?^)9T87x+6&BLQx?=&#xP-&WXg6b7Wl`S4GxCDxlSa*ye-n+V{X|O z>Rp$EYz1KB5T3h~#gnwDvht8#bbdSGW}vR}-5{hbaP4L3jEE~I-_B%eZV#BYkjY4^ zDQz4o>4W6NkQy_4tzaS~w?}e-WVeDl5v=6*uL927hi_ZU(c7!jm0~Q!XOT=};6I3( z_b*AsZATZas!@tTeTzi01myLtc(0Wf^>O#cBUf1V;I>BuG9fX|7wqeRMBJKf2zcKapo19Savz;cZT;x6nKSwc%;Cmh9=b1?7F0XE!x-=CsZ{ysw{zIc5gL&u()XWR zjZ-%6u5|iVoBC4SmU^wRc-jk)g0>wYcE$j^2AoJiByh|GjB)N!%gQ6ty#GVvVy=@| z#7i-xdiSk7;*b*gZ(+Kd5)S%AW=wY@&80_;sC2pEE}m~tTvj=KQByu4{QVz(MEvwr z-yiGikehln-@@xZHxx4S*9UK-f(B#P*=XIM1ea#ri)n-Dy&VADWM+zHb;)W0Ent9a z^E;l+Vh_6Va^6`L(`XCE=VDv}T**s@;HPc41F zjzRqk5wHE3YclgVe%`H3jrg^r`lnhM+j;w&*!4l=^8;O997Ct!66w=8HaG$n#Kdf= z1)Mx@wRG5_N>P4Ha4c8u-GAAQLc`26R^>P938_@fbu%Fo1X6k#$;3^_$7SCW`01>q zWkENk4+4aS^A639DNLau=9yLfMJ<1ozw&{@7GV`U8;q+8j35 zzWNJM^TLiC21JE-FVtDwGJ=xLmzW2oo!{Lx%2RfEJC?8dJlf&3s%S2Y?*LoJk~pFY z#+~mSLnbstmg$OxRbsmSDP;UA+~(??Wn&H5p01&V4KY^t(f1&32k1o%0_3?7+;8~sr%BNV~<{V%Zmo{T?L9oLKfNR%L} zmP_I-hb*J5aMdR{y?+UwP64KY=5;DCf*?yIlNusifl|OL-fX4HJJ(3?{Zd^Azrzws z(B0l{fGm3|q8)r`J^?;xw9ov&>HZ^lIUrq3RAhLLiQw5B$3g`2(#*BIbT`}leqAp> zU(Xs&zg%=rpQc^R5g1PoITV~x!}}uM9x>LxQ&Kq}CN9W=K)zh8(IKJ0YFIFDqL}_l zL>J>@a_l=scGS=ID@kR5dHhfQwWRxR7@ll74&TdOmu{~`X)19TFC9zKq;)F8@W%U` z!G%rZ4dLB5_lzKD=9(iA<5%$p2kPj!{f2;!_8rrgBG17cJ22~vPfkPX4-%FTb8LQNLU^yIc_Qp2{S}g^ki#o-!f(GrD7;O=e&2DS-Y^sH zrSt4FQ2uBVX4Euetgk)HpsNsQW`wZk{&qx z7glOLOy;h{J9@VJeu|Y{p}ye+4ra(_uzlU~yMb?x_b9jDr%??S=5>J}fp46LV-$(z zDcm@nM-YB4F9)p^|j>?vcV^Y;_tlkFY;8=I&wm>MiLqxpYs*fk0=x8vr4a!RiwZV!VVB z!VmN?LnXJ=lUGbyD)Tru&wA`*H4&4hrVIGJkB#LsbsPddQJCliadqg+9zmvTN`u*L zdJD|I%lo+VI2GMc;s?fomuNKO(z%1n8${S6l6i@LzWt~1OQguBaLy%HzBQ8?7mbWj z`IqZRV!xmz{1bMYY4QDxLejm6k!vGjmL?DXgM(?4M{E;GUCuoc-dTl2ST*73aM+}7 z1&vR)9x-t`FNe4-cb*F?n^FxCkaU>DXT2ijt%$8=t(U2ek2md)yu5{zm*+0!Hb~Xb z{S$cni{_7fUDGp6agkxkS7Aw&I!4w~)P2??#b;A4(!wuUEkM4Lr0$Ea(5JL@hm&)` zws$+Rh!-)u4Bx|ndalMfe{yc;bsm#vF#AR$NK=f=O~i`{lJ*}wTkRW|F8SL#4&LyY zN7hArOCm1^AZXb}={%h-y2Cx~{pH{XSfd_94%7iN4ulJyN-s)XF>c&jPr@?kU$H(1 z@GiIDK2R842hU_csTFZJ&+i|oU@Byr!mD`vQLK=?QkVL%9oW#O6}^Y%ptIzda+ zcFWK?X>E#O21g6ew>c2Nv4e!}_fIYWVgpowY2W)PnRz7{7Ih-?P%wYXrviw#AfVlq|vVy>Esy=|lT@IM(@x2fYb3V2nkO8l5 zJvB{f(q03kSTqNJ{YCePW%_PuBLmu>Jwx&S9xW*%-J1^U!Z4okgPN?Kh#OR6lqqU3e`;Wq^sPMW~=`{{MFZxTm z@vAq>O1D+(oherkdSyx&TFh-`c7%J^)z~SD4USZaq8;{g_h;(NwiJJy&Up`Sjg5Gy z`MgNi>6drha+Xr6LOBn@c?OS7ZHvQFujGI#_R~m?JrXr^9*5X8A-Jh7pwi{kvuR#>`%+py2N{wNRBO`02Xx=ZN$`XN(+rJy+@Rsm z`VjBCPcAkZPQs>`uMWJMhaxVmApN{T{S810`kwK_6agi%5Qk`sjI;@QxW3p+@V)$GO-8FvKrnNu2ZJtO3WfJ}`Uj08pi!ZMgs|dDl5SygTHx1;Pcr20}r^ zpc@@_RwB=A#M-1D4l79s<2m#T%^Jt!db(Sqr0lA(n!b&*(pYMQ_C&EQEs(X+Y+_L| z1KPNSbJ2^?+f)l2n#8G8*7EszTBjOYpe?5G(`wz>@a~(Z% zJ(N1kL6|0!)zv&2vu3(`_em18!`H1Dh%iV?355s(j&><;$ut0ToQZf&sRZ z4gpobXDr0uPV$FqR{x5|9P7%}Qna#Z z2fWtByaS=Hw}+Da>opq@az+`(x|9RaE9<|HyrS@-`Od?6WfO(2{c15(_;7Pd-<@+=U-b0PCrCC|F0pX|l~ipE(?DODQ;7FkRp$G%3ul$%?! zomB1ME$|mr?eye{<_&npQGWN|B`Y={01>P~4f{q%KT02hiMGu6Rgh`5VZ<&fIp}d5bda(O}_v6m+X(0}{v?rv5@` zr2@+`=FNd_f)+%@~^x0AJpAB?r1K{uNDLl{NUWGo==D zl|TLL)Kpsfv`w+%&BF?pG`~>up!iu4ybq6uS%l3lFyCC7DTlgfhNw2(OF3VC?0(`| z)J2N1B9zUYYs+Po;qf7|;iBnhvB`6|(pl50ExV-fL&t(o($`b#<$;qo}v+wd* z{j1kmL8eaaL+k8t;00{F`bB<3dlxvDvsc7M6Dj?gXX26J)qj=IzezgJj#Rll2u%89 z^RgD4br_NraD127G%eCLOtB0s(n3vry;;dyqqsNo)m2ZL%7}*xBifx^k;2ujI=rGr zNwT`VE$N%FOq!bp8>^3z4)*^&KN4PA=B0EOm~-b%R)=J(@|0TGk^$|N9O&ste!q%e zcF_Lg^wW=-+f|ZNKjCiq9rfclOJtHjCfI1Py_~>K=>CchpH>+L@8E-edrJ)z4NNo) zTn@b2MA8k0%T z2P!WXPfGO55|ifu-Nn^=?iU~;I8e1F4wS;m?6g|pB|ZO(0Q<;u-$l!W`8Vh~$z@Ut z5mO(rx;kx$l;bMF)I#KmvE84m%y~93H(T?Nv*IVAzlcj7x>e%eG;C6<%Vq0&?R~Wo z?Qn}U(YSv&#OIDI()H3D7M^*E-YL0zu{mqR;B;JwMH636cD1zf|K47WoK5O@ODmUZU6h&2i;Wfr^)3QZ&1Go3ZcsdjHW=E-NoE&SXU-#aioBHnwhBPWxiPG8&W`jg=;+SP zcXt_k(ilDkT67+pXS;b^I~iO^xy%RT-Sz47_$}&~UVOO3vgR>w41bb7#mE7@hatWb zpl;kmpX=X#EIa8q5CZ?RmC!q^ei--^?&B(CL?Q@wd=YrCI$k{O{K3?eOYmshWAXOa z7-T%-onll>3OYd_YICItnFhZTNCvj-`%?5lM3Pmq9j<^CQZYwc$F*Mi`?=j{rK!yQ z>-Bw*SF?EbTFBSR;~cLI+by9DZw^YBs3&I*GEyZZo?+dImm3Y@*y0*I?!0@GMVB@fx1kx-fl zS1}h_sv{BxY1yo=$oa%$DPg21|sS<%PKxD6eFZX&+ z*6ytIEn5CE?o#qA5?q6+R0(Uwdbd)uhPXS?R)qj+DKpV@<6BxH?zH$9_4C<|ZdooY z@ZOvfUJr__*Fqp1_y%({VRYJ#h9aBeyA-c(F-a?-obNi!4k;l^^7yM+P8R8soWNlI z`!%lsJMnA@-H$G_d-ca!SYwq1@v+@fk+H8-un?HTKQ8ux@H7M|-AEI4?r679$x@Rc zmT$K*7v9=ZfTYoj4$I}BPb^|_r=(yyBRr}0bx2O=z=}zzr?15}D^iBtn zOd+1Y9ookv=C#*!`WKh;!;Zzhdb@9{^Sd^B!vWj+0##9t8%Q>in|W*i9+$nS1%~NzV92A^@N{1uUHS z^CzpZwCiY|4_ikT>(I2(xLMHO&hDl*Y*cfZj2lBmE3< zR8pWs;14qo^aTH9$#W*M?dy#_8jlRIu7!7>eTM7-0f{%!UrdjRwQpYq{aB)tiJN&= zgM|YhkOBQkLQKN1m%(}#NmYQ@6fUB*J#Xp$x!E&ZkM+@uhLy~#j7kOC#yrUFS9+Ej z!q7VXKM=EtR9@&WdfI5BtslsMeDs^V#v#eG2#J@$Z>p4OZcHWacD!epZyoo3`CWDY znfCb;3zA!F>;7@U#Y)Z+vICk7AYIngvEqw#hzWZqEv6%%yIub6stGG0miYJJiE`FQ((m?h}&`{T=7MI9g&ZEv? zQKmIN!?$+{Pbn7+mdfWvDjBmediZBE^gQ7sVcVv5CY>RNXIm)(qc5xI-p}!U#4DYP zdr?W{m4CnIy}>*vxb4+9W}hf|$!UkuF3E0zv_?e?_%*O^f5eBI_2%5&_nI=gtAe2> zk@wum%q@;eC>>ie$KfaJLK~sbFGjeNdhMy*Vfl%L3WOS&P%K2@Xmx)MFbgfejN%{7 zOE7(6dVs%xw49+I>rk}O_cYmEQ=HA;4-7KuI`}6#uk%ucu)fzt9Msl1Jum@4sBc4& z#;zC5n(wc|5$#vq&s&o{zl6YiZd)YL?r0V&wiELJlp@9k0+Y_Bw?@`MFyk4R8x1fU zv% zJrqjNmIrh3LAM|Q&DLZ3erA_Wm1S&nq?eMv;A9^^^Pe#?E55aLD$TtcMKZ+&qe)3u zOG&Cs>KL5!bpk}r!;|?(!u`18g#Pcg^|{{cb{L#gt2Wd1gzVgBnaA8#E?2rH|C!_T z^l*;KYm~5)klBJ0?Dkk7ipD!h%o@?AS+%@2MRi|f{9m8+gxOkd;kmu9oA%`;%#W$S zJP(++Tik!XwzaL>)UJpbpVz6_vCeJ=2;%{v_wMYU1)Z4I!khY){3qtQDb|P2TSrcY{Q^9? z@`0CTHMZUtdbr3Abn{J)M_~jUZlHaB%@o>@_Q3&BXtu=WcDnPo!V;8UnONup#B*xC zrg3!soYu>+n6;j;%UjO$ok45{!b20Yw83-6tR|5Gd<4^EeG?B{*{VBb8iNeVg}|p+ z&>D-URX((OcijLy{;o0|S}u86e-%>(vB&VKE}jK%RsVVWay$4!jlU}fxYSD%{w7A4 zPE9$d+F8yacC+Fit%3r@VW>sANsAAytL53@?$^z>$|ob>RGi;o!+hFl7oaK#`MSho zBK~G=Ot)Vmhf_(jtLqEuu=vwwDC*O~&sUCQT<3xa@Xc~Ogf^kt)^AdH#T4N2VEaN1 zhN{=6dEdL<0F#C{tPgEEOZiJj0hgGSpXE^szmEq9=0>i`g7%k)aU02 zxKfwQ~sZwt}aHHBb0|Qq2#)^Pw$pvZ6LPb7>n zvFmbFmhYh7-gUnK!3OBFUuFjTe*i!b<{zeCq|wV}M0!0LqX6d^S3-3(bObLKQh~+i5?~HwN`&k334L?i<e ztJqVT{cZcMIqRXtr55pRJi5s)3;Jafqa091#1~X$%);yy%nuKQbO)2y0#%Zp!XJiA zeBk#cF*r%CA56RP$3?CRKxOP-dtiW4`Xlk%fa^!uZdS(KpoBweQ z>_`URuriva3XH^6yDX#0x|VsRIaTZ&ifo?w)F*j(AKS|O$kPj8RDnt2%x~~eG;Ogy z)ce%nwFnG;+S1+ZSzp%SmT)E*7eAQyy66fC#+@GE)7*;9K{^pSqMUo|AcUU$P&~SV zf-710wVtgEJ%ZGLA-A_EKtJV)#4e8|^~^KF!Jns+WJZgHB>#NNetvTN7iGcP=dy~> zyjVop(V&TGP}33aZ_MG`<<17hbZ9J!Z5|3e>`RORlTX7|b@!Z7JlIy&ZrjeV9}nZ) zh)jJeta3AT#IWOfo1D*y38VWYO@VZ^gzB{Jk$vGInA2-ETm=@}v(cTf@A%N)P2@u$ z7vZD-$B9j29M~DAfP3#_IL)g3qn+UN@MrviPEnt^j?5o2MV_+uWI<;e4x3r)MqIY- z02iYwMfn^6@HHiDJsJq=iHpS!NJ!fW;!WrMR?7XyK!ZHC-eX|YX#l>%HOy={+=1?J zok$;-MYsAYB=0pKRd3mbBPrHw5TIp37ULd%_H}cEh%EL4_fYANO<;((Ph$GOhcOa_ z%-7Zt4N{^4;?C-Z>g43mKHR4U4rg$W+eaxqXG`LZl_EI6gz)n*dSot7yfTurzKWkr$<0y{=6K{|FR_Iei(dHT2x3_8+F`mn6UDatX zt9#hIkf3x_*R5=lmRWV?;2XYbP5!S4p%+p9Fb#*wjHiEkR7A&V)kxn@K?NU_D4d?I z+SiNIYrOiI)U+&Z8pn0Oa&`phPAiET-k>+@KfTzNb zR}^ff-6r|?@_9*B)Kr&EK&Y+aZ&iIA zd%qKI^@Cy|cICGTec=henTOGsdYCx9w^~ z7DRyVmlUvXg3Lj*pvQGAqw+36>XO0KH*c|C-_qi^9vbLqYLueuZi$HPq&_#?{8(zR z4!~ZZW>1-tgL`1}Co5L}cHdIl2}WkA!hnzDqoxmE2VPf6$L=*TWbhn#$~a4zDDmTq&oM?{!1E8o$vpjLoRY>i1k{oJ1IPQ zv~z>QC3c51#@G&W+Ky$i$Z&Sr8=%Q2+kqvu@t2%uzfn~7#}&8l`w!ky%QTWTc>x3g z)Ao41ka2?xZ6Y`m$tF}CZin^83e86=%@O2`zh>HjtH|?J=IY`)-SY?cZEuB>)`Hj8 zH_9x(KM4x6J|&)8?lm=dQXxpM?&bzP6I>b&gREmslTZu*N11KCx<`UF=@fEz`>w|# z!$0)U9e&(vRhfw~J)Ri$&(6-$rgn2K2`CLlwxSU=276iIl=dM!C;U&mR7oM9f-6Hz zHg&{ux!1=LYEwl(>P@b3{z(}|Ac3tQt`^sORlbG=!1G7dz7F~~TH)|r^DWj9ob!#G z2B-iuyg4S}O%Mhf<9m9-f%iTi0J-_bH-s$H2j=lTtU8CRBp7wNc)xf=+>{%15?dU! z;(Pjc(3WY+TXUdaII4QrcZegqvw@E+(NlTd&z@8*Cpfv3BYlf(aG8&pzgGYX83oE- zRqmc|a0rJ+yu4jxh78C{Ar6Eq=QMhSeQ~I{*3Bt8nr{FtMvT_cU;f7CO!xaEM<^sA z+~*Kt%&e!v{ax+cXY>{DS2^CJTKgTpR#Pj7bU8=Yujji(J+L#UeVaaeN(P|GAaV&p zuU}b84GWh+dNR6p>;^jIldXueY8D~|$#UKe+5Q>SehH-WTs6nl;{-EMy?FP_asLpN zU2V3zvPHbKtquHJMdx9N%unWR4ml8e&rdABJUo zo>S{7EzyTJgTP)a@c6>r-tLyC3|Oyg$`)Mmrc=XC@M{b4#O_soJxStHh&bU8aMJRy z!E1=Hw5AZQNJ0S3RsZPa@jz5|mj4=Q)A1>MN%0ykllJc$p^>`*j*zZF!oOIcWLK!` zj>(Jaum*H#4jPje6YDgf?qXIpNrD+6A4rio<256_s>s6|X2XA}^pKzw^XM6DT8$;e zABxne{-sVKuWH_>o+4ASIUgTnq2cQsq%>8bOc23>2kF7Qq+YZ0_R}dJk-UnE(9i74 zN@uhb3F4$`&9d#~MJSEmLRwRJF9ECZ4Ca@duheJcd(QoQjD8cqrH|b;ex{m>hyFDa zGbF|SeE>Xr5b>`i5A*&y&Aj5wXEXIzzt=<2<9Mp8@6Bns1ug%CHVbpEMp@n*zAw*7 zIuvyonVSmc0zqxe-jfO$bJr&b&?0`mPr-D+ixb8@Yub$^504j{=0^$P>iWY%4IgR~cc!Nr<^H;d>g zE{=2gBX`kFPCQ)+FUD08+3|BCeIN>FZE&= zS6@RF)_0>hbCdEql3=;lEP`7k!Tfvxgapxt842_{4_x_!CZ+T_3caxIu-?!z@dH&Y z|DPY8e=FX%Y35ccPaI9NUia(a`; zUNcU{z;FG?mpLTq4U73xRsp96){}r}5!Gpp@=#ws}Xo>Fom#kg(+F!pu#|l_) z3Gs(_>)%|$dTnc%tkk}Lvg@Q7pC=yU*2?nvO5&TeUe#3S(5~n@&gTylw{`_WSgvop zkzQ@@zWGFJc|c8zZ`Kpd?HwAjSjqCYwjTsU_wpgrVmv`Lz||nHKPXn{x?NWE%6&vx zY}mh(UqMqhmyYeP^X1zNJ{o~>k+-!)0M}?pMr_XD?hRKDdj{irZ)yt>94D7S^om~Y zBV88uS(E4onPk6{Q7emi3s2>tu8T%1{o?~I4lOu;nBMjDK3K!QWoBx%z8v<&e*Um_ z@?}-58K4^De-5AvfzDWp7v> zLM<-VGq#s_{ukC-DjT02i>?n2_rIZNjInh$9f$|_>} zAi3_pWbOHHM(UH4Iy>xOkUVx~4L(V&dvY;U9c`yim<#!g4+EtFK$f#U@Lohcw7_f` zSy9NBPRF><@o^7|KNnkm(zl{v2D8oh%+5I`Cuo@C5F%ZSTCag6e_W_mRJ8** zW1;nJrUd{r@a4jfD}1JgU?-Rw7AZ3jr{0KhYzwU(H{W3gj++z0{8`W&J(-FV--o^l zG?n)^taa$6T^19ORo7HlwmSTWJ33>gK%&#+`VbhC_a6jy$1;6==73!`AN~P(f-ODF z-p>Jgz&aeR`c|0sW)^?KoI3G^KkSyo^J#xCw;`F?3V}ZI5-{iuZ7z?0gf%;ZXZ}^Q&bhZ{FoE62ATjT>$99-qJ zoKp&)s>TCM+nslaL>66C3$J7HDQ_sIa?W%K&=$*Eci|BFPyu1I3xSgGZYf2^FTkLo zO)CbfI9e}LRKAP5N=hdt3F7AEzE^2Vwu8ZaO`)0@y0`&b=TLlj_bU)I@y)d}PH>+p zldkDUR9a1On#0w+EPGkcIpZ;i#wijUS|22J&#dL|Dz3`jU*h@q*(8T%jG_1awDULn zaQZhGw`yU|R04{J8fKi%zma``xLn7&rrJm(^z3V*$IZdC!iNHtFdFv24D9OnwfM9` zC34;qCzEF!Sb0RU_9oB&(F>lwoZG~HukNHZCJ?Vh0_Sj|Zu+HaxYi46Q*|YHLNCqY zH)obRs*Mv{f|Eg$Gd}d0n{JQPC_<0cpAE|rsv8$fSjdt$8dzL^B=GUxythGin>j(z z8bk--BpzXvLsl5BvZBmgoCu0|x&^jMpUSBmB!grdEtS@$!Er@mmjFGP6B0jM#`&@& zhf^vT&R?~w92~NCIi*5XJDYhlu&~h8O}a(Q!}8ZE|Ir|QK-A~`D5t<$uQqynbn4V7 zM!ggh+L&12KK~pQY72u96fFtRyq*9IP@#um9MUsQ`PampE98nN+(jESQFY+jCIF)1 zUnW+`#%KGL-^%qE@}1Pu`iNnmsX(l9-!)-tG{>$(Iq^ZBaF~YKB(I6O=EUQtQbW;i z(p6ozs!}RrxG1BZF-q7=7W&1mzY0`Qk$j2AXsR_!zJlkc@vR=eS4q#LcBM~S%B&DF zod1oz@Xyob{*S@14R?~I^#|Fo3K|BXw^&T*^I&Q24E$T>e^X@Rj%gPx8;PopF*Gv@ zk+u<`b?D79IRz`0R&O$I*A+?_|BKDbc36+eUD!S^2{Km#A(k}`C@8qM|NR#LmAfR* zIaOjDAAJgbzC`yd)1MIhgC?)!`8_i_aZE7O@$22gLFCu4%fHw~P!A^BH*`$7`AaZ0 z7*61-P!J|4#}H}mKnehsUNN1rnL_mimMBs5g(|L{n~XwchEtQG25#HpX2!O_3pI>o z`2?*sUIF2xuWJ-4+uolAY4!m$Gw`S`)UXZ4}o0=^@3A zIY>QySa~faWXF> zzsPouMR5;E046CqaU`RSbvv}l7}hHaQZQlDxuxn zO#GUKZP5Wzga6m^XMG0q^t96C-6yP=&8StemU1LmkMwhHSY;jIV3BFmlBe%lVW_0) z?MfcyJjO-ELo!d0b0h)oQu7Zw5VvG{k-4U$})d_$NOMh|5pq4_|U9ZJZUENGG!P z*qk^h?Erox<|FCecbKbBo=ADIT_xm`z7(UdvTd39biuB^5M0N~WS;ne46#bsArJl^ z$sfMtAmX?-Qud>lv+hP>n51b4QSB^lIvOMk&T#bE%HxD%-@g8R$VQYH$3P3O`!Q2k#)eDhJX_!z$+;Odi6|%1t&n z^)y&4#vDxT7O0W+iJ$vCuC{1y7`8duup0b@+lGmNbYT)MmBoMlgG(K*yj=)iPe7JC z{_Abtw&MlOIf7>}=W+d_8>I#MJyk8DElW6RT-{NBur}APB;>@;nc~&U!C#PoW2P42 z(V}z1>L>Z*mmGqgg!aO=4c40xgsPq@sVPnx@=}Sy(}#w`1LTVw>Pscx{PQ8xZRbD_ z;sAs)d*at54E`imLSd3U;&w3ByghKNH~#?t{fkEt0^(uvqEuyNHg-Gz7|L;)-qh(& zu#rB)rjERoPY8?`?-MDo6F<=eLaS^{8T`quPvH-b zh@iR#Yl+kdtzLFrbry$PS}rp4gRJB>1Fx_5kHnc z`1@eqEn^sRU;pR!!3eb4nf})rlfn9#5up*Zr?Jj~$_CD~Ha9h|hg3Ux!1hLnbj}3o ziRXYFoQJ0dFUQN0kxl#nV(=%YzLGzuzYj)=#!k|98zzHtKy#nSGnEuG9T{AY0RP?XJpQ`E(w5 zfc?uPN`B_KuMoO_MLzKx#|QWf{`o^;K=c5gAK=gT|2~*mo}}kp`-8UyE^@ruF2R;e z$lh)>X~UM8dEd>t4pT~l(Y+jI3@!2G$l1?*(p8U`Qz901XUmh;l$Ozyx|;#k16m?r5TL!a-q}a^sl)K9ZV{kG^y0_LGY?W6VJDW zux-aeWhvY+h>1V&0Ty`;{$I`?eUn4;A(pnJ(!$T4M(E4upGY|9XovB`FgYW4Xl=KY z*s7A1c6AmUn!UoJT?rp;}+wzS*EMkhb)7jK|Kx2%_Y)o80vp15yGC{-0y$++`wPv}me zD^2{))m9Rw!Jl+8VUjj4@Q2?;1T}4131CJVVLEYLnr$bcRa{mL1L>=ynZgHC0VR6+ z&f`HN25nQ!&+Ax@zyrj)L+ez@u@(s;YC38`P+{$a?2OBfU7{(4W;*d344^akCvEC} z=FgvNw|ON28i~s}g~r4qiK%2&-J`m-@})(4<)OD1v{FK{3dwXB+f5H*#V)J&=_Lm3S7ST_${l-Dg`hqmwsjW$9p; zV>kgZw*1<~$}aA)`32&5eKGOJ3OM+S%wNr)Kg3>qSBCs|2|)?RvN8vWq+DXMsaYyT z^|HvXXh4ZGoF$XQkBXZdyS$85RR|T5gNu?b`GEuFMW?C|6r6@(08IFaUx0HKBt&_^ zs;UJhVaEjkT@L=6#&DCqSe5{cH*mC!}ER-mr2+n5Qa6D7VO^gCc35XI`C|Q*a?UDkO z@t;V~GJVr2E-k1JR#rQ=tp}AGzj|RQ!zk&-&KyUM5s^>)gFop(HigQ$>RD!tv~!I={DPS^wnX`iAB3Os?kqYA-A+;jdAEwj=;ppK>;4n z*)8qR~iSR^vN6aTq`as5xC28jVW z;Ag2GSEXZ3HY2+p>By>Vlg>0)7c-MtN&jMeCXWYHi6Zta2rZelRlz^KZ91-ckcmI$ zZty1|zK}osIT(g#6z#}&TFJKdSO9IyhghT+*KZHDcEVS7n@wj+=JYP$BJ7EO@D~Sy zqTq&>Zk6ITs%z1KDlanMv`6J*_|Kn%IWZzbBH8wYsa;OO`uwA3pa)#qQgGjbc2)g* zVQrV(!=Sh;s3DtyjG&;(roFjQhEM#RG=sm_|F7c@K7(au^O%MNGSMxNzt@DROyDUG z7Sfk#^e`*L^>7ylJ!t!6c+HY6dlXD0$wK881AXx7wiAC&-Qa)W#uxHOJD9k-Zw;O+ zm7T8VIXgwo6yh?irKTOcLdi%k23r(XthAjy#{I#&6&LKfpqVLPr-@ QPyhe`07*qoM6N<$g0vQ=y#N3J literal 0 HcmV?d00001 diff --git a/product/phone/src/main/resources/base/media/foreground.png b/product/phone/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..5af1e0d50a73f3cbd710198786dce64c7bf02d95 GIT binary patch literal 4155 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 FV1k!j!Z82< literal 0 HcmV?d00001 diff --git a/product/phone/src/main/resources/base/media/ic_alarm.svg b/product/phone/src/main/resources/base/media/ic_alarm.svg new file mode 100644 index 0000000..56d6316 --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_alarm.svg @@ -0,0 +1,12 @@ + + + ic_clock_clock + + + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/ic_alarm_activated.svg b/product/phone/src/main/resources/base/media/ic_alarm_activated.svg new file mode 100644 index 0000000..9249e36 --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_alarm_activated.svg @@ -0,0 +1,12 @@ + + + ic_clock_clock_click + + + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/ic_stopwatch.svg b/product/phone/src/main/resources/base/media/ic_stopwatch.svg new file mode 100644 index 0000000..497fdda --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_stopwatch.svg @@ -0,0 +1,10 @@ + + + ic_clock_timer2备份 + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/ic_stopwatch_activated.svg b/product/phone/src/main/resources/base/media/ic_stopwatch_activated.svg new file mode 100644 index 0000000..63b8090 --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_stopwatch_activated.svg @@ -0,0 +1,10 @@ + + + ic_clock_timer_click + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/ic_timer.svg b/product/phone/src/main/resources/base/media/ic_timer.svg new file mode 100644 index 0000000..ed3c6e3 --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_timer.svg @@ -0,0 +1,9 @@ + + + ic_clock_second chronograph + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/ic_timer_activated.svg b/product/phone/src/main/resources/base/media/ic_timer_activated.svg new file mode 100644 index 0000000..e134bcc --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_timer_activated.svg @@ -0,0 +1,9 @@ + + + ic_clock_second chronograph_click + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/ic_world_clock.svg b/product/phone/src/main/resources/base/media/ic_world_clock.svg new file mode 100644 index 0000000..e4e89a4 --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_world_clock.svg @@ -0,0 +1,9 @@ + + + ic_worldclock_filled + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/ic_world_clock_activated.svg b/product/phone/src/main/resources/base/media/ic_world_clock_activated.svg new file mode 100644 index 0000000..a23068a --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_world_clock_activated.svg @@ -0,0 +1,9 @@ + + + ic_worldclock_filled_activated + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/icon_clock_black.svg b/product/phone/src/main/resources/base/media/icon_clock_black.svg new file mode 100644 index 0000000..497fdda --- /dev/null +++ b/product/phone/src/main/resources/base/media/icon_clock_black.svg @@ -0,0 +1,10 @@ + + + ic_clock_timer2备份 + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/icon_clock_white.svg b/product/phone/src/main/resources/base/media/icon_clock_white.svg new file mode 100644 index 0000000..638a7b5 --- /dev/null +++ b/product/phone/src/main/resources/base/media/icon_clock_white.svg @@ -0,0 +1,10 @@ + + + ic_clock_timer2备份 + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/logo.json b/product/phone/src/main/resources/base/media/logo.json new file mode 100644 index 0000000..1038835 --- /dev/null +++ b/product/phone/src/main/resources/base/media/logo.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background": "$media:background", + "foreground": "$media:foreground" + } +} \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/starting_window.png b/product/phone/src/main/resources/base/media/starting_window.png new file mode 100644 index 0000000000000000000000000000000000000000..15aa2ce47e5972b17b7d746dd09dd738ac69ad74 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}K#X;^)4C~IxyaaMs(j9#r85lP9 zbN@+X1@buyJR*x382FBWFymBhK53vJucwP+h(vhukN^Mw*E4YbX8z0S?0O0)!QkoY K=d#Wzp$Pz>Z5;yu literal 0 HcmV?d00001 diff --git a/product/phone/src/main/resources/base/profile/backup_config.json b/product/phone/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000..143cf2d --- /dev/null +++ b/product/phone/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,26 @@ +{ + "allowToBackupRestore": true, + "supportScene": "next2next,hmos2next", + "includes": [ + "data/storage/el1/base/files/users/*/*.json", + + "data/storage/el1/database/", + "data/storage/el1/base/files/", + "data/storage/el1/base/preferences/", + "data/storage/el1/base/haps/*/database/", + "data/storage/el1/base/haps/*/files/", + "data/storage/el1/base/haps/*/preferences/", + "data/storage/el2/database/", + "data/storage/el2/base/files/", + "data/storage/el2/base/preferences/", + "data/storage/el2/base/haps/*/database/", + "data/storage/el2/base/haps/*/files/", + "data/storage/el2/base/haps/*/preferences/", + "data/storage/el2/distributedfiles/" + ], + "excludes": [ + "data/storage/el1/base/files/users/*/hidden.json" + ], + "fullBackupOnly": true, + "restoreDeps": "" +} \ No newline at end of file diff --git a/product/phone/src/main/resources/base/profile/form_config.json b/product/phone/src/main/resources/base/profile/form_config.json new file mode 100644 index 0000000..e32fc7f --- /dev/null +++ b/product/phone/src/main/resources/base/profile/form_config.json @@ -0,0 +1,23 @@ +{ + "forms": [ + { + "name": "ALARM_CARD", + "description": "$string:fa_alarm_alert", + "src": "./ets/widget/pages/WidgetCard.ets", + "uiSyntax": "arkts", + "window": { + "designWidth": 720, + "autoDesignWidth": true + }, + "colorMode": "auto", + "isDefault": false, + "updateEnabled": true, + "scheduledUpdateTime": "10:30", + "updateDuration": 0, + "defaultDimension": "1*2", + "supportDimensions": [ + "1*2" + ] + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/base/profile/insight_intent.json b/product/phone/src/main/resources/base/profile/insight_intent.json new file mode 100644 index 0000000..c40d53a --- /dev/null +++ b/product/phone/src/main/resources/base/profile/insight_intent.json @@ -0,0 +1,100 @@ +{ + "insightIntents": [ + { + "intentName": "DeleteAlarm", + "intentVersion": "1.0.1", + "domain": "AlarmDomain", + "srcEntry": "./ets/IntentAbility/DeleteAlarmIntent.ets", + "uiAbility": { + "ability": "com.example.ohosclock.phone", + "executeMode": [ + "background" + ] + } + }, + { + "intentName": "ModifyAlarm", + "intentVersion": "1.0.1", + "domain": "AlarmDomain", + "srcEntry": "./ets/IntentAbility/ModifyAlarmIntent.ets", + "uiAbility": { + "ability": "com.example.ohosclock.phone", + "executeMode": [ + "background" + ] + } + }, + { + "intentName": "StopAlarmRing", + "intentVersion": "1.0.1", + "domain": "AlarmDomain", + "srcEntry": "./ets/IntentAbility/StopRingIntent.ets", + "uiAbility": { + "ability": "com.example.ohosclock.phone", + "executeMode": [ + "background" + ] + } + }, + { + "intentName": "DelayAlarmRing", + "intentVersion": "1.0.1", + "domain": "AlarmDomain", + "srcEntry": "./ets/IntentAbility/DelayRingIntent.ets", + "uiAbility": { + "ability": "com.example.ohosclock.phone", + "executeMode": [ + "background" + ] + } + }, + { + "intentName": "SearchAlarm", + "intentVersion": "1.0.1", + "domain": "AlarmDomain", + "srcEntry": "./ets/IntentAbility/SearchAlarmIntent.ets", + "uiAbility": { + "ability": "com.example.ohosclock.phone", + "executeMode": [ + "background" + ] + } + }, + { + "intentName": "CreateAlarm", + "intentVersion": "1.0.1", + "domain": "AlarmDomain", + "srcEntry": "./ets/IntentAbility/CreateAlarm.ets", + "uiAbility": { + "ability": "com.example.ohosclock.phone", + "executeMode": [ + "background" + ] + } + }, + { + "intentName": "QueryAlarmRing", + "intentVersion": "1.0.1", + "domain": "AlarmDomain", + "srcEntry": "./ets/IntentAbility/QueryAlarmRing.ets", + "uiAbility": { + "ability": "com.example.ohosclock.phone", + "executeMode": [ + "background" + ] + } + }, + { + "intentName": "ViewAlarm", + "intentVersion": "1.0.1", + "domain": "AlarmDomain", + "srcEntry": "./ets/IntentAbility/ViewAlarm.ets", + "uiAbility": { + "ability": "com.example.ohosclock.phone", + "executeMode": [ + "foreground" + ] + } + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/base/profile/insight_intent_schema.json b/product/phone/src/main/resources/base/profile/insight_intent_schema.json new file mode 100644 index 0000000..c0534b1 --- /dev/null +++ b/product/phone/src/main/resources/base/profile/insight_intent_schema.json @@ -0,0 +1,160 @@ +{ + "title": "JSON schema for insight_intent.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "required": [ + "insightIntents" + ], + "propertyNames": { + "enum": [ + "insightIntents" + ] + }, + "properties": { + "insightIntents": { + "description": "Indicates the configuration of insightIntents.", + "type": "array", + "minItems": 1, + "maxItems": 32, + "uniqueItems": true, + "items": { + "type": "object", + "propertyNames": { + "enum": [ + "intentName", + "domain", + "intentVersion", + "srcEntry", + "uiAbility", + "serviceExtension", + "uiExtension", + "form" + ] + }, + "required": [ + "intentName", + "domain", + "intentVersion", + "srcEntry" + ], + "properties": { + "intentName": { + "description": "Indicates the name of insightIntent.It's also the intent interface to implement.", + "type": "string", + "pattern": "^[A-Z][a-zA-Z0-9]+$" + }, + "domain": { + "description": "Indicates the domain of insightIntent.", + "type": "string" + }, + "intentVersion": { + "description": "Indicates the version of insightIntent.", + "type": "string", + "pattern": "^(\\d+\\.){2}\\d+$" + }, + "srcEntry": { + "description": "Indicates the js code path corresponding to the ability.", + "type": "string", + "maxLength": 127 + }, + "uiAbility": { + "type": "object", + "propertyNames": { + "enum": [ + "ability", + "executeMode" + ] + }, + "required": [ + "ability", + "executeMode" + ], + "properties": { + "ability": { + "description": "Indicates the name of the ability.", + "type": "string", + "pattern": "^[a-zA-Z][0-9a-zA-Z_.]+$", + "maxLength": 127 + }, + "executeMode": { + "type": "array", + "items": { + "type":"string", + "enum": [ + "background", + "foreground" + ] + } + } + } + }, + "serviceExtension": { + "type": "object", + "propertyNames": { + "enum": [ + "ability" + ] + }, + "required": [ + "ability" + ], + "properties": { + "ability": { + "description": "Indicates the name of the ability.", + "type": "string", + "pattern": "^[a-zA-Z][0-9a-zA-Z_.]+$", + "maxLength": 127 + } + } + }, + "UIExtension": { + "type": "object", + "propertyNames": { + "enum": [ + "ability" + ] + }, + "required": [ + "ability" + ], + "properties": { + "ability": { + "description": "Indicates the name of the ability.", + "type": "string", + "pattern": "^[a-zA-Z][0-9a-zA-Z_.]+$", + "maxLength": 127 + } + } + }, + "form": { + "type": "object", + "propertyNames": { + "enum": [ + "ability", + "formName" + ] + }, + "required": [ + "ability", + "formName" + ], + "properties": { + "ability": { + "description": "Indicates the name of the ability.", + "type": "string", + "pattern": "^[a-zA-Z][0-9a-zA-Z_.]+$", + "maxLength": 127 + }, + "formName": { + "description": "Indicates the name of the form class. The tag value is a string of up to 127 bytes. The tag cannot be default.", + "type": "string", + "maxLength": 127 + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/resources/base/profile/main_pages.json b/product/phone/src/main/resources/base/profile/main_pages.json index feec276..b3d59bc 100644 --- a/product/phone/src/main/resources/base/profile/main_pages.json +++ b/product/phone/src/main/resources/base/profile/main_pages.json @@ -1,5 +1,14 @@ { "src": [ - "pages/index" + "pages/index", + "pages/ManageAlarmClock", + "pages/EditCities", + "pages/BannerAlarm", + "pages/FullScreenAlarm", + "pages/ForegroundPage", + "pages/AddCity", + "pages/FaManagerCity", + "pages/FullScreenTimer", + "pages/AddCityNew" ] -} +} \ No newline at end of file diff --git a/product/phone/src/main/resources/base/profile/static_subscriber_config.json b/product/phone/src/main/resources/base/profile/static_subscriber_config.json new file mode 100644 index 0000000..cefa318 --- /dev/null +++ b/product/phone/src/main/resources/base/profile/static_subscriber_config.json @@ -0,0 +1,15 @@ +{ + "commonEvents": [ + { + "name": "com.example.ohosclock.AlarmInitSubscriber", + "permission": "", + "types": [], + "events": [ + "usual.event.TIME_CHANGED", + "usual.event.TIMEZONE_CHANGED", + "usual.event.BOOT_COMPLETED", + "usual.event.LOCKED_BOOT_COMPLETED" + ] + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/bo_CN/element/string.json b/product/phone/src/main/resources/bo_CN/element/string.json new file mode 100644 index 0000000..cf143bc --- /dev/null +++ b/product/phone/src/main/resources/bo_CN/element/string.json @@ -0,0 +1,12 @@ +{ + "string":[ + { + "name":"fa_alarm_alert", + "value":"ཐིར་ལྡན་ཆུ་ཚོད་ཀྱི་དྲན་སྐུལ།" + }, + { + "name":"timer", + "value":"དུས་རྩིས་ཆས།" + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/element/color.json b/product/phone/src/main/resources/dark/element/color.json new file mode 100644 index 0000000..8bb4f2a --- /dev/null +++ b/product/phone/src/main/resources/dark/element/color.json @@ -0,0 +1,32 @@ +{ + "color": [ + { + "name": "white", + "value": "#FFFFFF" + }, + { + "name": "fa_background_color", + "value": "#F1F3F5" + }, + { + "name": "fa_clock_digit_color", + "value": "#E5D41919" + }, + { + "name": "fa_clock_digit_color_night", + "value": "#99FFFFFF" + }, + { + "name": "start_window_background", + "value": "#000000" + }, + { + "name": "item_title_font", + "value": "#000000" + }, + { + "name": "tab_bar_bg", + "value": "#202224" + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/element/float.json b/product/phone/src/main/resources/dark/element/float.json new file mode 100644 index 0000000..c64be9f --- /dev/null +++ b/product/phone/src/main/resources/dark/element/float.json @@ -0,0 +1,444 @@ +{ + "float": [ + { + "name": "tool_bar_width", + "value": "96vp" + }, + { + "name": "title_bar_height", + "value": "56vp" + }, + { + "name": "butt_ban_distance", + "value": "14vp" + }, + { + "name": "title_bar_padding", + "value": "24vp" + }, + { + "name": "content_offset", + "value": "40vp" + }, + { + "name": "clock_shadow_above_space", + "value": "35vp" + }, + { + "name": "main_tab_text_margin", + "value": "4vp" + }, + { + "name": "main_tab_text_size", + "value": "10vp" + }, + { + "name": "dial_bottom_position", + "value": "1" + }, + { + "name": "dial_shadow_radius", + "value": "28vp" + }, + { + "name": "dial_shadow_inside_x", + "value": "0vp" + }, + { + "name": "dial_shadow_inside_y", + "value": "-20vp" + }, + { + "name": "dial_shadow_outside_x", + "value": "0vp" + }, + { + "name": "dial_shadow_outside_y", + "value": "23vp" + }, + { + "name": "scale_top_position", + "value": "0" + }, + { + "name": "scale_center_position", + "value": "0.5" + }, + { + "name": "scale_bottom_position", + "value": "1" + }, + { + "name": "card_padding_vertical", + "value": "4vp" + }, + { + "name": "card_padding_horizontal", + "value": "4vp" + }, + { + "name": "card_inner_padding_horizontal", + "value": "8vp" + }, + { + "name": "timer_pick_padding_vertical", + "value": "16vp" + }, + { + "name": "card_margin_start", + "value": "12vp" + }, + { + "name": "card_margin_end", + "value": "12vp" + }, + { + "name": "default_corner_radius_l", + "value": "16vp" + }, + { + "name": "card_content_height", + "value": "64vp" + }, + { + "name": "card_tag_margin_end", + "value": "4vp" + }, + { + "name": "card_title_line_margin", + "value": "2vp" + }, + { + "name": "hour_shadow_offset_x", + "value": "5vp" + }, + { + "name": "hour_shadow_offset_y", + "value": "1vp" + }, + { + "name": "minute_shadow_offset_x", + "value": "0vp" + }, + { + "name": "minute_shadow_offset_y", + "value": "3vp" + }, + { + "name": "second_shadow_offset_x", + "value": "-4vp" + }, + { + "name": "second_shadow_offset_y", + "value": "-3vp" + }, + { + "name": "text_size_headline3", + "value": "60fp" + }, + { + "name": "right_icon_height", + "value": "24vp" + }, + { + "name": "right_icon_width", + "value": "12vp" + }, + { + "name": "header_footer_height", + "value": "56vp" + }, + { + "name": "dialog_padding_horizontal", + "value": "24vp" + }, + { + "name": "dialog_button_height", + "value": "40vp" + }, + { + "name": "dialog_content_padding_vertical", + "value": "8vp" + }, + { + "name": "dialog_button_divider_margin", + "value": "4vp" + }, + { + "name": "dialog_button_divider_height", + "value": "24vp" + }, + { + "name": "dialog_button_divider_width", + "value": "2vp" + }, + { + "name": "dialog_margin_bottom", + "value": "-16vp" + }, + { + "name": "input_height", + "value": "48vp" + }, + { + "name": "button_size", + "value": "48vp" + }, + { + "name": "button_shadow_radius", + "value": "8vp" + }, + { + "name": "button_shadow_x", + "value": "0vp" + }, + { + "name": "button_shadow_y", + "value": "6vp" + }, + { + "name": "button_icon_size", + "value": "24vp" + }, + { + "name": "digital_clock_text_size_normal", + "value": "18vp" + }, + { + "name": "digital_clock_text_margin", + "value": "4vp" + }, + { + "name": "confirm_dialog_padding_horizontal", + "value": "16vp" + }, + { + "name": "confirm_dialog_content_padding_top", + "value": "24vp" + }, + { + "name": "confirm_dialog_content_padding_bottom", + "value": "8vp" + }, + { + "name": "dialog_button_divider_margin_vertical", + "value": "8vp" + }, + { + "name": "confirm_dialog_border", + "value": "20vp" + }, + { + "name": "confirm_dialog_divider_height", + "value": "24vp" + }, + { + "name": "confirm_dialog_divider_width", + "value": "1vp" + }, + { + "name": "set_alarm_card_height", + "value": "48vp" + }, + { + "name": "title_font_size", + "value": "20vp" + }, + { + "name": "appbar_icon_margin", + "value": "16vp" + }, + { + "name": "appbar_icon_size", + "value": "24vp" + }, + { + "name": "response_region_size", + "value": "48vp" + }, + { + "name": "snooze_setting_margin_vertical_max", + "value": "16vp" + }, + { + "name": "snooze_setting_margin_vertical_min", + "value": "4vp" + }, + { + "name": "slider_margin_horizontal", + "value": "-16.5vp" + }, + { + "name": "slider_margin_offset_left", + "value": "6.75vp" + }, + { + "name": "divider_margin_horizontal", + "value": "-24vp" + }, + { + "name": "divider_margin_offset_left", + "value": "12vp" + }, + { + "name": "slider_label_opacity", + "value": "0.6" + }, + { + "name": "slider_label_size", + "value": "14fp" + }, + { + "name": "slider_step_label_size", + "value": "10fp" + }, + { + "name": "clock_margin_vertical", + "value": "16vp" + }, + { + "name": "left_time_tips_margin_bottom", + "value": "32vp" + }, + { + "name": "current_time_margin_top", + "value": "68vp" + }, + { + "name": "current_date_margin_top", + "value": "5vp" + }, + { + "name": "snooze_btn_margin_top", + "value": "16vp" + }, + { + "name": "snooze_btn_opacity", + "value": "0.7" + }, + { + "name": "slide_to_turn_off_font_size", + "value": "16fp" + }, + { + "name": "slide_to_turn_off_margin_bottom", + "value": "38.5vp" + }, + { + "name": "slide_to_turn_off_button_size", + "value": "50vp" + }, + { + "name": "slide_to_turn_off_button_border_width", + "value": "2vp" + }, + { + "name": "check_box_width", + "value": "20vp" + }, + { + "name": "switch_width", + "value": "60vp" + }, + { + "name": "switch_height", + "value": "20vp" + }, + { + "name": "slide_to_turn_off_margin_top", + "value": "14vp" + }, + { + "name": "snooze_button_padding_horizontal", + "value": "45vp" + }, + { + "name": "snooze_button_padding_vertical", + "value": "10vp" + }, + { + "name": "snooze_button_border_width", + "value": "1vp" + }, + { + "name": "snooze_button_border_radius", + "value": "46vp" + }, + { + "name": "select_all_text_size", + "value": "10vp" + }, + { + "name": "select_all_text_margin", + "value": "3vp" + }, + { + "name": "ohos_id_card_margin_middle", + "value": "12vp" + }, + { + "name": "ohos_id_default_padding_end", + "value": "12vp" + }, + { + "name": "ohos_id_elements_margin_vertical_l", + "value": "16vp" + }, + { + "name": "list_item_height", + "value": "48vp" + }, + { + "name": "main_tab_line_height", + "value": "14vp" + }, + { + "name": "left_time_line_height", + "value": "28vp" + }, + { + "name": "timer_picker_height", + "value": "200vp" + }, + { + "name": "timer_picker_width", + "value": "312vp" + }, + { + "name": "selected_all_padding", + "value": "24vp" + }, + { + "name": "selected_all_line_height", + "value": "13vp" + }, + { + "name": "radio_size", + "value": "20vp" + }, + { + "name": "title_bar_shadow_x", + "value": "0vp" + }, + { + "name": "title_bar_shadow_y", + "value": "6vp" + }, + { + "name": "title_bar_shadow_radius", + "value": "6vp" + }, + { + "name": "title_width", + "value": "300vp" + }, + { + "name": "change_cross", + "value": "16vp" + }, + { + "name": "change_vertical", + "value": "24vp" + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/element/string.json b/product/phone/src/main/resources/dark/element/string.json new file mode 100644 index 0000000..b097b1e --- /dev/null +++ b/product/phone/src/main/resources/dark/element/string.json @@ -0,0 +1,39 @@ +{ + "string": [ + { + "name": "phone_desc", + "value": "phone product" + }, + { + "name": "MainAbility_desc", + "value": "phone product ability" + }, + { + "name": "MainAbility_label", + "value": "clock" + }, + { + "name": "EntryFormAbility_desc", + "value": "form_description" + }, + { + "name": "EntryFormAbility_label", + "value": "form_label" + }, + { + "name": "FaCityManagerAbility_desc", + "value": "description" + }, + { + "name": "FaCityManagerAbility_label", + "value": "label" + },{ + "name": "timer", + "value": "timer" + }, + { + "name": "ServiceExtAbility", + "value": "back up exntension" + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/background.png b/product/phone/src/main/resources/dark/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..0ae8816b4cb7a1fc6528e536a93b2c35220d4927 GIT binary patch literal 19351 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 QPyhe`07*qoM6N<$g0vQ=y#N3J literal 0 HcmV?d00001 diff --git a/product/phone/src/main/resources/dark/media/foreground.png b/product/phone/src/main/resources/dark/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..5af1e0d50a73f3cbd710198786dce64c7bf02d95 GIT binary patch literal 4155 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 FV1k!j!Z82< literal 0 HcmV?d00001 diff --git a/product/phone/src/main/resources/dark/media/ic_alarm.svg b/product/phone/src/main/resources/dark/media/ic_alarm.svg new file mode 100644 index 0000000..56d6316 --- /dev/null +++ b/product/phone/src/main/resources/dark/media/ic_alarm.svg @@ -0,0 +1,12 @@ + + + ic_clock_clock + + + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/ic_alarm_activated.svg b/product/phone/src/main/resources/dark/media/ic_alarm_activated.svg new file mode 100644 index 0000000..9249e36 --- /dev/null +++ b/product/phone/src/main/resources/dark/media/ic_alarm_activated.svg @@ -0,0 +1,12 @@ + + + ic_clock_clock_click + + + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/ic_stopwatch.svg b/product/phone/src/main/resources/dark/media/ic_stopwatch.svg new file mode 100644 index 0000000..497fdda --- /dev/null +++ b/product/phone/src/main/resources/dark/media/ic_stopwatch.svg @@ -0,0 +1,10 @@ + + + ic_clock_timer2备份 + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/ic_stopwatch_activated.svg b/product/phone/src/main/resources/dark/media/ic_stopwatch_activated.svg new file mode 100644 index 0000000..63b8090 --- /dev/null +++ b/product/phone/src/main/resources/dark/media/ic_stopwatch_activated.svg @@ -0,0 +1,10 @@ + + + ic_clock_timer_click + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/ic_timer.svg b/product/phone/src/main/resources/dark/media/ic_timer.svg new file mode 100644 index 0000000..ed3c6e3 --- /dev/null +++ b/product/phone/src/main/resources/dark/media/ic_timer.svg @@ -0,0 +1,9 @@ + + + ic_clock_second chronograph + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/ic_timer_activated.svg b/product/phone/src/main/resources/dark/media/ic_timer_activated.svg new file mode 100644 index 0000000..e134bcc --- /dev/null +++ b/product/phone/src/main/resources/dark/media/ic_timer_activated.svg @@ -0,0 +1,9 @@ + + + ic_clock_second chronograph_click + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/ic_world_clock.svg b/product/phone/src/main/resources/dark/media/ic_world_clock.svg new file mode 100644 index 0000000..e4e89a4 --- /dev/null +++ b/product/phone/src/main/resources/dark/media/ic_world_clock.svg @@ -0,0 +1,9 @@ + + + ic_worldclock_filled + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/ic_world_clock_activated.svg b/product/phone/src/main/resources/dark/media/ic_world_clock_activated.svg new file mode 100644 index 0000000..a23068a --- /dev/null +++ b/product/phone/src/main/resources/dark/media/ic_world_clock_activated.svg @@ -0,0 +1,9 @@ + + + ic_worldclock_filled_activated + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/icon.png b/product/phone/src/main/resources/dark/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..388ea471d1414a2b4a21ce864245fa7861e08fb5 GIT binary patch literal 3149 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 literal 0 HcmV?d00001 diff --git a/product/phone/src/main/resources/dark/media/icon_clock_black.svg b/product/phone/src/main/resources/dark/media/icon_clock_black.svg new file mode 100644 index 0000000..497fdda --- /dev/null +++ b/product/phone/src/main/resources/dark/media/icon_clock_black.svg @@ -0,0 +1,10 @@ + + + ic_clock_timer2备份 + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/icon_clock_white.svg b/product/phone/src/main/resources/dark/media/icon_clock_white.svg new file mode 100644 index 0000000..638a7b5 --- /dev/null +++ b/product/phone/src/main/resources/dark/media/icon_clock_white.svg @@ -0,0 +1,10 @@ + + + ic_clock_timer2备份 + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/logo.json b/product/phone/src/main/resources/dark/media/logo.json new file mode 100644 index 0000000..1038835 --- /dev/null +++ b/product/phone/src/main/resources/dark/media/logo.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background": "$media:background", + "foreground": "$media:foreground" + } +} \ No newline at end of file diff --git a/product/phone/src/main/resources/dark/media/starting_window.png b/product/phone/src/main/resources/dark/media/starting_window.png new file mode 100644 index 0000000000000000000000000000000000000000..15aa2ce47e5972b17b7d746dd09dd738ac69ad74 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}K#X;^)4C~IxyaaMs(j9#r85lP9 zbN@+X1@buyJR*x382FBWFymBhK53vJucwP+h(vhukN^Mw*E4YbX8z0S?0O0)!QkoY K=d#Wzp$Pz>Z5;yu literal 0 HcmV?d00001 diff --git a/product/phone/src/main/resources/ug/element/string.json b/product/phone/src/main/resources/ug/element/string.json new file mode 100644 index 0000000..692850e --- /dev/null +++ b/product/phone/src/main/resources/ug/element/string.json @@ -0,0 +1,12 @@ +{ + "string":[ + { + "name":"fa_alarm_alert", + "value":"قوڭغۇراق سائەت" + }, + { + "name":"timer", + "value":"تەتۈر ساناق" + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/zh_CN/element/string.json b/product/phone/src/main/resources/zh_CN/element/string.json index 4e7d9f8..8046e5a 100644 --- a/product/phone/src/main/resources/zh_CN/element/string.json +++ b/product/phone/src/main/resources/zh_CN/element/string.json @@ -1,8 +1,43 @@ { "string": [ + { + "name": "phone_desc", + "value": "phone product" + }, + { + "name": "MainAbility_desc", + "value": "phone product ability" + }, { "name": "MainAbility_label", - "value": "时钟" + "value": "clock" + }, + { + "name": "EntryFormAbility_desc", + "value": "form_description" + }, + { + "name": "EntryFormAbility_label", + "value": "form_label" + }, + { + "name": "FaCityManagerAbility_desc", + "value": "description" + }, + { + "name": "FaCityManagerAbility_label", + "value": "label" + },{ + "name": "timer", + "value": "计时器" + }, + { + "name": "ServiceExtAbility", + "value": "back up exntension" + }, + { + "name": "fa_alarm_alert", + "value": "闹钟提醒" } ] } \ No newline at end of file diff --git a/product/phone/src/main/resources/zh_HK/element/string.json b/product/phone/src/main/resources/zh_HK/element/string.json new file mode 100644 index 0000000..19ce89c --- /dev/null +++ b/product/phone/src/main/resources/zh_HK/element/string.json @@ -0,0 +1,12 @@ +{ + "string":[ + { + "name":"fa_alarm_alert", + "value":"鬧鐘提醒" + }, + { + "name":"timer", + "value":"計時器" + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/zh_TW/element/string.json b/product/phone/src/main/resources/zh_TW/element/string.json new file mode 100644 index 0000000..19ce89c --- /dev/null +++ b/product/phone/src/main/resources/zh_TW/element/string.json @@ -0,0 +1,12 @@ +{ + "string":[ + { + "name":"fa_alarm_alert", + "value":"鬧鐘提醒" + }, + { + "name":"timer", + "value":"計時器" + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/zz_ZX/element/string.json b/product/phone/src/main/resources/zz_ZX/element/string.json new file mode 100644 index 0000000..d26a39d --- /dev/null +++ b/product/phone/src/main/resources/zz_ZX/element/string.json @@ -0,0 +1,44 @@ +{ + "string": [ + { + "name": "EntryFormAbility_desc", + "value": "[TS_843084]_form_description" + }, + { + "name": "EntryFormAbility_label", + "value": "[TS_843077]_form_label" + }, + { + "name": "fa_alarm_alert", + "value": "[TS_843080]_Alarm clock" + }, + { + "name": "phone_desc", + "value": "[TS_794208]_phone product" + }, + { + "name": "FaCityManagerAbility_desc", + "value": "[TS_846749]_description" + }, + { + "name": "FaCityManagerAbility_label", + "value": "[TS_846750]_label" + }, + { + "name": "MainAbility_desc", + "value": "[TS_794209]_phone product ability" + }, + { + "name": "MainAbility_label", + "value": "[TS_794207]_clock" + }, + { + "name": "timer", + "value": "[TS_869851]_Timer" + }, + { + "name": "ServiceExtAbility", + "value": "[TS_870997]_back up exntension" + } + ] +} \ No newline at end of file diff --git a/product/phone/src/ohosTest/ets/test/Ability.test.ets b/product/phone/src/ohosTest/ets/test/Ability.test.ets index 6c7475a..f7371b2 100644 --- a/product/phone/src/ohosTest/ets/test/Ability.test.ets +++ b/product/phone/src/ohosTest/ets/test/Ability.test.ets @@ -13,509 +13,509 @@ * limitations under the License. */ -import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' -import { TimerController, TimerState } from '@ohos/timer'; -import { TimerUtil } from '@ohos/common'; -<<<<<<< HEAD -import { CountdownController, CountdownState } from '@ohos/countdown'; -======= ->>>>>>> 34e999d (add timer ohosTest) +// import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' +// import { TimerController, TimerState } from '@ohos/timer'; +// import { TimerUtil } from '@ohos/common'; +// <<<<<<< HEAD +// import { CountdownController, CountdownState } from '@ohos/countdown'; +// ======= +// >>>>>>> 34e999d (add timer ohosTest) export default function abilityTest() { - describe('ActsAbilityTest', function () { - it('startState', 0, function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(TimerState.RUNNING); - testState = false; - } - }); - testState = true; - mTimerController.start(); - }) - - it('startTime', 0, function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setTimeUpdateListener((time) => { - if (testState) { - expect(time > 0).assertTrue(); - testState = false; - } - }); - testState = true; - mTimerController.start(); - }) - - it('clockUpdate', 0, function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setClockUpdateListener((time) => { - if (testState) { - expect(time > 0).assertTrue(); - testState = false; - } - }); - testState = true; - mTimerController.start(); - }) - - it('pauseState', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(TimerState.PAUSED); - testState = false; - } - }); - await mTimerController.start(); - testState = true; - setTimeout(async () => { - await mTimerController.pause(); - }, 100); - }) - - it('pauseTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - let testTime = 0; - mTimerController.setTimeUpdateListener((time) => { - if (testState) { - expect(testTime).assertEqual(time); - testState = false; - } else { - testTime = time; - } - }); - await mTimerController.start(); - await mTimerController.pause(); - testState = true; - setTimeout(async () => { - await mTimerController.start(); - }, 100); - }) - - it('resetAfterStartState', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(TimerState.IDLE); - testState = false; - } - }); - await mTimerController.start(); - testState = true; - setTimeout(async () => { - await mTimerController.reset(); - }, 100); - }) - - it('resetAfterStartTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setTimeUpdateListener((time) => { - if (testState) { - expect(time).assertEqual(0); - testState = false; - } - }); - await mTimerController.start(); - testState = true; - setTimeout(async () => { - await mTimerController.reset(); - }, 100); - }) - - it('reStartState', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(TimerState.RUNNING); - testState = false; - } - }); - await mTimerController.start(); - setTimeout(async () => { - await mTimerController.pause(); - testState = true; - await mTimerController.start(); - }, 100); - }) - - it('reStartStateTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - let testTime = 0; - mTimerController.setTimeUpdateListener((time) => { - if (testTime > 0) { - expect(testTime < time).assertTrue(); - testState = false; - } else if (testState == true) { - testTime = time; - } - }); - await mTimerController.start(); - await mTimerController.pause(); - testState = true; - setTimeout(async () => { - await mTimerController.start(); - }, 100); - }) - - it('resetAfterPauseState', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(TimerState.IDLE); - testState = false; - } - }); - await mTimerController.start(); - setTimeout(async () => { - await mTimerController.pause(); - testState = true; - await mTimerController.reset(); - }, 100); - }) - - it('resetAfterPauseTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setTimeUpdateListener((time) => { - if (testState) { - expect(time).assertEqual(0); - testState = false; - } - }); - await mTimerController.start(); - setTimeout(async () => { - await mTimerController.pause(); - testState = true; - await mTimerController.reset(); - }, 100); - }) - - it('writeTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setTimeListUpdateListener((timeList) => { - if (testState) { - expect(timeList.length > 1).assertTrue(); - testState = false; - } - }); - await mTimerController.start(); - setTimeout(async () => { - testState = true; - await mTimerController.writeTime(); - }, 100); - }) - - it('resetWriteTime', 0, async function () { - let mTimerController = new TimerController(); - let testState = false; - mTimerController.setTimeListUpdateListener((timeList) => { - if (testState) { - expect(timeList.length).assertEqual(0); - testState = false; - } - }); - await mTimerController.start(); - setTimeout(async () => { - await mTimerController.writeTime(); - testState = true; - mTimerController.reset(); - }, 100); - }) - - it('addZeroMin', 0, async function () { - let num = 0 - let res = TimerUtil.addZero(num) - expect(res).assertEqual('00') - }) - - it('addZeroMinus', 0, async function () { - let num = -1 - let res = TimerUtil.addZero(num) - expect(res).assertEqual('0-1') - }) - - it('addZeroMax', 0, async function () { - let num = 9 - let res = TimerUtil.addZero(num) - expect(res).assertEqual('09') - }) - - it('addZeroOver', 0, async function () { - let num = 10 - let res = TimerUtil.addZero(num) - expect(res).assertEqual('10') - }) - - it('addZeroNaN', 0, async function () { - let num = NaN - let res = TimerUtil.addZero(num) - expect(res).assertEqual('NaN') - }) - - it('timeFormatZero', 0, async function () { - let num = 0 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`00:00.00`) - }) - - it('timeFormatMinus', 0, async function () { - let num = -1 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`00:00.00`) - }) - - it('timeFormatMinMs', 0, async function () { - let num = 9 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`00:00.00`) - }) - - it('timeFormatMs', 0, async function () { - let num = 10 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`00:00.01`) - }) - - it('timeFormatSec', 0, async function () { - let num = 1000 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`00:01.00`) - }) - - it('timeFormatMin', 0, async function () { - let num = 1000 * 60 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`01:00.00`) - }) - - it('timeFormatHour', 0, async function () { - let num = 1000 * 60 * 60 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`01:00:00.00`) - }) - - it('timeFormatDay', 0, async function () { - let num = 1000 * 60 * 60 * 24 - let res = TimerUtil.timeFormat(num) - expect(res).assertEqual(`01:00:00:00.00`) -<<<<<<< HEAD - }) - }) - - describe('CountdownTest', function () { - it('setTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testState) { - expect(totalTimeMs).assertEqual(100); - testState = false; - } - }); - testState = true; - mCountdownController.setTime(100); - }) - - it('setTimeState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.PREPARED); - testState = false; - } - }); - testState = true; - mCountdownController.setTime(100); - }) - - it('startState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.RUNNING); - testState = false; - } - }); - mCountdownController.setTime(100); - testState = true; - mCountdownController.start(); - }) - - it('startTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testState) { - expect(currentTimeMs > 0).assertTrue(); - testState = false; - } - }); - mCountdownController.setTime(100); - testState = true; - mCountdownController.start(); - }) - - it('pauseState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.PAUSED); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - testState = true; - setTimeout(() => { - mCountdownController.pause(); - }, 100); - }) - - it('pauseTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - let testTime = 0; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testState) { - expect(testTime).assertEqual(totalTimeMs); - testState = false; - } else { - testTime = totalTimeMs; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - mCountdownController.pause(); - testState = true; - setTimeout(() => { - mCountdownController.start(); - }, 100); - }) - - it('resetAfterStartState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.IDLE); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - testState = true; - setTimeout(() => { - mCountdownController.reset(); - }, 100); - }) - - it('resetAfterStartTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testState) { - expect(totalTimeMs).assertEqual(0); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - testState = true; - setTimeout(() => { - mCountdownController.reset(); - }, 100); - }) - - it('reStartState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.RUNNING); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - setTimeout(() => { - mCountdownController.pause(); - testState = true; - mCountdownController.start(); - }, 100); - }) - - it('reStartStateTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - let testTime = 0; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testTime > 0) { - expect(testTime < totalTimeMs).assertTrue(); - testState = false; - } else if (testState == true) { - testTime = currentTimeMs; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - mCountdownController.pause(); - testState = true; - setTimeout(() => { - mCountdownController.start(); - }, 100); - }) - - it('resetAfterPauseState', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setStateUpdateListener((state) => { - if (testState) { - expect(state).assertEqual(CountdownState.IDLE); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - setTimeout(() => { - mCountdownController.pause(); - testState = true; - mCountdownController.reset(); - }, 100); - }) - - it('resetAfterPauseTime', 0, function () { - let mCountdownController = new CountdownController(); - let testState = false; - mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { - if (testState) { - expect(totalTimeMs).assertEqual(0); - testState = false; - } - }); - mCountdownController.setTime(10000); - mCountdownController.start(); - setTimeout(() => { - mCountdownController.pause(); - testState = true; - mCountdownController.reset(); - }, 100); -======= ->>>>>>> 34e999d (add timer ohosTest) - }) - }) +// describe('ActsAbilityTest', function () { +// it('startState', 0, function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(TimerState.RUNNING); +// testState = false; +// } +// }); +// testState = true; +// mTimerController.start(); +// }) +// +// it('startTime', 0, function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setTimeUpdateListener((time) => { +// if (testState) { +// expect(time > 0).assertTrue(); +// testState = false; +// } +// }); +// testState = true; +// mTimerController.start(); +// }) +// +// it('clockUpdate', 0, function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setClockUpdateListener((time) => { +// if (testState) { +// expect(time > 0).assertTrue(); +// testState = false; +// } +// }); +// testState = true; +// mTimerController.start(); +// }) +// +// it('pauseState', 0, async function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(TimerState.PAUSED); +// testState = false; +// } +// }); +// await mTimerController.start(); +// testState = true; +// setTimeout(async () => { +// await mTimerController.pause(); +// }, 100); +// }) +// +// it('pauseTime', 0, async function () { +// let mTimerController = new TimerController(); +// let testState = false; +// let testTime = 0; +// mTimerController.setTimeUpdateListener((time) => { +// if (testState) { +// expect(testTime).assertEqual(time); +// testState = false; +// } else { +// testTime = time; +// } +// }); +// await mTimerController.start(); +// await mTimerController.pause(); +// testState = true; +// setTimeout(async () => { +// await mTimerController.start(); +// }, 100); +// }) +// +// it('resetAfterStartState', 0, async function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(TimerState.IDLE); +// testState = false; +// } +// }); +// await mTimerController.start(); +// testState = true; +// setTimeout(async () => { +// await mTimerController.reset(); +// }, 100); +// }) +// +// it('resetAfterStartTime', 0, async function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setTimeUpdateListener((time) => { +// if (testState) { +// expect(time).assertEqual(0); +// testState = false; +// } +// }); +// await mTimerController.start(); +// testState = true; +// setTimeout(async () => { +// await mTimerController.reset(); +// }, 100); +// }) +// +// it('reStartState', 0, async function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(TimerState.RUNNING); +// testState = false; +// } +// }); +// await mTimerController.start(); +// setTimeout(async () => { +// await mTimerController.pause(); +// testState = true; +// await mTimerController.start(); +// }, 100); +// }) +// +// it('reStartStateTime', 0, async function () { +// let mTimerController = new TimerController(); +// let testState = false; +// let testTime = 0; +// mTimerController.setTimeUpdateListener((time) => { +// if (testTime > 0) { +// expect(testTime < time).assertTrue(); +// testState = false; +// } else if (testState == true) { +// testTime = time; +// } +// }); +// await mTimerController.start(); +// await mTimerController.pause(); +// testState = true; +// setTimeout(async () => { +// await mTimerController.start(); +// }, 100); +// }) +// +// it('resetAfterPauseState', 0, async function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(TimerState.IDLE); +// testState = false; +// } +// }); +// await mTimerController.start(); +// setTimeout(async () => { +// await mTimerController.pause(); +// testState = true; +// await mTimerController.reset(); +// }, 100); +// }) +// +// it('resetAfterPauseTime', 0, async function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setTimeUpdateListener((time) => { +// if (testState) { +// expect(time).assertEqual(0); +// testState = false; +// } +// }); +// await mTimerController.start(); +// setTimeout(async () => { +// await mTimerController.pause(); +// testState = true; +// await mTimerController.reset(); +// }, 100); +// }) +// +// it('writeTime', 0, async function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setTimeListUpdateListener((timeList) => { +// if (testState) { +// expect(timeList.length > 1).assertTrue(); +// testState = false; +// } +// }); +// await mTimerController.start(); +// setTimeout(async () => { +// testState = true; +// await mTimerController.writeTime(); +// }, 100); +// }) +// +// it('resetWriteTime', 0, async function () { +// let mTimerController = new TimerController(); +// let testState = false; +// mTimerController.setTimeListUpdateListener((timeList) => { +// if (testState) { +// expect(timeList.length).assertEqual(0); +// testState = false; +// } +// }); +// await mTimerController.start(); +// setTimeout(async () => { +// await mTimerController.writeTime(); +// testState = true; +// mTimerController.reset(); +// }, 100); +// }) +// +// it('addZeroMin', 0, async function () { +// let num = 0 +// let res = TimerUtil.addZero(num) +// expect(res).assertEqual('00') +// }) +// +// it('addZeroMinus', 0, async function () { +// let num = -1 +// let res = TimerUtil.addZero(num) +// expect(res).assertEqual('0-1') +// }) +// +// it('addZeroMax', 0, async function () { +// let num = 9 +// let res = TimerUtil.addZero(num) +// expect(res).assertEqual('09') +// }) +// +// it('addZeroOver', 0, async function () { +// let num = 10 +// let res = TimerUtil.addZero(num) +// expect(res).assertEqual('10') +// }) +// +// it('addZeroNaN', 0, async function () { +// let num = NaN +// let res = TimerUtil.addZero(num) +// expect(res).assertEqual('NaN') +// }) +// +// it('timeFormatZero', 0, async function () { +// let num = 0 +// let res = TimerUtil.timeFormat(num) +// expect(res).assertEqual(`00:00.00`) +// }) +// +// it('timeFormatMinus', 0, async function () { +// let num = -1 +// let res = TimerUtil.timeFormat(num) +// expect(res).assertEqual(`00:00.00`) +// }) +// +// it('timeFormatMinMs', 0, async function () { +// let num = 9 +// let res = TimerUtil.timeFormat(num) +// expect(res).assertEqual(`00:00.00`) +// }) +// +// it('timeFormatMs', 0, async function () { +// let num = 10 +// let res = TimerUtil.timeFormat(num) +// expect(res).assertEqual(`00:00.01`) +// }) +// +// it('timeFormatSec', 0, async function () { +// let num = 1000 +// let res = TimerUtil.timeFormat(num) +// expect(res).assertEqual(`00:01.00`) +// }) +// +// it('timeFormatMin', 0, async function () { +// let num = 1000 * 60 +// let res = TimerUtil.timeFormat(num) +// expect(res).assertEqual(`01:00.00`) +// }) +// +// it('timeFormatHour', 0, async function () { +// let num = 1000 * 60 * 60 +// let res = TimerUtil.timeFormat(num) +// expect(res).assertEqual(`01:00:00.00`) +// }) +// +// it('timeFormatDay', 0, async function () { +// let num = 1000 * 60 * 60 * 24 +// let res = TimerUtil.timeFormat(num) +// expect(res).assertEqual(`01:00:00:00.00`) +// <<<<<<< HEAD +// }) +// }) +// +// describe('CountdownTest', function () { +// it('setTime', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { +// if (testState) { +// expect(totalTimeMs).assertEqual(100); +// testState = false; +// } +// }); +// testState = true; +// mCountdownController.setTime(100); +// }) +// +// it('setTimeState', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// mCountdownController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(CountdownState.PREPARED); +// testState = false; +// } +// }); +// testState = true; +// mCountdownController.setTime(100); +// }) +// +// it('startState', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// mCountdownController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(CountdownState.RUNNING); +// testState = false; +// } +// }); +// mCountdownController.setTime(100); +// testState = true; +// mCountdownController.start(); +// }) +// +// it('startTime', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { +// if (testState) { +// expect(currentTimeMs > 0).assertTrue(); +// testState = false; +// } +// }); +// mCountdownController.setTime(100); +// testState = true; +// mCountdownController.start(); +// }) +// +// it('pauseState', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// mCountdownController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(CountdownState.PAUSED); +// testState = false; +// } +// }); +// mCountdownController.setTime(10000); +// mCountdownController.start(); +// testState = true; +// setTimeout(() => { +// mCountdownController.pause(); +// }, 100); +// }) +// +// it('pauseTime', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// let testTime = 0; +// mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { +// if (testState) { +// expect(testTime).assertEqual(totalTimeMs); +// testState = false; +// } else { +// testTime = totalTimeMs; +// } +// }); +// mCountdownController.setTime(10000); +// mCountdownController.start(); +// mCountdownController.pause(); +// testState = true; +// setTimeout(() => { +// mCountdownController.start(); +// }, 100); +// }) +// +// it('resetAfterStartState', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// mCountdownController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(CountdownState.IDLE); +// testState = false; +// } +// }); +// mCountdownController.setTime(10000); +// mCountdownController.start(); +// testState = true; +// setTimeout(() => { +// mCountdownController.reset(); +// }, 100); +// }) +// +// it('resetAfterStartTime', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { +// if (testState) { +// expect(totalTimeMs).assertEqual(0); +// testState = false; +// } +// }); +// mCountdownController.setTime(10000); +// mCountdownController.start(); +// testState = true; +// setTimeout(() => { +// mCountdownController.reset(); +// }, 100); +// }) +// +// it('reStartState', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// mCountdownController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(CountdownState.RUNNING); +// testState = false; +// } +// }); +// mCountdownController.setTime(10000); +// mCountdownController.start(); +// setTimeout(() => { +// mCountdownController.pause(); +// testState = true; +// mCountdownController.start(); +// }, 100); +// }) +// +// it('reStartStateTime', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// let testTime = 0; +// mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { +// if (testTime > 0) { +// expect(testTime < totalTimeMs).assertTrue(); +// testState = false; +// } else if (testState == true) { +// testTime = currentTimeMs; +// } +// }); +// mCountdownController.setTime(10000); +// mCountdownController.start(); +// mCountdownController.pause(); +// testState = true; +// setTimeout(() => { +// mCountdownController.start(); +// }, 100); +// }) +// +// it('resetAfterPauseState', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// mCountdownController.setStateUpdateListener((state) => { +// if (testState) { +// expect(state).assertEqual(CountdownState.IDLE); +// testState = false; +// } +// }); +// mCountdownController.setTime(10000); +// mCountdownController.start(); +// setTimeout(() => { +// mCountdownController.pause(); +// testState = true; +// mCountdownController.reset(); +// }, 100); +// }) +// +// it('resetAfterPauseTime', 0, function () { +// let mCountdownController = new CountdownController(); +// let testState = false; +// mCountdownController.setTimeUpdateListener((currentTimeMs: number, totalTimeMs: number) => { +// if (testState) { +// expect(totalTimeMs).assertEqual(0); +// testState = false; +// } +// }); +// mCountdownController.setTime(10000); +// mCountdownController.start(); +// setTimeout(() => { +// mCountdownController.pause(); +// testState = true; +// mCountdownController.reset(); +// }, 100); +// ======= +// >>>>>>> 34e999d (add timer ohosTest) +// }) +// }) } \ No newline at end of file diff --git a/signature/clock.cer b/signature/clock.cer new file mode 100644 index 0000000..d090a30 --- /dev/null +++ b/signature/clock.cer @@ -0,0 +1 @@ +MIICyTCCAlCgAwIBAgISICMSIRYVBCOngOKJ0dhuk4YSMAoGCCqGSM49BAMDMGQxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKDAZIdWF3ZWkxEzARBgNVBAsMCkh1YXdlaSBDQkcxLzAtBgNVBAMMJkh1YXdlaSBDQkcgU29mdHdhcmUgU2lnbmluZyBTZXJ2aWNlIENBMB4XDTIzMTIyMTA4MTYzN1oXDTI4MTIyMTA4MTYzN1owRzELMAkGA1UEBhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UECwwKSHVhd2VpIENCRzESMBAGA1UEAwwJSE1PU0Nsb2NrMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEevCiCDQa9OueIrEeoxF1jY3ZXhisqWGPHIrEdq4SOPSwBMNKZ+RWiMTzn+4Jdp2o/GWQ8ZVcMi3JCaZ0870C0KOB/jCB+zAfBgNVHSMEGDAWgBT69fe+IFZdXdTabfEUFTwdCduyNDAdBgNVHQ4EFgQUa46OLHffyuVHi7oYxljSJCLyrpUwRgYDVR0gBD8wPTA7BgRVHSAAMDMwMQYIKwYBBQUHAgEWJWh0dHA6Ly9wa2kuY29uc3VtZXIuaHVhd2VpLmNvbS9jYS9jcHMwDgYDVR0PAQH/BAQDAgeAMEwGA1UdHwRFMEMwQaA/oD2GO2h0dHA6Ly9wa2kuY29uc3VtZXIuaHVhd2VpLmNvbS9jYS9jcmwvc29mdF9zaWduX3Nydl9jcmwuY3JsMBMGA1UdJQQMMAoGCCsGAQUFBwMDMAoGCCqGSM49BAMDA2cAMGQCMEQEV47ikXRLxA8jFgO6yn8j324pYU6fJmnQ8RU+PH4Tb1kwf4Mx0uBYgC9XagkAOQIwcqcGF3K2uoiRGie6Cn9RloCllAo49Y9hnQQS+E1BswDh0kOzd/ImSRFTR4NYkyq1 \ No newline at end of file diff --git a/signature/clock.p12 b/signature/clock.p12 new file mode 100644 index 0000000000000000000000000000000000000000..330f05e8609a3864d0406737f71d9d8e7d833a3c GIT binary patch literal 8907 zcmb`Lbx=G zQ)gXgO^>tm?zieb`Nl?7-(t**1$4xzBv0Hy!)8xgQr1c>@}V%ErdZ#RBw$K>kmi|uL z|8cKcv@D?k!ICU!z!s|Z&C?*zlPSw(nrF>(DQKVio5})kNcHf?VyV>9_6xbpqIF-d zxu8R3qsqLQyw+8({kxy*v#*|5p5lCT<{!Rq^gICgp)TMPdzh(mSnPl@|1f~XB1A0k zZ)xbgQL+J8089b;fBHB5lN0G11?#uV;f>Cl{_l9Ou(EP+0ol1&xH;c=K>jTrQI+nN zTH#Y+_bN)kJVyOOxNxK;fAN6BXeDNwOfgFvKJbX6y1}h9w{Dgz{OF&QIosnV6m={r zHxhe1uItXhj`YMd16Zdv(yX3rK7aCg6<{~J%kR3N&EZM|SOf|=yffblHt8r8fL)Hc zV*#<407d_q2M00ezs=*nqQJ%pV&&vy=imeaeINf7!v8lRFiizC8z`l|bQwzgVEoku zch`pz`&UA+&y3WVG0K5gb#MZmR~nKG@QAQp=z4P?cFZwD7W}~(zbCPgTKlb8o&qPa ztq_HdZEoD@p51OHPdYi0yKpWIo*ir6`H)Tg?}UKiTVNQzg@ygUPl-Q6fQ=o<#`#A| zApH&Z-F|wd(!H+78+R||OX2Fuwf60df4PtSMdMkn%O_=h<%;*<5KCwZeX2g>Oy!B* z61QJWU2p9uu?Lxmt~;K?m)Vrxg2*j*C+!|zIglRASeGZ31$ttUww(5q}p4=#PN-0|)Hiz_ByP^s-Thq`(!3c%Othu{Y3f zfBhE@{ezKW!wZ#l3&4xInt87WyZoWc5m^F2w1XJt@K`sS_?JjA7TKD}^QZ5y93IR) zTivb_^<&5H?cvHV;zBYXthZOk=UaN>S-gO1|4ic(vDE*VhQS++uW!}T_`j?TAWjw* z77);P^>2V6oi$g-iAVR3PhYdCwDf|~h$M6U3j_vd-eK${MRZg8Y|*b%>vIe*--Ou@ zg$9zO`un{m7!(iR=OH#w%(t3yc@a=;Rh`F+Vv^O}JSUP)H6rmzR_ax1QP!2~sQFlw zfz1C5f}Hq|@@DY{LmyxcV0?@8KZ+sa+t~1}RWJga0IdJc9tR668#j=fjfEQo^dk`a z&t2hQbNmR{-y{}D2*BHo?jJJ{|I!>_|7eYbHF%MW09a`6l&HEd&yKvJOoqSy^z7q@ ze_5_(W%6U%jSs5(#AUBx_WIKeJA1OirA6U*dJw;U+_JbME*r1@)Xz^iB0*AtnDbak zb#wQ$`M;8j@^sZDI#sAWm#!r~kjIdaGbR53s<+ZkcJ0omXok^RVDp$8(uzJV+@iop zURltj%9$mOJ3aQ#v%AlETOKQgk2Z`6L4Rgkr!NSWiVwNsEQAVM`fTP5CyaF_8l@5o zc6Uup&vS)#J%#`HvP(pA6 zCCPOL`Zl{K+QU2jy05AC1na}cxs8l1Fm=kE^Ld*B@(%7NrSUum@kf#q@7l==j*443 zEOX7Ce`Cs+1sG_B+peECqdCojh||CnRGy|6T6E-43tO&xb`Os@yn(i{Q%V*JIQBc4 zB3B!_B3E_mv{xG`Sc4i++{(-^VJM333eoZUh00E16( z`BU5&=k>v`{;P_kY;{!F+%q$xKdbI!>%RI_5c+x?J_RbQKWDX;lP3B?lBatj31cj* zL3E1nE4ADYPZsf4$)Au9%4)ECwxBY}tU!E5)312esN(Y|V!Q5PBSY?C#E?<)t?)I~ z(K3O8{j{1m?x1Q4)WF_`Z=%wVR5V0(V31lksMxXzVyHmZqj;QKR=%B44y;>dQ_9>` zzlj@dT+A&!(5w@&Dht9y;v3a8cC?KpIzL947R)Iu54ev?1a zX=l?tSlYixTtEF$<$0ROJ}folIFV~ofPaXV`DM8Nhrk8UqCsWZ%WWy*OAge9-2C9Z zn1*I!f!Hu^@2H2ZBR-N;8D2qfFGTeX+aQQ6YLfdTAM3&A0PKla3=K3@8$ zS2WdFAucWrN|(@u?kQko7J68UBx!zu<)S)k-bN&5Vyfd<h8}@s{KZh0kSkt)+s8h2`E`n4%{Hhy`^Pw}F!*d{>bHdjtwGLxTfCk47XX9rF z4QSMee#v?F_1BF_?D0SkaI)x%)vB$a?>Is#vi3z5< zCYYQu8)~0=0w)M-wv2WD;9wB*JKta&7YCnV-Byv$j-4o1#Sd_8#5mnFBTSaI6#oYT z-qMpQb$1~dU8T)(pPK-GUpDdeqL^k>)(Qt>#0YB;4oU2q=d=04g z-AE!!iIzV~R~<2$bKE%l6f_Ic4BNx2tOd9;FmBO6VOw&N*=W$>jQoTl7})g<{v>p) z6U1`C+2==j4JzP`c}#jWZ*mo%S8bNXy;i%8gQyxo5Ha(r<5>~SjC*xCu4IMdzKW#n ztvhpPUfosEIpPCFL|R&5L{7E!b4zMKXYJHKzvNvmQ}~xD3<6_}!3qqC-`$IYrS4JJ zqKXw@7)T$aCAZX_Ek6@9n~gOFXd(Dof7y*h1a6_fEQOuVh3RD;&)=+?8|w~x-_2z{6o9yHMNFRAjnI94G)!|dXWKO-{Sunz zKrfpOGRuCo7jvWI2#d}@e}-Eu+JS=V{N?xi{t}os@4- zf}p(XJdfFJ55*l4Bieq&RAI+M?SX?bw+hq1%c(S%fi^8Ngc-Dya3sQ9d)k^1ao8L_ zv=D{*Z0acIk|Jc94#sr89&@qCOsl}a5+t+W@W3R3e&Httw=%lq;u7z^B_Am4zePzl;;7Y!#r&GsPL%~p)`JZ!y40hx^) zN%1_58iq3r!f_eppQQcm$duPfvEIbkPF~NEDx&@p89cTuvR0BQ<00m2?>|baZq}s# zp3yHn6%`OV9UgCwj~C zU#-y-q$op`y=sYUJL{yI`OgB_YD*neUrp%thH-Sh#Th7dV2me8BOw)7KEc!=i2R%w zZW)|+(4%*^d!n3d41#dKN|$GSuOk^%iciUBZhYD0HqN9<-#lUOAaaxA+O}%Lc&b&xLVv_9Hxk3pVRxkMN-oZsmgm@%`x$O}bOJA&kal7SWR3CZ1tO98TSW!Y@}Z50 zZZvhwsLI4n+-eBqXh(crzr*()|2PJV;5J4oCotRq?aPxdQPSA(@wV$KKqA1ke2r}% zj?z!2)=`7?8im;Ma9aYMgyXD?iIe8%SO2{Hpt9H^VzJ*C&KAY?F@-gKe(N67D; zOIj+-YzpbmR^z!F@vt*;wm*PQW(+k0%|l$aXoDDRmxV3~+N97+z+*V+qmo8id@(E8 zJ`=Rt$-Z_|o)*xEY}l91guV1ZAL`>r*0lERI4J*FJo#8jt2*uUp0tUN#UtEj930l~ z#!)V%PXqLN>u@PqdWq*e1c$t#xpEdNRX@do&F><7+;1~YzV0_(n-uKQiz$6Bz+~o` zO5Oz!$kTbxF&wj4y#m^#7@FxcLDj#oKm5ci+$s9@72+M;$yLVyPRZn??z~r}Zva0S zPG~}+c^ye_5hr4tgTw`OS#Zn3R^wqWkY%&(J_%9@mFG)o zfb7ZCbYEIzNp?+d#!m-?CF{=fi#R}%3k5dWbLNcBHyO0?I51)kejLOW0bqYzR0bTy$_fL~fS**qn zbO+ZTVY)e*WY$bllTOIgN`v6j?|7X*Vci-2!ayRVyt*3mokmDAPs-?34UgPDg(hnQ znCW^>L+xx+-seZFOLYhCaI-`kF39))*3MCw?uIEK)ugQ!(LS!wW{9=aT5UO8m z0`4pl^jCz&ixCBNCt+KT!?hCJ?ON_teJ3wc!4Erwwr)YdboFT~qHkK9p#Prim0<^U z``Ox4?h{KPL%ZQ3%5Szugv-%ghrKq zHlYRdDg`?!v_Zs5WNgOl!Z-&6S>^#opLP{jY!0|5=BfM)Jz~)+bWI)!4(a)n$tT9d zd{lNk;e&pj&584ZZ0+o0pX@wIqh$Fu^}4Tq^`WIk`T2LB_~0QK&iizg!=(~d7Y*xI zs0F`AzeIpiWg4R~kj#KNE#Rm`0b#Ye>TrdcgN~8kUzoHX1Nu;N^=bla*Cf}Zj7ZJl z9gpAR{YH_)H>4GETNGOQ8vUYD@_Ss*4`P49)R{WOmcF6rWe}&5t=}15==}vi`AJo8 zHhJ6;t-G`l4x(earlm`^@YQ+fot2AlOdWJb^Ek_SimL08{?ArQ{b38}cm#;q@$m$t zXXD&~@^tVg6~-4x%z{Dq`mJ}`xTb~2T+bU>``kv-8xk+9o>(7JwlsfG%-$Fp3{&dS znI+$_W7*0h5q%lV)@iB2zjX+czW8H(@KA9*tVp$d+pv?<4$Sw5Wqh)AQ`7to= zjqI?uuB-#T3}Cuq`=tEy?74XYiV19J@;knj7(?bGV}8SnEkmlB)QW8HQwD?6^w4fD zrwWE{6AJt2?qxj8C%9pwlv}z-HTRaOD3cS&9?A&*S6=GYqdsBg zNDm-LRFC*jl=t-@jis%py=bg$Rdn}54kF#Iy^DNv6LvQqv6bmHwL@AEtF)v@i~$l~ zGdkc&hgDp|rYcAtjTZV9;CEk1ro(BELV0;XR=gZ#lNVRy-Ml<;K2VB+<s zsa>)uQv0L96hd+Y8`d4;Gwd}y9_R<{?nGVD3k!;;C>9$Dipg~%%STfyB4Pp`h1QAm zI!$&$mZ5oGd4&ij0$MK6E-Ph6{DxJ0yYK5lpeYmBV=89+EWz@k_|m>xe%z7ZV zI=LTc%kQH-r3OqAt^TNpB^cm`QRXQsW>Q9SK0ZUe*rp^XR*+9*Xo)GE)t^-(j1{ff zfhS1zrl+u~y`T}4zhp_C-(b`)iG9PAy*8BagCuyCwJWm26)kEMLKi+# zZT+W6Pt}c2>1pWjR;T0iJ&xOgBb+Q98q~KJkUpEF?z1RC!~>1VwTwmL)%#gbk_1h^ z1(ygRNTM-+;^JuDeOP~!s_$GG4+*sCCW__!`8#Du1j33xqLAVa4SBf{v7H{|?j1>V zm&@;PLW*A5}1;Re>#BH5=O0O1a)}qw$v(MoRR0(Sfe(Y zEmR}G`Z)gWB)3@hCLLK}ig&sJR5Iql$jH$+mCiSPEknGtL6+s7t#r^u7GcU6H94Ez z0L!xe{;G(MUk1tAH!9!bHF&{+Ke9bu=;KAu`K^iOK@Tslzs#Hk`s7AJ+rQ}z3$V^u zX1Q~vUWlugp(dzrl~~lYLm#8vWpTaze!)r+PbY8pn4g>EAcND;P5c^eQQ{G^UY%t# zZeY07^hzF@4V4Vj*b0L+*Rlq07Q zQ^X023G2(LW~guvSoS5Os=M{wEK(U;^Jv!(91=9F`WjW4Igbs;>A%-#4KV zA9)2OMph7P4l&apmH%5D6g{Wn+y0nU=cR{*X{XGn4|n=r7OpyI|Njl+rXglTi|cF`cRIKK^wMTN^w+Ymjr% zDv^LG!%^`UbV{+D$a->l3n48gt7W3*nfG!5p6Q87zs}EF95k?b$c!_Q3kVMrZ`~IU z1JIsFRtRh87;*=w+kEtcv&7tPtD`d&n_4&t^$?RjWaLasqhCYRKT9T%k3tEE#fNNc+N42CM*=ze?-ldF|rFTGlJX-#g42E69fiF0VAxaEd>1%3N z4R?@>BS^b+7E{Aa+1A-~@%+6g*PP%uK1{2y-Q1~Noh!C~bdS=OcRQODzHkyO{WZFh zW}@O-E;huzVFXf$=(4QN2QJ(gPfz07O3K0GCkG_!PUvW41DF7NgN%0zW806XX0ULz zQ)_az{yxstJo6H=X&aVbXyw|ZmZyWX8$NEMl~K!Qd8I%6Q8A*+QatEWE0}w zx8)wn{b8j9=Dx84>g-~V)+o-H>hg>71$;H+EvcKG-^};cN9WuGn9NPBa z3R8lY3CZ?N@2*-d0;ga;^V+0>IQPHAX=+Z+cvhN0Pas3YKBs)+st8%P)NGiA6v|6s zs5lpP$Vu=)zD_2Jsd7JmcVRW8r`b)t9JL>MhEcq*>KQLwKep6tfiJuoun5i}JWKp! z_bSz5!2d`MnKt2<>Zrk-Y|l-!xxIBC9ze}QGI5%<*-Kl2?*zd;Cv+)ir=`d1K;=I{ za5e}Qm`4d*O#j^MQ^=6RK1-us9L{eb<0>a5SmXZ^Ef;j>vY)KL0Y}Nt^ZA)XcPP=d zBgP50xUT-^BnU}FV^w0Y)J^s4W-sDV#u)#uBh-~A?`yqv{4}4nqt=L56yqRt)(5feL(mCBGj<2lsU`Dl)k=&cWIcT)hSDCzaM0epaPuzkY08~_U4W{ z1z-wr($@Ef93dQ4Kwe|T!-9@Jl$~I~(}3xU?cNV@+cIl|>tjDKC@vxbq2rK!Yv`to z?k$e-WppAnXir+AV|&-K3E6T`kh{pqWH^=4XE|xCi{&VWZuNk}pZulu7j1{vn7c!g z6(lsDc%oaoPsEsR&J6m!NDQ@fdBfOs;XT(|zHLAwaGJC1HjHD$&trpi*`1x29hSP3 zePSM(P#06ooAJ(RWE{z0v%*X)(}r_;m*`Xi+!dSJHJ`&YnZi_a_r!$eb{;U!Hz?uz zW!i1Vg@m~q<*=+poqH}8=d9bTjE%?;1}81Gym)*)h=SP{iqZ(dNzd9+p)ODL9j+T^ z+t)`QiiZz_^$cb%jDOe#zo_KYmk*5|H%>p{yOyt?ePh#w2lRYQ{jyhhnv39@yK)RV zar>}ymU`2t0nwKp`}G=52$sTYtd|6IqqK4;Buy`q-1}tIQ2Z_eFTlR>72@F=0{6t= z;fH*wAwoAKc6hX*V_Hc_USv`2r$HH2Qaha%joK?7Rl=H&nG3fL?dCv7Y`ps;Rv#Kv|1f%;tc(4q7}f&v~@@stTpKY`pz^OQg*8^3*z*} zud~%;58eKIs}|)nR$-0?)OF4u!W9Wds-*&d24f@Ik(A6VR8IuNK#APER^m*eF2RI3 zi(#V8f0lX`Vm7)jY3D87g+H^gF7*9c3wg977c@Y^ce;&eq@7t}<$ZqayY1C`nTzLL zjpEUqlO5$(W@z=Dmok&pS0kQLE_DElf-*pQc#w?`z}ucG%BInyr=Xs0voHlsH#0W5 z^@&eJQks9Sj8V-B^CJTx@qDfoo1<|F3Ac%r4u0V4p6%s(#kWT8lOw9)tgvSn+5(t< z$)`HY??GvjaOI%c!VeVPQ)d!5tgainKTW5O4x4aiy0HQYt9Ke6rh@$M_Jvf%@Ma`ea#s6 z2|vFNoeIjwirQ8!*lJY`%EY6iW4ldeFU-a?J+@bh@Qi^Hwhs$srD*YWeF)(&1PTDj zfC#WK^r%pf_%HxyEInz(G#i4m`=vgs+lH~S=(c&9FVI+GElMccoaJTOKT7JnGCCIm N(!%y2p&){C{sXi>2F3sY literal 0 HcmV?d00001 diff --git a/signature/clock.p7b b/signature/clock.p7b new file mode 100644 index 0000000000000000000000000000000000000000..38e843194693eba4633834b03e00a3f23afed161 GIT binary patch literal 4118 zcmcgvdvp_38gCvAZE0n9&M85)wBuiDU)Q9Oq$H3c@3$+ z)*i+8<9MttyC_BWsAu80%dQ|QtBdRUT6K@^>LQDZ0=sa~gQDObJ<8rm3vG`c_3R(! zk7VxmefNIfz2EQm%N;P%Fk4Z*e2Z<%7j=>u(y@^S85pU*AeBgvIxtgFy?%>L!pP)O z2^dizlYx@45rs-VqF8jDs>oz>EYGPqI?bq7s?avj22?99nIn7@n$Z<1I+IaH*<3!$ zh6{OdLcWv{C-Av`mdi8oEG=5$%tC>ULc7*P>rGfVrbfeIy;>UujG6|~s2b>j!D!U# z7zQ8c177v$MQ@TnhhpIs|C=!X@zQ{ zf-I9_=o|wV3m3R(iczy%j2G97GDRlEXPB(IT#;Un8VyFhK?k%5FzR6ZwEaYwY@Us= z5jrn!OD%o~%4&BhNy_WDTkIt6r^?n|&S|%kMKej_108W(uoHOPZjadnW4E7(cMk{x z84ub!_>g@l2`F4}3L!J)aN+}29On0 z+RgqX<#ys1Yaru-^pdbENWFRx-Y;{kKnYD&^W zLUiiOzz>W#LSElcu#8O?WwX!mIVtq*s^nZWyv+UPE^> z9@7f(SPq_pO@NhoL6RWif`!MsT8lkcktMo&0$3NC#x_`pE~CxEg>y-dU=w%f1)T&D zq%01`)Jb5vUM|aWJzcp{Pex;e=Zqs8miNIar9dJCD7)W-yJsvYI zxOm)5r0^t#=MYaSf!e~!I9uvY*{zN|Z*TSMhu}GcBd+eA_K=mw!jASphu1ymb2x`c zk}H&gL6T{;^mqsPZZDq1!^T{!NCyT=R?@^0*=Ujv_au9{yoa}-bY5HNK_WdJ&S<%&e^$ zHz-NMiYx+`i$+=PoJph7%kz09i7TxrSTwf|1pq_z2#TSYDKxiEJ9P-6|4&j4P>(0Q za!OJkAIl4LmQniHILC5vrH{!L*$4v*0Y%WDm{H!SRHCSCywbN{Pjf!F_tZ(p@7~`zBAEk5B(uO` zSZ>P|@_C?nxM}B|p>v@FgSRhFF3oPX-Sgo+N0b$yMGUx1-YjqW^4u5CCQYLC|luI4qPU#Q!g$ohsaQXm^fZ6?+1Ng-B@s}drQ|3uakr4 zoMQ5tNH%3*Q;eAxxHkB_R-^*FU93jHm6|L>M1LIZhj!676?d|D1&B()fJBaXed~YI zyd-0Pa;Rh3+J@uT-201fcJ>`=ILdvB7FD0DZaw(%cIMV?xwBW#pLIeCl5FsW(F>p7 zys-Dl#~tb$4m5AD+<3b0jCp{?yc3ej+%Eq`=_)#e`s56Ka5@K#fy|pm2#4wIUNIh*vX>cr9Qd>=k5V zldaRIG>>1B(hQeZ`WZTnK+=hBS?$%L8_#4?;++U;k#u>aI|D3&mJ{^vB76V&13aO9k?!%k@^xnE}ijWBNAraC@ryEaqB~e*Ke-Uo?UtUpJzYTuTHMo`nG1* z6HmV)1@68&cgr>V492n2V|PDy=(=sn-G$aix1Tt@b@$0r`xf0aYMh==;-AQ`-{D~DhiQ)~EXNFY)G0l*}_NdUb_f$|VRwHO@2vKqjAX_b0Zui9z%pr-I(>Rudz12(vI|8Gz=Y}m zoFz5{c4m%TRwI#%fgPIt+h3bMS}X_Z0g50xBl4|4m@KqUKYD0v*02}|KL%I_s?(P1 z7+ikmIl-Vj30)X4AVydVWOccYEZh6y?2j(H`cfSzFk#tJ^5Y7jB zM2Estm{#x@{M-9s0+n~zB&zxPsZEt7DgYxzDvs?xb>T?Ef973ybKzp-DVOK`>tv98 zqcC{GqTjxH)hKzN+VnG~Y3`{9hb?#g_iFsK%J&b~zP0j; zrnT=slRf;v1KELL_lr9g%zbNy@vHqSP8@sp(^n26BN7U>4;N9D`Dw5ut-2)UQ{R@y zSV+2yuhrEu1o|$%V@u$*<>H{a5~&xPH;zV8m_tA_>6IfYxzh3X&ZzC#4<70~5#PH; zU-9l@QupTbcgL43b)S9sgm>BPQ;u3Hw*a$LS$XbN4