├── .gitignore ├── AppScope ├── app.json5 └── resources │ └── base │ ├── element │ └── string.json │ └── media │ └── app_icon.png ├── README.md ├── build-profile.json5 ├── entry ├── .gitignore ├── build-profile.json5 ├── hvigorfile.ts ├── oh-package-lock.json5 ├── oh-package.json5 └── src │ ├── main │ ├── ets │ │ ├── bean │ │ │ ├── AlbumResultBean.ets │ │ │ ├── CommentResultBean.ets │ │ │ ├── DynamicResultBean.ets │ │ │ ├── LoginResultBean.ets │ │ │ ├── LyricResultBean.ets │ │ │ ├── SongSheetDetailResultBean.ets │ │ │ ├── SongUrlResultBean.ets │ │ │ ├── UserPlaylistResultBean.ets │ │ │ └── index.ets │ │ ├── common │ │ │ ├── constant │ │ │ │ ├── ApiConstant.ets │ │ │ │ ├── EventConstants.ets │ │ │ │ ├── KVConstant.ets │ │ │ │ ├── StorageConstant.ets │ │ │ │ └── index.ets │ │ │ ├── core │ │ │ │ ├── MusicPlayController.ets │ │ │ │ ├── NCPlayer.ets │ │ │ │ ├── UserManager.ets │ │ │ │ └── index.ets │ │ │ ├── index.ets │ │ │ └── util │ │ │ │ ├── LyricUtil.ets │ │ │ │ └── index.ets │ │ ├── entryability │ │ │ ├── EntryAbility.ets │ │ │ ├── EntryAbility.js │ │ │ └── EntryAbility.js.map │ │ ├── index.ets │ │ └── pages │ │ │ ├── cpn │ │ │ ├── CpnCommentItem.ets │ │ │ ├── CpnDynamicItem.ets │ │ │ └── index.ets │ │ │ ├── dynamic │ │ │ ├── index.ets │ │ │ ├── view │ │ │ │ ├── DynamicCommnentList.ets │ │ │ │ ├── DynamicDetailPage.ets │ │ │ │ ├── DynamicLikeList.ets │ │ │ │ └── DynamicShareList.ets │ │ │ └── viewmodel │ │ │ │ ├── AttentionDynamicViewModel.ets │ │ │ │ └── DynamicCommentViewModel.ets │ │ │ ├── index.ets │ │ │ ├── login │ │ │ ├── index.ets │ │ │ ├── view │ │ │ │ └── Login.ets │ │ │ └── viewmodel │ │ │ │ └── LoginViewModel.ets │ │ │ ├── main │ │ │ ├── discovery │ │ │ │ ├── index.ets │ │ │ │ └── view │ │ │ │ │ └── Discovery.ets │ │ │ ├── dynamic │ │ │ │ ├── index.ets │ │ │ │ └── view │ │ │ │ │ ├── AttentionDynamic.ets │ │ │ │ │ ├── Dynamic.ets │ │ │ │ │ └── SquareDynamic.ets │ │ │ ├── index.ets │ │ │ ├── my │ │ │ │ ├── dynamic │ │ │ │ │ ├── index.ets │ │ │ │ │ ├── view │ │ │ │ │ │ └── MyDynamic.ets │ │ │ │ │ └── viewmodel │ │ │ │ │ │ └── UserDynamicViewModel.ets │ │ │ │ ├── index.ets │ │ │ │ ├── music │ │ │ │ │ ├── index.ets │ │ │ │ │ ├── view │ │ │ │ │ │ ├── AlbumMusic.ets │ │ │ │ │ │ ├── CollectMusic.ets │ │ │ │ │ │ └── CreateMusic.ets │ │ │ │ │ └── viewmodel │ │ │ │ │ │ ├── AlbumViewModel.ets │ │ │ │ │ │ └── SongSheetViewModel.ets │ │ │ │ ├── podcast │ │ │ │ │ ├── index.ets │ │ │ │ │ └── view │ │ │ │ │ │ ├── AllPodcast.ets │ │ │ │ │ │ ├── MyPodcast.ets │ │ │ │ │ │ └── VoiceBook.ets │ │ │ │ └── view │ │ │ │ │ ├── CpnAlbumItem.ets │ │ │ │ │ ├── CpnMusicPlayBar.ets │ │ │ │ │ ├── CpnMyHeader.ets │ │ │ │ │ ├── CpnPodcastSheetItem.ets │ │ │ │ │ ├── CpnSongSheetItem.ets │ │ │ │ │ ├── My.ets │ │ │ │ │ └── index.ets │ │ │ ├── podcast │ │ │ │ ├── index.ets │ │ │ │ └── view │ │ │ │ │ └── Podcast.ets │ │ │ └── view │ │ │ │ ├── Main.ets │ │ │ │ ├── cpn │ │ │ │ └── MainDrawer.ets │ │ │ │ └── index.ets │ │ │ ├── pics │ │ │ ├── index.ets │ │ │ └── view │ │ │ │ └── PicturePreview.ets │ │ │ ├── play │ │ │ ├── index.ets │ │ │ ├── view │ │ │ │ ├── CpnLyric.ets │ │ │ │ ├── MusicPlay.ets │ │ │ │ └── list │ │ │ │ │ ├── CurrentMusicPlayList.ets │ │ │ │ │ ├── HistoryMusicPlayList.ets │ │ │ │ │ └── MusicPlayListDialog.ets │ │ │ └── viewmodel │ │ │ │ └── LyricViewModel.ets │ │ │ ├── songsheet │ │ │ ├── index.ets │ │ │ ├── view │ │ │ │ └── SongSheetDetail.ets │ │ │ └── viewmodel │ │ │ │ └── SongSheetDetailViewModel.ets │ │ │ └── splash │ │ │ ├── index.ets │ │ │ └── view │ │ │ └── Splash.ets │ ├── module.json5 │ └── resources │ │ ├── base │ │ ├── element │ │ │ ├── color.json │ │ │ ├── float.json │ │ │ └── string.json │ │ ├── media │ │ │ ├── ic_add.svg │ │ │ ├── ic_app_logo.png │ │ │ ├── ic_arrow_down.svg │ │ │ ├── ic_bar_pause.svg │ │ │ ├── ic_bar_play.svg │ │ │ ├── ic_buy.svg │ │ │ ├── ic_cloud.svg │ │ │ ├── ic_collect.svg │ │ │ ├── ic_comment.svg │ │ │ ├── ic_default_avatar.svg │ │ │ ├── ic_delete.svg │ │ │ ├── ic_disk.webp │ │ │ ├── ic_empty.svg │ │ │ ├── ic_fail.svg │ │ │ ├── ic_like.svg │ │ │ ├── ic_loading.gif │ │ │ ├── ic_local_file.svg │ │ │ ├── ic_menu.svg │ │ │ ├── ic_more.svg │ │ │ ├── ic_more_item.svg │ │ │ ├── ic_nav_discovery.svg │ │ │ ├── ic_nav_dynamic.svg │ │ │ ├── ic_nav_my.svg │ │ │ ├── ic_nav_podcast.svg │ │ │ ├── ic_network_error.svg │ │ │ ├── ic_play_comment.svg │ │ │ ├── ic_play_download.svg │ │ │ ├── ic_play_like.svg │ │ │ ├── ic_play_list.svg │ │ │ ├── ic_play_mode_random.svg │ │ │ ├── ic_play_mode_repeat.svg │ │ │ ├── ic_play_mode_single.svg │ │ │ ├── ic_play_more.svg │ │ │ ├── ic_play_needle.webp │ │ │ ├── ic_play_next.svg │ │ │ ├── ic_play_play_list.svg │ │ │ ├── ic_play_pre.svg │ │ │ ├── ic_play_sing.svg │ │ │ ├── ic_play_start.svg │ │ │ ├── ic_play_stop.svg │ │ │ ├── ic_recent.svg │ │ │ ├── ic_search.svg │ │ │ ├── ic_selected.svg │ │ │ ├── ic_share.svg │ │ │ ├── ic_theme.svg │ │ │ └── icon.png │ │ └── profile │ │ │ └── main_pages.json │ │ ├── en_US │ │ └── element │ │ │ └── string.json │ │ └── zh_CN │ │ └── element │ │ └── string.json │ └── ohosTest │ ├── ets │ ├── test │ │ ├── Ability.test.ets │ │ └── List.test.ets │ ├── testability │ │ ├── TestAbility.ets │ │ └── pages │ │ │ └── Index.ets │ └── testrunner │ │ └── OpenHarmonyTestRunner.ts │ ├── module.json5 │ └── resources │ └── base │ ├── element │ ├── color.json │ └── string.json │ ├── media │ └── icon.png │ └── profile │ └── test_pages.json ├── hvigor ├── hvigor-config.json5 └── hvigor-wrapper.js ├── hvigorfile.ts ├── hvigorw ├── hvigorw.bat ├── lib_common ├── .gitignore ├── build-profile.json5 ├── hvigorfile.ts ├── index.ets ├── oh-package-lock.json5 ├── oh-package.json5 └── src │ └── main │ ├── ets │ ├── api │ │ ├── ApiRequest.ets │ │ └── index.ets │ ├── bean │ │ ├── BaseDataSource.ets │ │ ├── BaseResultBean.ets │ │ ├── RequestParamBean.ets │ │ └── index.ets │ ├── constant │ │ ├── RouterUrls.ets │ │ ├── SizeConstant.ts │ │ └── index.ets │ ├── cpn │ │ ├── ClickEffectLayout.ets │ │ ├── CollapsibleLayout.ets │ │ ├── CommonAppBar.ets │ │ ├── CommonNetworkImage.ets │ │ ├── CpnLoading.ets │ │ ├── CpnProgressBar.ets │ │ ├── RefreshLayout.ets │ │ ├── TabLayout.ets │ │ ├── index.ets │ │ └── viewstate │ │ │ ├── ViewStateEmpty.ets │ │ │ ├── ViewStateError.ets │ │ │ ├── ViewStateLayout.ets │ │ │ ├── ViewStateLoading.ets │ │ │ ├── ViewStatePagingLayout.ets │ │ │ └── index.ets │ ├── index.ets │ ├── util │ │ ├── ArrayUtil.ets │ │ ├── KVUtil.ets │ │ ├── LogUtil.ets │ │ ├── ScreenUtils.ets │ │ ├── StringUtil.ets │ │ ├── TimeUtil.ets │ │ ├── VelocityTracker.ets │ │ └── index.ets │ └── viewmodel │ │ ├── BaseViewModel.ets │ │ └── index.ets │ ├── module.json5 │ └── resources │ └── base │ └── media │ ├── ic_arrow_up.svg │ ├── ic_back.svg │ ├── ic_default_place_holder.svg │ ├── ic_empty.svg │ ├── ic_error.svg │ └── ic_network_error.svg ├── lib_theme ├── .gitignore ├── build-profile.json5 ├── hvigorfile.ts ├── index.ets ├── oh-package.json5 └── src │ └── main │ ├── ets │ ├── AppTheme.ets │ ├── ThemeType.ets │ ├── index.ets │ └── palette │ │ ├── DarkThemePalette.ets │ │ ├── DefaultThemePalette.ets │ │ ├── GreenThemePalette.ets │ │ ├── IThemePalette.ets │ │ ├── OriginThemePalette.ets │ │ └── index.ets │ └── module.json5 ├── oh-package-lock.json5 ├── oh-package.json5 └── screenshot ├── CollapsibleLayout.png ├── project_structure_1.jpg ├── project_structure_2.jpg ├── 主题切换.gif ├── 广场.gif ├── 音乐播放.gif └── 首页.gif /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /local.properties 4 | /.idea 5 | **/build 6 | /.hvigor 7 | .cxx 8 | /.clangd 9 | /.clang-format 10 | /.clang-tidy 11 | **/.test -------------------------------------------------------------------------------- /AppScope/app.json5: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "bundleName": "com.ssk.numusic", 4 | "vendor": "example", 5 | "versionCode": 1000000, 6 | "versionName": "1.0.0", 7 | "icon": "$media:app_icon", 8 | "label": "$string:app_name" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /AppScope/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "app_name", 5 | "value": "NCMusicHarmony" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /AppScope/resources/base/media/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/AppScope/resources/base/media/app_icon.png -------------------------------------------------------------------------------- /build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "compileSdkVersion": 9, 4 | "compatibleSdkVersion": 9, 5 | "products": [ 6 | { 7 | "name": "default", 8 | "signingConfig": "default" 9 | } 10 | ], 11 | }, 12 | "modules": [ 13 | { 14 | "name": "entry", 15 | "srcPath": "./entry", 16 | "targets": [ 17 | { 18 | "name": "default", 19 | "applyToProducts": [ 20 | "default" 21 | ] 22 | } 23 | ] 24 | }, 25 | { 26 | "name": "lib_theme", 27 | "srcPath": "./lib_theme" 28 | }, 29 | { 30 | "name": "lib_common", 31 | "srcPath": "./lib_common" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /entry/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /entry/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": 'stageMode', 3 | "buildOption": { 4 | }, 5 | "targets": [ 6 | { 7 | "name": "default", 8 | "runtimeOS": "HarmonyOS" 9 | }, 10 | { 11 | "name": "ohosTest", 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /entry/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | // Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. 2 | export { hapTasks } from '@ohos/hvigor-ohos-plugin'; 3 | -------------------------------------------------------------------------------- /entry/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 4 | "specifiers": { 5 | "class-transformer@^0.5.1": "class-transformer@0.5.1" 6 | }, 7 | "packages": { 8 | "class-transformer@0.5.1": { 9 | "resolved": "https://repo.harmonyos.com/ohpm/class-transformer/-/class-transformer-0.5.1.tgz", 10 | "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /entry/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "license": "", 3 | "devDependencies": {}, 4 | "author": "", 5 | "name": "entry", 6 | "description": "Please describe the basic information.", 7 | "main": "", 8 | "version": "1.0.0", 9 | "dependencies": { 10 | "class-transformer": "^0.5.1", 11 | "lib_theme": "file:../lib_theme", 12 | "lib_common": "file:../lib_common" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /entry/src/main/ets/bean/AlbumResultBean.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 专辑请求结果实体类 3 | */ 4 | import { BaseResultBean } from 'lib_common/src/main/ets/bean/BaseResultBean' 5 | import { ArBean } from './UserPlaylistResultBean' 6 | 7 | export class AlbumResultBean extends BaseResultBean { 8 | count: number 9 | hasMore: boolean 10 | paidCount: number 11 | data: AlbumBean[] 12 | } 13 | 14 | export class AlbumBean { 15 | subTime: number 16 | artists: ArBean[] 17 | picUrl: string 18 | name: string 19 | size: number 20 | } -------------------------------------------------------------------------------- /entry/src/main/ets/bean/CommentResultBean.ets: -------------------------------------------------------------------------------- 1 | import { BaseResultBean } from 'lib_common' 2 | 3 | /** 4 | * 评论请求结果实体类 5 | */ 6 | export class CommentResultBean extends BaseResultBean { 7 | comments: CommentBean[] 8 | } 9 | 10 | export class CommentBean { 11 | user: CommentUserBean 12 | beReplied: object // todo 13 | commentId: number 14 | content: string 15 | time: number 16 | timeStr: string 17 | likedCount: number 18 | owner: boolean 19 | liked: boolean 20 | } 21 | 22 | export class CommentUserBean { 23 | avatarUrl: string 24 | nickname: string 25 | userId: number 26 | } -------------------------------------------------------------------------------- /entry/src/main/ets/bean/DynamicResultBean.ets: -------------------------------------------------------------------------------- 1 | import { BaseResultBean } from 'lib_common' 2 | import { Al, Ar, ProfileBean, UserPlaylistBean } from '.' 3 | 4 | /** 动态请求结果实体类 */ 5 | export class DynamicResultBean extends BaseResultBean { 6 | event: DynamicBean[] 7 | lasttime: number 8 | } 9 | 10 | /** 用户动态请求结果实体类 */ 11 | export class UserDynamicResultBean extends BaseResultBean { 12 | events: DynamicBean[] 13 | lasttime: number 14 | } 15 | 16 | export class DynamicBean { 17 | threadId: string 18 | info: DynamicInfo 19 | user: ProfileBean 20 | eventTime: number 21 | pics: DynamicPics[] 22 | json: string 23 | jsonBean: DynamicJsonBean | null 24 | } 25 | 26 | export class DynamicJsonBean { 27 | msg: string 28 | playlist: UserPlaylistBean 29 | song: DynamicSongBean 30 | } 31 | 32 | 33 | export class DynamicSongBean { 34 | //歌曲id 35 | id: number 36 | //歌曲名称 37 | name: string 38 | artists: Ar[] 39 | album: Al 40 | } 41 | 42 | 43 | export class DynamicInfo { 44 | resourceType: number 45 | resourceId: number 46 | liked: boolean 47 | commentCount: number 48 | likedCount: number 49 | shareCount: number 50 | hotCount: number 51 | } 52 | 53 | export class DynamicPics { 54 | originUrl: string 55 | squareUrl: string 56 | rectangleUrl: string 57 | height: number 58 | width: number 59 | } 60 | -------------------------------------------------------------------------------- /entry/src/main/ets/bean/LoginResultBean.ets: -------------------------------------------------------------------------------- 1 | import { BaseResultBean } from 'lib_common' 2 | 3 | /** 登陆二维码key请求结果实体类 */ 4 | export class QrcodeKeyResultBean extends BaseResultBean { 5 | data: QrcodeKeyBean 6 | } 7 | 8 | export class QrcodeKeyBean { 9 | unikey: string 10 | } 11 | 12 | /** 登陆二维码value请求结果实体类 */ 13 | @Observed 14 | export class QrcodeValueResultBean extends BaseResultBean { 15 | data: QrcodeValueBean 16 | } 17 | 18 | @Observed 19 | export class QrcodeValueBean { 20 | qrurl: string 21 | qrimg: string 22 | } 23 | 24 | /** 二维码鉴权请求结果实体类 */ 25 | @Observed 26 | export class QrcodeAuthResultBean extends BaseResultBean { 27 | cookie: string 28 | nickname: string 29 | avatarUrl: string 30 | } 31 | 32 | 33 | /** 账户信息请求结果实体类 */ 34 | @Observed 35 | export class AccountInfoResultBean extends BaseResultBean { 36 | account: AccountBean 37 | profile: ProfileBean 38 | } 39 | 40 | @Observed 41 | export class AccountBean { 42 | id: number 43 | userName: string 44 | type: number 45 | status: number 46 | whitelistAuthority: number 47 | createTime: number 48 | tokenVersion: number 49 | ban: number 50 | baoyueVersion: number 51 | donateVersion: number 52 | vipType: number 53 | viptypeVersion: number 54 | anonimousUser: boolean 55 | } 56 | 57 | export class ProfileBean { 58 | followed: boolean 59 | userId: number 60 | defaultAvatar: boolean 61 | avatarUrl: string 62 | nickname: string 63 | birthday: number 64 | province: number 65 | accountStatus: number 66 | vipType: number 67 | gender: number 68 | djStatus: number 69 | mutual: boolean 70 | authStatus: number 71 | backgroundImgId: number 72 | userType: number 73 | city: number 74 | backgroundUrl: string 75 | followeds: number 76 | follows: number 77 | eventCount: number 78 | playlistCount: number 79 | playlistBeSubscribedCount: number 80 | } 81 | 82 | 83 | /** 用户详细信息请求结果实体类 */ 84 | export class UserDetailResultBean extends BaseResultBean { 85 | level: number 86 | listenSongs: number 87 | createTime: number 88 | createDays: number 89 | profile: UserDetailProfile 90 | } 91 | 92 | @Observed 93 | export class UserDetailProfile { 94 | avatarUrl: string 95 | backgroundUrl: string 96 | gender: number 97 | nickname: string 98 | userId: string 99 | followeds: number 100 | follows: number 101 | playlistCount: number 102 | } 103 | 104 | export interface IUserInfoBean { 105 | detail: UserDetailResultBean | null 106 | cookie: string | null 107 | } 108 | 109 | export class UserInfoBean { 110 | detail: UserDetailResultBean 111 | cookie: string | null 112 | 113 | constructor(model: IUserInfoBean) { 114 | this.detail = model.detail 115 | this.cookie = model.cookie 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /entry/src/main/ets/bean/LyricResultBean.ets: -------------------------------------------------------------------------------- 1 | import { BaseResultBean } from 'lib_common' 2 | 3 | 4 | /** 5 | * 歌词请求结果实体类 6 | */ 7 | export class LyricResultBean extends BaseResultBean { 8 | transUser?: LyricContributorBean 9 | lyricUser?: LyricContributorBean 10 | lrc?: LrcBean 11 | tlyric?: LrcBean 12 | } 13 | 14 | export class LyricContributorBean { 15 | id: number 16 | status: number 17 | demand: number 18 | userid: number 19 | nickname: string 20 | uptime: number 21 | } 22 | 23 | /** 24 | [00:00.000] 作词 : 潘源良 25 | [00:01.000] 作曲 : Eric Kwok 26 | [00:02.000] 编曲 : 张子坚 27 | [00:03.000] 制作人 : Eric Kwok 28 | [00:16.490]你 何以始终不说话 29 | [00:22.580]尽管讲出不快吧 30 | [00:26.170]事与冀盼有落差 31 | [00:29.010]请不必惊怕 32 | [00:31.240]我 仍然会冷静聆听 33 | [00:37.240]仍然紧守于身边 34 | [00:41.040]与你进退也共鸣 35 | [00:45.130]时日会蔓延再蔓延 36 | [00:49.290]某些不可改变的改变 37 | [00:52.730]与一些不要发现的发现 38 | [00:56.370]就这么放大了缺点 39 | [01:00.210]来让我问谁可决定 40 | [01:03.760]那些东西叫作完美至善 41 | [01:07.460]我只懂得 爱你在每天 42 | [01:14.090]当潮流爱新鲜 43 | [01:17.730]当旁人爱标签 44 | [01:21.380]幸得伴着你我 45 | [01:24.310]是窝心的自然 46 | [01:28.810]当闲言再尖酸 47 | [01:32.450]给他妒忌多点 48 | [01:36.100]因世上的至爱 49 | [01:38.970]是不计较条件 50 | [01:43.370]谁又可清楚看见 51 | [01:59.620]美 难免总有些缺憾 52 | [02:05.440]若果不甘心去问 53 | [02:09.190]问到最尾叫内心 54 | [02:11.870]也长出裂痕 55 | [02:14.250]笑 何妨与你又重温 56 | [02:20.800]仍然我说我庆幸 57 | [02:24.340]你永远胜过别人 58 | [02:28.550]期待美没完爱没完 59 | [02:32.640]放开不必打算的打算 60 | [02:36.130]作一些可以约定的约定 61 | [02:39.770]就抱紧以后每一天 62 | [02:43.680]其实你定然都发现 63 | [02:47.210]我有很多未达完美事情 64 | [02:50.810]我只懂得 再努力每天 65 | [02:57.490]当潮流爱新鲜 66 | [03:01.130]当旁人爱标签 67 | [03:04.820]幸得伴着你我 68 | [03:07.750]是窝心的自然 69 | [03:12.160]当闲言再尖酸 70 | [03:15.850]给他妒忌多点 71 | [03:19.500]因世上的至爱 72 | [03:22.530]是不计较条件 73 | [03:26.890]谁又可清楚看见 74 | */ 75 | export class LrcBean { 76 | version: number 77 | lyric: string 78 | } 79 | 80 | export class LyricModel { 81 | time: number 82 | lyric ?: string 83 | tLyric ?: string 84 | 85 | constructor(time: number, lyric? : string, tLyric?: string) { 86 | this.time = time 87 | this.lyric = lyric 88 | this.tLyric = tLyric 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /entry/src/main/ets/bean/SongSheetDetailResultBean.ets: -------------------------------------------------------------------------------- 1 | import { BaseResultBean } from 'lib_common' 2 | 3 | /** 歌单详情请求结果实体类 */ 4 | export class SongSheetDetailResultBean extends BaseResultBean { 5 | songs: SongBean[] 6 | } 7 | 8 | export class SongBean { 9 | //歌曲id 10 | id: number 11 | //歌曲名称 12 | name: string 13 | al: Al 14 | ar: Ar[] 15 | } 16 | 17 | export class Ar { 18 | id: number 19 | name: string 20 | } 21 | 22 | export class Al { 23 | id: number 24 | name: string 25 | picUrl: string 26 | } 27 | -------------------------------------------------------------------------------- /entry/src/main/ets/bean/SongUrlResultBean.ets: -------------------------------------------------------------------------------- 1 | import { BaseResultBean } from 'lib_common' 2 | 3 | /** 4 | * 歌曲url请求结果实体类 5 | */ 6 | export class SongUrlResultBean extends BaseResultBean { 7 | data: SongUrlBean[] 8 | } 9 | 10 | export class SongUrlBean { 11 | url: string 12 | } -------------------------------------------------------------------------------- /entry/src/main/ets/bean/UserPlaylistResultBean.ets: -------------------------------------------------------------------------------- 1 | /** 用户歌单列表请求结果实体类 */ 2 | import { BaseResultBean } from 'lib_common' 3 | 4 | /** 5 | * 歌单列表请求结果实体类 6 | */ 7 | export class UserPlaylistResultBean extends BaseResultBean { 8 | playlist: UserPlaylistBean[] 9 | } 10 | 11 | export class UserPlayListDetailResultBean extends BaseResultBean { 12 | playlist: UserPlaylistBean 13 | } 14 | 15 | export class UserPlaylistBean { 16 | tracks: TrackBean[] 17 | trackIds: TrackIdBean[] 18 | creator: SubscribersBean 19 | name: string 20 | coverImgUrl: string 21 | trackCount: number 22 | id: number 23 | playCount: number 24 | description: string 25 | shareCount: number 26 | commentCount: number 27 | subscribedCount: number 28 | userId: number 29 | } 30 | 31 | export class TrackBean { 32 | name: string 33 | id: number 34 | mv: number 35 | ar: ArBean[] 36 | al: AlBean 37 | } 38 | 39 | export class TrackIdBean { 40 | id: number 41 | v: number 42 | alg: string 43 | } 44 | 45 | export class SubscribersBean { 46 | userId: number 47 | avatarUrl: string 48 | nickname: string 49 | } 50 | 51 | export class ArBean { 52 | id: number 53 | name: string 54 | } 55 | 56 | export class AlBean { 57 | id: number 58 | v: number 59 | alg: string 60 | } 61 | -------------------------------------------------------------------------------- /entry/src/main/ets/bean/index.ets: -------------------------------------------------------------------------------- 1 | export * from './LoginResultBean' 2 | export * from './AlbumResultBean' 3 | export * from './UserPlaylistResultBean' 4 | export * from './SongSheetDetailResultBean' 5 | export * from './DynamicResultBean' 6 | export * from "./CommentResultBean" 7 | export * from './SongUrlResultBean' 8 | export * from './LyricResultBean' 9 | -------------------------------------------------------------------------------- /entry/src/main/ets/common/constant/ApiConstant.ets: -------------------------------------------------------------------------------- 1 | export class ApiConstant { 2 | // 获取登陆二维码Key 3 | static readonly URL_QRCODE_KEY = "/login/qr/key" 4 | // 获取登陆二维码Value 5 | static readonly URL_QRCODE_VALUE = "/login/qr/create" 6 | // 查询登陆鉴权 7 | static readonly URL_CHECK_QRCODE = "/login/qr/check" 8 | // 获取账户信息 9 | static readonly URL_ACCOUNT_INFO = "/user/account" 10 | // 获取用户详细信息 11 | static readonly URL_USER_DETAIL = "/user/detail" 12 | // 获取用户歌单列表 13 | static readonly URL_USER_PLAY_LIST = "/user/playlist" 14 | // 获取已收藏专辑列表 15 | static readonly URL_COLLECT_ALBUM_LIST = "/album/sublist" 16 | // 获取歌单详情 17 | static readonly URL_PLAY_LIST_DETAIL = "/playlist/detail" 18 | // 获取歌曲详情 19 | static readonly URL_SONG_DETAIL = "/song/detail" 20 | // 获取歌曲url 21 | static readonly URL_SONG_URL = "/song/url" 22 | // 获取动态 23 | static readonly URL_DYNAMIC = "/event" 24 | // 获取用户动态 25 | static readonly URL_USER_DYNAMIC = "/user/event" 26 | // 获取动态评论 27 | static readonly URL_DYNAMIC_COMMENT = "/comment/event" 28 | // 获取歌词 29 | static readonly URL_LYRIC = "/lyric" 30 | } -------------------------------------------------------------------------------- /entry/src/main/ets/common/constant/EventConstants.ets: -------------------------------------------------------------------------------- 1 | export class EventIds { 2 | // 播放状态事件ID 3 | static readonly PLAYER_STATUS = 1 4 | // 播放总时长事件ID 5 | static readonly PLAYER_DURING = 2 6 | // 当前播放时长事件ID 7 | static readonly PLAYER_TIME_UPDATE = 3 8 | // 播放模式事件ID 9 | static readonly PLAYER_MODE = 4 10 | // 当前播放索引事件ID 11 | static readonly PLAYER_INDEX = 5 12 | 13 | } 14 | -------------------------------------------------------------------------------- /entry/src/main/ets/common/constant/KVConstant.ets: -------------------------------------------------------------------------------- 1 | export class KVConstants { 2 | // 用户信息key 3 | static readonly KEY_USER_INFO = "userInfo" 4 | } -------------------------------------------------------------------------------- /entry/src/main/ets/common/constant/StorageConstant.ets: -------------------------------------------------------------------------------- 1 | // 存储在AppStorage的用户信息key, 2 | export const STORAGE_USER_INFO = "userInfo" 3 | 4 | // 存储在AppStorage的首页策划栏开关key, 5 | export const STORAGE_MAIN_DRAWER_TOGGLE = "mainDrawerToggle" -------------------------------------------------------------------------------- /entry/src/main/ets/common/constant/index.ets: -------------------------------------------------------------------------------- 1 | export * from './ApiConstant' 2 | export * from './KVConstant' 3 | export * from './StorageConstant' 4 | export * from './EventConstants' 5 | -------------------------------------------------------------------------------- /entry/src/main/ets/common/core/UserManager.ets: -------------------------------------------------------------------------------- 1 | import { KEY_COOKIE, KVUtil } from 'lib_common' 2 | import { KVConstants, STORAGE_USER_INFO } from '..' 3 | import { UserInfoBean } from '../../bean' 4 | 5 | export class UserManager { 6 | static async getUserInfo(): Promise { 7 | const result = await KVUtil.get(KVConstants.KEY_USER_INFO) 8 | return result 9 | } 10 | 11 | static async setUserInfo(userInfo: UserInfoBean) { 12 | AppStorage.Set(STORAGE_USER_INFO, userInfo) 13 | await KVUtil.putString(KVConstants.KEY_USER_INFO, JSON.stringify(userInfo)) 14 | await KVUtil.putString(KEY_COOKIE, userInfo.cookie) 15 | } 16 | } -------------------------------------------------------------------------------- /entry/src/main/ets/common/core/index.ets: -------------------------------------------------------------------------------- 1 | export * from './UserManager' 2 | 3 | export * from './NCPlayer' 4 | 5 | export * from './MusicPlayController' 6 | -------------------------------------------------------------------------------- /entry/src/main/ets/common/index.ets: -------------------------------------------------------------------------------- 1 | export * from './constant' 2 | export * from './core' 3 | export * from './util' 4 | -------------------------------------------------------------------------------- /entry/src/main/ets/common/util/LyricUtil.ets: -------------------------------------------------------------------------------- 1 | import { LyricModel, LyricResultBean } from '../../bean/LyricResultBean'; 2 | 3 | export class LyricUtil { 4 | static PATTERN_LINE = new RegExp("((\\[\\d\\d:\\d\\d\\.\\d{2,3}\\])+)(.+)") 5 | static PATTERN_TIME = /\[(\d\d):(\d\d)\.(\d{2,3})\]/ 6 | 7 | static parse(lyricResult: LyricResultBean): LyricModel[] { 8 | const originLrcTexts = lyricResult.lrc?.lyric || ""; 9 | const originTLyricTexts = lyricResult.tlyric?.lyric || ""; 10 | const lyricModelList = LyricUtil.parseLyrics(originLrcTexts); 11 | const tLyricModelList = LyricUtil.parseTLyrics(originTLyricTexts); 12 | lyricModelList.forEach(lyricModel => { 13 | tLyricModelList.forEach(tLyricModel => { 14 | if (lyricModel.time === tLyricModel.time) { 15 | lyricModel.tLyric = tLyricModel.tLyric; 16 | } 17 | }); 18 | }); 19 | 20 | return lyricModelList; 21 | } 22 | 23 | static parseLyrics(lyric: string): LyricModel[] { 24 | const entryList: LyricModel[] = []; 25 | 26 | if (!lyric) { 27 | return entryList; 28 | } 29 | 30 | const lines = lyric.split(/\r?\n/); 31 | 32 | lines.forEach(line => { 33 | const match = line.match(LyricUtil.PATTERN_LINE); 34 | if (match) { 35 | const timeMatch = match[0].match(LyricUtil.PATTERN_TIME); 36 | if (timeMatch) { 37 | const min = parseInt(timeMatch[1], 10); 38 | const sec = parseInt(timeMatch[2], 10); 39 | const milString = timeMatch[3]; 40 | let mil = parseInt(milString, 10); 41 | 42 | // 如果毫秒是两位数,需要乘以10 43 | if (milString.length === 2) { 44 | mil *= 10; 45 | } 46 | 47 | const time = min * 60000 + sec * 1000 + mil; 48 | const text = match[3].trim(); 49 | entryList.push(new LyricModel(time, text)); 50 | } 51 | } 52 | }); 53 | 54 | return entryList; 55 | } 56 | 57 | static parseTLyrics(lyric: string): LyricModel[] { 58 | const entryList: LyricModel[] = []; 59 | 60 | if (!lyric) { 61 | return entryList; 62 | } 63 | 64 | const lines = lyric.split(/\r?\n/); 65 | 66 | lines.forEach(line => { 67 | const match = line.match(LyricUtil.PATTERN_LINE); 68 | if (match) { 69 | const timeMatch = match[0].match(LyricUtil.PATTERN_TIME); 70 | if (timeMatch) { 71 | const min = parseInt(timeMatch[1], 10); 72 | const sec = parseInt(timeMatch[2], 10); 73 | const milString = timeMatch[3]; 74 | let mil = parseInt(milString, 10); 75 | 76 | // 如果毫秒是两位数,需要乘以10 77 | if (milString.length === 2) { 78 | mil *= 10; 79 | } 80 | 81 | const time = min * 60000 + sec * 1000 + mil; 82 | const text = match[3].trim(); 83 | entryList.push(new LyricModel(time, null, text)); 84 | } 85 | } 86 | }); 87 | 88 | return entryList; 89 | } 90 | } -------------------------------------------------------------------------------- /entry/src/main/ets/common/util/index.ets: -------------------------------------------------------------------------------- 1 | export * from './LyricUtil' 2 | -------------------------------------------------------------------------------- /entry/src/main/ets/entryability/EntryAbility.ets: -------------------------------------------------------------------------------- 1 | import UIAbility from '@ohos.app.ability.UIAbility'; 2 | import window from '@ohos.window'; 3 | import { RouterUrls, ScreenUtils } from 'lib_common'; 4 | 5 | export default class EntryAbility extends UIAbility { 6 | async onCreate() { 7 | window.getLastWindow(this.context, async (_, w) => { 8 | w.setWindowLayoutFullScreen(true) 9 | 10 | let win = await window.getLastWindow(this.context) 11 | let area = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); 12 | AppStorage.SetOrCreate("STATUS_BAR_HEIGHT", area.topRect.height) 13 | ScreenUtils.init(area) 14 | }) 15 | } 16 | 17 | onDestroy() { 18 | } 19 | 20 | onWindowStageCreate(windowStage: window.WindowStage) { 21 | windowStage.loadContent(RouterUrls.Splash); 22 | } 23 | 24 | onWindowStageDestroy() { 25 | } 26 | 27 | onForeground() { 28 | } 29 | 30 | onBackground() { 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /entry/src/main/ets/entryability/EntryAbility.js: -------------------------------------------------------------------------------- 1 | import UIAbility from '@ohos.app.ability.UIAbility'; 2 | import hilog from '@ohos.hilog'; 3 | export default class EntryAbility extends UIAbility { 4 | onCreate(want, launchParam) { 5 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 6 | } 7 | onDestroy() { 8 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); 9 | } 10 | onWindowStageCreate(windowStage) { 11 | // Main window is created, set main page for this ability 12 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); 13 | windowStage.loadContent('pages/Index', (err, data) => { 14 | var _a, _b; 15 | if (err.code) { 16 | hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', (_a = JSON.stringify(err)) !== null && _a !== void 0 ? _a : ''); 17 | return; 18 | } 19 | hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', (_b = JSON.stringify(data)) !== null && _b !== void 0 ? _b : ''); 20 | }); 21 | } 22 | onWindowStageDestroy() { 23 | // Main window is destroyed, release UI related resources 24 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); 25 | } 26 | onForeground() { 27 | // Ability has brought to foreground 28 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); 29 | } 30 | onBackground() { 31 | // Ability has back to background 32 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); 33 | } 34 | } 35 | //# sourceMappingURL=EntryAbility.js.map -------------------------------------------------------------------------------- /entry/src/main/ets/entryability/EntryAbility.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"EntryAbility.js","sourceRoot":"","sources":["EntryAbility.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,6BAA6B,CAAC;AACpD,OAAO,KAAK,MAAM,aAAa,CAAC;AAGhC,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,SAAS;IACjD,QAAQ,CAAC,IAAI,EAAE,WAAW;QACxB,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,kBAAkB,CAAC,CAAC;IAClE,CAAC;IAED,SAAS;QACP,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,mBAAmB,CAAC,CAAC;IACnE,CAAC;IAED,mBAAmB,CAAC,WAA+B;QACjD,yDAAyD;QACzD,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,6BAA6B,CAAC,CAAC;QAE3E,WAAW,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;;YACnD,IAAI,GAAG,CAAC,IAAI,EAAE;gBACZ,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,+CAA+C,EAAE,MAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,mCAAI,EAAE,CAAC,CAAC;gBAC3G,OAAO;aACR;YACD,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,oDAAoD,EAAE,MAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,mCAAI,EAAE,CAAC,CAAC;QAClH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,oBAAoB;QAClB,yDAAyD;QACzD,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,8BAA8B,CAAC,CAAC;IAC9E,CAAC;IAED,YAAY;QACV,oCAAoC;QACpC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,sBAAsB,CAAC,CAAC;IACtE,CAAC;IAED,YAAY;QACV,iCAAiC;QACjC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,sBAAsB,CAAC,CAAC;IACtE,CAAC;CACF"} -------------------------------------------------------------------------------- /entry/src/main/ets/index.ets: -------------------------------------------------------------------------------- 1 | export * from "./bean" 2 | export * from "./common" 3 | export * from "./pages" 4 | 5 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/cpn/CpnCommentItem.ets: -------------------------------------------------------------------------------- 1 | import { CommonNetworkImage, SizeConstant, TimeUtil } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | import { CommentBean } from '../..' 4 | 5 | /** 6 | * 评论item组件 7 | */ 8 | @Component 9 | export struct CpnCommentItem { 10 | bean: CommentBean 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | 13 | build() { 14 | Row() { 15 | CommonNetworkImage({url: this.bean.user.avatarUrl, placeHolder: $r('app.media.ic_default_avatar')}) 16 | .width(34) 17 | .height(34) 18 | .borderRadius(17) 19 | .clip(true) 20 | .margin({ right: SizeConstant.SPACE_M }) 21 | 22 | Column() { 23 | this.HeaderBuilder() 24 | this.ContentBuilder() 25 | this.DividerBuilder() 26 | }.layoutWeight(1).alignItems(HorizontalAlign.Start) 27 | } 28 | .width("100%") 29 | .alignItems(VerticalAlign.Top) 30 | .padding({ left: SizeConstant.SPACE_L, right: SizeConstant.SPACE_L, top: SizeConstant.SPACE_M }) 31 | 32 | } 33 | 34 | @Builder HeaderBuilder() { 35 | Row() { 36 | Column() { 37 | Text(this.bean.user.nickname) 38 | .fontSize(SizeConstant.TEXT_M) 39 | .fontColor(AppTheme.palette(this.themeType).firstText) 40 | .fontWeight(FontWeight.Medium) 41 | .margin({ bottom: SizeConstant.SPACE_S }) 42 | 43 | Text(TimeUtil.formatDate(this.bean.time)) 44 | .fontSize(SizeConstant.TEXT_S) 45 | .fontColor(AppTheme.palette(this.themeType).thirdText) 46 | }.alignItems(HorizontalAlign.Start).layoutWeight(1) 47 | 48 | Row() { 49 | Text(this.bean.likedCount.toString()) 50 | .fontSize(SizeConstant.TEXT_M) 51 | .fontColor(AppTheme.palette(this.themeType).thirdText) 52 | 53 | Image($r('app.media.ic_like')) 54 | .width(SizeConstant.ICON_S) 55 | .height(SizeConstant.ICON_S) 56 | .margin({ left: SizeConstant.SPACE_M }) 57 | .fillColor(AppTheme.palette(this.themeType).thirdIcon) 58 | }.justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center) 59 | }.alignItems(VerticalAlign.Center).justifyContent(FlexAlign.SpaceBetween) 60 | } 61 | 62 | @Builder ContentBuilder() { 63 | Text(this.bean.content) 64 | .fontSize(SizeConstant.TEXT_L) 65 | .fontColor(AppTheme.palette(this.themeType).firstText) 66 | .margin({ top: SizeConstant.SPACE_L, bottom: SizeConstant.SPACE_L }) 67 | } 68 | 69 | @Builder DividerBuilder() { 70 | Stack().width("100%").height("1px").backgroundColor(AppTheme.palette(this.themeType).divider) 71 | } 72 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/cpn/index.ets: -------------------------------------------------------------------------------- 1 | export * from './CpnCommentItem' 2 | 3 | export * from './CpnDynamicItem' 4 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/dynamic/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/DynamicDetailPage' 2 | 3 | export * from './view/DynamicCommnentList' 4 | 5 | export * from './view/DynamicLikeList' 6 | 7 | export * from './view/DynamicShareList' 8 | 9 | export * from './viewmodel/AttentionDynamicViewModel' 10 | 11 | export * from './viewmodel/DynamicCommentViewModel' 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/dynamic/view/DynamicCommnentList.ets: -------------------------------------------------------------------------------- 1 | import { CollapsibleMediator, PagingLayoutMediator, ViewState, ViewStatePagingLayout } from 'lib_common' 2 | import { ThemeType, THEME_TYPE } from 'lib_theme' 3 | import { CommentBean, CpnCommentItem, dynamicCommentViewModel } from '../../..' 4 | 5 | /** 6 | * 动态评论页面 7 | */ 8 | @Component 9 | export struct DynamicCommentListPage { 10 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 11 | mediator: CollapsibleMediator 12 | scroller: Scroller 13 | @State pagingLayoutMediator: PagingLayoutMediator = new PagingLayoutMediator({}) 14 | commentId: string 15 | 16 | aboutToAppear() { 17 | this.pagingLayoutMediator = new PagingLayoutMediator({ 18 | enableLoadMore: true, 19 | enableRefresh: false, 20 | collapsibleMediator: this.mediator, 21 | scroller: this.scroller 22 | }) 23 | } 24 | 25 | @Builder ItemBuilder(item: CommentBean) { 26 | CpnCommentItem({ bean: item }) 27 | } 28 | 29 | build() { 30 | ViewStatePagingLayout({ 31 | mediator: $pagingLayoutMediator, 32 | ItemBuilder: (item: object, _) => { 33 | this.ItemBuilder(item as CommentBean) 34 | }, 35 | onLoadData: (viewState: ViewState) => { 36 | dynamicCommentViewModel.getCommentList(this.commentId, viewState, this.pagingLayoutMediator) 37 | }, 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/dynamic/view/DynamicLikeList.ets: -------------------------------------------------------------------------------- 1 | import { CollapsibleMediator, SizeConstant } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | 4 | /** 5 | * 动态点赞用户列表页面 6 | */ 7 | @Component 8 | export struct DynamicLikeListPage { 9 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 10 | mediator: CollapsibleMediator 11 | scroller: Scroller 12 | 13 | build() { 14 | List({ scroller: this.scroller }) { 15 | ListItem() { 16 | Stack() { 17 | Text("TODO:动态喜欢列表").fontColor(AppTheme.palette(this.themeType).firstText).fontSize(SizeConstant.TEXT_M) 18 | }.width("100%").margin({ top: 100 }) 19 | .backgroundColor(AppTheme.palette(this.themeType).commonBackground) 20 | } 21 | } 22 | .edgeEffect(EdgeEffect.None) 23 | .height("100%") 24 | .width("100%") 25 | .onScrollFrameBegin((offset: number) => { 26 | return { offsetRemain: this.mediator?.getScrollerFrameRemainOffset(offset) } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/dynamic/view/DynamicShareList.ets: -------------------------------------------------------------------------------- 1 | import { CollapsibleMediator, SizeConstant } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | 4 | /** 5 | * 动态转发用户列表页面 6 | */ 7 | @Component 8 | export struct DynamicShareListPage { 9 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 10 | mediator: CollapsibleMediator 11 | scroller: Scroller 12 | 13 | build() { 14 | List({ scroller: this.scroller }) { 15 | ListItem() { 16 | Stack() { 17 | Text("TODO:动态转发列表").fontColor(AppTheme.palette(this.themeType).firstText).fontSize(SizeConstant.TEXT_M) 18 | }.width("100%").margin({ top: 100 }) 19 | .backgroundColor(AppTheme.palette(this.themeType).commonBackground) 20 | } 21 | } 22 | .edgeEffect(EdgeEffect.None) 23 | .height("100%") 24 | .width("100%") 25 | .onScrollFrameBegin((offset: number) => { 26 | return { offsetRemain: this.mediator?.getScrollerFrameRemainOffset(offset) } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/dynamic/viewmodel/AttentionDynamicViewModel.ets: -------------------------------------------------------------------------------- 1 | import { BaseViewModel, PagingLayoutMediator, RequestOptions, ViewState } from 'lib_common'; 2 | import { DynamicBean, DynamicResultBean, ApiConstant } from '../../..'; 3 | 4 | /** 5 | * 动态-关注ViewModel 6 | */ 7 | export class AttentionDynamicViewModel extends BaseViewModel { 8 | private lastTime: number = -1 9 | 10 | async getDynamicList(viewState: ViewState, 11 | pagingMediator: PagingLayoutMediator) { 12 | 13 | const result = await this.get( 14 | new RequestOptions({ 15 | url: ApiConstant.URL_DYNAMIC, 16 | data: { 17 | "pagesize": pagingMediator.pageCount, 18 | "lasttime": pagingMediator.page == 1 ? -1 : this.lastTime 19 | }, 20 | viewState: viewState, 21 | pagingMediator: pagingMediator, 22 | pagingListConverter: (result: DynamicResultBean) => { 23 | return result.event.map((item: DynamicBean) => { 24 | item.jsonBean = JSON.parse(item.json.replace("\\\"", "\"") 25 | .replace("\\n", "/") 26 | ) 27 | return item 28 | }) 29 | }, 30 | emptyCondition: (result: DynamicResultBean) => { 31 | return (!result.event) || result.event.length == 0 32 | } 33 | })) 34 | if (result) { 35 | this.lastTime = result.lasttime 36 | } 37 | } 38 | 39 | 40 | } 41 | 42 | export const attentionDynamicViewModel = new AttentionDynamicViewModel() -------------------------------------------------------------------------------- /entry/src/main/ets/pages/dynamic/viewmodel/DynamicCommentViewModel.ets: -------------------------------------------------------------------------------- 1 | import { BaseViewModel, PagingLayoutMediator, RequestOptions, ViewState } from 'lib_common'; 2 | import { CommentResultBean, ApiConstant } from '../../..'; 3 | 4 | /** 5 | * 动态评论ViewModel 6 | */ 7 | export class DynamicCommentViewModel extends BaseViewModel { 8 | async getCommentList(threadId: string, viewState: ViewState, pagingMediator: PagingLayoutMediator) { 9 | 10 | this.get( 11 | new RequestOptions({ 12 | url: ApiConstant.URL_DYNAMIC_COMMENT, 13 | data: { 14 | "limit": pagingMediator.pageCount, 15 | "offset": (pagingMediator.page - 1) * pagingMediator.pageCount, 16 | "threadId": threadId 17 | }, 18 | viewState: viewState, 19 | pagingMediator: pagingMediator, 20 | pagingListConverter: (result: CommentResultBean) => { 21 | return result.comments 22 | }, 23 | emptyCondition: (result: CommentResultBean) => { 24 | return result.comments.length == 0 25 | } 26 | })) 27 | 28 | } 29 | } 30 | 31 | export const dynamicCommentViewModel = new DynamicCommentViewModel() -------------------------------------------------------------------------------- /entry/src/main/ets/pages/index.ets: -------------------------------------------------------------------------------- 1 | export * from "./cpn" 2 | 3 | export * from "./dynamic" 4 | 5 | export * from "./main" 6 | 7 | export * from "./login" 8 | 9 | export * from "./play" 10 | 11 | export * from "./songsheet" 12 | 13 | export * from "./splash" 14 | 15 | export * from "./pics" 16 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/login/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/Login' 2 | 3 | export * from './viewmodel/LoginViewModel' 4 | 5 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/login/view/Login.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant, ViewStateLayout } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | import { QrcodeValueBean, LoginAuthStep, loginViewModel } from '../../..' 4 | 5 | /** 6 | * 登陆页面 7 | */ 8 | @Entry 9 | @Component 10 | struct LoginPage { 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | @State qrcodeValueBean: Partial = {} 13 | @State authStep: LoginAuthStep | undefined = undefined 14 | 15 | aboutToAppear() { 16 | this.authStep = LoginAuthStep.GENERATE_QRCODE_ING 17 | } 18 | 19 | build() { 20 | Stack({ alignContent: Alignment.Top }) { 21 | Image($r('app.media.ic_app_logo')) 22 | .width(60) 23 | .height(60) 24 | .margin({ top: 160 }) 25 | .borderRadius(30) 26 | .sharedTransition('imgAppLogo', { 27 | duration: 300, 28 | curve: Curve.Linear 29 | }) 30 | 31 | Column() { 32 | Text("扫码登陆体验") 33 | .fontSize(SizeConstant.TEXT_XXL) 34 | .fontColor(AppTheme.palette(this.themeType).firstText) 35 | .fontWeight(FontWeight.Medium) 36 | Stack() { 37 | this.QrcodeBuilder() 38 | }.layoutWeight(1) 39 | 40 | Text(this.getTextTip()) 41 | .fontSize(SizeConstant.TEXT_M) 42 | .textAlign(TextAlign.Center) 43 | .fontColor(AppTheme.palette(this.themeType).thirdText) 44 | .fontWeight(FontWeight.Normal) 45 | .padding({ left: SizeConstant.SPACE_XXL, right: SizeConstant.SPACE_XXL }) 46 | } 47 | .width("70%") 48 | .height(300) 49 | .margin(260) 50 | .padding({ top: SizeConstant.SPACE_XL, bottom: SizeConstant.SPACE_XL }) 51 | .backgroundColor(AppTheme.palette(this.themeType).commonBackground) 52 | .borderRadius(SizeConstant.RADIUS_L) 53 | .alignItems(HorizontalAlign.Center) 54 | .justifyContent(FlexAlign.SpaceEvenly) 55 | }.width("100%").height("100%").backgroundColor(AppTheme.palette(this.themeType).primary) 56 | } 57 | 58 | getTextTip(): string { 59 | let tip = "" 60 | if (this.authStep == LoginAuthStep.GENERATE_QRCODE_ING) { 61 | tip = "正在生成登陆二维码" 62 | } else if (this.authStep == LoginAuthStep.GENERATE_QRCODE_SUCCESS) { 63 | tip = "请使用网易云音乐app扫码授权登陆" 64 | } else if (this.authStep == LoginAuthStep.AUTH_ING) { 65 | tip = "等待授权中" 66 | } else { 67 | tip = "同步用户信息中" 68 | } 69 | return tip + `\(仅供学习使用)` 70 | } 71 | 72 | @Builder QrcodeBuilder() { 73 | ViewStateLayout({ onLoadData: async (viewState) => { 74 | this.qrcodeValueBean = (await loginViewModel.qrcodeAuth(viewState, 75 | (authStep: LoginAuthStep) => { 76 | this.authStep = authStep 77 | })).data 78 | } }) { 79 | CpnQRCode({ qrcodeValueBean: this.qrcodeValueBean as QrcodeValueBean }) 80 | } 81 | } 82 | 83 | pageTransition() { 84 | PageTransitionEnter({ duration: 300, curve: Curve.Linear }) 85 | .opacity(0) 86 | PageTransitionEnter({ type: RouteType.Pop, duration: 300, curve: Curve.Linear }) 87 | .opacity(1) 88 | PageTransitionExit({ duration: 300, curve: Curve.Linear }) 89 | .opacity(1) 90 | PageTransitionExit({ type: RouteType.Pop, duration: 300, curve: Curve.Linear }) 91 | .opacity(0) 92 | } 93 | } 94 | 95 | @Component 96 | struct CpnQRCode { 97 | @ObjectLink qrcodeValueBean: QrcodeValueBean 98 | 99 | build() { 100 | QRCode(this.qrcodeValueBean.qrurl).width(160).height(160) 101 | } 102 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/discovery/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/Discovery' 2 | 3 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/discovery/view/Discovery.ets: -------------------------------------------------------------------------------- 1 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 2 | 3 | /** 4 | * 首页-发现页面 5 | */ 6 | @Component 7 | export struct DiscoveryPage { 8 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 9 | 10 | build() { 11 | Stack() { 12 | Text("TODO:发现").fontColor(AppTheme.palette(this.themeType).firstText) 13 | }.width("100%").height("100%").backgroundColor(AppTheme.palette(this.themeType).commonBackground) 14 | } 15 | } 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/dynamic/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/AttentionDynamic' 2 | 3 | export * from './view/Dynamic' 4 | 5 | export * from './view/SquareDynamic' -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/dynamic/view/AttentionDynamic.ets: -------------------------------------------------------------------------------- 1 | import { PagingLayoutMediator, ViewState, ViewStatePagingLayout, } from 'lib_common' 2 | import { AppTheme, THEME_TYPE, ThemeType } from 'lib_theme' 3 | import { DynamicBean, CpnDynamicItem, attentionDynamicViewModel } from '../../../../' 4 | 5 | /** 6 | * 首页-动态-关注页面 7 | */ 8 | @Component 9 | export struct AttentionDynamicPage { 10 | @State pagingLayoutMediator: PagingLayoutMediator = new PagingLayoutMediator({}) 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | 13 | @Builder ItemBuilder(item: DynamicBean) { 14 | CpnDynamicItem({ dynamicBean: item }) 15 | } 16 | 17 | build() { 18 | ViewStatePagingLayout({ 19 | mediator: $pagingLayoutMediator, 20 | ItemBuilder: (item: object, _) => { 21 | this.ItemBuilder(item as DynamicBean) 22 | }, 23 | keyGenerator: (item: object, _) => { 24 | return (item as DynamicBean).threadId 25 | }, 26 | onLoadData: async (viewState: ViewState) => { 27 | attentionDynamicViewModel.getDynamicList(viewState, this.pagingLayoutMediator) 28 | }, 29 | }).backgroundColor(AppTheme.palette(this.themeType).commonBackground) 30 | } 31 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/dynamic/view/Dynamic.ets: -------------------------------------------------------------------------------- 1 | import { CommonAppBar, TabItem, TabLayout, TabLayoutPagerMediator, TabPager, SizeConstant } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | import { AttentionDynamicPage, SquareDynamicPage, STORAGE_MAIN_DRAWER_TOGGLE} from '../../../..' 4 | 5 | /** 6 | * 首页-动态页面 7 | */ 8 | @Component 9 | export struct DynamicPage { 10 | @StorageLink(STORAGE_MAIN_DRAWER_TOGGLE) mainDrawerToggle: boolean = false 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | private tabItems: TabItem[] = [new TabItem(0, "关注"), new TabItem(1, "广场")] 13 | @State private tabMediator: TabLayoutPagerMediator = new TabLayoutPagerMediator({ 14 | tabItems: this.tabItems, 15 | }) 16 | 17 | @Builder CustomerTitleBuilder() { 18 | TabLayout({ 19 | mediator: this.tabMediator, 20 | }) 21 | } 22 | 23 | @Builder CustomerRightBuilder() { 24 | Image($r('app.media.ic_add')) 25 | .width(SizeConstant.ICON_L + SizeConstant.SPACE_S * 2) 26 | .height(SizeConstant.ICON_L + SizeConstant.SPACE_S * 2) 27 | .padding(SizeConstant.SPACE_S) 28 | .fillColor(AppTheme.palette(this.themeType).primary) 29 | } 30 | 31 | @Builder TabPageBuilder(index: number) { 32 | if (index == 0) { 33 | AttentionDynamicPage() 34 | } else { 35 | SquareDynamicPage() 36 | } 37 | } 38 | 39 | build() { 40 | Column() { 41 | CommonAppBar({ leftIcon: $r('app.media.ic_menu'), 42 | leftIconClickCallback : () => { 43 | this.mainDrawerToggle = true 44 | }, 45 | CustomerTitleBuilder: () => { 46 | this.CustomerTitleBuilder() 47 | }, 48 | CustomerRightBuilder: () => { 49 | this.CustomerRightBuilder() 50 | } }) 51 | TabPager({ mediator: this.tabMediator, 52 | TabPageBuilder: (index: number) => { 53 | this.TabPageBuilder(index) 54 | } }).layoutWeight(1) 55 | 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/dynamic/view/SquareDynamic.ets: -------------------------------------------------------------------------------- 1 | import { AppTheme, THEME_TYPE, ThemeType } from 'lib_theme' 2 | 3 | /** 4 | * 首页-动态-广场页面 5 | */ 6 | @Component 7 | export struct SquareDynamicPage { 8 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 9 | 10 | build() { 11 | Stack() { 12 | Text("TODO:广场").fontColor(AppTheme.palette(this.themeType).firstText) 13 | }.width("100%").height("100%").backgroundColor(AppTheme.palette(this.themeType).commonBackground) 14 | } 15 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/index.ets: -------------------------------------------------------------------------------- 1 | export * from './discovery' 2 | 3 | export * from './dynamic' 4 | 5 | export * from './my' 6 | 7 | export * from './podcast' 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/dynamic/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/MyDynamic' 2 | 3 | export * from './viewmodel/UserDynamicViewModel' 4 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/dynamic/view/MyDynamic.ets: -------------------------------------------------------------------------------- 1 | import { CollapsibleMediator, PagingLayoutMediator, ViewState, ViewStatePagingLayout } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | import { DynamicBean, UserManager, userDynamicViewModel, CpnDynamicItem} from '../../../../..' 4 | 5 | /** 6 | * 首页-我的-动态页面 7 | */ 8 | @Component 9 | export struct MyDynamicPage { 10 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 11 | mediator: CollapsibleMediator 12 | scroller: Scroller 13 | @State pagingLayoutMediator: PagingLayoutMediator = new PagingLayoutMediator({}) 14 | 15 | aboutToAppear() { 16 | this.pagingLayoutMediator = new PagingLayoutMediator({ 17 | enableRefresh: false, 18 | enableLoadMore: true, 19 | collapsibleMediator: this.mediator, 20 | scroller: this.scroller 21 | }) 22 | } 23 | 24 | @Builder ItemBuilder(item: DynamicBean) { 25 | CpnDynamicItem({ dynamicBean: item }) 26 | } 27 | 28 | build() { 29 | ViewStatePagingLayout({ 30 | mediator: $pagingLayoutMediator, 31 | ItemBuilder: (item: object) => { 32 | this.ItemBuilder(item as DynamicBean) 33 | }, 34 | onLoadData: async (viewState: ViewState) => { 35 | userDynamicViewModel.getUserDynamicList((await UserManager.getUserInfo()).detail.profile.userId, viewState, this.pagingLayoutMediator) 36 | }, 37 | }).backgroundColor(AppTheme.palette(this.themeType).commonBackground) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/dynamic/viewmodel/UserDynamicViewModel.ets: -------------------------------------------------------------------------------- 1 | import { BaseViewModel, PagingLayoutMediator, RequestOptions, ViewState } from 'lib_common'; 2 | import { DynamicBean, UserDynamicResultBean, ApiConstant } from '../../../../..'; 3 | 4 | /** 5 | * 首页-我的-动态ViewModel 6 | */ 7 | export class UserDynamicViewModel extends BaseViewModel { 8 | 9 | lastTime : number = -1 10 | 11 | async getUserDynamicList(uid: string, 12 | viewState: ViewState, 13 | pagingMediator: PagingLayoutMediator) { 14 | 15 | const result = await this.get( 16 | new RequestOptions({ 17 | url: ApiConstant.URL_USER_DYNAMIC, 18 | data: { 19 | "uid": uid, 20 | "limit": pagingMediator.pageCount, 21 | "lasttime": pagingMediator.page == 1 ? -1 : this.lastTime 22 | }, 23 | viewState: viewState, 24 | pagingMediator: pagingMediator, 25 | pagingListConverter: (result: UserDynamicResultBean) => { 26 | return result.events.map((item: DynamicBean) => { 27 | item.jsonBean = JSON.parse(item.json.replace("\\\"", "\"") 28 | .replace("\\n", "/") 29 | ) 30 | return item 31 | }) 32 | }, 33 | emptyCondition: (result: UserDynamicResultBean) => { 34 | return (!result.events) || result.events.length == 0 35 | } 36 | })) 37 | if (result) { 38 | this.lastTime = result.lasttime 39 | } 40 | } 41 | } 42 | 43 | export const userDynamicViewModel = new UserDynamicViewModel(); 44 | 45 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/index.ets: -------------------------------------------------------------------------------- 1 | export * from './dynamic' 2 | 3 | export * from './music' 4 | 5 | export * from './podcast' 6 | 7 | export * from './view' 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/music/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/AlbumMusic' 2 | 3 | export * from './view/CollectMusic' 4 | 5 | export * from './view/CreateMusic' 6 | 7 | export * from './viewmodel/AlbumViewModel' 8 | 9 | export * from './viewmodel/SongSheetViewModel' 10 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/music/view/AlbumMusic.ets: -------------------------------------------------------------------------------- 1 | import { PagingLayoutMediator, ViewState } from 'lib_common' 2 | import { CollapsibleMediator, ViewStatePagingLayout } from 'lib_common' 3 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 4 | import { AlbumBean, albumViewModel, CpnAlbumItem } from '../../../../..' 5 | 6 | /** 7 | * 首页-我的-音乐-专辑页面 8 | */ 9 | @Component 10 | export struct AlbumMusicPage { 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | mediator: CollapsibleMediator 13 | scroller: Scroller 14 | @State pagingLayoutMediator: PagingLayoutMediator = new PagingLayoutMediator({}) 15 | 16 | aboutToAppear() { 17 | this.pagingLayoutMediator = new PagingLayoutMediator({ 18 | enableLoadMore: true, 19 | enableRefresh: false, 20 | collapsibleMediator: this.mediator, 21 | scroller: this.scroller 22 | }) 23 | } 24 | 25 | @Builder ItemBuilder(item: object) { 26 | CpnAlbumItem({ albumBean: item as AlbumBean }) 27 | } 28 | 29 | build() { 30 | ViewStatePagingLayout({ 31 | mediator: $pagingLayoutMediator, 32 | ItemBuilder: (item: object, _) => { 33 | this.ItemBuilder(item) 34 | }, 35 | onLoadData: (viewState: ViewState) => { 36 | albumViewModel.getAlbumList(viewState, this.pagingLayoutMediator) 37 | }, 38 | }).backgroundColor(AppTheme.palette(this.themeType).commonBackground) 39 | } 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/music/view/CollectMusic.ets: -------------------------------------------------------------------------------- 1 | import { CollapsibleMediator, PagingLayoutMediator, ViewState, ViewStatePagingLayout } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | import { UserPlaylistBean, UserManager, songSheetViewModel, CpnSongSheetItem } from '../../../../..' 4 | 5 | /** 6 | * 首页-我的-音乐-收藏页面 7 | */ 8 | @Component 9 | export struct CollectMusicPage { 10 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 11 | mediator: CollapsibleMediator 12 | scroller: Scroller 13 | @State pagingLayoutMediator: PagingLayoutMediator = new PagingLayoutMediator({}) 14 | 15 | aboutToAppear() { 16 | this.pagingLayoutMediator = new PagingLayoutMediator({ 17 | enableLoadMore: false, 18 | enableRefresh: false, 19 | collapsibleMediator: this.mediator, 20 | scroller: this.scroller 21 | }) 22 | } 23 | 24 | @Builder ItemBuilder(item: object) { 25 | CpnSongSheetItem({ playListBean: item as UserPlaylistBean }) 26 | } 27 | 28 | build() { 29 | ViewStatePagingLayout({ 30 | mediator: $pagingLayoutMediator, 31 | ItemBuilder: (item: object, _) => { 32 | this.ItemBuilder(item) 33 | }, 34 | onLoadData: async (viewState: ViewState) => { 35 | songSheetViewModel.getUserPlayList((await UserManager.getUserInfo()).detail.profile.userId, false, viewState, this.pagingLayoutMediator) 36 | }, 37 | }).backgroundColor(AppTheme.palette(this.themeType).commonBackground) 38 | } 39 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/music/view/CreateMusic.ets: -------------------------------------------------------------------------------- 1 | import { CollapsibleMediator, ViewStatePagingLayout, PagingLayoutMediator, ViewState } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | import { UserPlaylistBean, UserManager, songSheetViewModel, CpnSongSheetItem } from '../../../../..' 4 | 5 | /** 6 | * 首页-我的-音乐-创建页面 7 | */ 8 | @Component 9 | export struct CreateMusicPage { 10 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 11 | 12 | mediator: CollapsibleMediator 13 | scroller: Scroller 14 | @State pagingLayoutMediator: PagingLayoutMediator = new PagingLayoutMediator({}) 15 | 16 | aboutToAppear() { 17 | this.pagingLayoutMediator = new PagingLayoutMediator({ 18 | enableLoadMore: false, 19 | enableRefresh: false, 20 | collapsibleMediator: this.mediator, 21 | scroller: this.scroller 22 | }) 23 | } 24 | 25 | @Builder ItemBuilder(item: object) { 26 | CpnSongSheetItem({ playListBean: item as UserPlaylistBean }) 27 | } 28 | 29 | build() { 30 | ViewStatePagingLayout({ 31 | mediator: $pagingLayoutMediator, 32 | ItemBuilder: (item: object, _) => { 33 | this.ItemBuilder(item) 34 | }, 35 | onLoadData: async (viewState: ViewState) => { 36 | songSheetViewModel.getUserPlayList((await UserManager.getUserInfo()).detail.profile.userId, true, viewState, this.pagingLayoutMediator) 37 | }, 38 | }).backgroundColor(AppTheme.palette(this.themeType).commonBackground) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/music/viewmodel/AlbumViewModel.ets: -------------------------------------------------------------------------------- 1 | import { BaseViewModel, PagingLayoutMediator, RequestOptions, ViewState } from 'lib_common' 2 | import { AlbumResultBean, ApiConstant } from '../../../../..' 3 | 4 | /** 5 | * 首页-我的-音乐-专辑ViewModel 6 | */ 7 | export class AlbumViewModel extends BaseViewModel { 8 | async getAlbumList(viewState: ViewState, 9 | pagingMediator: PagingLayoutMediator) { 10 | 11 | this.get( 12 | new RequestOptions({ 13 | url: ApiConstant.URL_COLLECT_ALBUM_LIST, 14 | data: { 15 | "limit": pagingMediator.pageCount, 16 | "offset": (pagingMediator.page - 1) * pagingMediator.pageCount 17 | }, 18 | viewState: viewState, 19 | pagingMediator: pagingMediator, 20 | pagingListConverter: (result: AlbumResultBean) => { 21 | return result.data 22 | } 23 | })) 24 | } 25 | } 26 | 27 | export const albumViewModel = new AlbumViewModel() 28 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/music/viewmodel/SongSheetViewModel.ets: -------------------------------------------------------------------------------- 1 | import { BaseViewModel, PagingLayoutMediator, RequestOptions, ViewState } from 'lib_common'; 2 | import { UserPlaylistBean, UserPlaylistResultBean, ApiConstant } from '../../../../..'; 3 | 4 | /** 5 | * 首页-我的-音乐-歌单ViewModel 6 | */ 7 | export class SongSheetViewModel extends BaseViewModel { 8 | async getUserPlayList(uid: string, 9 | isSelfCreate: boolean, 10 | viewState: ViewState, 11 | pagingMediator: PagingLayoutMediator) { 12 | 13 | this.get( 14 | new RequestOptions({ 15 | url: ApiConstant.URL_USER_PLAY_LIST, 16 | data: { "uid": uid }, 17 | viewState: viewState, 18 | pagingMediator: pagingMediator, 19 | pagingListConverter: (result: UserPlaylistResultBean) => { 20 | return result.playlist.filter((item: UserPlaylistBean) => { 21 | return isSelfCreate ? item.userId.toString() == uid : item.userId.toString() != uid 22 | }) 23 | } 24 | })) 25 | } 26 | } 27 | 28 | export const songSheetViewModel = new SongSheetViewModel(); 29 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/podcast/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/AllPodcast' 2 | 3 | export * from './view/MyPodcast' 4 | 5 | export * from './view/VoiceBook' 6 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/podcast/view/AllPodcast.ets: -------------------------------------------------------------------------------- 1 | import { CollapsibleMediator, SizeConstant } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | 4 | /** 5 | * 首页-我的-播客-全部页面 6 | */ 7 | @Component 8 | export struct AllPodcastPage { 9 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 10 | mediator: CollapsibleMediator 11 | scroller: Scroller 12 | 13 | build() { 14 | List({ scroller: this.scroller }) { 15 | ListItem() { 16 | Stack() { 17 | Text("TODO:我的全部播客").fontColor(AppTheme.palette(this.themeType).firstText).fontSize(SizeConstant.TEXT_M) 18 | }.width("100%").margin({ top: 100 }) 19 | } 20 | } 21 | .edgeEffect(EdgeEffect.None) 22 | .height("100%") 23 | .width("100%") 24 | .backgroundColor(AppTheme.palette(this.themeType).commonBackground) 25 | .onScrollFrameBegin((offset: number) => { 26 | return { offsetRemain: this.mediator?.getScrollerFrameRemainOffset(offset) } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/podcast/view/MyPodcast.ets: -------------------------------------------------------------------------------- 1 | import { CollapsibleMediator, SizeConstant } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | 4 | /** 5 | * 首页-我的-播客-我的播客页面 6 | */ 7 | @Component 8 | export struct MyPodcastPage { 9 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 10 | mediator: CollapsibleMediator 11 | scroller: Scroller 12 | 13 | build() { 14 | List({ scroller: this.scroller }) { 15 | ListItem() { 16 | Stack() { 17 | Text("TODO:我的播客").fontColor(AppTheme.palette(this.themeType).firstText).fontSize(SizeConstant.TEXT_M) 18 | }.width("100%").margin({ top: 100 }) 19 | } 20 | } 21 | .edgeEffect(EdgeEffect.None) 22 | .height("100%") 23 | .width("100%") 24 | .backgroundColor(AppTheme.palette(this.themeType).commonBackground) 25 | .onScrollFrameBegin((offset: number) => { 26 | return { offsetRemain: this.mediator?.getScrollerFrameRemainOffset(offset) } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/podcast/view/VoiceBook.ets: -------------------------------------------------------------------------------- 1 | import { CollapsibleMediator, SizeConstant } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | 4 | /** 5 | * 首页-我的-播客-有声书页面 6 | */ 7 | @Component 8 | export struct VoiceBookPage { 9 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 10 | mediator: CollapsibleMediator 11 | scroller: Scroller 12 | 13 | build() { 14 | List({ scroller: this.scroller }) { 15 | ListItem() { 16 | Stack() { 17 | Text("TODO:我的有声书").fontColor(AppTheme.palette(this.themeType).firstText).fontSize(SizeConstant.TEXT_M) 18 | }.width("100%").margin({ top: 100 }) 19 | } 20 | } 21 | .edgeEffect(EdgeEffect.None) 22 | .height("100%") 23 | .width("100%") 24 | .backgroundColor(AppTheme.palette(this.themeType).commonBackground) 25 | .onScrollFrameBegin((offset: number) => { 26 | return { offsetRemain: this.mediator?.getScrollerFrameRemainOffset(offset) } 27 | }) 28 | } 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/view/CpnAlbumItem.ets: -------------------------------------------------------------------------------- 1 | import router from '@ohos.router' 2 | import { ClickEffectLayout, RouterUrls, SizeConstant } from 'lib_common' 3 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 4 | import { AlbumBean } from '../../../..' 5 | 6 | /** 7 | * 专辑item组件 8 | */ 9 | @Component 10 | export struct CpnAlbumItem { 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | albumBean: AlbumBean 13 | 14 | build() { 15 | ClickEffectLayout({ click: () => { 16 | router.pushUrl({ url: RouterUrls.SongSheetDetail }) 17 | } }) { 18 | Row() { 19 | Image(this.albumBean.picUrl) 20 | .width(48) 21 | .height(48) 22 | .borderRadius(SizeConstant.RADIUS_M) 23 | .margin({ right: SizeConstant.SPACE_M }) 24 | Column() { 25 | Text(this.albumBean.name) 26 | .fontSize(SizeConstant.TEXT_L) 27 | .fontColor(AppTheme.palette(this.themeType).firstText) 28 | .fontWeight(FontWeight.Medium) 29 | .margin({ bottom: SizeConstant.SPACE_S }) 30 | Row() { 31 | Text(`${this.albumBean.size}首`) 32 | .fontSize(SizeConstant.TEXT_S) 33 | .fontColor(AppTheme.palette(this.themeType).secondText) 34 | Text(` · ${this.albumBean.artists[0].name}`) 35 | .fontSize(SizeConstant.TEXT_S) 36 | .fontColor(AppTheme.palette(this.themeType).secondText) 37 | } 38 | } 39 | .layoutWeight(1) 40 | .alignItems(HorizontalAlign.Start) 41 | .justifyContent(FlexAlign.Center) 42 | } 43 | .width("100%") 44 | .padding({ 45 | left: SizeConstant.SPACE_L, 46 | right: SizeConstant.SPACE_L, 47 | top: SizeConstant.SPACE_S, 48 | bottom: SizeConstant.SPACE_S 49 | }) 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/view/CpnMusicPlayBar.ets: -------------------------------------------------------------------------------- 1 | import router from '@ohos.router' 2 | import { ClickEffectLayout, RouterUrls, SizeConstant } from 'lib_common' 3 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 4 | import { MusicPlayController, PlayerStatus, MusicPlayListDialog, EventIds } from '../../../..' 5 | import emitter from '@ohos.events.emitter' 6 | 7 | /** 8 | * 底部音乐播放栏组件 9 | */ 10 | @Component 11 | export struct CpnMusicPlayBar { 12 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 13 | customDialogController: CustomDialogController = new CustomDialogController({ 14 | builder: MusicPlayListDialog(), 15 | customStyle: true, 16 | alignment: DialogAlignment.Bottom, 17 | autoCancel: true 18 | }) 19 | // 当前播放索引 20 | @State playerIndex: number = 0 21 | // 播放状态 22 | @State playerStatus: PlayerStatus | undefined = undefined 23 | 24 | aboutToAppear() { 25 | this.playerIndex = MusicPlayController.playerIndex 26 | // 监听播放列表索引 27 | emitter.on({ eventId: EventIds.PLAYER_INDEX }, (data) => { 28 | this.playerIndex = data.data.index 29 | }) 30 | // 监听播放状态 31 | emitter.on({ eventId: EventIds.PLAYER_STATUS }, (data) => { 32 | this.playerStatus = data.data.status 33 | }) 34 | } 35 | 36 | 37 | build() { 38 | Column() { 39 | ClickEffectLayout({ click: () => { 40 | router.pushUrl({ url: RouterUrls.MusicPlay }) 41 | } }) { 42 | Row() { 43 | Image(MusicPlayController.playList[this.playerIndex]?.al?.picUrl??"") 44 | .width(30) 45 | .height(30) 46 | .borderRadius(SizeConstant.RADIUS_M) 47 | .margin({ right: SizeConstant.SPACE_S }) 48 | 49 | Text() { 50 | Span(MusicPlayController.playList[this.playerIndex]?.name??"").fontSize(SizeConstant.TEXT_M) 51 | .fontColor(AppTheme.palette(this.themeType).firstText) 52 | Span(` - ${MusicPlayController.playList[this.playerIndex]?.ar[0]?.name??""}`) 53 | .fontSize(SizeConstant.TEXT_M) 54 | .fontColor(AppTheme.palette(this.themeType).thirdText) 55 | }.layoutWeight(1) 56 | 57 | Row() { 58 | ClickEffectLayout({ click: () => { 59 | if (this.playerStatus == PlayerStatus.STARTED) { 60 | MusicPlayController.pause() 61 | } else { 62 | MusicPlayController.start() 63 | } 64 | } }) { 65 | Image(this.playerStatus == PlayerStatus.STARTED ? $r('app.media.ic_play_stop') : $r('app.media.ic_play_start')) 66 | .width(SizeConstant.ICON_L + SizeConstant.SPACE_S * 2) 67 | .height(SizeConstant.ICON_L + SizeConstant.SPACE_S * 2) 68 | .padding(SizeConstant.SPACE_S) 69 | .fillColor(AppTheme.palette(this.themeType).secondIcon) 70 | .margin({ right: SizeConstant.SPACE_M }) 71 | } 72 | 73 | ClickEffectLayout({ click: () => { 74 | this.customDialogController.open() 75 | } }) { 76 | Image($r('app.media.ic_play_list')) 77 | .width(SizeConstant.ICON_L + SizeConstant.SPACE_S * 2) 78 | .height(SizeConstant.ICON_L + SizeConstant.SPACE_S * 2) 79 | .padding(SizeConstant.SPACE_S) 80 | .fillColor(AppTheme.palette(this.themeType).secondIcon) 81 | } 82 | } 83 | } 84 | .width("100%") 85 | .height(SizeConstant.MUSIC_PLAY_BAR_HEIGHT) 86 | .padding({ left: SizeConstant.SPACE_L, right: SizeConstant.SPACE_L }) 87 | .alignItems(VerticalAlign.Center) 88 | } 89 | 90 | Stack() 91 | .height(`0.5px`) 92 | .width("100%") 93 | .backgroundColor(AppTheme.palette(this.themeType).divider) 94 | }.height(SizeConstant.MUSIC_PLAY_BAR_HEIGHT) 95 | .backgroundColor(AppTheme.palette(this.themeType).navBarBackground) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/view/CpnPodcastSheetItem.ets: -------------------------------------------------------------------------------- 1 | 2 | import { ClickEffectLayout, SizeConstant } from 'lib_common' 3 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 4 | 5 | 6 | @Component 7 | export struct CpnPodcastSheetItem { 8 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 9 | index: number = 0 10 | 11 | build() { 12 | ClickEffectLayout() { 13 | Row() { 14 | Image($r('app.media.ic_default_place_holder')) 15 | .width(48) 16 | .height(48) 17 | .borderRadius(SizeConstant.RADIUS_M) 18 | .margin({ right: SizeConstant.SPACE_M }) 19 | Column() { 20 | Text("钢琴纯音乐") 21 | .fontSize(SizeConstant.TEXT_M) 22 | .fontColor(AppTheme.palette(this.themeType).firstText) 23 | .fontWeight(FontWeight.Medium) 24 | .margin({ bottom: SizeConstant.SPACE_S }) 25 | Text("60年代末,艺术摇滚在摇滚界方兴未艾,有着Kinks和Who的摇滚歌剧、Deep Purple与皇家爱乐乐团的合作演出,Pink Floyd也与前卫作曲家Ron Geesin一起合作了这张专辑") 26 | .fontSize(SizeConstant.TEXT_M) 27 | .fontColor(AppTheme.palette(this.themeType).secondText) 28 | .maxLines(2) 29 | .textOverflow({ overflow: TextOverflow.Ellipsis }) 30 | } 31 | .layoutWeight(1) 32 | .alignItems(HorizontalAlign.Start) 33 | .justifyContent(FlexAlign.Center) 34 | 35 | } 36 | .width("100%") 37 | .padding({ 38 | left: SizeConstant.SPACE_L, 39 | right: SizeConstant.SPACE_L, 40 | top: SizeConstant.SPACE_S, 41 | bottom: SizeConstant.SPACE_S 42 | }) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/view/CpnSongSheetItem.ets: -------------------------------------------------------------------------------- 1 | import router from '@ohos.router' 2 | import { ClickEffectLayout, CommonNetworkImage, RouterUrls, SizeConstant } from 'lib_common' 3 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 4 | import { UserPlaylistBean } from '../../../..' 5 | 6 | /** 7 | * 歌单item组件 8 | */ 9 | @Component 10 | export struct CpnSongSheetItem { 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | playListBean: UserPlaylistBean 13 | 14 | build() { 15 | ClickEffectLayout({ click: () => { 16 | router.pushUrl({ url: RouterUrls.SongSheetDetail, params: this.playListBean }) 17 | } }) { 18 | Row() { 19 | CommonNetworkImage({url: this.playListBean.coverImgUrl}) 20 | .width(48) 21 | .height(48) 22 | .borderRadius(SizeConstant.RADIUS_M) 23 | .margin({ right: SizeConstant.SPACE_M }) 24 | .clip(true) 25 | Column() { 26 | Text(this.playListBean.name) 27 | .fontSize(SizeConstant.TEXT_L) 28 | .fontColor(AppTheme.palette(this.themeType).firstText) 29 | .fontWeight(FontWeight.Medium) 30 | .margin({ bottom: SizeConstant.SPACE_S }) 31 | Row() { 32 | Text(`${this.playListBean.trackCount}首`) 33 | .fontSize(SizeConstant.TEXT_S) 34 | .fontColor(AppTheme.palette(this.themeType).secondText) 35 | .margin({ right: SizeConstant.SPACE_L }) 36 | Text(`${this.playListBean.playCount}次播放`) 37 | .fontSize(SizeConstant.TEXT_S) 38 | .fontColor(AppTheme.palette(this.themeType).secondText) 39 | } 40 | } 41 | .layoutWeight(1) 42 | .alignItems(HorizontalAlign.Start) 43 | .justifyContent(FlexAlign.Center) 44 | 45 | 46 | Image($r('app.media.ic_more')) 47 | .width(SizeConstant.ICON_M) 48 | .height(SizeConstant.ICON_M) 49 | .fillColor(AppTheme.palette(this.themeType).thirdIcon) 50 | } 51 | .width("100%") 52 | .padding({ 53 | left: SizeConstant.SPACE_L, 54 | right: SizeConstant.SPACE_L, 55 | top: SizeConstant.SPACE_S, 56 | bottom: SizeConstant.SPACE_S 57 | }) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/my/view/index.ets: -------------------------------------------------------------------------------- 1 | export * from './My' 2 | 3 | export * from './CpnAlbumItem' 4 | 5 | export * from './CpnMusicPlayBar' 6 | 7 | export * from './CpnMyHeader' 8 | 9 | export * from './CpnPodcastSheetItem' 10 | 11 | export * from './CpnSongSheetItem' 12 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/podcast/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/Podcast' 2 | 3 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/podcast/view/Podcast.ets: -------------------------------------------------------------------------------- 1 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 2 | 3 | /** 4 | * 首页-播客页面 5 | */ 6 | @Component 7 | export struct PodcastPage { 8 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 9 | 10 | build() { 11 | Stack() { 12 | Text("TODO:播客").fontColor(AppTheme.palette(this.themeType).firstText) 13 | }.width("100%").height("100%").backgroundColor(AppTheme.palette(this.themeType).commonBackground) 14 | } 15 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/main/view/index.ets: -------------------------------------------------------------------------------- 1 | export * from "./Main" 2 | 3 | export * from "./cpn/MainDrawer" -------------------------------------------------------------------------------- /entry/src/main/ets/pages/pics/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/PicturePreview' 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/pics/view/PicturePreview.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant, TabLayoutPagerMediator, TabPager } from 'lib_common' 2 | import { CommonAppBar } from 'lib_common' 3 | import router from '@ohos.router' 4 | 5 | /** 6 | * 图片预览页面 7 | */ 8 | @Entry 9 | @Component 10 | struct PicturePreviewPage { 11 | pics: PicturePreviewBean[] 12 | @State private tabMediator: TabLayoutPagerMediator = new TabLayoutPagerMediator() 13 | 14 | aboutToAppear() { 15 | const params = (router.getParams() as PicturePreviewRouterParams) 16 | this.pics = params.pics 17 | this.tabMediator = new TabLayoutPagerMediator({ 18 | tabItems: this.pics, 19 | cacheCount: 0, // 动态改变TabPager的cacheCount为0,否则如果有多张图片的时候,打开页面、退出页面做共享动画会出现奇怪的效果 20 | currentIndex: params.index, 21 | }) 22 | } 23 | 24 | onBackPress() { 25 | // 动态改变TabPager的cacheCount为0,否则如果有多张图片的时候,打开页面、退出页面做共享动画会出现奇怪的效果 26 | this.tabMediator = new TabLayoutPagerMediator({ 27 | tabItems: this.pics, 28 | cacheCount: 0, 29 | currentIndex: this.tabMediator.currentIndex, 30 | }) 31 | } 32 | 33 | build() { 34 | Column() { 35 | CommonAppBar({ 36 | bgColor: Color.Black, 37 | contentColor: Color.White, 38 | CustomerTitleBuilder: () => { 39 | this.CustomerTitleBuilder() 40 | } 41 | }) 42 | TabPager({ mediator: this.tabMediator, 43 | TabPageBuilder: (index: number) => { 44 | this.TabPageBuilder(index) 45 | } }).layoutWeight(1) 46 | .onAppear(() => { 47 | setTimeout(() => { 48 | // 动态改变TabPager的cacheCount为1,预加载图片 49 | this.tabMediator = new TabLayoutPagerMediator({ 50 | tabItems: this.pics, 51 | cacheCount: 1, 52 | currentIndex: this.tabMediator.currentIndex, 53 | }) 54 | }, 300) 55 | }) 56 | }.width("100%") 57 | .height("100%") 58 | .backgroundColor(Color.Black) 59 | } 60 | 61 | @Builder CustomerTitleBuilder() { 62 | Text(`${Math.round(this.tabMediator.currentIndex) + 1} / ${this.pics.length}`) 63 | .fontSize(SizeConstant.TEXT_XL) 64 | .fontWeight(FontWeight.Bold) 65 | .fontColor(Color.White) 66 | .maxLines(1) 67 | .width("100%") 68 | .textAlign(TextAlign.Center) 69 | .textOverflow({ overflow: TextOverflow.Ellipsis }) 70 | .margin({ left: SizeConstant.SPACE_M, right: SizeConstant.SPACE_M }) 71 | } 72 | 73 | @Builder TabPageBuilder(index: number) { 74 | Stack({ alignContent: Alignment.Center }) { 75 | Image(this.pics[index].url).width("100%") 76 | .sharedTransition(`picPreview:${this.pics[index].url}`, { 77 | duration: 300, 78 | curve: Curve.Linear 79 | }) 80 | }.width("100%").height("100%") 81 | } 82 | 83 | pageTransition() { 84 | PageTransitionEnter({ duration: 300, curve: Curve.Linear }) 85 | .opacity(0) 86 | PageTransitionEnter({ type: RouteType.Pop, duration: 300, curve: Curve.Linear }) 87 | .opacity(1) 88 | PageTransitionExit({ duration: 300, curve: Curve.Linear }) 89 | .opacity(1) 90 | PageTransitionExit({ type: RouteType.Pop, duration: 300, curve: Curve.Linear }) 91 | .opacity(0) 92 | } 93 | } 94 | 95 | 96 | export class PicturePreviewBean { 97 | url: string 98 | 99 | constructor(url: string) { 100 | this.url = url 101 | } 102 | } 103 | 104 | export class PicturePreviewRouterParams { 105 | index: number 106 | pics: PicturePreviewBean[] 107 | 108 | constructor(index: number, pics: PicturePreviewBean[]) { 109 | this.index = index 110 | this.pics = pics 111 | } 112 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/play/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/list/CurrentMusicPlayList' 2 | 3 | export * from './view/list/HistoryMusicPlayList' 4 | 5 | export * from './view/list/MusicPlayListDialog' 6 | 7 | export * from './view/CpnLyric' 8 | 9 | export * from './view/MusicPlay' 10 | 11 | export * from './viewmodel/LyricViewModel' 12 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/play/view/CpnLyric.ets: -------------------------------------------------------------------------------- 1 | import { ListDataSource, SizeConstant, ViewStateLayout, ViewState } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | import { MusicPlayController, LyricModel, lyricViewModel } from '../../..' 4 | 5 | /** 6 | * 歌词组件 7 | */ 8 | @Component 9 | export struct CpnLyric { 10 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 11 | lazyDataSource: ListDataSource = new ListDataSource() 12 | @Prop @Watch("indexChange") index: number 13 | @Prop @Watch("currentDuringChange") currentDuring: number 14 | @State @Watch("currentPlayLineChange") currentPlayLine: number = 0 15 | viewState: ViewState | undefined = undefined 16 | scroller: Scroller = new Scroller() 17 | 18 | build() { 19 | ViewStateLayout({ onLoadData: async (viewState) => { 20 | this.viewState = viewState 21 | this.lazyDataSource.reloadData(await lyricViewModel.fetchData(MusicPlayController.playList[this.index].id, viewState)) 22 | } }) { 23 | List({ space: 0, scroller: this.scroller }) { 24 | LazyForEach(this.lazyDataSource, (item: LyricModel, index: number) => { 25 | ListItem() { 26 | Column() { 27 | if (item.lyric) { 28 | Text(item.lyric) 29 | .textAlign(TextAlign.Center) 30 | .fontSize(SizeConstant.TEXT_M) 31 | .fontColor(this.currentPlayLine != index ? Color.White : AppTheme.palette(this.themeType).primary) 32 | .width("100%") 33 | .margin(SizeConstant.SPACE_S) 34 | } 35 | if (item.tLyric) { 36 | Text(item.tLyric) 37 | .textAlign(TextAlign.Center) 38 | .fontSize(SizeConstant.TEXT_S) 39 | .fontColor(this.currentPlayLine != index ? Color.White : AppTheme.palette(this.themeType).primary) 40 | .width("100%") 41 | .margin(SizeConstant.SPACE_S) 42 | } 43 | }.padding({ left: SizeConstant.SPACE_L, right: SizeConstant.SPACE_L }) 44 | } 45 | }, (item: object, _) => { 46 | return JSON.stringify(item) 47 | }) 48 | }.margin({ top: SizeConstant.SPACE_XXL, bottom: SizeConstant.SPACE_XXL }).width("100%") 49 | } 50 | } 51 | 52 | currentDuringChange() { 53 | for (let index = 0; index < this.lazyDataSource.dataArray.length; index++) { 54 | const model = this.lazyDataSource.dataArray[index] as LyricModel 55 | if (this.currentDuring < model.time) { 56 | this.currentPlayLine = Math.max(0, index - 1) 57 | return 58 | } 59 | } 60 | } 61 | 62 | async indexChange() { 63 | this.lazyDataSource.reloadData(await lyricViewModel.fetchData(MusicPlayController.playList[this.index].id, this.viewState)) 64 | } 65 | 66 | currentPlayLineChange() { 67 | this.scroller.scrollToIndex(this.currentPlayLine) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/play/view/list/HistoryMusicPlayList.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 历史播放列表组件(底部播放列表弹框中) 3 | */ 4 | @Component 5 | export struct HistoryMusicPlayList { 6 | build() { 7 | Stack() { 8 | Text("TODO:历史播放") 9 | }.width("100%") 10 | .height("100%") 11 | } 12 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/play/view/list/MusicPlayListDialog.ets: -------------------------------------------------------------------------------- 1 | import { SizeConstant, TabItem, TabLayout, TabLayoutPagerMediator, TabPager } from 'lib_common' 2 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 3 | import { CurrentMusicPlayList, HistoryMusicPlayList } from '../../../..' 4 | 5 | /** 6 | * 音乐播放列表底部弹框 7 | */ 8 | @CustomDialog 9 | export struct MusicPlayListDialog { 10 | controller: CustomDialogController 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | private tabItems: TabItem[] = [new TabItem(0, "历史播放"), new TabItem(1, "当前播放")] 13 | @State private tabMediator: TabLayoutPagerMediator = new TabLayoutPagerMediator({ 14 | currentIndex: 1, 15 | tabItems: this.tabItems, 16 | }) 17 | 18 | @Builder TabPageBuilder(index: number) { 19 | if (index == 0) { 20 | HistoryMusicPlayList() 21 | } else { 22 | CurrentMusicPlayList() 23 | } 24 | } 25 | 26 | build() { 27 | Column() { 28 | TabLayout({ mediator: this.tabMediator, }).margin({ top: SizeConstant.SPACE_S, bottom: SizeConstant.SPACE_S }) 29 | TabPager({ mediator: this.tabMediator, 30 | TabPageBuilder: (index: number) => { 31 | this.TabPageBuilder(index) 32 | } }).layoutWeight(1) 33 | } 34 | .width("100%") 35 | .height("70%") 36 | .backgroundColor(AppTheme.palette(this.themeType).commonBackground) 37 | .borderRadius(SizeConstant.RADIUS_L) 38 | .justifyContent(FlexAlign.Start) 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/play/viewmodel/LyricViewModel.ets: -------------------------------------------------------------------------------- 1 | import { BaseViewModel, RequestOptions, ViewState } from 'lib_common'; 2 | import { ApiConstant } from '../../..'; 3 | import { LyricModel, LyricResultBean, LyricUtil } from '../../..'; 4 | 5 | export class LyricViewModel extends BaseViewModel { 6 | async fetchData(id: number, viewState: ViewState) : Promise { 7 | const lyricResult = await this.get( 8 | new RequestOptions({ 9 | url: ApiConstant.URL_LYRIC, 10 | data: { "id": id}, 11 | viewState: viewState 12 | })) 13 | 14 | const lyricModels = LyricUtil.parse(lyricResult) 15 | return lyricModels 16 | } 17 | } 18 | 19 | export const lyricViewModel = new LyricViewModel() -------------------------------------------------------------------------------- /entry/src/main/ets/pages/songsheet/index.ets: -------------------------------------------------------------------------------- 1 | export * from './view/SongSheetDetail' 2 | 3 | export * from './viewmodel/SongSheetDetailViewModel' 4 | 5 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/songsheet/viewmodel/SongSheetDetailViewModel.ets: -------------------------------------------------------------------------------- 1 | import { BaseViewModel, PagingLayoutMediator, RequestOptions, ViewState } from 'lib_common'; 2 | import { SongSheetDetailResultBean, ApiConstant, UserPlayListDetailResultBean } from '../../..'; 3 | 4 | 5 | /** 6 | * 歌单详情ViewModel 7 | */ 8 | export class SongSheetDetailViewModel extends BaseViewModel { 9 | async getSongDetail(id: number, 10 | viewState: ViewState, 11 | pagingMediator: PagingLayoutMediator): Promise { 12 | 13 | const playlistDetailResultBean = await this.get( 14 | new RequestOptions({ 15 | url: ApiConstant.URL_PLAY_LIST_DETAIL, 16 | data: { "id": id }, 17 | viewState: viewState, 18 | })) 19 | if (playlistDetailResultBean) { 20 | const trackIdBeans = playlistDetailResultBean.playlist.trackIds 21 | let ids = "" 22 | if (trackIdBeans) { 23 | for (let index = 0; index < trackIdBeans.length; index++) { 24 | ids += trackIdBeans[index].id; 25 | if (index != trackIdBeans.length - 1) { 26 | ids += "," 27 | } 28 | } 29 | } 30 | this.get( 31 | new RequestOptions({ 32 | url: ApiConstant.URL_SONG_DETAIL, 33 | data: { "ids": ids }, 34 | viewState: viewState, 35 | pagingMediator: pagingMediator, 36 | pagingListConverter: (result: SongSheetDetailResultBean) => { 37 | return result.songs 38 | } 39 | })) 40 | } 41 | return playlistDetailResultBean 42 | } 43 | } 44 | 45 | export const songSheetDetailViewModel = new SongSheetDetailViewModel(); 46 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/splash/index.ets: -------------------------------------------------------------------------------- 1 | export * from "./view/Splash" 2 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/splash/view/Splash.ets: -------------------------------------------------------------------------------- 1 | import {RouterUrls } from 'lib_common' 2 | import { AppTheme, THEME_TYPE, ThemeType } from 'lib_theme' 3 | import router from '@ohos.router' 4 | import { IUserInfoBean, UserInfoBean, UserManager, STORAGE_USER_INFO } from '../../..' 5 | 6 | 7 | /** 8 | * 欢迎页面 9 | */ 10 | @Entry 11 | @Component 12 | export default struct SplashPage { 13 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 14 | @StorageLink(STORAGE_USER_INFO) userInfo: UserInfoBean = new UserInfoBean({} as IUserInfoBean) 15 | 16 | async aboutToAppear() { 17 | this.userInfo = await UserManager.getUserInfo() 18 | setTimeout(() => { 19 | router.replaceUrl({ url: this.userInfo ? RouterUrls.Index : RouterUrls.Login }) 20 | }, 1000) 21 | } 22 | 23 | build() { 24 | Stack({ alignContent: Alignment.Top }) { 25 | Image($r('app.media.ic_app_logo')) 26 | .width(80) 27 | .height(80) 28 | .margin({ top: 300 }) 29 | .borderRadius(40) 30 | .sharedTransition('imgAppLogo', { 31 | duration: 300, 32 | curve: Curve.Linear 33 | }) 34 | 35 | }.width("100%").height("100%").backgroundColor(AppTheme.palette(this.themeType).primary) 36 | } 37 | 38 | pageTransition() { 39 | PageTransitionEnter({ type: RouteType.Push, duration: 300, curve: Curve.Linear }) 40 | .opacity(0) 41 | PageTransitionEnter({ type: RouteType.Pop, duration: 300, curve: Curve.Linear }) 42 | .opacity(1) 43 | PageTransitionExit({ type: RouteType.Push, duration: 300, curve: Curve.Linear }) 44 | .opacity(1) 45 | PageTransitionExit({ type: RouteType.Pop, duration: 300, curve: Curve.Linear }) 46 | .opacity(0) 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /entry/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry", 4 | "type": "entry", 5 | "description": "$string:module_desc", 6 | "mainElement": "EntryAbility", 7 | "deviceTypes": [ 8 | "phone", 9 | "tablet" 10 | ], 11 | "deliveryWithInstall": true, 12 | "installationFree": false, 13 | "pages": "$profile:main_pages", 14 | "abilities": [ 15 | { 16 | "name": "EntryAbility", 17 | "srcEntry": "./ets/entryability/EntryAbility.ets", 18 | "description": "$string:EntryAbility_desc", 19 | "icon": "$media:ic_app_logo", 20 | "label": "$string:EntryAbility_label", 21 | "startWindowIcon": "$media:ic_app_logo", 22 | "startWindowBackground": "$color:start_window_background", 23 | "exported": true, 24 | "skills": [ 25 | { 26 | "entities": [ 27 | "entity.system.home" 28 | ], 29 | "actions": [ 30 | "action.system.home" 31 | ] 32 | } 33 | ] 34 | } 35 | ], 36 | "requestPermissions": [ 37 | { 38 | "name": "ohos.permission.INTERNET", 39 | } 40 | ] 41 | } 42 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#00FFFFFF" 6 | }, 7 | { 8 | "name": "primary", 9 | "value": "#F0484E" 10 | }, 11 | { 12 | "name": "secondary", 13 | "value": "#F0888C" 14 | }, 15 | { 16 | "name": "common_bg", 17 | "value": "#FFFFFF" 18 | }, 19 | { 20 | "name": "nav_bar_bg", 21 | "value": "#FAFAFA" 22 | }, 23 | { 24 | "name": "first_text", 25 | "value": "#333333" 26 | }, 27 | { 28 | "name": "second_text", 29 | "value": "#666666" 30 | }, 31 | { 32 | "name": "third_text", 33 | "value": "#999999" 34 | }, 35 | { 36 | "name": "first_icon", 37 | "value": "#333333" 38 | }, 39 | { 40 | "name": "second_icon", 41 | "value": "#666666" 42 | }, 43 | { 44 | "name": "third_icon", 45 | "value": "#999999" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/float.json: -------------------------------------------------------------------------------- 1 | { 2 | "float": [ 3 | { 4 | "name": "app_bar_height", 5 | "value": "88" 6 | }, 7 | { 8 | "name": "tab_bar_height", 9 | "value": "88" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_desc", 5 | "value": "module description" 6 | }, 7 | { 8 | "name": "EntryAbility_desc", 9 | "value": "description" 10 | }, 11 | { 12 | "name": "EntryAbility_label", 13 | "value": "NCMusic" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_add.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_app_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/entry/src/main/resources/base/media/ic_app_logo.png -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_arrow_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_bar_pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_bar_play.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_buy.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_collect.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_default_avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | 20 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_disk.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/entry/src/main/resources/base/media/ic_disk.webp -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_like.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/entry/src/main/resources/base/media/ic_loading.gif -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_local_file.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | 20 | 24 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_more.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_more_item.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_nav_discovery.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_nav_dynamic.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_nav_my.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_nav_podcast.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | 20 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_download.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | 20 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_like.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_list.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | 20 | 24 | 28 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_mode_random.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_mode_repeat.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_mode_single.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 21 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_more.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | 20 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_needle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/entry/src/main/resources/base/media/ic_play_needle.webp -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_next.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_play_list.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_pre.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_sing.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_start.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_play_stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_recent.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_search.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 21 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_share.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/ic_theme.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | 20 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/entry/src/main/resources/base/media/icon.png -------------------------------------------------------------------------------- /entry/src/main/resources/base/profile/main_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "pages/splash/view/Splash", 4 | "pages/main/view/Main", 5 | "pages/songsheet/view/SongSheetDetail", 6 | "pages/play/view/MusicPlay", 7 | "pages/login/view/Login", 8 | "pages/dynamic/view/DynamicDetailPage", 9 | "pages/pics/view/PicturePreview" 10 | ], 11 | "window": { 12 | "autoDesignWidth": true 13 | } 14 | } -------------------------------------------------------------------------------- /entry/src/main/resources/en_US/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_desc", 5 | "value": "module description" 6 | }, 7 | { 8 | "name": "EntryAbility_desc", 9 | "value": "description" 10 | }, 11 | { 12 | "name": "EntryAbility_label", 13 | "value": "NCMusic" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /entry/src/main/resources/zh_CN/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_desc", 5 | "value": "模块描述" 6 | }, 7 | { 8 | "name": "EntryAbility_desc", 9 | "value": "description" 10 | }, 11 | { 12 | "name": "EntryAbility_label", 13 | "value": "NCMusic" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import hilog from '@ohos.hilog'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', function () { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(function () { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }) 11 | beforeEach(function () { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }) 16 | afterEach(function () { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }) 21 | afterAll(function () { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }) 25 | it('assertContain',0, function () { 26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); 28 | let a = 'abc' 29 | let b = 'b' 30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 31 | expect(a).assertContain(b) 32 | expect(a).assertEqual(a) 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test' 2 | 3 | export default function testsuite() { 4 | abilityTest() 5 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/testability/TestAbility.ets: -------------------------------------------------------------------------------- 1 | import UIAbility from '@ohos.app.ability.UIAbility'; 2 | import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; 3 | import hilog from '@ohos.hilog'; 4 | import { Hypium } from '@ohos/hypium'; 5 | import testsuite from '../test/List.test'; 6 | import window from '@ohos.window'; 7 | 8 | export default class TestAbility extends UIAbility { 9 | onCreate(want, launchParam) { 10 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); 11 | hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); 12 | hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); 13 | var abilityDelegator: any 14 | abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() 15 | var abilityDelegatorArguments: any 16 | abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() 17 | hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); 18 | Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) 19 | } 20 | 21 | onDestroy() { 22 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); 23 | } 24 | 25 | onWindowStageCreate(windowStage: window.WindowStage) { 26 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); 27 | windowStage.loadContent('testability/pages/Index', (err, data) => { 28 | if (err.code) { 29 | hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 30 | return; 31 | } 32 | hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', 33 | JSON.stringify(data) ?? ''); 34 | }); 35 | } 36 | 37 | onWindowStageDestroy() { 38 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); 39 | } 40 | 41 | onForeground() { 42 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); 43 | } 44 | 45 | onBackground() { 46 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); 47 | } 48 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/testability/pages/Index.ets: -------------------------------------------------------------------------------- 1 | import hilog from '@ohos.hilog'; 2 | 3 | @Entry 4 | @Component 5 | struct Index { 6 | aboutToAppear() { 7 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); 8 | } 9 | @State message: string = 'Hello World' 10 | build() { 11 | Row() { 12 | Column() { 13 | Text(this.message) 14 | .fontSize(50) 15 | .fontWeight(FontWeight.Bold) 16 | Button() { 17 | Text('next page') 18 | .fontSize(20) 19 | .fontWeight(FontWeight.Bold) 20 | }.type(ButtonType.Capsule) 21 | .margin({ 22 | top: 20 23 | }) 24 | .backgroundColor('#0D9FFB') 25 | .width('35%') 26 | .height('5%') 27 | .onClick(()=>{ 28 | }) 29 | } 30 | .width('100%') 31 | } 32 | .height('100%') 33 | } 34 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts: -------------------------------------------------------------------------------- 1 | import hilog from '@ohos.hilog'; 2 | import TestRunner from '@ohos.application.testRunner'; 3 | import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; 4 | 5 | var abilityDelegator = undefined 6 | var abilityDelegatorArguments = undefined 7 | 8 | async function onAbilityCreateCallback() { 9 | hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); 10 | } 11 | 12 | async function addAbilityMonitorCallback(err: any) { 13 | hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); 14 | } 15 | 16 | export default class OpenHarmonyTestRunner implements TestRunner { 17 | constructor() { 18 | } 19 | 20 | onPrepare() { 21 | hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); 22 | } 23 | 24 | async onRun() { 25 | hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); 26 | abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() 27 | abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() 28 | var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' 29 | let lMonitor = { 30 | abilityName: testAbilityName, 31 | onAbilityCreate: onAbilityCreateCallback, 32 | }; 33 | abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) 34 | var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName 35 | var debug = abilityDelegatorArguments.parameters['-D'] 36 | if (debug == 'true') 37 | { 38 | cmd += ' -D' 39 | } 40 | hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); 41 | abilityDelegator.executeShellCommand(cmd, 42 | (err: any, d: any) => { 43 | hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); 44 | hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); 45 | hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); 46 | }) 47 | hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); 48 | } 49 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry_test", 4 | "type": "feature", 5 | "description": "$string:module_test_desc", 6 | "mainElement": "TestAbility", 7 | "deviceTypes": [ 8 | "phone", 9 | "tablet" 10 | ], 11 | "deliveryWithInstall": true, 12 | "installationFree": false, 13 | "pages": "$profile:test_pages", 14 | "abilities": [ 15 | { 16 | "name": "TestAbility", 17 | "srcEntry": "./ets/testability/TestAbility.ets", 18 | "description": "$string:TestAbility_desc", 19 | "icon": "$media:icon", 20 | "label": "$string:TestAbility_label", 21 | "exported": true, 22 | "startWindowIcon": "$media:icon", 23 | "startWindowBackground": "$color:start_window_background", 24 | "skills": [ 25 | { 26 | "actions": [ 27 | "action.system.home" 28 | ], 29 | "entities": [ 30 | "entity.system.home" 31 | ] 32 | } 33 | ] 34 | } 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /entry/src/ohosTest/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#FFFFFF" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_test_desc", 5 | "value": "test ability description" 6 | }, 7 | { 8 | "name": "TestAbility_desc", 9 | "value": "the test ability" 10 | }, 11 | { 12 | "name": "TestAbility_label", 13 | "value": "test label" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/resources/base/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/entry/src/ohosTest/resources/base/media/icon.png -------------------------------------------------------------------------------- /entry/src/ohosTest/resources/base/profile/test_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "testability/pages/Index" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /hvigor/hvigor-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | "hvigorVersion": "2.4.2", 3 | "dependencies": { 4 | "@ohos/hvigor-ohos-plugin": "2.4.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /hvigorfile.ts: -------------------------------------------------------------------------------- 1 | // Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. 2 | export { appTasks } from '@ohos/hvigor-ohos-plugin'; -------------------------------------------------------------------------------- /hvigorw: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ---------------------------------------------------------------------------- 4 | # Hvigor startup script, version 1.0.0 5 | # 6 | # Required ENV vars: 7 | # ------------------ 8 | # NODE_HOME - location of a Node home dir 9 | # or 10 | # Add /usr/local/nodejs/bin to the PATH environment variable 11 | # ---------------------------------------------------------------------------- 12 | 13 | HVIGOR_APP_HOME=$(dirname $(readlink -f $0)) 14 | HVIGOR_WRAPPER_SCRIPT=${HVIGOR_APP_HOME}/hvigor/hvigor-wrapper.js 15 | warn() { 16 | echo "" 17 | echo -e "\033[1;33m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m" 18 | } 19 | 20 | error() { 21 | echo "" 22 | echo -e "\033[1;31m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m" 23 | } 24 | 25 | fail() { 26 | error "$@" 27 | exit 1 28 | } 29 | 30 | # Determine node to start hvigor wrapper script 31 | if [ -n "${NODE_HOME}" ];then 32 | EXECUTABLE_NODE="${NODE_HOME}/bin/node" 33 | if [ ! -x "$EXECUTABLE_NODE" ];then 34 | fail "ERROR: NODE_HOME is set to an invalid directory,check $NODE_HOME\n\nPlease set NODE_HOME in your environment to the location where your nodejs installed" 35 | fi 36 | else 37 | EXECUTABLE_NODE="node" 38 | which ${EXECUTABLE_NODE} > /dev/null 2>&1 || fail "ERROR: NODE_HOME is not set and not 'node' command found in your path" 39 | fi 40 | 41 | # Check hvigor wrapper script 42 | if [ ! -r "$HVIGOR_WRAPPER_SCRIPT" ];then 43 | fail "ERROR: Couldn't find hvigor/hvigor-wrapper.js in ${HVIGOR_APP_HOME}" 44 | fi 45 | 46 | # start hvigor-wrapper script 47 | exec "${EXECUTABLE_NODE}" \ 48 | "${HVIGOR_WRAPPER_SCRIPT}" "$@" 49 | -------------------------------------------------------------------------------- /hvigorw.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Hvigor startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 17 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 18 | 19 | set WRAPPER_MODULE_PATH=%APP_HOME%\hvigor\hvigor-wrapper.js 20 | set NODE_EXE=node.exe 21 | 22 | goto start 23 | 24 | :start 25 | @rem Find node.exe 26 | if defined NODE_HOME goto findNodeFromNodeHome 27 | 28 | %NODE_EXE% --version >NUL 2>&1 29 | if "%ERRORLEVEL%" == "0" goto execute 30 | 31 | echo. 32 | echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH. 33 | echo. 34 | echo Please set the NODE_HOME variable in your environment to match the 35 | echo location of your NodeJs installation. 36 | 37 | goto fail 38 | 39 | :findNodeFromNodeHome 40 | set NODE_HOME=%NODE_HOME:"=% 41 | set NODE_EXE_PATH=%NODE_HOME%/%NODE_EXE% 42 | 43 | if exist "%NODE_EXE_PATH%" goto execute 44 | echo. 45 | echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH. 46 | echo. 47 | echo Please set the NODE_HOME variable in your environment to match the 48 | echo location of your NodeJs installation. 49 | 50 | goto fail 51 | 52 | :execute 53 | @rem Execute hvigor 54 | "%NODE_EXE%" %WRAPPER_MODULE_PATH% %* 55 | 56 | if "%ERRORLEVEL%" == "0" goto hvigorwEnd 57 | 58 | :fail 59 | exit /b 1 60 | 61 | :hvigorwEnd 62 | if "%OS%" == "Windows_NT" endlocal 63 | 64 | :end 65 | -------------------------------------------------------------------------------- /lib_common/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /lib_common/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | }, 5 | "targets": [ 6 | { 7 | "name": "default", 8 | "runtimeOS": "HarmonyOS" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /lib_common/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | // Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. 2 | export { harTasks } from '@ohos/hvigor-ohos-plugin'; -------------------------------------------------------------------------------- /lib_common/index.ets: -------------------------------------------------------------------------------- 1 | // export * from './src/main/ets/constant' 2 | // 3 | // export * from './src/main/ets/cpn' 4 | // 5 | // export * from './src/main/ets/util' 6 | // 7 | // export * from './src/main/ets/model' 8 | export * from './src/main/ets/' 9 | -------------------------------------------------------------------------------- /lib_common/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 4 | "specifiers": { 5 | "class-transformer@^0.5.1": "class-transformer@0.5.1" 6 | }, 7 | "packages": { 8 | "class-transformer@0.5.1": { 9 | "resolved": "https://repo.harmonyos.com/ohpm/class-transformer/-/class-transformer-0.5.1.tgz", 10 | "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /lib_common/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "license": "Apache-2.0", 3 | "devDependencies": {}, 4 | "author": "", 5 | "name": "lib_common", 6 | "description": "Please describe the basic information.", 7 | "main": "index.ets", 8 | "version": "1.0.0", 9 | "dependencies": { 10 | "class-transformer": "^0.5.1", 11 | "lib_theme": "file:../lib_theme" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/api/index.ets: -------------------------------------------------------------------------------- 1 | export * from './ApiRequest' 2 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/bean/BaseDataSource.ets: -------------------------------------------------------------------------------- 1 | 2 | export class BaseDataSource implements IDataSource { 3 | private listeners: DataChangeListener[] = []; 4 | 5 | public totalCount(): number { 6 | return 0; 7 | } 8 | 9 | public getData(index: number): object | null { 10 | return null ; 11 | } 12 | 13 | // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听 14 | registerDataChangeListener(listener: DataChangeListener): void { 15 | if (this.listeners.indexOf(listener) < 0) { 16 | console.info('add listener'); 17 | this.listeners.push(listener); 18 | } 19 | } 20 | 21 | // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听 22 | unregisterDataChangeListener(listener: DataChangeListener): void { 23 | const pos = this.listeners.indexOf(listener); 24 | if (pos >= 0) { 25 | console.info('remove listener'); 26 | this.listeners.splice(pos, 1); 27 | } 28 | } 29 | 30 | // 通知LazyForEach组件需要重载所有子组件 31 | notifyDataReload(): void { 32 | this.listeners.forEach(listener => { 33 | listener.onDataReloaded(); 34 | }) 35 | } 36 | 37 | // 通知LazyForEach组件需要在index对应索引处添加子组件 38 | notifyDataAdd(index: number): void { 39 | this.listeners.forEach(listener => { 40 | listener.onDataAdd(index); 41 | }) 42 | } 43 | 44 | // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件 45 | notifyDataChange(index: number): void { 46 | this.listeners.forEach(listener => { 47 | listener.onDataChange(index); 48 | }) 49 | } 50 | 51 | // 通知LazyForEach组件需要在index对应索引处删除该子组件 52 | notifyDataDelete(index: number): void { 53 | this.listeners.forEach(listener => { 54 | listener.onDataDelete(index); 55 | }) 56 | } 57 | } 58 | 59 | export class ListDataSource extends BaseDataSource { 60 | dataArray: object[] = []; 61 | 62 | public totalCount(): number { 63 | return this.dataArray.length; 64 | } 65 | 66 | public getData(index: number): object { 67 | return this.dataArray[index]; 68 | } 69 | 70 | public addData(index: number, data: object): void { 71 | this.dataArray.splice(index, 0, data); 72 | this.notifyDataAdd(index); 73 | } 74 | 75 | public pushData(data: object): void { 76 | this.dataArray.push(data); 77 | this.notifyDataAdd(this.dataArray.length - 1); 78 | } 79 | public reloadData (list: object[]) { 80 | this.dataArray = list 81 | this.notifyDataReload() // 重新加载 82 | } 83 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/bean/BaseResultBean.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 接口请求返回结果基类 3 | */ 4 | export class BaseResultBean { 5 | code?: number 6 | message?: string 7 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/bean/RequestParamBean.ets: -------------------------------------------------------------------------------- 1 | import { PagingLayoutMediator, ViewState } from '..' 2 | 3 | /** 4 | * 请求数据模型 5 | */ 6 | export interface IRequestOptions { 7 | // 请求url 8 | url: string 9 | // 请求参数 10 | data?: object 11 | // 和页面绑定的ViewState 12 | viewState?: ViewState, 13 | // 分页协调工具 14 | pagingMediator?: PagingLayoutMediator, 15 | // 请求成功条件,默认code==200 16 | successCondition?: (result: object) => boolean 17 | // 判断空条件 18 | emptyCondition?: (result: object) => boolean 19 | // 分页数据转换 20 | pagingListConverter?: (result: object) => object[] 21 | // 请求失败时是否还返回result 22 | interceptWhenNoSuccess?: boolean 23 | } 24 | 25 | export class RequestOptions { 26 | url: string 27 | data?: object 28 | viewState?: ViewState 29 | pagingMediator?: PagingLayoutMediator = null 30 | successCondition?: (result: object) => boolean = null 31 | emptyCondition?: (result: object) => boolean = null 32 | pagingListConverter?: (result: object) => object[] = null 33 | interceptWhenNoSuccess?: boolean = true 34 | 35 | constructor(options: IRequestOptions) { 36 | this.url = options.url 37 | this.data = options.data 38 | this.viewState = options.viewState 39 | this.pagingMediator = options.pagingMediator 40 | this.pagingListConverter = options.pagingListConverter 41 | this.successCondition = options.successCondition 42 | this.emptyCondition = options.emptyCondition 43 | if (options.interceptWhenNoSuccess != undefined) { 44 | this.interceptWhenNoSuccess = options.interceptWhenNoSuccess 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/bean/index.ets: -------------------------------------------------------------------------------- 1 | export * from './BaseDataSource' 2 | 3 | export * from './BaseResultBean' 4 | 5 | export * from './RequestParamBean' 6 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/constant/RouterUrls.ets: -------------------------------------------------------------------------------- 1 | export class RouterUrls { 2 | // 欢迎页面 3 | static readonly Splash = "pages/splash/view/Splash" 4 | // 登陆页面 5 | static readonly Login = "pages/login/view/Login" 6 | // 主页 7 | static readonly Index = "pages/main/view/Main" 8 | // 歌单详情页 9 | static readonly SongSheetDetail = "pages/songsheet/view/SongSheetDetail" 10 | // 音乐播放页面 11 | static readonly MusicPlay = "pages/play/view/MusicPlay" 12 | // 动态详情页 13 | static readonly DynamicDetail = "pages/dynamic/view/DynamicDetailPage" 14 | // 图片预览页 15 | static readonly PicturePreview = "pages/pics/view/PicturePreview" 16 | 17 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/constant/SizeConstant.ts: -------------------------------------------------------------------------------- 1 | export class SizeConstant { 2 | // 标题栏高度 3 | static readonly APP_BAR_HEIGHT = 38 4 | // TabLayout高度 5 | static readonly TAB_LAYOUT_HEIGHT = 38 6 | // TabLayout指示器宽度 7 | static readonly TAB_LAYOUT_INDICATOR_WIDTH = 14 8 | // TabLayout指示器高度 9 | static readonly TAB_LAYOUT_INDICATOR_HEIGHT = 2.5 10 | // 底部导航栏高度 11 | static readonly NAV_BAR_HEIGHT = 48 12 | // 音乐播放栏高度 13 | static readonly MUSIC_PLAY_BAR_HEIGHT = 44 14 | 15 | 16 | // 文本大小相关 17 | // 小号文本 18 | static readonly TEXT_S = 10 19 | // 中号 20 | static readonly TEXT_M = 12 21 | // 大号文本 22 | static readonly TEXT_L = 14 23 | // X大号文本 24 | static readonly TEXT_XL = 16 25 | // XX大号文本 26 | static readonly TEXT_XXL = 18 27 | 28 | 29 | // 图标大小相关 30 | // 小号图标 31 | static readonly ICON_S = 13 32 | // 中号图标 33 | static readonly ICON_M = 16 34 | // 大号图标 35 | static readonly ICON_L = 19 36 | // X大号图标 37 | static readonly ICON_XL = 24 38 | // XX大号图标 39 | static readonly ICON_XXL = 32 40 | 41 | // 圆角大小相关 42 | // 小号圆角 43 | static readonly RADIUS_S = 3 44 | // 中号圆角 45 | static readonly RADIUS_M = 5 46 | // 大号圆角 47 | static readonly RADIUS_L = 14 48 | 49 | // 间距相关 50 | // 小号间距 51 | static readonly SPACE_S = 5 52 | // 中号间距 53 | static readonly SPACE_M = 10 54 | // 大号间距 55 | static readonly SPACE_L = 14 56 | // X大号间距 57 | static readonly SPACE_XL = 18 58 | // XX大号间距 59 | static readonly SPACE_XXL = 26 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/constant/index.ets: -------------------------------------------------------------------------------- 1 | export * from './SizeConstant' 2 | 3 | export * from './RouterUrls' 4 | 5 | 6 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/cpn/ClickEffectLayout.ets: -------------------------------------------------------------------------------- 1 | let globalClickEffecting: boolean = false 2 | 3 | /** 4 | * 自定义点击效果容器 5 | * 点击的时候,被包裹的组件,会做透明度1->0.8,缩放1->0.97的动画 6 | */ 7 | @Component 8 | export struct ClickEffectLayout { 9 | @BuilderParam private content: () => void 10 | @State private alphaState: number = 1 11 | @State private scaleState: number = 1 12 | private lastTouchX = 0 13 | private lastTouchY = 0 14 | private touchOffsetX = 0 15 | private touchOffsetY = 0 16 | private downTouchTimeStamp = 0 17 | private animEffect = false 18 | click?: (event?: ClickEvent) => void 19 | 20 | build() { 21 | Stack() { 22 | this.content() 23 | }.opacity(this.alphaState) 24 | .scale({ 25 | centerX: "50%", 26 | centerY: "50%", 27 | x: this.scaleState, 28 | y: this.scaleState 29 | }).onTouch((event: TouchEvent) => { 30 | if (this.click) { 31 | if (event.type == TouchType.Down) { 32 | this.lastTouchX = event.touches[0].x 33 | this.lastTouchY = event.touches[0].y 34 | this.touchOffsetX = 0 35 | this.touchOffsetY = 0 36 | this.downTouchTimeStamp = event.timestamp 37 | this.animEffect = false 38 | } else if (event.type == TouchType.Move) { 39 | if (globalClickEffecting) { 40 | return 41 | } 42 | const cost = event.timestamp - this.downTouchTimeStamp 43 | const curTouchX = event.touches[0].x 44 | const curTouchY = event.touches[0].y 45 | const dx = curTouchX - this.lastTouchX 46 | const dy = curTouchY - this.lastTouchY 47 | this.touchOffsetX += dx 48 | this.touchOffsetY += dy 49 | this.lastTouchX = curTouchX 50 | this.lastTouchY = curTouchY 51 | if (event.timestamp - this.downTouchTimeStamp >= 80000000 && !this.animEffect) { 52 | if (Math.abs(this.touchOffsetX) <= 1 && Math.abs(this.touchOffsetY) <= 1) { 53 | this.animEffect = true 54 | globalClickEffecting = true 55 | animateTo({ 56 | duration: 100, 57 | }, () => { 58 | this.alphaState = 0.8 59 | this.scaleState = 0.97 60 | }) 61 | } 62 | } 63 | } else if (event.type == TouchType.Up || event.type == TouchType.Cancel) { 64 | if (this.alphaState !== 1 && this.scaleState !== 1) { 65 | this.restore() 66 | } 67 | } 68 | } 69 | }).onClick((event) => { 70 | if (this.click) { 71 | this.click(event) 72 | if (!this.animEffect) { 73 | globalClickEffecting = true 74 | animateTo({ 75 | duration: 100, 76 | onFinish: () => { 77 | this.restore() 78 | } 79 | }, () => { 80 | this.alphaState = 0.8 81 | this.scaleState = 0.97 82 | }) 83 | } 84 | } 85 | }) 86 | } 87 | 88 | private restore() { 89 | animateTo({ 90 | duration: 100, 91 | onFinish: () => { 92 | globalClickEffecting = false 93 | } 94 | }, () => { 95 | this.alphaState = 1 96 | this.scaleState = 1 97 | }) 98 | } 99 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/cpn/CommonNetworkImage.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * 通用网络图片加载组件 3 | * 添加placeHolder功能 4 | */ 5 | @Component 6 | export struct CommonNetworkImage { 7 | url: string | undefined = undefined 8 | placeHolder: Resource = $r('app.media.ic_default_place_holder') 9 | @State status: NetworkImageLoadStatus = NetworkImageLoadStatus.LOADING 10 | @State imageOpacity: number = 0 11 | 12 | build() { 13 | Stack() { 14 | if (this.status != NetworkImageLoadStatus.SUCCESS) { 15 | Image(this.placeHolder) 16 | .width("100%") 17 | .height("100%") 18 | .constraintSize({ maxWidth: 72, maxHeight: 72 }) 19 | } 20 | // Image(this.placeHolder) 21 | // .width("100%") 22 | // .height("100%") 23 | // .constraintSize({ maxWidth: 72, maxHeight: 72 }) 24 | // .visibility(this.status != NetworkImageLoadStatus.SUCCESS ? Visibility.Visible : Visibility.Hidden) 25 | 26 | Image(this.url) 27 | .width("100%") 28 | .height("100%") 29 | .onComplete((event: { 30 | width: number; 31 | height: number; 32 | componentWidth: number; 33 | componentHeight: number; 34 | loadingStatus: number; 35 | }) => { 36 | if (event.loadingStatus == 1) { 37 | this.status = NetworkImageLoadStatus.SUCCESS 38 | // animateTo({ 39 | // duration: 200 40 | // }, () => { 41 | // this.imageOpacity = 1 42 | // }) 43 | } 44 | }) 45 | .onError(() => { 46 | this.status = NetworkImageLoadStatus.ERROR 47 | }) 48 | .visibility(this.status == NetworkImageLoadStatus.SUCCESS ? Visibility.Visible : Visibility.Hidden) 49 | // .opacity(this.imageOpacity) 50 | } 51 | } 52 | } 53 | 54 | enum NetworkImageLoadStatus { 55 | LOADING, 56 | SUCCESS, 57 | ERROR 58 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/cpn/CpnLoading.ets: -------------------------------------------------------------------------------- 1 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 2 | 3 | /** 4 | * 加载组件 5 | */ 6 | @Preview 7 | @Component 8 | export struct CpnLoading { 9 | private loadingRectArray: Array = [new LoadingRect({ startRatio: 0.7, endRatio: 0.15, animDuring: 600 }), 10 | new LoadingRect({ startRatio: 0.23, endRatio: 0.85, animDuring: 550 }), 11 | new LoadingRect({ startRatio: 0.55, endRatio: 0.3, animDuring: 500 }), 12 | new LoadingRect({ startRatio: 0.26, endRatio: 0.78, animDuring: 600 })] 13 | @State private rect1HeightPercent: number = 0.7 14 | @State private rect2HeightPercent: number = 0.23 15 | @State private rect3HeightPercent: number = 0.55 16 | @State private rect4HeightPercent: number = 0.26 17 | private loadingWidth = 26 18 | private loadingHeight = 22 19 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 20 | 21 | build() { 22 | Row() { 23 | Stack().height(this.loadingHeight * this.rect1HeightPercent).rectStyle() 24 | Stack().height(this.loadingHeight * this.rect2HeightPercent).rectStyle() 25 | Stack().height(this.loadingHeight * this.rect3HeightPercent).rectStyle() 26 | Stack().height(this.loadingHeight * this.rect4HeightPercent).rectStyle() 27 | } 28 | .justifyContent(FlexAlign.SpaceBetween) 29 | .alignItems(VerticalAlign.Bottom) 30 | .width(this.loadingWidth) 31 | .height(this.loadingHeight) 32 | .onVisibleAreaChange([0, 1], (isVisible: boolean, currentRatio: number) => { 33 | if (isVisible) { 34 | console.log("CpnLoading", "CpnLoading isVisible=" + isVisible + ",currentRatio:" + currentRatio) 35 | this.doAnim() 36 | } 37 | }) 38 | } 39 | 40 | private doAnim() { 41 | for (let index = 0; index < this.loadingRectArray.length; index++) { 42 | const rect = this.loadingRectArray[index]; 43 | animateTo({ 44 | duration: rect.animDuring, 45 | playMode: PlayMode.Alternate, 46 | iterations: -1, 47 | }, () => { 48 | if (index == 0) { 49 | this.rect1HeightPercent = rect.endRatio 50 | } else if (index == 1) { 51 | this.rect2HeightPercent = rect.endRatio 52 | } else if (index == 2) { 53 | this.rect3HeightPercent = rect.endRatio 54 | } else if (index == 3) { 55 | this.rect4HeightPercent = rect.endRatio 56 | } 57 | }) 58 | } 59 | } 60 | 61 | @Styles rectStyle() { 62 | .width("18%") 63 | .backgroundColor(AppTheme.palette(this.themeType).primary) 64 | .borderRadius({ topLeft: this.loadingWidth * 0.15 * 0.4, topRight: this.loadingWidth * 0.15 * 0.4 }) 65 | 66 | } 67 | } 68 | 69 | interface ILoadingRect { 70 | startRatio: number 71 | endRatio: number 72 | animDuring: number 73 | } 74 | 75 | export class LoadingRect implements ILoadingRect { 76 | startRatio: number = 0 77 | endRatio: number = 0 78 | animDuring: number = 0 79 | 80 | constructor(model: ILoadingRect) { 81 | this.startRatio = model.startRatio 82 | this.endRatio = model.endRatio 83 | this.animDuring = model.animDuring 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/cpn/CpnProgressBar.ets: -------------------------------------------------------------------------------- 1 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 2 | 3 | /** 4 | * 进度条组件 5 | */ 6 | @Component 7 | export struct CpnProgressBar { 8 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 9 | progressHeight: number = 1 10 | progressActiveHeight: number = 2 11 | progressThumbSize: number = 6 12 | progressThumbActiveSize: number = 12 13 | progressColor: ResourceColor = AppTheme.palette(this.themeType).primary 14 | bgColor: ResourceColor = Color.White 15 | thumbColor: ResourceColor = Color.White 16 | @Link progress: number 17 | @State isActive: boolean = false 18 | @State progressWidth: number = 0 19 | dragCallback: (isFinish: boolean) => void = null 20 | 21 | build() { 22 | Stack({ alignContent: Alignment.Start }) { 23 | Stack() 24 | .width("100%") 25 | .height(this.isActive ? this.progressActiveHeight : this.progressHeight) 26 | .backgroundColor(this.bgColor) 27 | .borderRadius(this.isActive ? this.progressActiveHeight / 2 : this.progressHeight / 2) 28 | Stack() 29 | .width(`${this.progress}%`) 30 | .height(this.isActive ? this.progressActiveHeight : this.progressHeight) 31 | .backgroundColor(this.progressColor) 32 | .borderRadius(this.isActive ? this.progressActiveHeight / 2 : this.progressHeight / 2) 33 | Stack() 34 | .width(this.isActive ? this.progressThumbActiveSize : this.progressThumbSize) 35 | .height(this.isActive ? this.progressThumbActiveSize : this.progressThumbSize) 36 | .backgroundColor(this.thumbColor) 37 | .borderRadius(this.isActive ? this.progressThumbActiveSize / 2 : this.progressThumbSize / 2) 38 | .offset({ 39 | x: (this.progressWidth - (this.isActive ? this.progressThumbActiveSize : this.progressThumbSize)) * this.progress / 100.0 40 | }) 41 | } 42 | .height("100%") 43 | .width("100%") 44 | .onAreaChange((_, newValue) => { 45 | this.progressWidth = newValue.width as number 46 | }) 47 | .onTouch((event: TouchEvent) => { 48 | if (event.type == TouchType.Down || event.type == TouchType.Move) { 49 | this.isActive = true 50 | this.progress = Math.max(0, Math.min(100, event.touches[0].x / this.progressWidth * 100)) 51 | if (this.dragCallback) { 52 | this.dragCallback(false) 53 | } 54 | } else if (event.type == TouchType.Up || event.type == TouchType.Cancel) { 55 | this.isActive = false 56 | this.progress = Math.max(0, Math.min(100, event.touches[0].x / this.progressWidth * 100)) 57 | if (this.dragCallback) { 58 | this.dragCallback(true) 59 | } 60 | } 61 | }) 62 | } 63 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/cpn/index.ets: -------------------------------------------------------------------------------- 1 | export * from './ClickEffectLayout' 2 | 3 | export * from './CommonAppBar' 4 | 5 | export * from './CollapsibleLayout' 6 | 7 | export * from './CpnLoading' 8 | 9 | export * from './CpnProgressBar' 10 | 11 | export * from './RefreshLayout' 12 | 13 | export * from './TabLayout' 14 | 15 | export * from './viewstate' 16 | 17 | export * from './CommonNetworkImage' 18 | 19 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/cpn/viewstate/ViewStateEmpty.ets: -------------------------------------------------------------------------------- 1 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 2 | import { ClickEffectLayout } from '../..' 3 | import { SizeConstant } from '../../constant/SizeConstant' 4 | 5 | /** 6 | * 空状态布局组件 7 | */ 8 | @Preview 9 | @Component 10 | export struct ViewStateEmpty { 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | private tip: string = "暂无数据" 13 | retryCallback: () => void = () => { 14 | } 15 | yPosition: string = "40%" 16 | 17 | build() { 18 | ClickEffectLayout({ click: this.retryCallback }) { 19 | Stack() { 20 | Column() { 21 | Image($r('app.media.ic_empty')).width(60).height(60) 22 | Text(this.tip) 23 | .fontSize(SizeConstant.TEXT_M) 24 | .fontColor(AppTheme.palette(this.themeType).secondText) 25 | .fontWeight(FontWeight.Normal) 26 | .margin(SizeConstant.SPACE_M) 27 | } 28 | .position({ y: this.yPosition }).width("100%").alignItems(HorizontalAlign.Center) 29 | }.width('100%') 30 | .height('100%') 31 | } 32 | 33 | } 34 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/cpn/viewstate/ViewStateError.ets: -------------------------------------------------------------------------------- 1 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 2 | import { ClickEffectLayout } from '..' 3 | import { SizeConstant } from '../..' 4 | 5 | /** 6 | * 错误状态布局组件 7 | */ 8 | @Preview 9 | @Component 10 | export struct ViewStateError { 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | private errorCode?: number 13 | private errorMessage?: string 14 | yPosition: string = "40%" 15 | retryCallback: () => void = () => { 16 | } 17 | 18 | getErrorTip() { 19 | if (this.errorCode == 2300006) { 20 | return "网络错误,点击重试" 21 | } else { 22 | return `${this.errorMessage ? this.errorMessage : "未知错误"},点击重试` 23 | } 24 | } 25 | 26 | build() { 27 | ClickEffectLayout({click: this.retryCallback}) { 28 | Stack() { 29 | Column() { 30 | Image($r('app.media.ic_error')).width(92).height(60) 31 | Text(this.getErrorTip()) 32 | .fontSize(SizeConstant.TEXT_M) 33 | .fontColor(AppTheme.palette(this.themeType).secondText) 34 | .fontWeight(FontWeight.Normal) 35 | .margin(SizeConstant.SPACE_M) 36 | } 37 | .position({ y: this.yPosition }).width("100%").alignItems(HorizontalAlign.Center) 38 | 39 | } 40 | .width('100%') 41 | .height('100%') 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/cpn/viewstate/ViewStateLayout.ets: -------------------------------------------------------------------------------- 1 | import { ViewStateEmpty } from './ViewStateEmpty' 2 | import { ViewStateError } from './ViewStateError' 3 | import { ViewStateLoading } from './ViewStateLoading' 4 | 5 | /** 6 | * 页面状态切换组件 7 | */ 8 | @Component 9 | export struct ViewStateLayout { 10 | @State viewState: ViewState = new ViewState(ViewStateType.LOADING) 11 | // 数据加载回调 12 | onLoadData: (viewState: ViewState) => void = () => {} 13 | 14 | // 重新加载 15 | onRetry() { 16 | this.onLoadData(this.viewState) 17 | } 18 | 19 | @BuilderParam 20 | ContentBuilder: () => void = () => { 21 | } 22 | 23 | aboutToAppear() { 24 | this.onLoadData(this.viewState) 25 | } 26 | 27 | build() { 28 | Stack() { 29 | if (this.viewState.type === ViewStateType.LOADING) { 30 | ViewStateLoading() 31 | } else if (this.viewState.type === ViewStateType.EMPTY) { 32 | ViewStateEmpty({ retryCallback: () => { 33 | this.onRetry() 34 | } }) 35 | } else if (this.viewState.type === ViewStateType.ERROR) { 36 | ViewStateError({ 37 | errorCode: this.viewState.errorCode, 38 | errorMessage: this.viewState.errorMessage, 39 | retryCallback: () => { 40 | this.onRetry() 41 | } 42 | }) 43 | } else { 44 | this.ContentBuilder() 45 | } 46 | }.width("100%").height("100%") 47 | } 48 | } 49 | 50 | @Observed 51 | export class ViewState { 52 | public errorCode: number = 0 53 | public errorMessage: string = "" 54 | public type: ViewStateType 55 | 56 | constructor(type: ViewStateType) { 57 | this.type = type 58 | } 59 | } 60 | 61 | export enum ViewStateType { 62 | LOADING = 0, 63 | SUCCESS = 1, 64 | EMPTY = 2, 65 | ERROR = 3 66 | } 67 | 68 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/cpn/viewstate/ViewStateLoading.ets: -------------------------------------------------------------------------------- 1 | import { AppTheme, ThemeType, THEME_TYPE } from 'lib_theme' 2 | import { CpnLoading } from '..' 3 | import { SizeConstant } from '../..' 4 | 5 | /** 6 | * 加载状态布局组件 7 | */ 8 | @Preview 9 | @Component 10 | export struct ViewStateLoading { 11 | @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT 12 | private tip: string = "" 13 | flexAlign: FlexAlign = FlexAlign.Center 14 | yPosition: string = "40%" 15 | 16 | build() { 17 | Stack() { 18 | Column() { 19 | CpnLoading() 20 | Text(this.tip) 21 | .fontSize(SizeConstant.TEXT_M) 22 | .fontColor(AppTheme.palette(this.themeType).secondText) 23 | .fontWeight(FontWeight.Normal) 24 | .margin(SizeConstant.SPACE_M) 25 | }.position({ y: this.yPosition }).width("100%").alignItems(HorizontalAlign.Center) 26 | }.width('100%') 27 | .height('100%') 28 | } 29 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/cpn/viewstate/index.ets: -------------------------------------------------------------------------------- 1 | export * from './ViewStateEmpty' 2 | 3 | export * from './ViewStateError' 4 | 5 | export * from './ViewStateLoading' 6 | 7 | export * from './ViewStateLayout' 8 | 9 | export * from './ViewStatePagingLayout' 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/index.ets: -------------------------------------------------------------------------------- 1 | export * from './constant' 2 | 3 | export * from './cpn' 4 | 5 | export * from './util' 6 | 7 | export * from './bean' 8 | 9 | export * from './viewmodel' 10 | 11 | export * from './api' 12 | 13 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/util/ArrayUtil.ets: -------------------------------------------------------------------------------- 1 | const numberArrayCache: Array> = [] 2 | 3 | export class ArrayUtil { 4 | static generateNumberArray(count: number): Array { 5 | if (numberArrayCache[count]) { 6 | return numberArrayCache[count] 7 | } else { 8 | const numberArray: Array = [] 9 | for (let index = 0; index < count; index++) { 10 | numberArray[index] = index 11 | } 12 | numberArrayCache[count] = numberArray 13 | return numberArrayCache[count] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/util/KVUtil.ets: -------------------------------------------------------------------------------- 1 | import dataPreferences from '@ohos.data.preferences'; 2 | import { LogUtil } from './LogUtil'; 3 | 4 | let preference: dataPreferences.Preferences; 5 | 6 | /** 7 | * 键值对存储工具类 8 | */ 9 | export class KVUtil { 10 | static async getInstance() { 11 | try { 12 | preference = await dataPreferences.getPreferences(getContext(), "NCMusicHarmony"); 13 | } catch (err) { 14 | LogUtil.error(`Failed to get preferences, Cause: ${err}`); 15 | } 16 | } 17 | 18 | static async putString(key: string, value: string) { 19 | try { 20 | if (!preference) { 21 | await this.getInstance(); 22 | } 23 | try { 24 | await preference.put(key, value); 25 | } catch (err) { 26 | LogUtil.error(`Failed to put value, Cause: ${err}`); 27 | } 28 | await preference.flush(); 29 | } catch (err) { 30 | LogUtil.error(`Failed to get preferences, Cause: ${err}`); 31 | } 32 | } 33 | 34 | static async getString(key: string) { 35 | let value: string = null; 36 | if (!preference) { 37 | await this.getInstance(); 38 | } 39 | try { 40 | value = (await preference.get(key, '')).toString(); 41 | } catch (err) { 42 | LogUtil.error(`Failed to get value, Cause: ${err}`); 43 | } 44 | return value; 45 | } 46 | 47 | static async get(key: string) { 48 | let value: T = null; 49 | if (!preference) { 50 | await this.getInstance(); 51 | } 52 | try { 53 | const json = await this.getString(key) 54 | if (json) { 55 | // value = plainToClass(Object, JSON.parse(json)) as T; 56 | value = JSON.parse(json) as T 57 | } 58 | } catch (err) { 59 | LogUtil.error(`Failed to get value, Cause: ${err}`); 60 | } 61 | return value; 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/util/LogUtil.ets: -------------------------------------------------------------------------------- 1 | import hilog from '@ohos.hilog'; 2 | 3 | /** 4 | * 日志工具类 5 | */ 6 | export class LogUtil { 7 | private static domain: number = 0xFF00; 8 | private static prefix: string = '[NCMusicLog]'; 9 | private static format: string = '%{public}s'; 10 | 11 | static debug(...args: string[]) { 12 | hilog.debug(this.domain, this.prefix, this.format, args); 13 | } 14 | 15 | static info(...args: string[]) { 16 | hilog.info(this.domain, this.prefix, this.format, args); 17 | } 18 | 19 | static warn(...args: string[]) { 20 | hilog.warn(this.domain, this.prefix, this.format, args); 21 | } 22 | 23 | static error(...args: string[]) { 24 | hilog.error(this.domain, this.prefix, this.format, args); 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/util/ScreenUtils.ets: -------------------------------------------------------------------------------- 1 | export class ScreenUtils { 2 | static avoidArea 3 | 4 | static init(avoidArea) { 5 | ScreenUtils.avoidArea = avoidArea 6 | } 7 | 8 | 9 | static getStatusBarHeight(): number{ 10 | return px2vp(ScreenUtils.avoidArea.topRect.height) 11 | } 12 | 13 | 14 | static getNavigationBarHeight(): number{ 15 | return ScreenUtils.avoidArea.bottomRect.height 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/util/StringUtil.ets: -------------------------------------------------------------------------------- 1 | export class StringUtil { 2 | static friendlyNumber(number: number, fixed: number = 1): string { 3 | if (number >= 100000000) { 4 | return (number / 100000000).toFixed(fixed) + "亿" 5 | } 6 | if (number >= 10000) { 7 | return (number / 10000).toFixed(fixed) + "万" 8 | } 9 | return number.toString() 10 | } 11 | 12 | 13 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/util/TimeUtil.ets: -------------------------------------------------------------------------------- 1 | export class TimeUtil { 2 | 3 | static formatCurrentDate() { 4 | const currentDate: Date = new Date(); 5 | 6 | const year: number = currentDate.getFullYear(); 7 | const month: number = currentDate.getMonth() + 1; // 月份从0开始,需要加1 8 | const day: number = currentDate.getDate(); 9 | const hours: number = currentDate.getHours(); 10 | const minutes: number = currentDate.getMinutes(); 11 | const seconds: number = currentDate.getSeconds(); 12 | 13 | // 使用模板字符串拼接日期和时间 14 | const formattedDate: string = `${year}${TimeUtil.padZero(month)}${TimeUtil.padZero(day)} ${TimeUtil.padZero(hours)}:${TimeUtil.padZero(minutes)}:${TimeUtil.padZero(seconds)}`; 15 | 16 | return formattedDate; 17 | } 18 | 19 | static padZero(num: number): string { 20 | return num < 10 ? `0${num}` : `${num}`; 21 | } 22 | 23 | static formatDate(timestamp) { 24 | const date = new Date(timestamp); 25 | const year = date.getFullYear(); 26 | const month = ("0" + (date.getMonth() + 1)).slice(-2); 27 | const day = ("0" + date.getDate()).slice(-2); 28 | const hours = ("0" + date.getHours()).slice(-2); 29 | const minutes = ("0" + date.getMinutes()).slice(-2); 30 | const seconds = ("0" + date.getSeconds()).slice(-2); 31 | 32 | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; 33 | } 34 | 35 | static msToTime(ms: number) { 36 | const seconds = Math.floor((ms / 1000) % 60); 37 | const minutes = Math.floor((ms / (1000 * 60)) % 60); 38 | return (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /lib_common/src/main/ets/util/index.ets: -------------------------------------------------------------------------------- 1 | export * from './ScreenUtils' 2 | 3 | export * from './VelocityTracker' 4 | 5 | export * from './KVUtil' 6 | 7 | export * from './LogUtil' 8 | 9 | export * from './StringUtil' 10 | 11 | export * from './TimeUtil' 12 | 13 | export * from './ArrayUtil' -------------------------------------------------------------------------------- /lib_common/src/main/ets/viewmodel/BaseViewModel.ets: -------------------------------------------------------------------------------- 1 | import { BaseResultBean, RequestOptions, ApiRequest } from '..' 2 | 3 | export class BaseViewModel { 4 | async get( 5 | requestBean: RequestOptions 6 | ): Promise { 7 | return ApiRequest.get(requestBean) 8 | } 9 | 10 | 11 | async post( 12 | requestBean: RequestOptions 13 | ): Promise { 14 | return ApiRequest.post(requestBean) 15 | } 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib_common/src/main/ets/viewmodel/index.ets: -------------------------------------------------------------------------------- 1 | export * from './BaseViewModel' 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib_common/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "lib_common", 4 | "type": "har", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib_common/src/main/resources/base/media/ic_arrow_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /lib_common/src/main/resources/base/media/ic_back.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | 20 | -------------------------------------------------------------------------------- /lib_common/src/main/resources/base/media/ic_default_place_holder.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | 20 | -------------------------------------------------------------------------------- /lib_common/src/main/resources/base/media/ic_empty.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 16 | -------------------------------------------------------------------------------- /lib_theme/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /lib_theme/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | }, 5 | "targets": [ 6 | { 7 | "name": "default", 8 | "runtimeOS": "HarmonyOS" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /lib_theme/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | // Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. 2 | export { harTasks } from '@ohos/hvigor-ohos-plugin'; -------------------------------------------------------------------------------- /lib_theme/index.ets: -------------------------------------------------------------------------------- 1 | export * from './src/main/ets/' 2 | -------------------------------------------------------------------------------- /lib_theme/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "license": "Apache-2.0", 3 | "devDependencies": {}, 4 | "author": "", 5 | "name": "lib_theme", 6 | "description": "Please describe the basic information.", 7 | "main": "index.ets", 8 | "version": "1.0.0", 9 | "dependencies": {} 10 | } 11 | -------------------------------------------------------------------------------- /lib_theme/src/main/ets/AppTheme.ets: -------------------------------------------------------------------------------- 1 | import { DarkThemePalette, DefaultThemePalette, IThemePalette, OriginThemePalette, GreenThemePalette, ThemeType } from '.' 2 | 3 | // 默认主题 4 | export const defaultThemePalette = new DefaultThemePalette() 5 | // 黑色主题 6 | export const darkThemePalette = new DarkThemePalette() 7 | // 橙色主题 8 | export const originThemePalette = new OriginThemePalette() 9 | // 绿色主题 10 | export const greenThemePalette = new GreenThemePalette() 11 | 12 | export class AppTheme { 13 | /** 14 | * 获取主题取色盘 15 | */ 16 | static palette(themeType: ThemeType): IThemePalette { 17 | if (themeType == ThemeType.DEFAULT) { 18 | return defaultThemePalette 19 | } else if (themeType == ThemeType.DARK) { 20 | return darkThemePalette 21 | } else if (themeType == ThemeType.ORIGIN) { 22 | return originThemePalette 23 | } else if (themeType == ThemeType.GREEN) { 24 | return greenThemePalette 25 | } else { 26 | return defaultThemePalette 27 | } 28 | } 29 | } 30 | 31 | // 主题类型 32 | export const THEME_TYPE = "THEME_TYPE" 33 | 34 | -------------------------------------------------------------------------------- /lib_theme/src/main/ets/ThemeType.ets: -------------------------------------------------------------------------------- 1 | export enum ThemeType { 2 | DEFAULT, 3 | DARK, 4 | ORIGIN, 5 | GREEN 6 | } -------------------------------------------------------------------------------- /lib_theme/src/main/ets/index.ets: -------------------------------------------------------------------------------- 1 | export * from './palette' 2 | export * from './AppTheme' 3 | export * from './ThemeType' 4 | -------------------------------------------------------------------------------- /lib_theme/src/main/ets/palette/DarkThemePalette.ets: -------------------------------------------------------------------------------- 1 | import { IThemePalette } from '.' 2 | 3 | /** 4 | * 黑色主题取色盘 5 | */ 6 | export class DarkThemePalette implements IThemePalette { 7 | primary: ResourceColor = "#FFF0484E" 8 | secondary: ResourceColor = "#FFF0888C" 9 | pure: ResourceColor = "#FF000000" 10 | divider: ResourceColor = "#FF555555" 11 | commonBackground: ResourceColor = "#FF222222" 12 | deepenBackground: ResourceColor = "#FF444444" 13 | titleBackground: ResourceColor = "#FF333333" 14 | navBarBackground: ResourceColor = "#FF333333" 15 | drawerBackground: ResourceColor = "#FF333333" 16 | firstText: ResourceColor = "#FFFFFFFF" 17 | secondText: ResourceColor = "#FFBBBBBB" 18 | thirdText: ResourceColor = "#FF999999" 19 | firstIcon: ResourceColor = "#FFFFFFFF" 20 | secondIcon: ResourceColor = "#FFBBBBBB" 21 | thirdIcon: ResourceColor = "#FF999999" 22 | } -------------------------------------------------------------------------------- /lib_theme/src/main/ets/palette/DefaultThemePalette.ets: -------------------------------------------------------------------------------- 1 | 2 | import { IThemePalette } from '.' 3 | 4 | /** 5 | * 默认主题取色盘 6 | */ 7 | export class DefaultThemePalette implements IThemePalette { 8 | primary: ResourceColor = "#FFF0484E" 9 | secondary: ResourceColor = "#FFF0888C" 10 | pure: ResourceColor = "#FFFFFFFF" 11 | divider: ResourceColor = "#FFDDDDDD" 12 | commonBackground: ResourceColor = "#FFFFFFFF" 13 | deepenBackground: ResourceColor = "#FFEEEEEE" 14 | titleBackground: ResourceColor = "#FFFAFAFA" 15 | navBarBackground: ResourceColor = "#FFFAFAFA" 16 | drawerBackground: ResourceColor = "#FFFAFAFA" 17 | firstText: ResourceColor = "#FF333333" 18 | secondText: ResourceColor = "#FF666666" 19 | thirdText: ResourceColor = "#FF999999" 20 | firstIcon: ResourceColor = "#FF333333" 21 | secondIcon: ResourceColor = "#FF666666" 22 | thirdIcon: ResourceColor = "#FF999999" 23 | } -------------------------------------------------------------------------------- /lib_theme/src/main/ets/palette/GreenThemePalette.ets: -------------------------------------------------------------------------------- 1 | 2 | import { IThemePalette } from '.' 3 | 4 | /** 5 | * 默认主题取色盘 6 | */ 7 | export class GreenThemePalette implements IThemePalette { 8 | primary: ResourceColor = "#FF3EC73E" 9 | secondary: ResourceColor = "#FF9FE69F" 10 | pure: ResourceColor = "#FFFFFFFF" 11 | divider: ResourceColor = "#FFDDDDDD" 12 | commonBackground: ResourceColor = "#FFFFFFFF" 13 | deepenBackground: ResourceColor = "#FFEEEEEE" 14 | titleBackground: ResourceColor = "#FFFAFAFA" 15 | navBarBackground: ResourceColor = "#FFFAFAFA" 16 | drawerBackground: ResourceColor = "#FFFAFAFA" 17 | firstText: ResourceColor = "#FF333333" 18 | secondText: ResourceColor = "#FF666666" 19 | thirdText: ResourceColor = "#FF999999" 20 | firstIcon: ResourceColor = "#FF333333" 21 | secondIcon: ResourceColor = "#FF666666" 22 | thirdIcon: ResourceColor = "#FF999999" 23 | } -------------------------------------------------------------------------------- /lib_theme/src/main/ets/palette/IThemePalette.ets: -------------------------------------------------------------------------------- 1 | export interface IThemePalette { 2 | primary: ResourceColor 3 | secondary: ResourceColor, 4 | pure: ResourceColor, 5 | divider: ResourceColor, 6 | commonBackground: ResourceColor, 7 | deepenBackground: ResourceColor, 8 | titleBackground: ResourceColor, 9 | navBarBackground: ResourceColor, 10 | drawerBackground: ResourceColor, 11 | firstText: ResourceColor, 12 | secondText: ResourceColor, 13 | thirdText: ResourceColor, 14 | firstIcon: ResourceColor, 15 | secondIcon: ResourceColor, 16 | thirdIcon: ResourceColor, 17 | } 18 | -------------------------------------------------------------------------------- /lib_theme/src/main/ets/palette/OriginThemePalette.ets: -------------------------------------------------------------------------------- 1 | 2 | import { IThemePalette } from '.' 3 | 4 | /** 5 | * 默认主题取色盘 6 | */ 7 | export class OriginThemePalette implements IThemePalette { 8 | primary: ResourceColor = "#FFFF6633" 9 | secondary: ResourceColor = "#FFF3906F" 10 | pure: ResourceColor = "#FFFFFFFF" 11 | divider: ResourceColor = "#FFDDDDDD" 12 | commonBackground: ResourceColor = "#FFFFFFFF" 13 | deepenBackground: ResourceColor = "#FFEEEEEE" 14 | titleBackground: ResourceColor = "#FFFAFAFA" 15 | navBarBackground: ResourceColor = "#FFFAFAFA" 16 | drawerBackground: ResourceColor = "#FFFAFAFA" 17 | firstText: ResourceColor = "#FF333333" 18 | secondText: ResourceColor = "#FF666666" 19 | thirdText: ResourceColor = "#FF999999" 20 | firstIcon: ResourceColor = "#FF333333" 21 | secondIcon: ResourceColor = "#FF666666" 22 | thirdIcon: ResourceColor = "#FF999999" 23 | } -------------------------------------------------------------------------------- /lib_theme/src/main/ets/palette/index.ets: -------------------------------------------------------------------------------- 1 | export * from './IThemePalette' 2 | export * from './DefaultThemePalette' 3 | export * from './DarkThemePalette' 4 | export * from './GreenThemePalette' 5 | export * from './OriginThemePalette' 6 | -------------------------------------------------------------------------------- /lib_theme/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "lib_theme", 4 | "type": "har", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 4 | "specifiers": { 5 | "@ohos/hypium@1.0.6": "@ohos/hypium@1.0.6" 6 | }, 7 | "packages": { 8 | "@ohos/hypium@1.0.6": { 9 | "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.6.tgz", 10 | "integrity": "sha512-bb3DWeWhYrFqj9mPFV3yZQpkm36kbcK+YYaeY9g292QKSjOdmhEIQR2ULPvyMsgSR4usOBf5nnYrDmaCCXirgQ==" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "license": "", 3 | "devDependencies": { 4 | "@ohos/hypium": "1.0.6" 5 | }, 6 | "author": "", 7 | "name": "ncmusicharmony", 8 | "description": "Please describe the basic information.", 9 | "main": "", 10 | "version": "1.0.0", 11 | "dependencies": {} 12 | } 13 | -------------------------------------------------------------------------------- /screenshot/CollapsibleLayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/screenshot/CollapsibleLayout.png -------------------------------------------------------------------------------- /screenshot/project_structure_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/screenshot/project_structure_1.jpg -------------------------------------------------------------------------------- /screenshot/project_structure_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/screenshot/project_structure_2.jpg -------------------------------------------------------------------------------- /screenshot/主题切换.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/screenshot/主题切换.gif -------------------------------------------------------------------------------- /screenshot/广场.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/screenshot/广场.gif -------------------------------------------------------------------------------- /screenshot/音乐播放.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/screenshot/音乐播放.gif -------------------------------------------------------------------------------- /screenshot/首页.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskEvan/NCMusicHarmony/b3877616bd2e5ec45bd3c63c8ac40c3b3e5033a9/screenshot/首页.gif --------------------------------------------------------------------------------