diff --git a/README.en.md b/README.en.md
index c29a178a85a238866173059bfaa6efc9d83da183..94d1e6ba2c9f84d838b38b9e0a847b8c7141ac38 100644
--- a/README.en.md
+++ b/README.en.md
@@ -1,9 +1,10 @@
# 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.
+## Effect Preview
The figure shows the effect on the mobile phone.

@@ -16,7 +17,55 @@ The figure shows the effect on the tablet.

-### How to Use
+The figure shows the effect on the wearable.
+
+
+
+
+## Engineering catalogue
+```
+├──commons // Public Competency Layer
+│ ├──constantsCommon/src/main/ets // Public Constants
+│ │ └──constants
+│ └──mediaCommon/src/main/ets // Public media approach
+│ └──utils
+│ └──viewmodel
+├──features // Basic Feature Layer
+│ ├──live/src/main/ets // Live Stream Page
+│ │ ├──constants
+│ │ ├──view
+│ │ └──viewmodel
+│ ├──live/src/main/resources // Resource file directory
+│ ├──musicComment/src/main/ets // Music Review Page
+│ │ ├──constants
+│ │ ├──view
+│ │ └──viewmodel
+│ ├──musicComment/src/main/resources // Resource file directory
+│ ├──musicList/src/main/ets // Song List Page
+│ │ ├──components
+│ │ ├──constants
+│ │ ├──lyric
+│ │ ├──view
+│ │ └──viewmodel
+│ └──musicList/src/main/resources // Resource file directory
+└──products // Product Customization Layer
+ ├──phone/src/main/ets // Supports smartphones, foldable screens, tablets, and PCs/2in1 devices
+ │ ├──common
+ │ ├──entryability
+ │ ├──pages
+ │ ├──phonebackupextability
+ │ └──viewmodel
+ ├──phone/src/main/resources // Resource file directory
+ ├──watch/src/main/ets // Support for smart wearables
+ │ ├──constants
+ │ ├──pages
+ │ ├──view
+ │ ├──watchability
+ │ └──watchbackupability
+ └──watch/src/main/resources // Resource file directory
+```
+
+## 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.
@@ -30,7 +79,7 @@ N/A
### Constraints
-1. The sample is only supported on Huawei phones, tablets with standard systems.
-2. HarmonyOS: HarmonyOS 5.0.5 Release or later.
-3. DevEco Studio: DevEco Studio 5.0.5 Release or later.
-4. HarmonyOS SDK: HarmonyOS 5.0.5 Release SDK or later.
+1. The sample is only supported on Huawei phones, tablets and smart wearables with standard systems.
+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 74e5c1f713b9b2a53c915da4f1008cbece6fddad..8fea407484a1ff7fcadcb44b6cd9a788f6a47101 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,10 @@
# 多设备音乐界面
-### 简介
+## 项目简介
基于自适应和响应式布局,实现一次开发、多端部署音乐专辑。
+## 效果预览
手机效果图如下:

@@ -15,22 +16,76 @@
平板效果图如下:

+
+
+智能穿戴效果图如下:
+
+
-### 使用说明
+## 工程目录
+```
+├──commons // 公共能力层
+│ ├──constantsCommon/src/main/ets // 公共常量
+│ │ └──constants
+│ └──mediaCommon/src/main/ets // 公共媒体方法
+│ └──utils
+│ └──viewmodel
+├──features // 基础特性层
+│ ├──live/src/main/ets // 直播页
+│ │ ├──constants
+│ │ ├──view
+│ │ └──viewmodel
+│ ├──live/src/main/resources // 资源文件目录
+│ ├──musicComment/src/main/ets // 音乐评论页
+│ │ ├──constants
+│ │ ├──view
+│ │ └──viewmodel
+│ ├──musicComment/src/main/resources // 资源文件目录
+│ ├──musicList/src/main/ets // 歌曲列表页
+│ │ ├──components
+│ │ ├──constants
+│ │ ├──lyric
+│ │ ├──view
+│ │ └──viewmodel
+│ └──musicList/src/main/resources // 资源文件目录
+└──products // 产品定制层
+ ├──phone/src/main/ets // 支持手机、折叠屏、平板、PC/2in1
+ │ ├──common
+ │ ├──entryability
+ │ ├──pages
+ │ ├──phonebackupextability
+ │ └──viewmodel
+ ├──phone/src/main/resources // 资源文件目录
+ ├──watch/src/main/ets // 支持智能穿戴
+ │ ├──constants
+ │ ├──pages
+ │ ├──view
+ │ ├──watchability
+ │ └──watchbackupability
+ └──watch/src/main/resources // 资源文件目录
+```
-1. 分别在手机、折叠屏、平板安装并打开应用,不同设备的应用页面通过响应式布局和自适应布局呈现不同的效果。
+## 使用说明
+
+1. 分别在手机、折叠屏、平板、智能穿戴安装并打开应用,不同设备的应用页面通过响应式布局和自适应布局呈现不同的效果。
2. 点击界面上播放/暂停、上一首、下一首图标控制音乐播放功能。
3. 点击界面上播放控制区空白处或列表歌曲跳转到播放页面。
4. 点击界面上评论按钮跳转到对应的评论页面。
5. 其他按钮无实际点击事件或功能。
-### 相关权限
+## 具体实现
+1. 使用栅格布局监听断点变化,实现不同断点下的差异显示。
+2. 通过Tabs组件或Swiper组件进行区域的切换。
+3. 使用Blank组件实现中间空格自适应拉伸。
+4. 智能穿戴设备设置borderRadius实现圆形表盘。
+
+## 相关权限
不涉及
-### 约束与限制
+## 约束与限制
-1. 本示例仅支持标准系统上运行,支持设备:华为手机、平板。
-2. HarmonyOS系统:HarmonyOS 5.0.5 Release及以上。
-3. DevEco Studio版本:DevEco Studio 5.0.5 Release及以上。
-4. HarmonyOS SDK版本:HarmonyOS 5.0.5 Release SDK及以上。
\ No newline at end of file
+1. 本示例仅支持标准系统上运行,支持设备:华为手机、平板、智能穿戴。
+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 3d0f99af14199f64903e884cfc9a389996a09759..de53589d8883948be10dbf11bb60e97deed3f21a 100644
--- a/build-profile.json5
+++ b/build-profile.json5
@@ -5,8 +5,8 @@
{
"name": "default",
"signingConfig": "default",
- "compatibleSdkVersion": "5.0.5(17)",
- "targetSdkVersion": "5.0.5(17)",
+ "compatibleSdkVersion": "5.1.0(18)",
+ "targetSdkVersion": "5.1.0(18)",
"runtimeOS": "HarmonyOS"
}
],
@@ -75,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..bb85812b2801a121f9af637b49b9fdf871a6e169 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: [
@@ -55,7 +55,9 @@ export class BackgroundUtil {
Logger.error('this avPlayer: ',
`startBackgroundRunning failed. code ${(error as BusinessError).code} message ${(error as BusinessError).message}`);
}
- });
+ }).catch((err: BusinessError) => {
+ Logger.error(`getWantAgent failed, code: ${JSON.stringify(err.code)}, message: ${JSON.stringify(err.message)}`);
+ })
}
/**
diff --git a/common/mediaCommon/src/main/ets/utils/BreakpointSystem.ets b/common/mediaCommon/src/main/ets/utils/BreakpointSystem.ets
index ceb41c74234c05ca6f1c08b6c2a7cad080a01149..5b802ced25445e6a650aeb14478f6a23911624cb 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 a6a4e714a1399d249cff9902e1fef263157a0de1..d07f5411c9451bfe2b5a8a4cae3f7123843badbd 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,16 +202,22 @@ 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() {
if (!this.context) {
return;
}
- this.session = await avSession.createAVSession(this.context, 'SESSION_NAME', 'audio');
- this.session.activate();
- Logger.info(TAG, `session create done : sessionId : ${this.session.sessionId}`);
+ try {
+ this.session = await avSession.createAVSession(this.context, 'SESSION_NAME', 'audio');
+ Logger.info(TAG, `session create done : sessionId : ${this.session.sessionId}`);
+ this.session.activate();
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
+ }
this.setAVMetadata();
let wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [
@@ -217,12 +229,15 @@ 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);
}
})
+ .catch((err: BusinessError) => {
+ console.error(`getWantAgent failed, code: ${JSON.stringify(err.code)}, message: ${JSON.stringify(err.message)}`);
+ });
this.setListenerForMesFromController();
}
@@ -230,20 +245,28 @@ export class MediaService {
if (!this.session) {
return;
}
- this.session.on('play', this.playCall);
- this.session.on('pause', this.pauseCall);
- this.session.on('playNext', this.playNextCall);
- this.session.on('playPrevious', this.playPreviousCall);
+ try {
+ this.session.on('play', this.playCall);
+ this.session.on('pause', this.pauseCall);
+ this.session.on('playNext', this.playNextCall);
+ this.session.on('playPrevious', this.playPreviousCall);
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
+ }
}
async unregisterSessionListener() {
if (!this.session) {
return;
}
- this.session.off('play');
- this.session.off('pause');
- this.session.off('playNext');
- this.session.off('playPrevious');
+ try {
+ this.session.off('play');
+ this.session.off('pause');
+ this.session.off('playNext');
+ this.session.off('playPrevious');
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
+ }
}
async setAVMetadata() {
@@ -278,8 +301,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 +315,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 +399,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;
@@ -389,12 +416,16 @@ export class MediaService {
if (!this.isPrepared) {
this.start(0);
} else if (this.avPlayer) {
- this.avPlayer.play().then(() => {
- Logger.info(TAG, 'progressTime play() current time:' + this.getCurrentTime());
- this.seek(this.getCurrentTime());
- this.updateIsPlay(true);
- this.state = AudioPlayerState.PLAY;
- })
+ try {
+ this.avPlayer.play().then(() => {
+ Logger.info(TAG, 'progressTime play() current time:' + this.getCurrentTime());
+ this.seek(this.getCurrentTime());
+ this.updateIsPlay(true);
+ this.state = AudioPlayerState.PLAY;
+ });
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
+ }
}
}
@@ -410,7 +441,9 @@ export class MediaService {
if (this.context) {
BackgroundUtil.stopContinuousTask(this.context);
}
- });
+ }).catch((err: BusinessError) => {
+ Logger.error(TAG, 'Failed to pause,error message is :' + err.message);
+ })
}
}
@@ -426,7 +459,11 @@ export class MediaService {
if (isFromControl) {
this.playNext();
} else if (this.avPlayer) {
- this.avPlayer.play();
+ try {
+ this.avPlayer.play();
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
+ }
}
break;
case MusicPlayMode.ORDER:
@@ -489,11 +526,15 @@ export class MediaService {
public async stop() {
Logger.info(TAG, 'stop()');
if (this.isPrepared && this.avPlayer) {
- await this.avPlayer.stop();
- this.updateIsPlay(false);
- this.state = AudioPlayerState.PAUSE;
- if (this.context) {
- BackgroundUtil.stopContinuousTask(this.context);
+ try {
+ await this.avPlayer.stop();
+ this.updateIsPlay(false);
+ this.state = AudioPlayerState.PAUSE;
+ if (this.context) {
+ BackgroundUtil.stopContinuousTask(this.context);
+ }
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
}
}
}
@@ -502,7 +543,11 @@ export class MediaService {
Logger.info(TAG, 'reset()');
await this.songItemBuilder.release();
if (this.avPlayer) {
- await this.avPlayer.reset();
+ try {
+ await this.avPlayer.reset();
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
+ }
}
this.isPrepared = false;
}
@@ -512,20 +557,24 @@ export class MediaService {
*/
public release() {
if (this.avPlayer && this.session && this.context) {
- this.updateIsPlay(false);
- this.stop();
- this.reset();
- this.avPlayer.release();
- this.state = AudioPlayerState.IDLE;
- BackgroundUtil.stopContinuousTask(this.context);
- this.unregisterSessionListener();
- this.session.destroy((err: BusinessError) => {
- if (err) {
- Logger.error(TAG, `Failed to destroy session. Code: ${err.code}, message: ${err.message}`);
- } else {
- Logger.info(TAG, `Destroy : SUCCESS `);
- }
- });
+ try {
+ this.updateIsPlay(false);
+ this.stop();
+ this.reset();
+ this.avPlayer.release();
+ this.state = AudioPlayerState.IDLE;
+ BackgroundUtil.stopContinuousTask(this.context);
+ this.unregisterSessionListener();
+ this.session.destroy((err: BusinessError) => {
+ if (err) {
+ Logger.error(TAG, `Failed to destroy session. Code: ${err.code}, message: ${err.message}`);
+ } else {
+ Logger.info(TAG, `Destroy : SUCCESS `);
+ }
+ });
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
+ }
}
}
@@ -589,7 +638,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 +649,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,10 +666,14 @@ 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);
+ try {
+ await formProvider.updateForm(this.formIds[index], formInfo);
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
+ }
}
}
}
\ No newline at end of file
diff --git a/common/mediaCommon/src/main/ets/utils/PreferencesUtil.ets b/common/mediaCommon/src/main/ets/utils/PreferencesUtil.ets
index be10d451eaf9a941edb60ff77a7e7dbd2520ae43..77d67c0058d0d773042e2244940aead1d38189d1 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,11 +81,13 @@ export class PreferencesUtil {
}
resolve(value);
});
- })
+ });
}
removePreferencesFromCache(context: Context): void {
- preferences.removePreferencesFromCache(context, MY_STORE);
+ preferences.removePreferencesFromCache(context, MY_STORE).catch((err: BusinessError) => {
+ console.error("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
+ })
}
async getFormIds(context: Context): Promise> {
@@ -104,13 +106,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..3e31b9ea2c0bd9bd55174df7fc0c46b28f9697dd 100644
--- a/common/mediaCommon/src/main/ets/utils/SongItemBuilder.ets
+++ b/common/mediaCommon/src/main/ets/utils/SongItemBuilder.ets
@@ -32,24 +32,28 @@ 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;
}
public async release(): Promise {
if (this.context && this.context !== null && this.songItem !== null) {
- this.context.resourceManager.closeRawFd(this.songItem.src);
+ try {
+ this.context.resourceManager.closeRawFd(this.songItem.src);
+ } catch (error) {
+ Logger.error('Failed to closeRawFd');
+ }
}
this.songItem = null;
}
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 cbfac26929b2ce9dd661416726b30425270a850c..6a0a2032694a9d734a4e4ed8d1360a312feea7bc 100644
--- a/features/musicComment/src/main/ets/view/MusicCommentPage.ets
+++ b/features/musicComment/src/main/ets/view/MusicCommentPage.ets
@@ -87,7 +87,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) => {
@@ -142,7 +142,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, index: number) => {
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 ca2427f72f6efe9101831b933af8bfcb2c8c8ef0..cd31cd89ef31a8084b011191bb42e7c6ab74ecec 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 6c96ad716120b87e88a7c8b74bc529ec1a4002b7..51d096acb8513ff41cc0ec303065a174084e3b54 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 0a0d2f0559309902b43d7cb2b16d6e62a8fced99..0018b1bd0effd859895b8f2db149639591dfdde5 100644
--- a/features/musicList/src/main/ets/components/LyricsComponent.ets
+++ b/features/musicList/src/main/ets/components/LyricsComponent.ets
@@ -15,7 +15,7 @@
import { common } from '@kit.AbilityKit';
import { util } from '@kit.ArkTS';
-import { BreakpointType, SongItem } from 'mediacommon';
+import { BreakpointType, Logger, SongItem } from 'mediacommon';
import { BreakpointConstants, StyleConstants } from 'constantscommon';
import { LrcEntry } from '../lyric/LrcEntry';
import { parseKrcLyric, parseLrcLyric } from '../lyric/LrcUtils';
@@ -25,6 +25,8 @@ import { ControlAreaComponent } from './ControlAreaComponent';
import { LyricFile } from '../lyric/LyricConst';
import { PlayerConstants } from '../constants/PlayerConstants';
+const TAG = 'LyricsComponent';
+
@Component
export struct LyricsComponent {
@StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM;
@@ -52,16 +54,20 @@ export struct LyricsComponent {
if (!this.context) {
return;
}
- this.context .resourceManager .getRawFileContent(this.songList[this.selectIndex].lyric)
- .then((value: Uint8Array) => {
- let textDecoder = util.TextDecoder.create(PlayerConstants.ENCODING, { ignoreBOM: true });
- let stringData = textDecoder.decodeToString(value, { stream: false });
- if (this.songList[this.selectIndex].lyric.endsWith(LyricFile.KRC)) {
- this.mLrcEntryList = parseKrcLyric(stringData);
- } else if (this.songList[this.selectIndex].lyric.endsWith(LyricFile.LRC)) {
- this.mLrcEntryList = parseLrcLyric(stringData);
- }
- })
+ try {
+ this.context.resourceManager.getRawFileContent(this.songList[this.selectIndex].lyric)
+ .then((value: Uint8Array) => {
+ let textDecoder = util.TextDecoder.create(PlayerConstants.ENCODING, { ignoreBOM: true });
+ let stringData = textDecoder.decodeToString(value, { stream: false });
+ if (this.songList[this.selectIndex].lyric.endsWith(LyricFile.KRC)) {
+ this.mLrcEntryList = parseKrcLyric(stringData);
+ } else if (this.songList[this.selectIndex].lyric.endsWith(LyricFile.LRC)) {
+ this.mLrcEntryList = parseLrcLyric(stringData);
+ }
+ });
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
+ }
}
build() {
diff --git a/features/musicList/src/main/ets/components/MusicControlComponent.ets b/features/musicList/src/main/ets/components/MusicControlComponent.ets
index 557736299db42580e6a14c5ee31bc4ce4a732f63..72d6894072ffb7747c5a4f7794e03cb8753ea72f 100644
--- a/features/musicList/src/main/ets/components/MusicControlComponent.ets
+++ b/features/musicList/src/main/ets/components/MusicControlComponent.ets
@@ -20,12 +20,16 @@ import { common } from '@kit.AbilityKit';
import { BusinessError, Callback } from '@kit.BasicServicesKit';
import { BreakpointConstants, StyleConstants } from 'constantscommon';
import { ColorConversion, Logger, SongItem } from 'mediacommon';
+import { BreakpointConstants, StyleConstants } from 'constantscommon';
+import { ColorConversion, Logger, SongItem } from 'mediacommon';
import { LyricsComponent } from './LyricsComponent';
import { MusicInfoComponent } from './MusicInfoComponent';
import { ControlAreaComponent } from './ControlAreaComponent';
import { TopAreaComponent } from './TopAreaComponent';
import { PlayerConstants } from '../constants/PlayerConstants';
+const TAG = 'MusicControlComponent';
+
@Preview
@Component
export struct MusicControlComponent {
@@ -72,7 +76,11 @@ export struct MusicControlComponent {
aboutToDisappear(): void {
if (canIUse('SystemCapability.Window.SessionManager')) {
- display.off('foldDisplayModeChange', this.callback);
+ try {
+ display.off('foldDisplayModeChange', this.callback);
+ } catch (error) {
+ Logger.error(TAG, `${error.code} + ${error.message}`);
+ }
this.isFoldFull = false;
}
}
@@ -259,21 +267,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 b85f98f3237947c5bac67302a2fe7bcce6d53815..6f19935dff57c85d1f14b76a46679e421f5a082f 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 718aed16e523d7998334b71b0fbc433047970b10..8afebdc7863eb36386cf9cab04b49dbab3a4b495 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");
@@ -101,7 +102,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'))
@@ -110,19 +111,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)
@@ -130,6 +131,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 a6c77cb7cd2b3d7c71945d016be5206b00ec4386..aaad3ffb501f1cd4f093efba3055c84eb8beacc7 100644
--- a/features/musicList/src/main/ets/components/Player.ets
+++ b/features/musicList/src/main/ets/components/Player.ets
@@ -219,7 +219,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 56a9e27c7751d0aa42b582eaf58cd4a51460ada2..d86b460285c3d000bc8664667f95926bc0ecae8b 100644
--- a/features/musicList/src/main/ets/lyric/LrcUtils.ets
+++ b/features/musicList/src/main/ets/lyric/LrcUtils.ets
@@ -33,10 +33,15 @@ const krcWordRegex2 = new RegExp('<(\\d+),(\\d+),(\\d+)>(.*)');
export async function getRawStringData(context: Context, rawFilePath: string): Promise {
- let value = await context.resourceManager.getRawFileContent(rawFilePath);
- let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
- let stringData = textDecoder.decodeToString(value, { stream: false });
- return stringData;
+ try {
+ let value = await context.resourceManager.getRawFileContent(rawFilePath);
+ let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
+ let stringData = textDecoder.decodeToString(value, { stream: false });
+ return stringData;
+ } catch (error) {
+ Logger.error('Failed to getRawStringData', `${error.code} + ${error.message}`);
+ return ''
+ }
}
/**
@@ -68,7 +73,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 +130,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 640d29d2de2f7a5b239981acf8ac1445fd788647..bf0f49a3514bd407f944166dd72bd9c86ac3b9a0 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 18d86a88f95c500ce2160fc13d1071959221d025..43ec4e9d38626b338d0989862813b0e17a5b1f98 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 'constantscom
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 d9a8238e58c596e0db3ce37d2df14cde235b34ec..95694cb7fded0de337080579e70d5a91712f98b9 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 5362023ce7790b1dccddb9ee32a1ad7b79bdd6c0..058c88220d2fe06553b89555b840c5fae719dd08 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 40e406e35f978b0d584f0b98eeb32a0a35f66d12..670dea9caf5867edf48fcf00b49d193ed9211e16 100644
--- a/products/phone/src/main/ets/entryability/EntryAbility.ets
+++ b/products/phone/src/main/ets/entryability/EntryAbility.ets
@@ -22,7 +22,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');
}
@@ -56,6 +56,9 @@ export default class EntryAbility extends UIAbility {
avoidArea = data.getWindowAvoidArea(type);
let topRectHeight = avoidArea.topRect.height;
AppStorage.setOrCreate('topRectHeight', topRectHeight);
+ }).catch((error: BusinessError) => {
+ hilog.error(0x0000, 'testTag', 'Failed to getMainWindow. Cause: ',
+ JSON.stringify(error.code) + JSON.stringify(error.message));
})
windowStage.loadContent('pages/Index', (err) => {
@@ -69,16 +72,20 @@ export default class EntryAbility extends UIAbility {
}
private updateBreakpoint(windowWidth: number): void{
- let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels;
- let curBp: string = '';
- if (windowWidthVp < BreakpointConstants.BREAKPOINT_VALUE_NUMBER[1]) {
- curBp = BreakpointConstants.BREAKPOINT_SM;
- } else if (windowWidthVp < BreakpointConstants.BREAKPOINT_VALUE_NUMBER[2]) {
- curBp = BreakpointConstants.BREAKPOINT_MD;
- } else {
- curBp = BreakpointConstants.BREAKPOINT_LG;
+ try {
+ let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels;
+ let curBp: string = '';
+ if (windowWidthVp < BreakpointConstants.BREAKPOINT_VALUE_NUMBER[1]) {
+ curBp = BreakpointConstants.BREAKPOINT_SM;
+ } else if (windowWidthVp < BreakpointConstants.BREAKPOINT_VALUE_NUMBER[2]) {
+ curBp = BreakpointConstants.BREAKPOINT_MD;
+ } else {
+ curBp = BreakpointConstants.BREAKPOINT_LG;
+ }
+ AppStorage.setOrCreate('currentBreakpoint', curBp);
+ } catch (error) {
+ hilog.error(0x0000, 'testTag', 'Failed to updateBreakpoint. Cause: %{public}s', JSON.stringify(error) ?? '');
}
- AppStorage.setOrCreate('currentBreakpoint', curBp);
}
onWindowStageDestroy() {
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..295e77c4650b6ef5eab327866e90e9daff2a3636
--- /dev/null
+++ b/products/watch/src/main/ets/pages/Index.ets
@@ -0,0 +1,89 @@
+/*
+ * 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, // The properties of ArcSwiper depend on the ArcSwiperAttribute object for import.
+ 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';
+
+@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) {
+ // [Start home_swiper]
+ Column() {
+ Row() {
+ ArcSwiper(this.wearableSwiperController) {
+ Home()
+ PlayList()
+ }
+ .duration(400)
+ .indicator(this.arcDotIndicator
+ .arcDirection(ArcDirection.SIX_CLOCK_DIRECTION)
+ .selectedItemColor('#FE1B48')
+ )
+ // [StartExclude home_swiper]
+ .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;
+ })
+
+ // [EndExclude home_swiper]
+ }
+ .height(StyleConstants.FULL_HEIGHT)
+ }
+ .width(StyleConstants.FULL_WIDTH)
+
+ // [End home_swiper]
+ }
+ .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..42309b04c6927bc72e903cce3f9e040d3538ae62
--- /dev/null
+++ b/products/watch/src/main/ets/view/Home.ets
@@ -0,0 +1,117 @@
+/*
+ * 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 // The properties of ArcList depend on the ArcListAttribute object for import.
+} 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() {
+ // [Start home_list]
+ 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)
+ .backgroundColor($r('app.color.home_btn_background'))
+ // [StartExclude home_list]
+ .onClick(() => {
+ if (item.pathName === 'setting') {
+ return;
+ }
+ this.pageStack.replacePathByName(item.pathName, null);
+ })
+
+ // [EndExclude home_list]
+ }
+ }, (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)
+
+ // [End home_list]
+ }
+}
\ 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..db5b87ebd079d915ea2cd0dcaa7c7981201ec400
--- /dev/null
+++ b/products/watch/src/main/ets/view/PlayList.ets
@@ -0,0 +1,140 @@
+/*
+ * 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, // The properties of ArcSwiper depend on the ArcSwiperAttribute object for import.
+ 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() {
+ // [Start play_list]
+ Column() {
+ ArcSwiper(this.wearableSwiperController) {
+ ForEach(this.playList, (item: PlayListSheet) => {
+ 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') })
+ // [StartExclude play_list]
+ .onClick(() => {
+ this.pageStack.replacePathByName('songList', null);
+ })
+
+ // [EndExclude play_list]
+ }, (item: PlayListSheet, index?: number) => index + JSON.stringify(item))
+ }
+ .index(0)
+ .duration(400)
+ .focusable(true)
+ .focusOnTouch(true)
+ .defaultFocus(true)
+ .vertical(true)
+ .indicator(false)
+ // [StartExclude play_list]
+ .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;
+ })
+
+ // [EndExclude play_list]
+ }
+ .width(StyleConstants.FULL_WIDTH)
+ .height(StyleConstants.FULL_HEIGHT)
+
+ // [End play_list]
+ }
+ .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..46d52a0c099f3e941d9502875564072c1c7ba178
--- /dev/null
+++ b/products/watch/src/main/ets/view/SongList.ets
@@ -0,0 +1,98 @@
+/*
+ * 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, // The properties of ArcList depend on ArcListAttribute and ArcListItemAttribute objects for import.
+ 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() {
+ // [Start song_list]
+ Column() {
+ ArcList({ initialIndex: 0 }) {
+ ForEach(this.songList, (item: SongItem, index: number) => {
+ 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)
+ // [StartExclude song_list]
+ .onClick(async () => {
+ await MediaService.getInstance().loadAssent(index);
+ this.pageStack.replacePathByName('songPage', null);
+ })
+
+ // [EndExclude song_list]
+ }, (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)
+
+ // [End song_list]
+ }
+ .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..cec3f63136797f521d80449bbe84e58e1578188c
--- /dev/null
+++ b/products/watch/src/main/ets/view/SongPage.ets
@@ -0,0 +1,147 @@
+/*
+ * 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() {
+ // [Start song_page]
+ 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'))
+ }
+ // [StartExclude song_page]
+ .onClick(() => {
+ MediaService.getInstance().playPrevious();
+ })
+
+ // [EndExclude song_page]
+ 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)
+ // [StartExclude song_page]
+ .onClick(() => {
+ if (MediaService.getInstance().getFirst()) {
+ MediaService.getInstance().loadAssent(0);
+ } else {
+ this.isPlay ? MediaService.getInstance().pause() : MediaService.getInstance().play();
+ }
+ })
+ // [EndExclude song_page]
+
+ Image($r('app.media.pause_btn'))
+ .width($r('app.float.play_song_img'))
+ .visibility(this.isPlay === true ? Visibility.Visible : Visibility.None)
+ // [StartExclude song_page]
+ .onClick(() => {
+ MediaService.getInstance().pause();
+ })
+ // [EndExclude song_page]
+ }
+ .width(this.HALF_WIDTH)
+ .align(Alignment.Center)
+
+ Column() {
+ Image($r('app.media.next_btn'))
+ .width($r('app.float.play_song_img'))
+ }
+ // [StartExclude song_page]
+ .onClick(() => {
+ MediaService.getInstance().playNextAuto(true);
+ })
+
+ // [EndExclude song_page]
+ }
+ .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)
+
+ // [End song_page]
+ 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..1ceb26e8e992d068203d52121122d8335c9d9d61
--- /dev/null
+++ b/products/watch/src/main/ets/view/VolumeSliderComponent.ets
@@ -0,0 +1,106 @@
+/*
+ * 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() {
+ // [Start volume_slider]
+ 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
+ })
+
+ // [End volume_slider]
+ }
+}
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..c9e33b769ae30730569fba4c3f2e9ce7714215a6
--- /dev/null
+++ b/products/watch/src/main/ets/watchability/WatchAbility.ets
@@ -0,0 +1,60 @@
+/*
+ * 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 { 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..2a9f646b70f1ff739aa1f995490bbd9e0aeca74f
--- /dev/null
+++ b/products/watch/src/main/ets/watchbackupability/WatchBackupAbility.ets
@@ -0,0 +1,31 @@
+/*
+ * 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 { 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..0e6d8920ab281b964c6180983b67665bb939f012
--- /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": "MusicHome"
+ },
+ {
+ "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..75c40fb622988ae44ed8fb440f09aaf1e80f34f5
--- /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": "MusicHome"
+ },
+ {
+ "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..d6af1dc5aabcc1bda25b0d8a53fc919d5ea1aafa
--- /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": "多设备界面音乐"
+ },
+ {
+ "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