diff --git a/README.en.md b/README.en.md
index 56685eaebf56e84ce66c42cfcf369877c740811c..3e1eb6e265586b8b6e99674d0f5f361b10b0e84a 100644
--- a/README.en.md
+++ b/README.en.md
@@ -1,6 +1,6 @@
# Music Album
-### Introduction
+## Project Introduction
This codelab implements music album pages based on the adaptive layout and responsive layout, achieving one-time development for multi-device deployment.
@@ -16,7 +16,12 @@ The figure shows the effect on the tablet.

-### How to Use
+The figure shows the effect on the wearable.
+
+
+
+
+## How to Use
1. Install and open an app on a mobile phone, foldable phone, or tablet. The responsive layout and adaptive layout are used to display different effects on the app pages over different devices.
2. Touch the Play/Pause, Previous, or Next icon on the screen to control music playback.
@@ -24,13 +29,13 @@ The figure shows the effect on the tablet.
4. Tap the comment button on the page to go to the corresponding comment page.
5. Other buttons do not have actual tap events or functions.
-### Permissions
+## Permissions
N/A
-### Constraints
+## Constraints
-1. The sample is only supported on Huawei phones with standard systems.
-2. HarmonyOS: HarmonyOS 5.0.0 Release or later.
-3. DevEco Studio: DevEco Studio 5.0.0 Release or later.
-4. HarmonyOS SDK: HarmonyOS 5.0.0 Release SDK or later.
+1. The sample is only supported on standard systems and is compatible with the following devices: Huawei smartphones, tablets, PCs/2in1s, and smart wearables.
+2. HarmonyOS: HarmonyOS 5.1.0 Release or later.
+3. DevEco Studio: DevEco Studio 5.1.0 Release or later.
+4. HarmonyOS SDK: HarmonyOS 5.1.0 Release SDK or later.
diff --git a/README.md b/README.md
index 17c075c2b843df793450a416e2f65f914897e68c..c8fa3fffc6ffecd631fea269327209143dc85950 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# 多设备音乐界面
-### 简介
+## 项目简介
基于自适应和响应式布局,实现一次开发、多端部署音乐专辑。
@@ -16,21 +16,26 @@

-### 使用说明
+智能穿戴效果图如下:
-1. 分别在手机、折叠屏、平板安装并打开应用,不同设备的应用页面通过响应式布局和自适应布局呈现不同的效果。
+
+
+
+## 使用说明
+
+1. 分别在手机、折叠屏、平板、智能穿戴安装并打开应用,不同设备的应用页面通过响应式布局和自适应布局呈现不同的效果。
2. 点击界面上播放/暂停、上一首、下一首图标控制音乐播放功能。
3. 点击界面上播放控制区空白处或列表歌曲跳转到播放页面。
4. 点击界面上评论按钮跳转到对应的评论页面。
5. 其他按钮无实际点击事件或功能。
-### 相关权限
+## 相关权限
不涉及
-### 约束与限制
+## 约束与限制
-1. 本示例仅支持标准系统上运行,支持设备:华为手机。
-2. HarmonyOS系统:HarmonyOS 5.0.0 Release及以上。
-3. DevEco Studio版本:DevEco Studio 5.0.0 Release及以上。
-4. HarmonyOS SDK版本:HarmonyOS 5.0.0 Release SDK及以上。
\ No newline at end of file
+1. 本示例仅支持标准系统上运行,支持设备:华为手机、平板、PC/2in1、智能穿戴。
+2. HarmonyOS系统:HarmonyOS 5.1.0 Release及以上。
+3. DevEco Studio版本:DevEco Studio 5.1.0 Release及以上。
+4. HarmonyOS SDK版本:HarmonyOS 5.1.0 Release SDK及以上。
\ No newline at end of file
diff --git a/build-profile.json5 b/build-profile.json5
index 146e5bd23f2d3904774233687b69066f0beddca1..87aa30168395a8a5e8a7998f74923c2004140ee9 100644
--- a/build-profile.json5
+++ b/build-profile.json5
@@ -5,8 +5,9 @@
{
"name": "default",
"signingConfig": "default",
- "compatibleSdkVersion": "5.0.0(12)",
- "runtimeOS": "HarmonyOS"
+ "compatibleSdkVersion": "5.1.0(18)",
+ "runtimeOS": "HarmonyOS",
+ "targetSdkVersion": "5.1.0(18)"
}
],
"buildModeSet": [
@@ -74,6 +75,18 @@
{
"name": "constantsCommon",
"srcPath": "./common/constantsCommon"
+ },
+ {
+ "name": "watch",
+ "srcPath": "./products/watch",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
}
]
}
\ No newline at end of file
diff --git a/common/mediaCommon/src/main/ets/utils/BackgroundUtil.ets b/common/mediaCommon/src/main/ets/utils/BackgroundUtil.ets
index 1ac92b37f02f31b7492d9b4eedb320bb0a450f93..74715949ebd97426464acd26444f135ac0d53bee 100644
--- a/common/mediaCommon/src/main/ets/utils/BackgroundUtil.ets
+++ b/common/mediaCommon/src/main/ets/utils/BackgroundUtil.ets
@@ -27,7 +27,7 @@ export class BackgroundUtil {
public static startContinuousTask(context?: common.UIAbilityContext): void {
if (!context) {
Logger.error('this avPlayer: ', `context undefined`);
- return
+ return;
}
let wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [
diff --git a/common/mediaCommon/src/main/ets/utils/BreakpointSystem.ets b/common/mediaCommon/src/main/ets/utils/BreakpointSystem.ets
index e1a5789bf1ad5e9f625c222fa741adea9454d318..2c1cdd2f998e7c7e2f4573042fb7b4142fdd2634 100644
--- a/common/mediaCommon/src/main/ets/utils/BreakpointSystem.ets
+++ b/common/mediaCommon/src/main/ets/utils/BreakpointSystem.ets
@@ -60,17 +60,17 @@ export class BreakpointSystem {
if (mediaQueryResult.matches) {
this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_SM);
}
- }
+ };
private isBreakpointMD = (mediaQueryResult: mediaquery.MediaQueryResult): void => {
if (mediaQueryResult.matches) {
this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_MD);
}
- }
+ };
private isBreakpointLG = (mediaQueryResult: mediaquery.MediaQueryResult): void => {
if (mediaQueryResult.matches) {
this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_LG);
}
- }
+ };
public register(): void {
this.smListener = uiContext?.getMediaQuery().matchMediaSync(BreakpointConstants.RANGE_SM);
diff --git a/common/mediaCommon/src/main/ets/utils/MediaService.ets b/common/mediaCommon/src/main/ets/utils/MediaService.ets
index 424268401b4b39b9a79454174807713330189582..5b65c2d919af606e5cf5cf086eaef35015d27dbc 100644
--- a/common/mediaCommon/src/main/ets/utils/MediaService.ets
+++ b/common/mediaCommon/src/main/ets/utils/MediaService.ets
@@ -46,6 +46,7 @@ export class MediaService {
private songList: SongItem[] = [];
private formIds: string[] = [];
private isCurrent: boolean = true;
+ private isNeedPlay: boolean = true;
private seekCall: (seekDoneTime: number) => void = (seekDoneTime: number) => {
this.isCurrent = true;
@@ -97,10 +98,11 @@ export class MediaService {
this.isPrepared = true;
AppStorage.setOrCreate('totalTime', MediaTools.msToCountdownTime(this.getDuration()));
AppStorage.setOrCreate('progressMax', this.getDuration());
- if (this.avPlayer) {
+ if (this.avPlayer && this.isNeedPlay) {
this.avPlayer.play();
}
this.setAVMetadata();
+ this.isNeedPlay = true;
Logger.info(TAG, 'AVPlayer prepared succeeded.');
break;
case 'playing':
@@ -155,6 +157,10 @@ export class MediaService {
Logger.info(TAG, `on playPrevious , do playPrevious task`);
this.playPrevious();
};
+ private volumeChangeCall: (volume: number) => void = (volume: number) => {
+ Logger.info(TAG, `on volumeChangeCall, do volumeChange task`);
+ AppStorage.setOrCreate('volume', volume);
+ };
constructor() {
let list: SongItem[] | undefined = AppStorage.get('songList');
@@ -182,8 +188,8 @@ export class MediaService {
this.createSession();
}
}) .catch((error: BusinessError) => {
- Logger.error(TAG, 'this avPlayer: ', `catch error happened,error code is ${error.code}`)
- })
+ Logger.error(TAG, 'this avPlayer: ', `catch error happened,error code is ${error.code}`);
+ });
}
private setAVPlayerCallback() {
@@ -196,7 +202,9 @@ export class MediaService {
this.avPlayer.on('timeUpdate', this.updateTimeCall);
- this.avPlayer.on('stateChange', this.stateCall)
+ this.avPlayer.on('stateChange', this.stateCall);
+
+ this.avPlayer.on('volumeChange', this.volumeChangeCall);
}
async createSession() {
@@ -217,12 +225,12 @@ export class MediaService {
operationType: wantAgent.OperationType.START_ABILITIES,
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
- }
+ };
wantAgent.getWantAgent(wantAgentInfo).then((agent) => {
if (this.session) {
this.session.setLaunchAbility(agent);
}
- })
+ });
this.setListenerForMesFromController();
}
@@ -278,8 +286,9 @@ export class MediaService {
* Play music by index.
*
* @param musicIndex
+ * @param isNeedPlay
*/
- async loadAssent(musicIndex: number) {
+ async loadAssent(musicIndex: number, loadOnly?: boolean) {
if (musicIndex >= this.songList.length) {
Logger.error(TAG, `current musicIndex ${musicIndex}`);
return;
@@ -291,6 +300,9 @@ export class MediaService {
this.songItem = await this.songItemBuilder.build(this.songList[this.musicIndex]);
let url = this.songItemBuilder.getRealUrl();
if (url) {
+ if (loadOnly) {
+ this.isNeedPlay = false;
+ }
let avFileDescriptor: media.AVFileDescriptor = { fd: url.fd, offset: url.offset, length: url.length };
this.avPlayer.fdSrc = avFileDescriptor;
Logger.info(TAG, 'loadAsset avPlayer.url:' + this.avPlayer.fdSrc);
@@ -372,7 +384,7 @@ export class MediaService {
if (this.avPlayer) {
this.avPlayer.prepare().then(() => {
}).catch((error: BusinessError) => {
- Logger.error(TAG, `start error ${JSON.stringify(error)}`)
+ Logger.error(TAG, `start error ${JSON.stringify(error)}`);
this.state = AudioPlayerState.ERROR;
this.updateIsPlay(false);
this.isPrepared = false;
@@ -394,7 +406,7 @@ export class MediaService {
this.seek(this.getCurrentTime());
this.updateIsPlay(true);
this.state = AudioPlayerState.PLAY;
- })
+ });
}
}
@@ -589,7 +601,7 @@ export class MediaService {
musicCover: this.songList[this.musicIndex].label,
musicSinger: this.songList[this.musicIndex].singer,
cardSongList: cardSongList
- }
+ };
let formInfo = formBindingData.createFormBindingData(formData);
this.formIds.forEach(formId => {
@@ -600,8 +612,8 @@ export class MediaService {
if (error.code === SongConstants.ID_NO_EXIT && this.context) {
PreferencesUtil.getInstance().removeFormId(this.context, formId);
}
- })
- })
+ });
+ });
} catch (error) {
Logger.error(TAG, `updateCardData err: ${(error as BusinessError).code}`);
}
@@ -617,7 +629,7 @@ export class MediaService {
}
let formData: Record = {
'isPlay': false
- }
+ };
let formInfo = formBindingData.createFormBindingData(formData);
for (let index = 0; index < this.formIds.length; index++) {
await formProvider.updateForm(this.formIds[index], formInfo);
diff --git a/common/mediaCommon/src/main/ets/utils/PreferencesUtil.ets b/common/mediaCommon/src/main/ets/utils/PreferencesUtil.ets
index be10d451eaf9a941edb60ff77a7e7dbd2520ae43..2dc7dcbc44ac7e240b19a282cb6d3120e8e962a6 100644
--- a/common/mediaCommon/src/main/ets/utils/PreferencesUtil.ets
+++ b/common/mediaCommon/src/main/ets/utils/PreferencesUtil.ets
@@ -40,8 +40,8 @@ export class PreferencesUtil {
return;
}
resolve(pref);
- })
- })
+ });
+ });
}
preferencesFlush(preferences: preferences.Preferences) {
@@ -49,7 +49,7 @@ export class PreferencesUtil {
if (err) {
Logger.error(TAG, `Failed to flush. Code:${err.code}, message:${err.message}`);
}
- })
+ });
}
preferencesPut(preferences: preferences.Preferences, formIds: Array): Promise {
@@ -63,7 +63,7 @@ export class PreferencesUtil {
}
Logger.info(TAG, `preferencesPut succeed,formIds: ${JSON.stringify(formIds)}`);
resolve(true);
- })
+ });
} catch (error) {
Logger.error(TAG, `Failed to put data. Code: ${(error as BusinessError).code},
message:${(error as BusinessError).message}`);
@@ -81,7 +81,7 @@ export class PreferencesUtil {
}
resolve(value);
});
- })
+ });
}
removePreferencesFromCache(context: Context): void {
@@ -104,13 +104,13 @@ export class PreferencesUtil {
}
resolve(value as Array);
Logger.info(TAG, `Succeeded in getting value of 'formIds'. val: ${value}.`);
- })
- })
+ });
+ });
} catch (error) {
Logger.error(TAG, `WANG Failed to get value of 'formIds'. Code:${(error as BusinessError).code},
message:${(error as BusinessError).message}`);
}
- return new Promise((resolve, reject) => {})
+ return new Promise((resolve, reject) => {});
}
async addFormId(context: Context, formId: string) {
diff --git a/common/mediaCommon/src/main/ets/utils/SongItemBuilder.ets b/common/mediaCommon/src/main/ets/utils/SongItemBuilder.ets
index 500224b53738fdc28a3c95c956cd71bf6e208b5b..cd01303505cdd73b94d6010799447728175f6257 100644
--- a/common/mediaCommon/src/main/ets/utils/SongItemBuilder.ets
+++ b/common/mediaCommon/src/main/ets/utils/SongItemBuilder.ets
@@ -32,18 +32,18 @@ export default class SongItemBuilder {
let rawfileFd = await this.context.resourceManager.getRawFd(songItem.src)
.catch((error: BusinessError) => {
Logger.error(`resourceManager error code ${error.code} message ${error.message}`);
- })
+ });
if (rawfileFd) {
this.realUrl = rawfileFd;
} else {
- Logger.error('get rawfileFd failed')
+ Logger.error('get rawfileFd failed');
}
Logger.info('MediaAssetBuilder build realUrl:' + this.realUrl);
return this.songItem;
}
public getRealUrl(): resourceManager.RawFileDescriptor | undefined {
- Logger.info(`url ${this.realUrl}`)
+ Logger.info(`url ${this.realUrl}`);
return this.realUrl;
}
diff --git a/common/mediaCommon/src/main/ets/viewmodel/MusicData.ets b/common/mediaCommon/src/main/ets/viewmodel/MusicData.ets
index c99528f39c427e2c513ba6c1c1410661f907e9d4..67e8a98f668cd4aff532f493b3a2cb97151d6a82 100644
--- a/common/mediaCommon/src/main/ets/viewmodel/MusicData.ets
+++ b/common/mediaCommon/src/main/ets/viewmodel/MusicData.ets
@@ -36,4 +36,4 @@ enum AudioPlayerState {
UNKNOWN
}
-export { MusicPlayMode, AudioPlayerState }
\ No newline at end of file
+export { MusicPlayMode, AudioPlayerState };
\ No newline at end of file
diff --git a/features/live/src/main/ets/view/LiveList.ets b/features/live/src/main/ets/view/LiveList.ets
index df4f8b48759614b5881e9b0d88a5a8bb65c2ec4d..74f778cfa706bb09292059e47aedd68cd1509bda 100644
--- a/features/live/src/main/ets/view/LiveList.ets
+++ b/features/live/src/main/ets/view/LiveList.ets
@@ -37,7 +37,7 @@ export struct LiveList {
Scroll(this.scroller) {
Column() {
ForEach(this.liveStreams, (item: LiveStream, index: number) => {
- this.LiveItem(item, index)
+ this.LiveItem(item, index);
}, (item: LiveStream, index: number) => index + JSON.stringify(item))
}
.width(LiveConstants.FULL_WIDTH_PERCENT)
diff --git a/features/musicComment/src/main/ets/view/MusicCommentPage.ets b/features/musicComment/src/main/ets/view/MusicCommentPage.ets
index 4a09f627e82a0ee26d61a0cd6d3afb6e4aae12dc..a95d12e8df53ed387b10c923be1ec3838fcb7f5f 100644
--- a/features/musicComment/src/main/ets/view/MusicCommentPage.ets
+++ b/features/musicComment/src/main/ets/view/MusicCommentPage.ets
@@ -84,7 +84,7 @@ export struct MusicCommentPage {
$r('app.float.margin_right_sm') : $r('app.float.margin_right')
})
- this.ShowTitle($r('app.string.wonderful_comment'))
+ this.ShowTitle($r('app.string.wonderful_comment'));
List() {
ForEach(this.wonderfulComment, (comment: Comment, index?: number) => {
@@ -139,7 +139,7 @@ export struct MusicCommentPage {
$r('app.float.margin_right_sm') : $r('app.float.margin_right_list')
})
- this.ShowTitle($r('app.string.new_comment'))
+ this.ShowTitle($r('app.string.new_comment'));
List() {
ForEach(this.newComment, (comment: Comment) => {
diff --git a/features/musicList/Index.ets b/features/musicList/Index.ets
index 917bff724095291c5613e4b6fb5c6d40a5c5aa16..c91b68b13ef892f0810c9fb21afb14e6f0090ea1 100644
--- a/features/musicList/Index.ets
+++ b/features/musicList/Index.ets
@@ -2,3 +2,4 @@ export { Content } from './src/main/ets/components/ListContent';
export { Header } from './src/main/ets/components/Header';
export { Player } from './src/main/ets/components/Player';
export { MusicListPage } from './src/main/ets/view/MusicListPage';
+export {songList} from '../musicList/src/main/ets/viewmodel/SongListData';
\ No newline at end of file
diff --git a/features/musicList/src/main/ets/components/AlbumComponent.ets b/features/musicList/src/main/ets/components/AlbumComponent.ets
index 47bb2eaa741c6ed2b8c6836496ab601ef837e02e..f6b8f56e94d493880afecaad80ed3c6afd0c1586 100644
--- a/features/musicList/src/main/ets/components/AlbumComponent.ets
+++ b/features/musicList/src/main/ets/components/AlbumComponent.ets
@@ -140,19 +140,19 @@ export struct AlbumComponent {
GridCol({
span: { sm: GridConstants.SPAN_FOUR, md: GridConstants.SPAN_TWELVE, lg: GridConstants.SPAN_TWELVE }
}) {
- this.CoverImage()
+ this.CoverImage();
}
GridCol({
span: { sm: GridConstants.SPAN_EIGHT, md: GridConstants.SPAN_TWELVE, lg: GridConstants.SPAN_TWELVE }
}) {
- this.CoverIntroduction()
+ this.CoverIntroduction();
}
GridCol({
span: { sm: GridConstants.SPAN_TWELVE, md: GridConstants.SPAN_TWELVE, lg: GridConstants.SPAN_TWELVE }
}) {
- this.CoverOptions()
+ this.CoverOptions();
}
.padding({
top: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM ? $r('app.float.option_margin') : 0,
diff --git a/features/musicList/src/main/ets/components/ControlAreaComponent.ets b/features/musicList/src/main/ets/components/ControlAreaComponent.ets
index 49f0327152805a448b262a608fed63b4b04f0e44..6605ea33659af3543224b7e47e97ddede1be650e 100644
--- a/features/musicList/src/main/ets/components/ControlAreaComponent.ets
+++ b/features/musicList/src/main/ets/components/ControlAreaComponent.ets
@@ -165,5 +165,5 @@ export struct ControlAreaComponent {
function controlImageBuilder() {
.aspectRatio(1)
.opacity(0.86)
- .objectFit(ImageFit.Contain)
+ .objectFit(ImageFit.Contain);
}
diff --git a/features/musicList/src/main/ets/components/LyricsComponent.ets b/features/musicList/src/main/ets/components/LyricsComponent.ets
index 667b6a5b4296d84970432fd0113b054fd16f5638..9b1ea3de6a8b103a1de60bd3ccf76bea1aa6a33a 100644
--- a/features/musicList/src/main/ets/components/LyricsComponent.ets
+++ b/features/musicList/src/main/ets/components/LyricsComponent.ets
@@ -61,7 +61,7 @@ export struct LyricsComponent {
} else if (this.songList[this.selectIndex].lyric.endsWith(LyricFile.LRC)) {
this.mLrcEntryList = parseLrcLyric(stringData);
}
- })
+ });
}
build() {
diff --git a/features/musicList/src/main/ets/components/MusicControlComponent.ets b/features/musicList/src/main/ets/components/MusicControlComponent.ets
index e68a0ef83022911a18d05e14e221fb5e524c73b9..aec824a1876adb3a0d4dee2d8a1655725f9fbfb2 100644
--- a/features/musicList/src/main/ets/components/MusicControlComponent.ets
+++ b/features/musicList/src/main/ets/components/MusicControlComponent.ets
@@ -258,21 +258,21 @@ export struct MusicControlComponent {
let colorArr = ColorConversion.dealColor(color.red, color.green, color.blue);
this.imageColor = `rgba(${colorArr[0]}, ${colorArr[1]}, ${colorArr[2]}, 1)`;
}
- })
+ });
let headFilter = effectKit.createEffect(pixelMap);
if (headFilter !== null) {
headFilter.blur(PlayerConstants.IMAGE_BLUR);
headFilter.getEffectPixelMap().then((value) => {
this.imageLabel = value;
- })
+ });
}
})
.catch((error: BusinessError) => {
- Logger.error(`${error.code} + ${error.message}`)
- })
+ Logger.error(`${error.code} + ${error.message}`);
+ });
})
.catch((error: BusinessError) => {
- Logger.error(`${error.code} + ${error.message}`)
- })
+ Logger.error(`${error.code} + ${error.message}`);
+ });
}
}
\ No newline at end of file
diff --git a/features/musicList/src/main/ets/components/MusicInfoComponent.ets b/features/musicList/src/main/ets/components/MusicInfoComponent.ets
index 3828c58bafc0e78c385460baf2b5a721cd1f90a0..967036f453d4e6b8fb454bd29987406d1148836a 100644
--- a/features/musicList/src/main/ets/components/MusicInfoComponent.ets
+++ b/features/musicList/src/main/ets/components/MusicInfoComponent.ets
@@ -47,8 +47,8 @@ export struct MusicInfoComponent {
offset: { md: BreakpointConstants.OFFSET_MD }
}) {
Column() {
- this.CoverInfo()
- this.MusicInfo()
+ this.CoverInfo();
+ this.MusicInfo();
Blank()
ControlAreaComponent()
}
diff --git a/features/musicList/src/main/ets/components/PlayList.ets b/features/musicList/src/main/ets/components/PlayList.ets
index 9ed1b12767c1a6eb5416b3e0b5da692584bc455d..1ad3f2e5d6363a729dcdd1b772a602902a39f3fe 100644
--- a/features/musicList/src/main/ets/components/PlayList.ets
+++ b/features/musicList/src/main/ets/components/PlayList.ets
@@ -1,3 +1,4 @@
+/* eslint-disable */
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -100,7 +101,7 @@ export struct PlayList {
.width($r('app.float.order_icon_size'))
}
.onClick(() => {
- MediaService.getInstance().loadAssent(index)
+ MediaService.getInstance().loadAssent(index);
this.isShowPlay = true;
})
.height($r('app.float.list_item_height'))
@@ -109,19 +110,19 @@ export struct PlayList {
build() {
Column() {
- this.PlayAll()
+ this.PlayAll();
List() {
LazyForEach(new SongDataSource(this.songList), (item: SongItem, index: number) => {
ListItem() {
Column() {
- this.SongItem(item, index)
+ this.SongItem(item, index);
}
.padding({
left: $r('app.float.list_item_padding'),
right: $r('app.float.list_item_padding')
})
}
- }, (item: SongItem, index?: number) => JSON.stringify(item) + index)
+ }, (item: SongItem, index?: number) => (item).toString() + index)
}
.width(StyleConstants.FULL_WIDTH)
.backgroundColor(Color.White)
@@ -129,6 +130,7 @@ export struct PlayList {
.lanes(this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ?
ContentConstants.COL_TWO : ContentConstants.COL_ONE)
.layoutWeight(1)
+ .cachedCount(2)
.divider({
color: $r('app.color.list_divider'),
strokeWidth: $r('app.float.stroke_width'),
diff --git a/features/musicList/src/main/ets/components/Player.ets b/features/musicList/src/main/ets/components/Player.ets
index de49d77050c9e74b81b20aa39aeeee7c45a553e7..9e7c65a38993e41267cd9f7b2bb1ca9e5ef18713 100644
--- a/features/musicList/src/main/ets/components/Player.ets
+++ b/features/musicList/src/main/ets/components/Player.ets
@@ -198,7 +198,7 @@ export struct Player {
this.topArea = this.getUIContext().px2vp(area.topRect.height);
let bottomArea = windowStage.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
this.bottomArea = this.getUIContext().px2vp(bottomArea.bottomRect.height);
- Logger.info('bottomArea ' + this.bottomArea)
+ Logger.info('bottomArea ' + this.bottomArea);
if (this.topArea > 0) {
windowStage.setWindowLayoutFullScreen(true);
}
@@ -207,7 +207,7 @@ export struct Player {
};
windowStage.setWindowSystemBarProperties(sysBarProps);
}).catch((error: BusinessError) => {
- Logger.error(`${error.code} + ${error.message}`)
+ Logger.error(`${error.code} + ${error.message}`);
});
})
.onDisAppear(() => {
@@ -224,7 +224,7 @@ export struct Player {
};
windowStage.setWindowSystemBarProperties(sysBarProps);
}).catch((error: BusinessError) => {
- Logger.error(`${error.code} + ${error.message}`)
+ Logger.error(`${error.code} + ${error.message}`);
});
})
.gesture(
diff --git a/features/musicList/src/main/ets/lyric/LrcUtils.ets b/features/musicList/src/main/ets/lyric/LrcUtils.ets
index 38f2cdc9d9a89ea4773a9aee3304134971443169..44f47c320961b49b8097baee3207f5875af79c1d 100644
--- a/features/musicList/src/main/ets/lyric/LrcUtils.ets
+++ b/features/musicList/src/main/ets/lyric/LrcUtils.ets
@@ -68,7 +68,7 @@ export function parseLrcLyric(text: string): Array {
if (lrc && lrc.length > 0) {
lrc.sort((a, b) => {
return a.lineStartTime - b.lineStartTime;
- })
+ });
for (let i = 0; i < lrc.length; i++) {
if (i === lrc.length - 1) {
lrc[i].lineDuration = Number.MAX_VALUE;
@@ -125,7 +125,7 @@ export function parseKrcLyric(lyricText: string): LrcEntry[] {
}
lyricLines.sort((a, b) => {
return a.lineStartTime - b.lineStartTime;
- })
+ });
return lyricLines;
}
diff --git a/features/musicList/src/main/ets/lyric/LrcView.ets b/features/musicList/src/main/ets/lyric/LrcView.ets
index 1eaf47c67bee4d4366b1b0592e8999feb0be1271..260100e661d1ab17de081ece33f11c69fd762d61 100644
--- a/features/musicList/src/main/ets/lyric/LrcView.ets
+++ b/features/musicList/src/main/ets/lyric/LrcView.ets
@@ -481,7 +481,7 @@ export default struct shiLrcView {
this.context.fillText(text, this.lrcX, this.lrcY, this.lrcWidth);
}
else {
- this.context.textAlign = 'center'
+ this.context.textAlign = 'center';
this.context.fillText(text, this.lrcX, this.lrcY, this.lrcWidth);
}
break;
diff --git a/features/musicList/src/main/ets/view/MusicListPage.ets b/features/musicList/src/main/ets/view/MusicListPage.ets
index ad29881858629d3deded3b08f30cb0852810d06b..8be59aeb2e2fa4204f118baf0f029170151dd3ef 100644
--- a/features/musicList/src/main/ets/view/MusicListPage.ets
+++ b/features/musicList/src/main/ets/view/MusicListPage.ets
@@ -18,7 +18,7 @@ import { StyleConstants, BreakpointConstants, SongConstants } from '@ohos/consta
import { Header } from '../components/Header';
import { Player } from '../components/Player';
import { Content } from '../components/ListContent';
-import { songList } from '../viewmodel/SongListData'
+import { songList } from '../viewmodel/SongListData';
@Component
export struct MusicListPage {
diff --git a/features/musicList/src/main/ets/viewmodel/SongDataSource.ets b/features/musicList/src/main/ets/viewmodel/SongDataSource.ets
index 866be1222ad2caa3d115eccef7a6a2f067dd4e0f..6cd8ea376cb8725c6a9fe0bc157efeeb34c5adfb 100644
--- a/features/musicList/src/main/ets/viewmodel/SongDataSource.ets
+++ b/features/musicList/src/main/ets/viewmodel/SongDataSource.ets
@@ -65,24 +65,24 @@ export class SongDataSource implements IDataSource {
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);
- })
+ });
}
}
\ No newline at end of file
diff --git a/features/musicList/src/main/ets/viewmodel/SongListData.ets b/features/musicList/src/main/ets/viewmodel/SongListData.ets
index d7f5303cd441e6d04ef1e361064095465d1b2935..808e80e02d9156f2c31662598469aab574b40d56 100644
--- a/features/musicList/src/main/ets/viewmodel/SongListData.ets
+++ b/features/musicList/src/main/ets/viewmodel/SongListData.ets
@@ -86,7 +86,7 @@ const songList: SongItem[] = [
label: $r('app.media.ic_avatar17'), src: 'power.wav', index:32, lyric: '' },
{ id: 34, title: '无归', singer: '小安安', mark: $r('app.media.ic_sq'),
label: $r('app.media.ic_avatar11'), src: 'power.wav', index:33, lyric: '' }
-]
+];
const optionList : OptionItem[] = [
{ image: $r('app.media.ic_collect'), text: $r('app.string.collect') },
@@ -95,7 +95,7 @@ const optionList : OptionItem[] = [
pageIndexInfos.pushPathByName(RouterUrlConstants.MUSIC_COMMENT, null);
}},
{ image: $r('app.media.ic_share'), text: $r('app.string.share') }
-]
+];
class OptionItem {
image: Resource = $r('app.media.ic_collect');
@@ -103,4 +103,4 @@ class OptionItem {
action?: (pageIndexInfos: NavPathStack) => void;
}
-export { optionList, OptionItem, songList }
\ No newline at end of file
+export { optionList, OptionItem, songList };
\ No newline at end of file
diff --git a/products/phone/src/main/ets/entryability/EntryAbility.ets b/products/phone/src/main/ets/entryability/EntryAbility.ets
index e617cbb6e1da95de9b9e2571e0b526a291d47dbd..0317f56b7e96088a8a66deb1d135e2e5e5a5bc46 100644
--- a/products/phone/src/main/ets/entryability/EntryAbility.ets
+++ b/products/phone/src/main/ets/entryability/EntryAbility.ets
@@ -21,7 +21,7 @@ export default class EntryAbility extends UIAbility {
private windowObj?: window.Window;
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
- AppStorage.setOrCreate('context', this.context)
+ AppStorage.setOrCreate('context', this.context);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
@@ -38,8 +38,8 @@ export default class EntryAbility extends UIAbility {
this.updateBreakpoint(this.windowObj.getWindowProperties().windowRect.width);
this.windowObj.on('windowSizeChange', (windowSize: window.Size) => {
this.updateBreakpoint(windowSize.width);
- })
- })
+ });
+ });
windowStage.loadContent('pages/Index', (err) => {
AppStorage.setOrCreate('uiContext', windowStage.getMainWindowSync().getUIContext());
diff --git a/products/watch/build-profile.json5 b/products/watch/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..4d611879c7913fb0610c686e2399258ab3a6dad1
--- /dev/null
+++ b/products/watch/build-profile.json5
@@ -0,0 +1,28 @@
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ },
+ "buildOptionSet": [
+ {
+ "name": "release",
+ "arkOptions": {
+ "obfuscation": {
+ "ruleOptions": {
+ "enable": false,
+ "files": [
+ "./obfuscation-rules.txt"
+ ]
+ }
+ }
+ }
+ },
+ ],
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/products/watch/hvigorfile.ts b/products/watch/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b0e3a1ab98a91bc918d6404b2413111a5011f14a
--- /dev/null
+++ b/products/watch/hvigorfile.ts
@@ -0,0 +1,6 @@
+import { hapTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins: [] /* Custom plugin to extend the functionality of Hvigor. */
+}
\ No newline at end of file
diff --git a/products/watch/obfuscation-rules.txt b/products/watch/obfuscation-rules.txt
new file mode 100644
index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b
--- /dev/null
+++ b/products/watch/obfuscation-rules.txt
@@ -0,0 +1,23 @@
+# Define project specific obfuscation rules here.
+# You can include the obfuscation configuration files in the current module's build-profile.json5.
+#
+# For more details, see
+# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
+
+# Obfuscation options:
+# -disable-obfuscation: disable all obfuscations
+# -enable-property-obfuscation: obfuscate the property names
+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
+# -compact: remove unnecessary blank spaces and all line feeds
+# -remove-log: remove all console.* statements
+# -print-namecache: print the name cache that contains the mapping from the old names to new names
+# -apply-namecache: reuse the given cache file
+
+# Keep options:
+# -keep-property-name: specifies property names that you want to keep
+# -keep-global-name: specifies names that you want to keep in the global scope
+
+-enable-property-obfuscation
+-enable-toplevel-obfuscation
+-enable-filename-obfuscation
+-enable-export-obfuscation
\ No newline at end of file
diff --git a/products/watch/oh-package.json5 b/products/watch/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..1edae348bc0ae542154e24101e6e2a0b3a988678
--- /dev/null
+++ b/products/watch/oh-package.json5
@@ -0,0 +1,17 @@
+{
+ "name": "watch",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "author": "",
+ "license": "",
+ "dependencies": {
+ "musicList": "file:../../features/musicList",
+ "@ohos/constantsCommon": "file:../../common/constantsCommon",
+ "@ohos/live": "file:../../features/live",
+ "@ohos/musicComment": "file:../../features/musicComment",
+ "@ohos/musicList": "file:../../features/musicList",
+ "@ohos/mediaCommon": "file:../../common/mediaCommon",
+ }
+}
+
diff --git a/products/watch/src/main/ets/constants/StyleConstants.ets b/products/watch/src/main/ets/constants/StyleConstants.ets
new file mode 100644
index 0000000000000000000000000000000000000000..aa6725807601e82c63f4b3fb841f792985a3343a
--- /dev/null
+++ b/products/watch/src/main/ets/constants/StyleConstants.ets
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2025 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.
+ */
+
+/**
+ * Common constants for all styles.
+ */
+export class StyleConstants {
+ /**
+ * Component width percentage: 100%.
+ */
+ static readonly FULL_WIDTH: string = '100%';
+ /**
+ * Component height percentage: 100%.
+ */
+ static readonly FULL_HEIGHT: string = '100%';
+ /**
+ * Circle Border Radius.
+ */
+ static readonly CIRCLE_BORDER_RADIUS: string = '50%';
+}
\ No newline at end of file
diff --git a/products/watch/src/main/ets/pages/Index.ets b/products/watch/src/main/ets/pages/Index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..ce1dcf429ab1397ae102ba317fbcf11082702bd6
--- /dev/null
+++ b/products/watch/src/main/ets/pages/Index.ets
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2025 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 {
+ ArcSwiper,
+ ArcSwiperAttribute,
+ ArcDotIndicator,
+ ArcDirection,
+ ArcSwiperController
+} from '@kit.ArkUI';
+import { Home } from '../view/Home';
+import { PlayList } from '../view/PlayList';
+import { MediaService } from '@ohos/mediaCommon';
+import { songList } from 'musicList';
+import { StyleConstants } from '../constants/StyleConstants';
+
+@Preview
+@Entry
+@Component
+struct Index {
+ @Provide pageStack: NavPathStack = new NavPathStack();
+ innerSelectedIndex: number = 0;
+ private wearableSwiperController: ArcSwiperController = new ArcSwiperController();
+ private arcDotIndicator: ArcDotIndicator = new ArcDotIndicator();
+
+ aboutToAppear(): void {
+ AppStorage.setOrCreate('songList', songList);
+ MediaService.getInstance();
+ }
+
+ build() {
+ Navigation(this.pageStack) {
+ Column() {
+ Row() {
+ ArcSwiper(this.wearableSwiperController) {
+ Home()
+ PlayList()
+ }
+ .duration(400)
+ .indicator(this.arcDotIndicator
+ .arcDirection(ArcDirection.SIX_CLOCK_DIRECTION)
+ .selectedItemColor('#FE1B48')
+ )
+ .onAnimationStart((index: number, targetIndex: number) => {
+ this.innerSelectedIndex = targetIndex;
+ })
+ .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer,
+ others: Array): GestureJudgeResult => { // When the recognizer is about to succeed, set the recognizer enable status based on the current component state.
+ if (current) {
+ let target = current.getEventTargetInfo();
+ if (target && current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) {
+ // Here, the condition `swiperTarget.isBegin()` or `innerSelectedIndex === 0` indicates that the ArcSwiper has been swiped to the beginning.
+ let swiperTarget = target as ScrollableTargetInfo;
+ if (swiperTarget instanceof ScrollableTargetInfo &&
+ (swiperTarget.isBegin() || this.innerSelectedIndex === 0)) {
+ let panEvent = event as PanGestureEvent;
+ if (panEvent && panEvent.offsetX > 0 && (swiperTarget.isBegin() || this.innerSelectedIndex === 0)) {
+ return GestureJudgeResult.REJECT;
+ }
+ }
+ }
+ }
+ return GestureJudgeResult.CONTINUE;
+ })
+ }
+ .height(StyleConstants.FULL_HEIGHT)
+ }
+ .width(StyleConstants.FULL_WIDTH)
+ }
+ .hideTitleBar(true)
+ }
+}
\ No newline at end of file
diff --git a/products/watch/src/main/ets/view/Home.ets b/products/watch/src/main/ets/view/Home.ets
new file mode 100644
index 0000000000000000000000000000000000000000..f5d4a896e65bd89b0c048bcd276222dc68560c56
--- /dev/null
+++ b/products/watch/src/main/ets/view/Home.ets
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2025 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 { LengthMetrics } from '@kit.ArkUI';
+import { ArcList, ArcListItem, ArcListAttribute } from '@kit.ArkUI';
+import { StyleConstants } from '../constants/StyleConstants';
+
+class Menu {
+ icon: Resource;
+ text: Resource;
+ pathName: string;
+
+ constructor(icon: Resource, text: Resource, pathName: string) {
+ this.icon = icon;
+ this.text = text;
+ this.pathName = pathName;
+ }
+}
+
+@Preview
+@Component
+export struct Home {
+ @Consume pageStack: NavPathStack;
+ readonly HOME_BTN_WIDTH: string = '90%';
+ private menuList: Menu[] = [
+ new Menu(
+ $r('app.media.hottest_playlists'),
+ $r('app.string.home_hottest_playlists'),
+ 'playList'
+ ),
+ new Menu(
+ $r('app.media.my_favorite'),
+ $r('app.string.home_my_favorite'),
+ 'songList'
+ ),
+ new Menu(
+ $r('app.media.is_playing'),
+ $r('app.string.home_is_playing'),
+ 'songPage'
+ ),
+ new Menu(
+ $r('app.media.setting'),
+ $r('app.string.home_setting'),
+ 'setting'
+ )
+ ];
+
+ build() {
+ Column() {
+ ArcList({ initialIndex: 0 }) {
+ ForEach(this.menuList, (item: Menu) => {
+ ArcListItem() {
+ Row() {
+ Image(item.icon)
+ .width($r('app.float.home_icon_width'))
+ .height($r('app.float.home_icon_width'))
+ .borderRadius(StyleConstants.CIRCLE_BORDER_RADIUS)
+ .backgroundColor($r('app.color.home_icon_background'))
+ .padding($r('app.float.home_icon_padding'))
+
+ Text(item.text)
+ .fontColor($r('app.color.font_color'))
+ .fontSize($r('app.float.home_font_size'))
+
+ Image($r('app.media.chevron_right'))
+ .width($r('app.float.home_icon_jump_width'))
+ }
+ .width(this.HOME_BTN_WIDTH)
+ .height($r('app.float.home_btn_height'))
+ .padding({ left: $r('app.float.list_btn_padding'), right: $r('app.float.list_btn_padding') })
+ .justifyContent(FlexAlign.SpaceBetween)
+ .borderRadius(StyleConstants.CIRCLE_BORDER_RADIUS)
+ .focusable(true)
+ .focusOnTouch(true)
+ .backgroundColor($r('app.color.home_btn_background'))
+ .onClick(() => {
+ if (item.pathName === 'setting') {
+ return;
+ }
+ this.pageStack.replacePathByName(item.pathName, null);
+ })
+ }
+ },
+ (item: Menu, index: number) => JSON.stringify(item) + index)
+ }
+ .scrollBar(BarState.Off)
+ .space(LengthMetrics.vp(5))
+ .borderRadius(StyleConstants.CIRCLE_BORDER_RADIUS)
+ .focusable(true)
+ .focusOnTouch(true)
+ .defaultFocus(true)
+ }
+ .align(Alignment.Center)
+ .width(StyleConstants.FULL_WIDTH)
+ .height(StyleConstants.FULL_HEIGHT)
+ .borderRadius(StyleConstants.CIRCLE_BORDER_RADIUS)
+ }
+}
\ No newline at end of file
diff --git a/products/watch/src/main/ets/view/PlayList.ets b/products/watch/src/main/ets/view/PlayList.ets
new file mode 100644
index 0000000000000000000000000000000000000000..a643e708df4d0c263fee832926807333d8409fa8
--- /dev/null
+++ b/products/watch/src/main/ets/view/PlayList.ets
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2025 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 { ArcSwiper, ArcSwiperAttribute, ArcSwiperController } from '@kit.ArkUI';
+import { StyleConstants } from '../constants/StyleConstants';
+
+@Builder
+export function PlayListBuilder() {
+ PlayList();
+}
+
+class PlayListSheet {
+ name: Resource;
+ background: Resource;
+ title: Resource;
+
+ constructor(name: Resource, background: Resource, title: Resource) {
+ this.name = name;
+ this.background = background;
+ this.title = title;
+ }
+}
+
+@Preview
+@Component
+export struct PlayList {
+ @Consume pageStack: NavPathStack;
+ @State itemSimpleColor: Color | number | string = '';
+ @State selectedItemSimpleColor: Color | number | string = '';
+ innerSelectedIndex: number = 0;
+ private wearableSwiperController: ArcSwiperController = new ArcSwiperController();
+ private playList: PlayListSheet[] = [
+ new PlayListSheet(
+ $r('app.string.playlist_name_one'),
+ $r('app.media.playlist_bg1'),
+ $r('app.string.playlist_title_one')
+ ),
+ new PlayListSheet(
+ $r('app.string.playlist_name_two'),
+ $r('app.media.playlist_bg2'),
+ $r('app.string.playlist_title_two')
+ )
+ ];
+
+ build() {
+ NavDestination() {
+ Column() {
+ ArcSwiper(this.wearableSwiperController) {
+ ForEach(this.playList, (item: PlayListSheet, index: number) => {
+ Column({ space: 10 }) {
+ Row() {
+ Text(item.name)
+ .fontWeight(FontWeight.Bold)
+ .fontColor($r('app.color.font_color'))
+ .fontSize($r('app.float.home_font_size'))
+ Image($r('app.media.chevron_right'))
+ .width($r('app.float.home_icon_jump_width'))
+ .margin({ left: $r('app.float.playlist_padding') })
+ }
+
+ Image($r('app.media.play_btn_fill'))
+ .width($r('app.float.playlist_icon'))
+ .height($r('app.float.playlist_icon'))
+ .position({ x: '25%', y: '65%' })
+ Text(item.title)
+ }
+ .width(StyleConstants.FULL_WIDTH)
+ .height(StyleConstants.FULL_HEIGHT)
+ .backgroundImage(item.background, ImageRepeat.NoRepeat)
+ .backgroundImageSize({ width: StyleConstants.FULL_WIDTH, height: StyleConstants.FULL_HEIGHT })
+ .justifyContent(FlexAlign.SpaceBetween)
+ .padding({ top: $r('app.float.playlist_row_padding'), bottom: $r('app.float.playlist_row_padding') })
+ .onClick(() => {
+ this.pageStack.replacePathByName('songList', null);
+ })
+ }, (item: PlayListSheet, index?: number) => index + JSON.stringify(item))
+ }
+ .index(0)
+ .duration(400)
+ .vertical(true)
+ .indicator(false)
+ .disableSwipe(false)
+ .digitalCrownSensitivity(CrownSensitivity.MEDIUM)
+ .disableTransitionAnimation(false)
+ .onAnimationStart((index: number, targetIndex: number) => {
+ this.innerSelectedIndex = targetIndex;
+ })
+ .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer,
+ ): GestureJudgeResult => { // When the recognizer is about to succeed, set the recognizer enable status based on the current component state.
+ if (current) {
+ let target = current.getEventTargetInfo();
+ if (target && current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) {
+ // Here, the condition `swiperTarget.isBegin()` or `innerSelectedIndex === 0` indicates that the ArcSwiper has been swiped to the beginning.
+ let swiperTarget = target as ScrollableTargetInfo;
+ if (swiperTarget instanceof ScrollableTargetInfo &&
+ (swiperTarget.isBegin() || this.innerSelectedIndex === 0)) {
+ let panEvent = event as PanGestureEvent;
+ if (panEvent && panEvent.offsetX > 0 && (swiperTarget.isBegin() || this.innerSelectedIndex === 0)) {
+ return GestureJudgeResult.REJECT;
+ }
+ }
+ }
+ }
+ return GestureJudgeResult.CONTINUE;
+ })
+ }
+ .width(StyleConstants.FULL_WIDTH)
+ .height(StyleConstants.FULL_HEIGHT)
+ }
+ .hideTitleBar(true)
+ }
+}
\ No newline at end of file
diff --git a/products/watch/src/main/ets/view/SongList.ets b/products/watch/src/main/ets/view/SongList.ets
new file mode 100644
index 0000000000000000000000000000000000000000..3cb4fa50ea1962d45215a41c9583e40c57772b50
--- /dev/null
+++ b/products/watch/src/main/ets/view/SongList.ets
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2025 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 { LengthMetrics } from '@kit.ArkUI';
+import { ArcList, ArcListItem, ArcListAttribute, ArcListItemAttribute } from '@kit.ArkUI';
+import { MediaService, SongItem } from '@ohos/mediaCommon';
+import { StyleConstants } from '../constants/StyleConstants';
+
+@Builder
+export function SongListBuilder() {
+ SongList();
+}
+
+@Preview
+@Component
+struct SongList {
+ readonly HOME_BTN_WIDTH: string = '90%';
+ @Consume pageStack: NavPathStack;
+ @StorageLink('songList') songList: SongItem[] = [];
+
+ aboutToAppear(): void {
+ MediaService.getInstance();
+ }
+
+ build() {
+ NavDestination() {
+ Column() {
+ ArcList({ initialIndex: 0 }) {
+ ForEach(this.songList, (item: SongItem) => {
+ ArcListItem() {
+ Row() {
+ Image(item.label)
+ .width($r('app.float.home_icon_width'))
+ .height($r('app.float.home_icon_width'))
+ .borderRadius(StyleConstants.CIRCLE_BORDER_RADIUS)
+
+ Column() {
+ Text(item.title)
+ .fontWeight(FontWeight.Bold)
+ .fontColor($r('app.color.font_color'))
+ Text(item.singer)
+ .fontColor($r('app.color.text_color'))
+ }
+ .layoutWeight(1)
+ }
+ .width(this.HOME_BTN_WIDTH)
+ .height($r('app.float.home_btn_height'))
+ .padding({ left: $r('app.float.list_btn_padding'), right: $r('app.float.list_btn_padding') })
+ .borderRadius(StyleConstants.CIRCLE_BORDER_RADIUS)
+ .focusable(true)
+ .focusOnTouch(true)
+ .backgroundColor($r('app.color.home_btn_background'))
+ }
+ .align(Alignment.Center)
+ .onClick(async () => {
+ await MediaService.getInstance().loadAssent(index);
+ this.pageStack.replacePathByName('songPage', null);
+ })
+ }, (item: SongItem, index: number) => JSON.stringify(item) + index)
+ }
+ .scrollBar(BarState.Off)
+ .space(LengthMetrics.vp(5))
+ .borderRadius(StyleConstants.CIRCLE_BORDER_RADIUS)
+ .focusable(true)
+ .focusOnTouch(true)
+ .defaultFocus(true)
+ }
+ .align(Alignment.Center)
+ .width(StyleConstants.FULL_WIDTH)
+ .height(StyleConstants.FULL_HEIGHT)
+ .borderRadius(StyleConstants.CIRCLE_BORDER_RADIUS)
+ }
+ .hideTitleBar(true)
+ }
+}
\ No newline at end of file
diff --git a/products/watch/src/main/ets/view/SongPage.ets b/products/watch/src/main/ets/view/SongPage.ets
new file mode 100644
index 0000000000000000000000000000000000000000..8e1638d25be6c81b98b4484a83c4692951a84515
--- /dev/null
+++ b/products/watch/src/main/ets/view/SongPage.ets
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2025 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 { MediaService, SongItem } from '@ohos/mediaCommon';
+import { StyleConstants } from '../constants/StyleConstants';
+import { VolumeSlider } from './VolumeSliderComponent';
+
+@Builder
+export function SongPageBuilder() {
+ SongPage();
+}
+
+@Preview
+@Component
+struct SongPage {
+ @StorageLink('songList') songList: SongItem[] = [];
+ @StorageLink('selectIndex') selectIndex: number = 0;
+ @StorageLink('isPlay') isPlay: boolean = false;
+ @StorageLink('progress') time: number = 0;
+ @StorageLink('progressMax') max: number = 0;
+ @StorageLink('isFirstLaunch') isFirstLaunch: boolean = true;
+ readonly HALF_WIDTH: string = '50%';
+
+ aboutToAppear(): void {
+ if (this.isFirstLaunch) {
+ MediaService.getInstance().loadAssent(0, true);
+ }
+ AppStorage.setOrCreate('isFirstLaunch', false);
+ }
+
+ build() {
+ NavDestination() {
+ Column() {
+ Column() {
+ Text(this.songList[this.selectIndex].title)
+ .fontWeight(FontWeight.Bold)
+ .fontColor($r('app.color.font_color'))
+ Text(this.songList[this.selectIndex].singer)
+ .fontColor($r('app.color.play_singer_color'))
+ }
+
+ Row() {
+ Column() {
+ Image($r('app.media.previous_btn'))
+ .width($r('app.float.play_song_img'))
+ }
+ .onClick(() => {
+ MediaService.getInstance().playPrevious();
+ })
+
+ Stack() {
+ Image(this.songList[this.selectIndex].label)
+ .width($r('app.float.play_circle_img'))
+ .height($r('app.float.play_circle_img'))
+ .borderRadius(StyleConstants.CIRCLE_BORDER_RADIUS)
+
+ Progress({ value: this.time, total: this.max, type: ProgressType.Ring })
+ .width($r('app.float.play_progress_width'))
+ .backgroundColor(Color.Transparent)
+ .color($r('app.color.font_color'))
+
+ Image($r('app.media.play_btn'))
+ .width($r('app.float.play_song_img'))
+ .visibility(this.isPlay === true ? Visibility.None : Visibility.Visible)
+ .onClick(() => {
+ if (MediaService.getInstance().getFirst()) {
+ MediaService.getInstance().loadAssent(0);
+ } else {
+ this.isPlay ? MediaService.getInstance().pause() : MediaService.getInstance().play();
+ }
+ })
+
+ Image($r('app.media.pause_btn'))
+ .width($r('app.float.play_song_img'))
+ .visibility(this.isPlay === true ? Visibility.Visible : Visibility.None)
+ .onClick(() => {
+ MediaService.getInstance().pause();
+ })
+ }
+ .width(this.HALF_WIDTH)
+ .align(Alignment.Center)
+
+ Column() {
+ Image($r('app.media.next_btn'))
+ .width($r('app.float.play_song_img'))
+ }
+ .onClick(() => {
+ MediaService.getInstance().playNextAuto(true);
+ })
+ }
+ .justifyContent(FlexAlign.SpaceAround)
+ .width('85%')
+
+ Row() {
+ Image($r('app.media.download'))
+ .width($r('app.float.play_icon_width'))
+ Image($r('app.media.repeat'))
+ .width($r('app.float.play_icon_width'))
+ Image($r('app.media.full_screen'))
+ .width($r('app.float.play_icon_width'))
+ }
+ .width('60%')
+ .justifyContent(FlexAlign.SpaceAround)
+ }
+ .width(StyleConstants.FULL_WIDTH)
+ .height(StyleConstants.FULL_HEIGHT)
+ .padding({ top: $r('app.float.play_column_padding'), bottom: $r('app.float.play_column_padding') })
+ .justifyContent(FlexAlign.SpaceAround)
+
+ VolumeSlider()
+ }
+ .hideTitleBar(true)
+ .focusable(true)
+ .focusOnTouch(true)
+ .defaultFocus(true)
+ .linearGradient({
+ direction: GradientDirection.Bottom,
+ colors: [
+ ['#5E4C4B', 1.0],
+ ['#695951', 1.0]
+ ]
+ })
+ }
+}
\ No newline at end of file
diff --git a/products/watch/src/main/ets/view/VolumeSliderComponent.ets b/products/watch/src/main/ets/view/VolumeSliderComponent.ets
new file mode 100644
index 0000000000000000000000000000000000000000..ffd156b1a0bfcde9cb23fa5ed4ec802a5026bf85
--- /dev/null
+++ b/products/watch/src/main/ets/view/VolumeSliderComponent.ets
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2025 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 {
+ ArcSlider,
+ ArcSliderLayoutOptions,
+ ArcSliderLayoutOptionsConstructorOptions,
+ ArcSliderOptions,
+ ArcSliderOptionsConstructorOptions,
+ ArcSliderPosition,
+ ArcSliderStyleOptions,
+ ArcSliderStyleOptionsConstructorOptions,
+ ArcSliderValueOptions,
+ ArcSliderValueOptionsConstructorOptions
+} from '@kit.ArkUI';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { MediaService } from '@ohos/mediaCommon';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+
+@Preview
+@Component
+export struct VolumeSlider {
+ @StorageLink('volume') volume: number = 0.4;
+ valueOptionsConstructorOptions: ArcSliderValueOptionsConstructorOptions = {
+ progress: this.volume * 100,
+ min: 0,
+ max: 100
+ };
+ layoutOptionsConstructorOptions: ArcSliderLayoutOptionsConstructorOptions = {
+ reverse: true,
+ position: ArcSliderPosition.RIGHT
+ };
+ styleOptionsConstructorOptions: ArcSliderStyleOptionsConstructorOptions = {
+ trackThickness: 5,
+ activeTrackThickness: 8,
+ trackColor: '#33f1ebeb',
+ selectedColor: '#FE1B48',
+ trackBlur: 20
+ };
+ valueOptions: ArcSliderValueOptions = new ArcSliderValueOptions(this.valueOptionsConstructorOptions);
+ layoutOptions: ArcSliderLayoutOptions = new ArcSliderLayoutOptions(this.layoutOptionsConstructorOptions);
+ styleOptions: ArcSliderStyleOptions = new ArcSliderStyleOptions(this.styleOptionsConstructorOptions);
+ arcSliderOptionsConstructorOptions: ArcSliderOptionsConstructorOptions = {
+ valueOptions: this.valueOptions,
+ layoutOptions: this.layoutOptions,
+ styleOptions: this.styleOptions,
+ digitalCrownSensitivity: CrownSensitivity.LOW,
+ onChange: (progress: number) => {
+ this.setAVPlayerVolume(progress);
+ },
+ };
+ arcSliderOptions: ArcSliderOptions = new ArcSliderOptions(this.arcSliderOptionsConstructorOptions);
+
+ setAVPlayerVolume(progress: number) {
+ try {
+ MediaService.getInstance().avPlayer?.setVolume(progress / 100);
+ } catch (error) {
+ hilog.error(0x0000, 'volumeSlider', "ArcSlider setVolume failed", (error as BusinessError).code);
+ }
+ }
+
+ build() {
+ Column() {
+ ArcSlider({ options: this.arcSliderOptions })
+ .focusable(true)
+ .focusOnTouch(true)
+ .defaultFocus(true)
+ .zIndex(999)
+ .onDigitalCrown((event: CrownEvent) => {
+ event.stopPropagation();
+ const STEP_DEGREE = 20;
+ let newVolume = this.volume + event.degree / STEP_DEGREE;
+ newVolume = Math.max(0, Math.min(100, newVolume));
+ this.setAVPlayerVolume(newVolume);
+ })
+ Image($r('app.media.speaker_fill'))
+ .width($r('app.float.volume_icon_width'))
+ .height($r('app.float.volume_icon_width'))
+ .rotate({ angle: '-30deg' })
+ .position({
+ right: $r('app.float.volume_icon_right'),
+ top: $r('app.float.volume_icon_top'),
+ })
+ }
+ .hitTestBehavior(HitTestMode.Transparent)
+ .position({
+ top: 0,
+ right: 0
+ })
+ }
+}
diff --git a/products/watch/src/main/ets/watchability/WatchAbility.ets b/products/watch/src/main/ets/watchability/WatchAbility.ets
new file mode 100644
index 0000000000000000000000000000000000000000..efa9a310feac83f5d23874b405a26a8b26dc341c
--- /dev/null
+++ b/products/watch/src/main/ets/watchability/WatchAbility.ets
@@ -0,0 +1,45 @@
+import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { window } from '@kit.ArkUI';
+
+const DOMAIN = 0x0000;
+
+export default class WatchAbility extends UIAbility {
+ onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
+ AppStorage.setOrCreate('context', this.context);
+ hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
+ }
+
+ onDestroy(): void {
+ hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
+ }
+
+ onWindowStageCreate(windowStage: window.WindowStage): void {
+ // Main window is created, set main page for this ability
+ hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
+
+ windowStage.loadContent('pages/Index', (err) => {
+ if (err.code) {
+ hilog.error(DOMAIN, 'testTag', `${err.code} + ${err.message}`);
+ return;
+ }
+ AppStorage.setOrCreate('uiContext', windowStage.getMainWindowSync().getUIContext());
+ hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
+ });
+ }
+
+ onWindowStageDestroy(): void {
+ // Main window is destroyed, release UI related resources
+ hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
+ }
+
+ onForeground(): void {
+ // Ability has brought to foreground
+ hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
+ }
+
+ onBackground(): void {
+ // Ability has back to background
+ hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
+ }
+}
diff --git a/products/watch/src/main/ets/watchbackupability/WatchBackupAbility.ets b/products/watch/src/main/ets/watchbackupability/WatchBackupAbility.ets
new file mode 100644
index 0000000000000000000000000000000000000000..d5e190e5ff258fc2f7ea97890fe735cc1099e095
--- /dev/null
+++ b/products/watch/src/main/ets/watchbackupability/WatchBackupAbility.ets
@@ -0,0 +1,16 @@
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
+
+const DOMAIN = 0x0000;
+
+export default class WatchBackupAbility extends BackupExtensionAbility {
+ async onBackup() {
+ hilog.info(DOMAIN, 'testTag', 'onBackup ok');
+ await Promise.resolve();
+ }
+
+ async onRestore(bundleVersion: BundleVersion) {
+ hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
+ await Promise.resolve();
+ }
+}
\ No newline at end of file
diff --git a/products/watch/src/main/module.json5 b/products/watch/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..99c543c0991fe3d32f63f66926dc96b292ca3110
--- /dev/null
+++ b/products/watch/src/main/module.json5
@@ -0,0 +1,51 @@
+{
+ "module": {
+ "name": "watch",
+ "type": "entry",
+ "description": "$string:module_desc",
+ "mainElement": "WatchAbility",
+ "deviceTypes": [
+ "wearable",
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "routerMap": "$profile:router_map",
+ "abilities": [
+ {
+ "name": "WatchAbility",
+ "srcEntry": "./ets/watchability/WatchAbility.ets",
+ "description": "$string:WatchAbility_desc",
+ "icon": "$media:layered_image",
+ "label": "$string:WatchAbility_label",
+ "startWindowIcon": "$media:startIcon",
+ "startWindowBackground": "$color:start_window_background",
+ "exported": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "ohos.want.action.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "extensionAbilities": [
+ {
+ "name": "WatchBackupAbility",
+ "srcEntry": "./ets/watchbackupability/WatchBackupAbility.ets",
+ "type": "backup",
+ "exported": false,
+ "metadata": [
+ {
+ "name": "ohos.extension.backup",
+ "resource": "$profile:backup_config"
+ }
+ ],
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/element/color.json b/products/watch/src/main/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..834cb898dc34373415e6571cb055404e74fe262b
--- /dev/null
+++ b/products/watch/src/main/resources/base/element/color.json
@@ -0,0 +1,36 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ },
+ {
+ "name": "font_color",
+ "value": "#F6F5F6"
+ },
+ {
+ "name": "text_color",
+ "value": "#767674"
+ },
+ {
+ "name": "home_icon_background",
+ "value": "#FC1B46"
+ },
+ {
+ "name": "home_btn_background",
+ "value": "#2A2A29"
+ },
+ {
+ "name": "playlist_color_one",
+ "value": "#4C5363"
+ },
+ {
+ "name": "playlist_color_two",
+ "value": "#5D544D"
+ },
+ {
+ "name": "play_singer_color",
+ "value": "#BAAEAD"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/element/float.json b/products/watch/src/main/resources/base/element/float.json
new file mode 100644
index 0000000000000000000000000000000000000000..58ea5f8f08aefc7f6d98d70d9081349aa4e81bdf
--- /dev/null
+++ b/products/watch/src/main/resources/base/element/float.json
@@ -0,0 +1,84 @@
+{
+ "float": [
+ {
+ "name": "home_font_size",
+ "value": "18vp"
+ },
+ {
+ "name": "home_icon_width",
+ "value": "45vp"
+ },
+ {
+ "name": "home_icon_jump_width",
+ "value": "16vp"
+ },
+ {
+ "name": "home_icon_padding",
+ "value": "6vp"
+ },
+ {
+ "name": "home_btn_space",
+ "value": "10vp"
+ },
+ {
+ "name": "home_btn_height",
+ "value": "66vp"
+ },
+ {
+ "name": "list_btn_padding",
+ "value": "10vp"
+ },
+ {
+ "name": "playlist_padding",
+ "value": "6vp"
+ },
+ {
+ "name": "playlist_icon",
+ "value": "30vp"
+ },
+ {
+ "name": "playlist_img_border_radius",
+ "value": "12vp"
+ },
+ {
+ "name": "playlist_margin",
+ "value": "4vp"
+ },
+ {
+ "name": "playlist_row_padding",
+ "value": "16vp"
+ },
+ {
+ "name": "play_song_img",
+ "value": "36vp"
+ },
+ {
+ "name": "play_icon_width",
+ "value": "24vp"
+ },
+ {
+ "name": "play_circle_img",
+ "value": "90vp"
+ },
+ {
+ "name": "play_progress_width",
+ "value": "100vp"
+ },
+ {
+ "name": "play_column_padding",
+ "value": "16vp"
+ },
+ {
+ "name": "volume_icon_width",
+ "value": "15vp"
+ },
+ {
+ "name": "volume_icon_right",
+ "value": "22vp"
+ },
+ {
+ "name": "volume_icon_top",
+ "value": "60vp"
+ }
+ ]
+}
diff --git a/products/watch/src/main/resources/base/element/string.json b/products/watch/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..874b5aae0ca020ea2a9b6114596f3feae9753be7
--- /dev/null
+++ b/products/watch/src/main/resources/base/element/string.json
@@ -0,0 +1,48 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "WatchAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "WatchAbility_label",
+ "value": "label"
+ },
+ {
+ "name": "home_hottest_playlists",
+ "value": "hottest_playlists"
+ },
+ {
+ "name": "home_my_favorite",
+ "value": "My Favorite"
+ },
+ {
+ "name": "home_is_playing",
+ "value": "Is Playing"
+ },
+ {
+ "name": "home_setting",
+ "value": "Setting"
+ },
+ {
+ "name": "playlist_name_one",
+ "value": "Popular Playlists"
+ },
+ {
+ "name": "playlist_name_two",
+ "value": "Exercise Music"
+ },
+ {
+ "name": "playlist_title_one",
+ "value": "New independent singer-songwriter"
+ },
+ {
+ "name": "playlist_title_two",
+ "value": "Pleasant jogging "
+ }
+ ]
+}
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/chevron_right.svg b/products/watch/src/main/resources/base/media/chevron_right.svg
new file mode 100644
index 0000000000000000000000000000000000000000..8dc4eb7efe15b8a6f24dabed3f23ce4de8a4009f
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/chevron_right.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/download.svg b/products/watch/src/main/resources/base/media/download.svg
new file mode 100644
index 0000000000000000000000000000000000000000..71509bddc564c5dc30a0e7ce6d78ee0be02d2fc6
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/download.svg
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/full_screen.svg b/products/watch/src/main/resources/base/media/full_screen.svg
new file mode 100644
index 0000000000000000000000000000000000000000..ae7f22ccca468bd7901c02ea2383264d6a246089
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/full_screen.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/hottest_playlists.svg b/products/watch/src/main/resources/base/media/hottest_playlists.svg
new file mode 100644
index 0000000000000000000000000000000000000000..27c8c75c77a06a3600247a3f7fc56c1be0099b9d
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/hottest_playlists.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/is_playing.svg b/products/watch/src/main/resources/base/media/is_playing.svg
new file mode 100644
index 0000000000000000000000000000000000000000..45111b3022fd893b700dd2ae45e5cd62c7c25040
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/is_playing.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/layered_image.json b/products/watch/src/main/resources/base/media/layered_image.json
new file mode 100644
index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/layered_image.json
@@ -0,0 +1,7 @@
+{
+ "layered-image":
+ {
+ "background" : "$media:background",
+ "foreground" : "$media:foreground"
+ }
+}
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/my_favorite.svg b/products/watch/src/main/resources/base/media/my_favorite.svg
new file mode 100644
index 0000000000000000000000000000000000000000..837bf06c5176aa9258275c1e0370d1a59edb221a
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/my_favorite.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/next_btn.svg b/products/watch/src/main/resources/base/media/next_btn.svg
new file mode 100644
index 0000000000000000000000000000000000000000..873a96a3f3934747a8c2482ea0b9ae7e7880a6f8
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/next_btn.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/pause_btn.svg b/products/watch/src/main/resources/base/media/pause_btn.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6b931769ca31ecf78dbeae95e6506e00dbc70fa0
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/pause_btn.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/play_btn.svg b/products/watch/src/main/resources/base/media/play_btn.svg
new file mode 100644
index 0000000000000000000000000000000000000000..78098f400d6bb2d3ebdabc651c57b0d89ee72ead
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/play_btn.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/play_btn_fill.svg b/products/watch/src/main/resources/base/media/play_btn_fill.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0b004278380e9481e96763c2bf379eb2eb0e50e8
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/play_btn_fill.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/playlist_bg1.png b/products/watch/src/main/resources/base/media/playlist_bg1.png
new file mode 100644
index 0000000000000000000000000000000000000000..3743af2116d88ad8ec06725c6b48db252106b8e4
Binary files /dev/null and b/products/watch/src/main/resources/base/media/playlist_bg1.png differ
diff --git a/products/watch/src/main/resources/base/media/playlist_bg2.png b/products/watch/src/main/resources/base/media/playlist_bg2.png
new file mode 100644
index 0000000000000000000000000000000000000000..ec5ebf0553df24f59e5fba17f8e6f15b51a21600
Binary files /dev/null and b/products/watch/src/main/resources/base/media/playlist_bg2.png differ
diff --git a/products/watch/src/main/resources/base/media/previous_btn.svg b/products/watch/src/main/resources/base/media/previous_btn.svg
new file mode 100644
index 0000000000000000000000000000000000000000..1740ea4acdd38df4f726459d5fbbf8f1487fcba6
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/previous_btn.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/repeat.svg b/products/watch/src/main/resources/base/media/repeat.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e146ead47915e642eb5f4362413cf497a43a52ee
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/repeat.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/setting.svg b/products/watch/src/main/resources/base/media/setting.svg
new file mode 100644
index 0000000000000000000000000000000000000000..98cc6d8a4bda4a0402f4fd89d1ad5179f10a490c
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/setting.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/speaker_fill.svg b/products/watch/src/main/resources/base/media/speaker_fill.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0e9b38170c4310d099988184772eec6ef023cc29
--- /dev/null
+++ b/products/watch/src/main/resources/base/media/speaker_fill.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/media/startIcon.png b/products/watch/src/main/resources/base/media/startIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b
Binary files /dev/null and b/products/watch/src/main/resources/base/media/startIcon.png differ
diff --git a/products/watch/src/main/resources/base/profile/backup_config.json b/products/watch/src/main/resources/base/profile/backup_config.json
new file mode 100644
index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a
--- /dev/null
+++ b/products/watch/src/main/resources/base/profile/backup_config.json
@@ -0,0 +1,3 @@
+{
+ "allowToBackupRestore": true
+}
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/profile/main_pages.json b/products/watch/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..55c3f007f87b7ce5206d325f968cc56f2f79441f
--- /dev/null
+++ b/products/watch/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "pages/Index"
+ ]
+}
\ No newline at end of file
diff --git a/products/watch/src/main/resources/base/profile/router_map.json b/products/watch/src/main/resources/base/profile/router_map.json
new file mode 100644
index 0000000000000000000000000000000000000000..12b3ff12fa24cf000ef6408cda5748fd00cd7c22
--- /dev/null
+++ b/products/watch/src/main/resources/base/profile/router_map.json
@@ -0,0 +1,19 @@
+{
+ "routerMap" : [
+ {
+ "name" : "playList",
+ "pageSourceFile" : "src/main/ets/view/PlayList.ets",
+ "buildFunction" : "PlayListBuilder"
+ },
+ {
+ "name" : "songList",
+ "pageSourceFile" : "src/main/ets/view/SongList.ets",
+ "buildFunction" : "SongListBuilder"
+ },
+ {
+ "name" : "songPage",
+ "pageSourceFile" : "src/main/ets/view/SongPage.ets",
+ "buildFunction" : "SongPageBuilder"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/products/watch/src/main/resources/en_US/element/string.json b/products/watch/src/main/resources/en_US/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..324307dde82780388e6d75c763c01e7417904fa0
--- /dev/null
+++ b/products/watch/src/main/resources/en_US/element/string.json
@@ -0,0 +1,48 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "WatchAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "WatchAbility_label",
+ "value": "label"
+ },
+ {
+ "name": "home_hottest_playlists",
+ "value": "hottest Playlists"
+ },
+ {
+ "name": "home_my_favorite",
+ "value": "My Favorite"
+ },
+ {
+ "name": "home_is_playing",
+ "value": "Is Playing"
+ },
+ {
+ "name": "home_setting",
+ "value": "Setting"
+ },
+ {
+ "name": "playlist_name_one",
+ "value": "Popular Playlists "
+ },
+ {
+ "name": "playlist_name_two",
+ "value": "Exercise Music "
+ },
+ {
+ "name": "playlist_title_one",
+ "value": "New independent singer-songwriter"
+ },
+ {
+ "name": "playlist_title_two",
+ "value": "Pleasant jogging "
+ }
+ ]
+}
\ No newline at end of file
diff --git a/products/watch/src/main/resources/zh_CN/element/string.json b/products/watch/src/main/resources/zh_CN/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..adf5fb63588a9fd5f2e386c7358a415c58bb558a
--- /dev/null
+++ b/products/watch/src/main/resources/zh_CN/element/string.json
@@ -0,0 +1,48 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "WatchAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "WatchAbility_label",
+ "value": "label"
+ },
+ {
+ "name": "home_hottest_playlists",
+ "value": "热门歌单"
+ },
+ {
+ "name": "home_my_favorite",
+ "value": "我喜欢"
+ },
+ {
+ "name": "home_is_playing",
+ "value": "正在播放"
+ },
+ {
+ "name": "home_setting",
+ "value": "设置"
+ },
+ {
+ "name": "playlist_name_one",
+ "value": "热门歌单"
+ },
+ {
+ "name": "playlist_name_two",
+ "value": "运动音乐"
+ },
+ {
+ "name": "playlist_title_one",
+ "value": "新锐独立唱作人"
+ },
+ {
+ "name": "playlist_title_two",
+ "value": "惬意慢跑"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/screenshots/device/wearable.png b/screenshots/device/wearable.png
new file mode 100644
index 0000000000000000000000000000000000000000..be1a81e2c985f0a444c673ce5458e4b2158c25d6
Binary files /dev/null and b/screenshots/device/wearable.png differ