NewIndex.ets 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276
  1. import { AppUtil, DeviceUtil, DisplayUtil, LogUtil, PreferencesUtil, ToastUtil } from '@pura/harmony-utils';
  2. import TitleBar from '../view/TitleBar';
  3. import { curves, LengthMetrics, router, SegmentButton, SegmentButtonOptions, SymbolGlyphModifier } from '@kit.ArkUI';
  4. import mainViewModel, { MainViewModel } from '../viewmodel/MainViewModel';
  5. import ItemData from '../viewmodel/ItemData';
  6. import type { sysResource } from '../viewmodel/ItemData';
  7. import { CommonConstants } from '../common/constants/CommonConstants';
  8. import { EventConstants } from '../common/constants/EventConstants';
  9. import { VideoItem } from '../viewmodel/VideoItem';
  10. import ScreenUtil from '../common/util/ScreenUtil';
  11. import { Utility } from '../common/util/Utility';
  12. import { BusinessError, emitter } from '@kit.BasicServicesKit';
  13. import { common } from '@kit.AbilityKit';
  14. // import { AdSlot, AdSlotBuilder,
  15. // CSJAdCreator,
  16. // CSJAdSdk, CSJNativeExpressAd, NativeExpressAdListener } from '@csj/openadsdk';
  17. import { PrintBiddingTokenUtils } from '../common/util/PrintBiddintTokenUtils';
  18. import { ArrayList } from '@kit.ArkTS';
  19. import { CSJUtil } from '../common/util/CSJUtil';
  20. import { StreamContent } from '../view/StreamContent';
  21. import { AvSessionController } from '../controller/AvSessionController';
  22. import { BreakpointSystem, BreakpointTypeEnum } from '../common/util/BreakpointSystem';
  23. import dataStorage from '@ohos.data.storage';
  24. import ConfigurationConstant from '@ohos.app.ability.ConfigurationConstant';
  25. import { SettingPage } from './SettingPage';
  26. import { deviceInfo } from '@kit.BasicServicesKit';
  27. import { resourceManager } from '@kit.LocalizationKit';
  28. import { systemShare } from '@kit.ShareKit';
  29. import { uniformTypeDescriptor } from '@kit.ArkData';
  30. import { hilog } from '@kit.PerformanceAnalysisKit';
  31. import { DialogHelper, DialogAction } from '@pura/harmony-dialog';
  32. import UserUtil from '../common/util/UserUtil';
  33. import json from '@ohos.util.json';
  34. import { UserCenter } from './UserCenter';
  35. import { ScanFilePage } from './ScanFilePage';
  36. import { AboutPage, gotoShare } from './AboutPage';
  37. import { smartMobilityCommon } from '@kit.CarKit';
  38. import { UpdateLogManager } from '../common/util/UpdateLogManager';
  39. import OnlineUpdateLogDialog from '../dialog/OnlineUpdateLog';
  40. import OnlineUpdateLog from '../dialog/OnlineUpdateLog';
  41. import { LocalMusic } from '../view/LocalMusic';
  42. import { ChartsCount } from './ChartsCount';
  43. import { image } from '@kit.ImageKit';
  44. import { Playlist } from '../viewmodel/Playlist';
  45. import PlaylistTable from '../common/util/PlaylistTable';
  46. import { convertPlaylistSongsToVideoItems } from './PlaylistDetailPage';
  47. // import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg';
  48. const TAG = 'NewIndex'; // 日志标签
  49. /**
  50. * 首页主组件,包含顶部标题栏、主内容区(本地音乐/网络内容)、侧边抽屉菜单等。
  51. * 支持多端适配、广告集成、导航、生命周期管理等功能。
  52. */
  53. @Preview
  54. @Entry
  55. @Component
  56. struct NewIndex {
  57. /** 页面上下文 */
  58. context = this.getUIContext().getHostContext() as common.UIAbilityContext
  59. @Provide currentSongList: Array<VideoItem> = []//当前歌单
  60. @Provide currentSongListName:string = '' //当前歌单名称
  61. @Provide currentSongListID:string='' //当前歌单ID
  62. @State isDarkMode: boolean = false
  63. /** 列表滚动器,用于抽屉菜单列表滚动 */
  64. private scroller: Scroller = new Scroller();
  65. /** 应用包名 */
  66. @State bundleName: string = ''
  67. @State appName: string = ''
  68. /** 是否显示侧边抽屉菜单 */
  69. @Provide isShowDrawer: boolean = false;
  70. /** 抽屉菜单 X 轴偏移量,用于手势滑动动画 */
  71. @Provide offsetX: number = 0;
  72. /** 本地视频列表,页面参数传入 */
  73. @Provide videoLocalList: Array<VideoItem> = []
  74. /** Y 轴偏移量(预留) */
  75. @State offsetY: number = 0;
  76. /** 主内容类型(0:本地音乐,1:网络内容) */
  77. @Provide mType: number = 0
  78. /** 模式类型(如文件夹 媒体库 艺术家、专辑等) */
  79. @Provide modeType: number = 0
  80. /** 是否为零状态(预留) */
  81. @Provide('isZero') isZero: boolean = false;
  82. /** 是否显示赞助入口 */
  83. @State isShowSponsorship: boolean = false
  84. /** 音乐是否为零状态(预留) */
  85. @Provide('isMusicZero') isMusicZero: boolean = false;
  86. @State isShowTitleBar: boolean = true //是否显示分类导航条
  87. /** 本地音乐列表,持久化存储 */
  88. @StorageLink('musicLocalList') musicLocalList: Array<VideoItem> = []
  89. @Provide isFavMusic: boolean = false
  90. @StorageProp('windowWidth') windowWidth: number = 0;
  91. @StorageProp('windowHeight') windowHeight: number = 0;
  92. @StorageProp('isHiCarStatus') isHiCarStatus: boolean = false;
  93. @StorageProp('curDisplayIsHiCar') curDisplayIsHiCar: boolean = false;
  94. @State idDefaultMediaKu: boolean = false
  95. /**
  96. * 是否显示更新日志开关
  97. */
  98. @State isShowUpdateDialog: boolean = false
  99. /** 标题栏配置模型 */
  100. @State titleBarModel: TitleBar.Model = new TitleBar.Model()
  101. .setTitleTextStyle(FontStyle.Normal)
  102. .setTitleBarStyle(TitleBar.BarStyle.TRANSPARENT)
  103. .setLeftIcon($r('app.media.menu'))
  104. .setLeftIconWidth(28)
  105. .setLeftIconHeight(28)
  106. .setTitleBarMinHeight(64)
  107. .setTitleName('首页')// .setRightIcon($r('app.media.add'))
  108. .setTitleFontSize(19)
  109. .setTitleFontColor(Color.White)
  110. .setTitleBarBackground($r('app.color.title_bar_bg'))
  111. .setLeftTitleBackground($r('app.color.title_bar_bg'))
  112. .setOnLeftClickListener(() => {
  113. // 打开菜单栏动画
  114. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  115. this.isShowDrawer = true
  116. this.offsetX = 0
  117. })
  118. })
  119. .setRightTitleBackground($r('app.color.title_bar_bg'))
  120. // .setOnRightClickListener(()=>{
  121. // // this.goSelectVideo()
  122. // this.showSheelDialog()
  123. // })
  124. /** 应用版本号 */
  125. @State versionName: string = ''
  126. /** 音频根目录路径 */
  127. @Provide rootPath: string = ''
  128. /** 当前路径 */
  129. @Provide currentPath: string = ''
  130. /** 是否为历史记录页面 */
  131. @Provide isHistory: boolean = false
  132. /** 是否可以返回上一级 */
  133. @Provide isCanBack: boolean = false
  134. @StorageProp('currentColorMode') currentMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
  135. @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
  136. /**
  137. * 一多界面适配断点系统
  138. */
  139. private breakpointSystem: BreakpointSystem = new BreakpointSystem();
  140. /** 当前断点类型(如大屏/小屏) */
  141. @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD;
  142. /** 记录上一次点击返回键的时间戳,用于双击退出 */
  143. private backTime: number = 0;
  144. @State tabOptions: SegmentButtonOptions = SegmentButtonOptions.tab({
  145. buttons: [{ text: '分类' }, { text: '歌单' }],
  146. direction: Direction.Ltr,
  147. backgroundColor: $r('app.color.start_window_background'),
  148. selectedBackgroundColor:$r('app.color.index_background'),
  149. selectedFontColor: $r('app.color.text_color'),
  150. localizedTextPadding: {
  151. end: LengthMetrics.vp(10),
  152. start: LengthMetrics.vp(10)
  153. }
  154. })
  155. @State tabSelectedIndexes: number[] = [0]
  156. // 歌单相关状态变量
  157. @State playlistList: Playlist[] = []
  158. @State isShowCreatePlaylistDialog: boolean = false
  159. @State selectedPlaylist: Playlist | null = null
  160. private playlistTable: PlaylistTable | null = null
  161. /**
  162. * 返回键处理逻辑:
  163. * - 如果不是根目录或有历史记录,发送广播通知更新列表
  164. * - 如果是根目录,双击返回键退出应用,否则提示
  165. */
  166. onBackPress(): boolean | void {
  167. if (this.currentPath !== this.rootPath || this.isHistory || this.isFavMusic ||
  168. (this.modeType !== 0 && this.isCanBack)) {
  169. console.info('onecold 返回键处理逻辑:发送音频广播通知更新列表');
  170. const eventData: emitter.EventData = {};
  171. emitter.emit({ eventId: EventConstants.EVENT_SWIPE_BACK_UPDATE }, eventData); // 发送音频广播通知更新doSwipBack
  172. }else if(this.mType > 0){
  173. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  174. // 动画闭包内控制Image组件的出现和消失
  175. // this.isShowDrawer = !this.isShowDrawer
  176. // this.offsetX = 0
  177. this.mType =0
  178. })
  179. } else {
  180. console.info('onecold 返回键处理 关闭');
  181. if (this.isShowDrawer) {
  182. // 如果抽屉菜单打开,先关闭抽屉
  183. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  184. this.isShowDrawer = false
  185. })
  186. } else {
  187. return false
  188. }
  189. }
  190. return true;
  191. }
  192. /**
  193. * 刷新@State用户信息变量,确保UI同步
  194. */
  195. refreshUserInfoState() {
  196. this.isLogin = PreferencesUtil.getBooleanSync('isLogin', false);
  197. this.userId = PreferencesUtil.getNumberSync('userId', 0);
  198. this.userName = PreferencesUtil.getStringSync('userName', '未登录用户');
  199. this.userAvatarUrl = PreferencesUtil.getStringSync('userAvatarUrl', '');
  200. this.subscriptionName = PreferencesUtil.getStringSync('subscriptionName', '');
  201. this.subscriptionEndDate = PreferencesUtil.getStringSync('subscriptionEndDate', '');
  202. this.hasActiveSubscription = PreferencesUtil.getBooleanSync('hasActiveSubscription', false);
  203. }
  204. onPageShow() {
  205. // 加载歌单列表
  206. this.loadPlaylistList()
  207. }
  208. /**
  209. * 页面显示生命周期钩子
  210. * - 注册断点系统
  211. * - 获取页面参数
  212. * - 设置状态栏样式
  213. * - 初始化图片缓存
  214. * - 判断是否显示赞助入口
  215. */
  216. async aboutToAppear() {
  217. this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true)
  218. this.idDefaultMediaKu = PreferencesUtil.getBooleanSync('idDefaultMediaKu', false)
  219. if(this.idDefaultMediaKu){
  220. this.modeType = 1
  221. }
  222. Utility.enableFullScreen()
  223. this.topRectHeight = px2vp(AppUtil.getStatusBarHeight());
  224. Utility.getAppName(getContext(this)).then((appName: string) => {
  225. this.appName = appName
  226. })
  227. this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
  228. this.breakpointSystem.register();
  229. let params = router.getParams() as Record<string, Object>;
  230. this.videoLocalList = params.videoList as VideoItem[];
  231. // Utility.setStatusBarLight()
  232. ScreenUtil.setScreenSize();
  233. this.bundleName = AppUtil.getBundleName()
  234. this.versionName = AppUtil.getVersionName();
  235. // await ImageKnife.getInstance().initFileCache(this.context, 256, 256 * 1024 * 1024)
  236. this.isShowSponsorship = Utility.isOpenTime()
  237. let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
  238. hilog.info(0x0000, 'Heanup', '当前 NewIndex themeColor:' + themeColor);
  239. AppStorage.setOrCreate('themeColor', themeColor);
  240. this.themeColor = themeColor
  241. // ====== 本地会员同步提醒逻辑 ======
  242. // this.refreshUserInfoState();
  243. // if (this.isLogin) {
  244. // void this.fetchUserInfo();
  245. // }
  246. await UserUtil.fetchUserInfo();
  247. this.refreshUserInfoState();
  248. let changeUserState: emitter.InnerEvent = { eventId: EventConstants.EVENT_USER_STATE_CHANGE }
  249. emitter.on(changeUserState, () => {
  250. this.refreshUserInfoState();
  251. });
  252. let eventSetting: emitter.InnerEvent = { eventId: EventConstants.EVENT_SETTING_UPDATE }
  253. // 监听广播事件(通用设置配置更新)
  254. emitter.on(eventSetting, (eventData: emitter.EventData) => {
  255. this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true)
  256. });
  257. console.log('getHiCarStatus isHiCarStatus:' + this.isHiCarStatus)
  258. if(this.isHiCarStatus||this.isBigScreen()){
  259. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  260. this.isShowDrawer = true
  261. this.offsetX = 0
  262. })
  263. this.isShowTitleBar = false
  264. }
  265. // 检查并显示更新日志
  266. this.checkAndShowUpdateLog();
  267. // 初始化歌单数据库
  268. await this.initPlaylistTable();
  269. // 监听歌单刷新事件
  270. emitter.on({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH }, (eventData: emitter.EventData) => {
  271. this.loadPlaylistList()
  272. });
  273. }
  274. /**
  275. * 检查并显示更新日志
  276. */
  277. private async checkAndShowUpdateLog() {
  278. try {
  279. const shouldShow = await UpdateLogManager.checkAndShowUpdateLog();
  280. if (shouldShow) {
  281. this.isShowUpdateDialog = !this.isShowUpdateDialog
  282. UpdateLogManager.markCurrentVersionShown()
  283. }
  284. // this.isShowUpdateDialog = true;//调试期间显示更新日志,测试完成后请删除
  285. } catch (error) {
  286. console.error('NewIndex checkAndShowUpdateLog error:', error);
  287. }
  288. }
  289. /**
  290. * 页面消失生命周期钩子
  291. * 注销断点系统
  292. */
  293. aboutToDisappear() {
  294. console.info('NewIndex aboutToDisappear');
  295. this.breakpointSystem.unregister();
  296. emitter.off(EventConstants.EVENT_SWIPE_BACK_UPDATE);
  297. emitter.off(EventConstants.EVENT_USER_STATE_CHANGE);
  298. // 监听歌单刷新事件
  299. emitter.off(EventConstants.EVENT_PLAYLIST_REFRESH);
  300. }
  301. build() {
  302. SideBarContainer(SideBarContainerType.AUTO) {
  303. Column() {
  304. this.getLeftView()
  305. }
  306. .backgroundColor(Color.Transparent)
  307. Column() {
  308. Stack() {
  309. // 本地音乐内容区
  310. LocalMusic()
  311. .visibility(this.mType === 0 ? Visibility.Visible : Visibility.None)
  312. // 网络内容区
  313. UserCenter()
  314. .visibility(this.mType === 1 ? Visibility.Visible : Visibility.None)
  315. ScanFilePage()
  316. .visibility(this.mType === 2 ? Visibility.Visible : Visibility.None)
  317. ChartsCount({
  318. isShowDrawer:this.isShowDrawer,
  319. offsetX:this.offsetX
  320. })
  321. .visibility(this.mType === 3 ? Visibility.Visible : Visibility.None)
  322. SettingPage()
  323. .visibility(this.mType === 4 ? Visibility.Visible : Visibility.None)
  324. AboutPage()
  325. .visibility(this.mType === 5 ? Visibility.Visible : Visibility.None)
  326. // 抽屉打开时的遮罩层,用于拦截点击事件 这个只能正常尺寸的手机竖屏的才能生效
  327. if (this.isShowDrawer&&this.isPhonePortrait()) {
  328. Column()
  329. .width('100%')
  330. .height('100%')
  331. .backgroundBlurStyle(BlurStyle.BACKGROUND_THIN)
  332. .transition(TransitionEffect.OPACITY.animation({
  333. curve: curves.springMotion(0, 1)
  334. }))
  335. .onClick(() => {
  336. // 点击遮罩层关闭抽屉
  337. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  338. this.isShowDrawer = false
  339. })
  340. })
  341. }
  342. }
  343. .bindSheet($$this.isShowUpdateDialog, this.updateSheet(), {
  344. height: '90%',
  345. dragBar: true,
  346. preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM,
  347. showClose: true,
  348. blurStyle: BlurStyle.Regular,
  349. title: { title: '更新日志' }
  350. })
  351. }
  352. .gesture(SwipeGesture({ direction: SwipeDirection.Horizontal }).onAction((event: GestureEvent) => {
  353. if (event) {//手势返回
  354. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  355. this.isShowDrawer = !this.isShowDrawer
  356. if(this.isShowDrawer)
  357. this.offsetX = 0
  358. })
  359. }
  360. }))
  361. }
  362. .showControlButton(false)
  363. .minContentWidth(0)
  364. .sideBarWidth(260)
  365. .autoHide(true)
  366. .showSideBar($$this.isShowDrawer)
  367. .onChange((value: boolean) => {
  368. this.isShowDrawer = value
  369. })
  370. }
  371. //是不是hicar的屏幕分辨率
  372. isHiCar() {
  373. return this.isHiCarStatus&&this.curDisplayIsHiCar;
  374. return false;
  375. }
  376. //是不是正常的手机竖屏
  377. isPhonePortrait() {
  378. LogUtil.info('twocold this.currentHeightBreakpoint = '+this.currentHeightBreakpoint)
  379. LogUtil.info('twocold this.currentWidthBreakpoint = '+this.currentWidthBreakpoint)
  380. if(DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE
  381. &&this.currentHeightBreakpoint == HeightBreakpoint.HEIGHT_LG
  382. &&this.currentWidthBreakpoint==WidthBreakpoint.WIDTH_SM){//如果是手机横屏,返回false
  383. return true
  384. }
  385. return false
  386. }
  387. /**
  388. * 判断是否横屏
  389. * @returns
  390. */
  391. isBigScreen() {
  392. if (this.currentWidthBreakpoint == WidthBreakpoint.WIDTH_LG) {
  393. return true
  394. }
  395. if (DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE
  396. && this.currentWidthBreakpoint === WidthBreakpoint.WIDTH_SM
  397. && this.currentHeightBreakpoint == HeightBreakpoint.HEIGHT_LG) {
  398. return false
  399. } else {
  400. return true
  401. }
  402. return false
  403. }
  404. @Builder
  405. getLeftView() {
  406. Column() {
  407. Column() {
  408. this.getDrawerView()
  409. }
  410. .width('100%')
  411. .height('100%')
  412. .onClick((event: ClickEvent) => {
  413. // 点击抽屉内部不关闭,通过返回值阻止事件传播
  414. return true
  415. })
  416. }
  417. // .width(this.currentBreakpoint !== BreakpointTypeEnum.SM?'36%':'70%')
  418. .height('100%')
  419. .borderRadius({
  420. topLeft: 0,
  421. topRight: 20,
  422. bottomLeft: 0,
  423. bottomRight: 20
  424. })
  425. // .backgroundColor($r('app.color.silvery'))
  426. // .backgroundColor($r('app.color.index_background'))
  427. .backgroundColor($r('app.color.user_center_card_background'))
  428. .alignItems(HorizontalAlign.Start)
  429. .translate({ x: this.offsetX, y: 0, z: 0 })
  430. .transition({ type: TransitionType.Insert, translate: { x: -DisplayUtil.getWidth(), y: 0 } })
  431. .transition({ type: TransitionType.Delete, translate: { x: -DisplayUtil.getWidth(), y: 0 } })
  432. .gesture(
  433. PanGesture()
  434. .onActionUpdate((event: GestureEvent) => {
  435. // 手势滑动更新抽屉偏移
  436. if (event.offsetX < 0) {
  437. this.offsetX = event.offsetX;
  438. }
  439. })
  440. .onActionEnd(() => {
  441. // 手势结束判断是否关闭抽屉
  442. if (this.offsetX < -DisplayUtil.getWidth() / 15) {
  443. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  444. this.isShowDrawer = false
  445. })
  446. } else {
  447. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  448. this.isShowDrawer = true
  449. this.offsetX = 0
  450. })
  451. }
  452. })
  453. )
  454. }
  455. @State isLogin: boolean = false;
  456. @State userAvatar: Resource = $r('app.media.icon_person2');
  457. @State userAvatarUrl: string = '';
  458. @State isVip: boolean = false;
  459. @State userName: string = '未登录用户';
  460. @State userId: number = 0;
  461. @State hasActiveSubscription: boolean = false;
  462. @State subscriptionName: string = '';
  463. @State subscriptionEndDate: string = '';
  464. // 用户信息卡片
  465. @Builder
  466. buildUserInfoCard() {
  467. Column() {
  468. this.userInfoView()
  469. Column({ space: 20 }) {
  470. SegmentButton({ options: this.tabOptions, selectedIndexes: $tabSelectedIndexes })
  471. }
  472. .padding({ bottom:10 })
  473. .width('80%')
  474. }
  475. }
  476. @Builder
  477. userInfoView() {
  478. Column() {
  479. Row() {
  480. Stack({ alignContent: Alignment.BottomEnd }){
  481. Image(this.userAvatarUrl && this.userAvatarUrl.length > 0 ? this.userAvatarUrl : this.userAvatar)
  482. .width(55)
  483. .height(55)
  484. .margin({ left: 12 })
  485. .borderRadius('50%')
  486. .clip(true)
  487. // .fillColor(this.themeColor)
  488. // .backgroundColor(this.isDarkMode ? Color.Black : Color.White)
  489. Column() {
  490. Text('VIP')
  491. .fontSize(9)
  492. .padding(2)
  493. .textAlign(TextAlign.Center)
  494. .fontWeight(FontWeight.Bolder)
  495. .fontColor(Color.White)
  496. }
  497. .width(28)
  498. .height(16)
  499. .visibility(Utility.isNoble()?Visibility.Visible:Visibility.None)
  500. .borderRadius(15)
  501. .backgroundColor(this.themeColor)
  502. }
  503. Column() {
  504. Row() {
  505. Text(this.isLogin ? this.userName : '未登录用户')
  506. .fontSize(14)
  507. // .fontWeight(FontWeight.Bold)
  508. // .fontColor(this.isDarkMode ? Color.White : Color.Black)
  509. .textAlign(TextAlign.Start)
  510. .maxLines(1)
  511. .width(110)
  512. .margin({ right: 12 })
  513. }
  514. .justifyContent(FlexAlign.Start)
  515. .margin({ top: 2 })
  516. if (this.isLogin) {
  517. if (this.hasActiveSubscription) {
  518. // Text(this.subscriptionName)
  519. // .fontSize(13)
  520. // .fontColor(themeColorWithAlpha(this.themeColor, 0.8, false))
  521. // .textAlign(TextAlign.Start)
  522. // .padding({top:5})
  523. this.TimeTextBuilder()
  524. } else {
  525. Text('普通用户')
  526. .fontSize(13)
  527. .fontColor(this.isDarkMode ? Color.White : Color.Grey)
  528. .textAlign(TextAlign.Start)
  529. .padding({top:5})
  530. this.TimeTextBuilder()
  531. }
  532. } else {
  533. this.TimeTextBuilder()
  534. }
  535. }
  536. .alignItems(HorizontalAlign.Start)
  537. .width(80)
  538. .margin({ left: 10 })
  539. }
  540. .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7})
  541. .onClick(() => {
  542. // if (!this.isLogin) {
  543. this.mType = 1
  544. if (!this.isBigScreen()) {
  545. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  546. this.isShowDrawer = false
  547. })
  548. // }
  549. }
  550. })
  551. .width('100%')
  552. .padding(10)
  553. }
  554. .backgroundColor($r('app.color.user_center_card_background'))
  555. .borderRadius(24)
  556. .width('86%')
  557. .padding(8)
  558. .margin({ left: 10, right: 10, top: this.topRectHeight + 10,bottom:10 })
  559. .shadow({
  560. radius: 8,
  561. color: 0x11000000,
  562. offsetX: 0,
  563. offsetY: 2
  564. })
  565. }
  566. @Builder
  567. TimeTextBuilder() {
  568. Text(Utility.getTimePeriod())
  569. .fontSize(13)
  570. .margin({ top: 5 })
  571. .fontColor($r('app.color.text_color'))
  572. .align(Alignment.Start)
  573. }
  574. /**
  575. * 动态生成抽屉菜单内容
  576. * 根据 isShowSponsorship 展示不同菜单项
  577. */
  578. @Builder
  579. getDrawerView() {
  580. List({ space: 0, scroller: this.scroller }) {
  581. ListItemGroup({ header: this.buildUserInfoCard() }) {
  582. if(this.tabSelectedIndexes[0]==0){//分类
  583. this.buildTabCate()
  584. }else{//歌单
  585. this.buildPlaylistTab()
  586. }
  587. }
  588. }
  589. .scrollBar(BarState.Off)
  590. .width('86%')
  591. .borderRadius(20)
  592. .margin({
  593. bottom: 20,
  594. left: 10,
  595. right: 10,
  596. top: 10
  597. })
  598. .alignListItem(ListItemAlign.Center)
  599. .layoutWeight(1)
  600. .backgroundColor($r('app.color.left_draw_bg'))
  601. .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果
  602. }
  603. @Builder
  604. buildTabCate() {
  605. ForEach(this.isHiCar()||!this.isShowTitleBar?mainViewModel.getDrawerData():mainViewModel.getDrawerData2(), (item: ItemData, index: number) => {
  606. ListItem() {
  607. Button({ type: ButtonType.Capsule, stateEffect: true }) {
  608. Row() {
  609. // 菜单图标
  610. if (typeof item.img === 'object' && item.img !== null && (item.img as sysResource).type === 'symbol') {
  611. SymbolGlyph((item.img as sysResource).value as Resource)// .size({ width: 22, height: 22 })
  612. .fontSize(22)
  613. .fontColor([this.themeColor])
  614. .effectStrategy(SymbolEffectStrategy.HIERARCHICAL)
  615. .alignSelf(ItemAlign.Center)
  616. .margin({ left: 25 })
  617. } else {
  618. Image(item.img as Resource)
  619. .height(22)
  620. .alignSelf(ItemAlign.Center)
  621. .fillColor(this.themeColor)
  622. .margin({ left: 25 })
  623. }
  624. // 菜单标题
  625. Text(item.title)
  626. .margin({ left: 10, right: 20 })
  627. .fontSize(15)
  628. .fontColor(this.isTextSelected(index) ? this.themeColor : $r('app.color.index_tab_font_color'))
  629. .fontWeight(480)
  630. Blank()
  631. // 右侧箭头
  632. Image($r('app.media.arrow_right'))
  633. .width(22)
  634. .height(22)
  635. .margin({ left: 20, right: 0 })
  636. .align(Alignment.Center)
  637. }
  638. .width('100%')
  639. .height(55)
  640. }
  641. .backgroundColor(Color.Transparent)
  642. .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
  643. .onClick(async () => {
  644. // 根据 item.id 跳转或切换功能
  645. switch (item.id) {
  646. case MainViewModel.MENU_MUSIC:
  647. this.mType = 0
  648. this.modeType = 0
  649. this.doShowDrawer()
  650. break
  651. case MainViewModel.MENU_FILE_SCAN:
  652. this.mType = 2
  653. this.doShowDrawer()
  654. break
  655. case MainViewModel.MENU_CHARTS:
  656. this.mType = 3
  657. this.modeType = 0
  658. this.doShowDrawer()
  659. break
  660. case MainViewModel.MENU_MIEDIA_KU:
  661. this.modeType = 1
  662. this.mType = 0
  663. this.doShowDrawer()
  664. break
  665. case MainViewModel.MENU_MIEDIA_ARTIST:
  666. this.modeType = 2
  667. this.mType = 0
  668. this.doShowDrawer()
  669. break
  670. case MainViewModel.MENU_MIEDIA_ALBUM:
  671. this.modeType = 3
  672. this.mType = 0
  673. this.doShowDrawer()
  674. break
  675. case MainViewModel.MENU_SETTING:
  676. this.mType = 4
  677. this.doShowDrawer()
  678. break
  679. case MainViewModel.MENU_VIP:
  680. router.pushUrl({
  681. url: 'pages/VipPage'
  682. }, router.RouterMode.Single);
  683. break
  684. case MainViewModel.MENU_USER:
  685. this.mType = 1
  686. this.doShowDrawer()
  687. break
  688. case MainViewModel.MENU_NET_CONNECT:
  689. this.mType = 1
  690. this.doShowDrawer()
  691. break
  692. case MainViewModel.MENU_SIMI:
  693. router.pushUrl({
  694. url: 'pages/VerifyPage'
  695. });
  696. break
  697. case MainViewModel.MENU_DUTY:
  698. router.pushUrl({
  699. url: 'pages/WebIndex',
  700. params: { titleName: '用户协议', webUrl: CommonConstants.NEW_DUTY }
  701. });
  702. break
  703. case MainViewModel.MENU_HAOPING:
  704. Utility.gotoMarket(getContext(this) as common.UIAbilityContext, this.bundleName)
  705. break
  706. case MainViewModel.MENU_ABOUT:
  707. this.mType = 5
  708. this.doShowDrawer()
  709. break
  710. case MainViewModel.MENU_UPDATE:
  711. AlertDialog.show({
  712. title: '版本更新',
  713. message: '当前版本为最新版本:' + this.versionName,
  714. autoCancel: true,
  715. alignment: DialogAlignment.Center,
  716. offset: { dx: 0, dy: -20 }, //在Y轴方向上的编译量
  717. confirm: {
  718. value: '确定',
  719. fontColor: Color.White,
  720. backgroundColor: $r('app.color.title_bar_bg'),
  721. action: () => {
  722. }
  723. }
  724. })
  725. break
  726. case MainViewModel.MENU_YSZC:
  727. router.pushUrl({
  728. url: 'pages/WebIndex',
  729. params: { titleName: '隐私政策', webUrl: CommonConstants.NEW_YS_HW }
  730. });
  731. break
  732. case MainViewModel.MENU_SHARE:
  733. gotoShare(this.context,this.appName,this.bundleName)
  734. break
  735. }
  736. })
  737. }
  738. .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
  739. .animation({ duration: 600, curve: Curve.Ease, delay: 60 * index }))
  740. .width('90%')
  741. .height(55)
  742. })
  743. }
  744. isTextSelected(index:number):boolean{
  745. if(this.isShowTitleBar&&!this.isHiCar()){
  746. return this.mType ==index
  747. }else{
  748. if(this.mType == 0){
  749. return this.modeType == index
  750. }
  751. return this.mType+3 == index
  752. }
  753. return false
  754. }
  755. doShowDrawer(){
  756. if (!this.isBigScreen()) {
  757. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  758. this.isShowDrawer = false
  759. })
  760. }
  761. }
  762. @Builder
  763. updateSheet() {
  764. Scroll() {
  765. Column() {
  766. OnlineUpdateLog()
  767. }
  768. }
  769. .width('100%')
  770. .height('100%')
  771. }
  772. // /**
  773. // * ================== 穿山甲广告相关 ==================
  774. // */
  775. // /** Banner广告对象 */
  776. // private declare bannerAd: CSJNativeExpressAd;
  777. // /** 广告加载状态 */
  778. // @State private status: string = "未加载"
  779. // /** 是否展示广告 */
  780. // @State private isShowAd: boolean = false
  781. // /** 广告位配置信息 */
  782. // private declare mAdSlot: AdSlot;
  783. // /** 服务端bidding广告内容(可选) */
  784. // private mBiddingAdm = ''
  785. // /** 广告加载监听器 */
  786. // private expressLoadAdListener: NativeExpressAdListener = {
  787. // /**
  788. // * 广告加载失败回调
  789. // */
  790. // onError: (code: number, message: string) => {
  791. // console.error("加载广告失败,code=" + code + ",message=" + message);
  792. // this.status = "加载广告失败,code=" + code + ",message=" + message;
  793. // },
  794. // /**
  795. // * 广告加载成功回调
  796. // * @param ads 返回的广告列表
  797. // */
  798. // onNativeExpressAdLoad: (ads: ArrayList<CSJNativeExpressAd>) => {
  799. // console.log("BannerExpressAdPage==onNativeExpressAdLoad......success");
  800. // if (ads && ads.length > 0) {
  801. // ads.forEach((ad: CSJNativeExpressAd, idx: number) => {
  802. // // 设置广告交互监听
  803. // ad.setExpressInteractionListener({
  804. // /** 广告点击回调 */
  805. // onAdClicked: (type: number) => {
  806. // console.log("BannerExpressAdPage==onAdClicked......");
  807. // },
  808. // /** 广告展示回调 */
  809. // onAdShow: (type: number) => {
  810. // console.log("BannerExpressAdPage==onAdShow......");
  811. // },
  812. // /** 模板渲染失败回调 */
  813. // onRenderFail: (code: number, msg: string) => {
  814. // console.log("BannerExpressAdPage==onRenderFail...code=" + code + ",msg=" + msg);
  815. // },
  816. // /** 模板渲染成功回调 */
  817. // onRenderSuccess: (width: number, height: number) => {
  818. // console.log("BannerExpressAdPage==onRenderSuccess......width=" + width + ",height=" + height);
  819. // this.bannerAd = ad;
  820. // this.status = "广告加载完成待展示";
  821. // this.showBannerAd()
  822. // }
  823. // })
  824. // // 设置广告轮播时间
  825. // ad.setSlideIntervalTime(30 * 1000);//设置轮播时间长
  826. // this.setDislikeCallback(ad); //设置dislike
  827. // ad.render(this.getUIContext()) // 开始渲染广告
  828. // this.status = "广告加载中...";
  829. // });
  830. // }
  831. // }
  832. // }
  833. //
  834. // /**
  835. // * 设置广告 dislike 回调
  836. // * @param ad 广告对象
  837. // */
  838. // private setDislikeCallback(ad: CSJNativeExpressAd) {
  839. // ad.setDislikeCallback({
  840. // /** dislike 弹窗显示 */
  841. // onShow: () => {
  842. // console.log("BannerExpressAdPage==dislike......show");
  843. // },
  844. // /** 选择 dislike 选项 */
  845. // onSelected: (position: number, value: string, enforceRemove: boolean) => {
  846. // this.isShowAd = false;
  847. // console.log("BannerExpressAdPage==dislike......onSelected:position=" + position + ",value:" + value + ",enforce=" + enforceRemove);
  848. // },
  849. // /** 点击取消 dislike */
  850. // onCancel: () => {
  851. // console.log("BannerExpressAdPage==dislike......onCancel");
  852. // }
  853. // })
  854. // }
  855. //
  856. // /**
  857. // * 加载Banner广告
  858. // * @param rit 广告位ID
  859. // */
  860. // loadBannerAd(rit: string) {
  861. // // 仅在满足条件时加载广告
  862. // if(!Utility.isNoble()){
  863. // return
  864. // }
  865. // if(!Utility.isPassInstallTime(2)){
  866. // return
  867. // }
  868. // this.isShowAd = false;
  869. // let adCreator:CSJAdCreator = CSJAdSdk.getAdCreator()
  870. // this.mAdSlot = new AdSlotBuilder()
  871. // .setCodeId(rit)
  872. // .setAcceptSize(CSJUtil.BANNER_WIGTH, CSJUtil.BANNER_HEIGHT)
  873. // .setAdCount(1)
  874. // .setBidAdm(this.mBiddingAdm)
  875. // .build()
  876. // PrintBiddingTokenUtils.printBiddingToken(this.mAdSlot, adCreator);
  877. // adCreator.loadBannerAd(this.mAdSlot, this.expressLoadAdListener)
  878. // this.status = "广告加载中..."
  879. // }
  880. @StorageProp('currentHeightBreakpoint') currentHeightBreakpoint: HeightBreakpoint | undefined =
  881. HeightBreakpoint.HEIGHT_LG;
  882. @StorageProp('currentWidthBreakpoint') currentWidthBreakpoint: WidthBreakpoint | undefined = WidthBreakpoint.WIDTH_SM;
  883. @StorageProp('topRectHeight') topRectHeight: number = px2vp(AppUtil.getStatusBarHeight());
  884. //是否是Pura X外屏,或者小屏幕
  885. isPuraWP() {
  886. return this.currentWidthBreakpoint === WidthBreakpoint.WIDTH_SM
  887. && this.currentHeightBreakpoint === HeightBreakpoint.HEIGHT_MD
  888. }
  889. //是否是Pura X外屏,或者小屏幕或者手机横屏
  890. isCoverOpacity() {
  891. if (this.isPuraWP()) {
  892. return true
  893. }
  894. if (this.isPhoneLan()) {
  895. return true
  896. }
  897. if (deviceInfo.marketName.includes('Pura X')
  898. && this.currentHeightBreakpoint === HeightBreakpoint.HEIGHT_SM) {
  899. return true
  900. }
  901. return false
  902. }
  903. //是不是手机横屏
  904. isPhoneLan() {
  905. if (DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE
  906. && this.currentBreakpoint !== BreakpointTypeEnum.SM) {
  907. if (DisplayUtil.getFoldStatus() === 0) {
  908. return true
  909. }
  910. }
  911. return false
  912. }
  913. isLoginChange() {
  914. this.refreshUserInfoState();
  915. }
  916. /**
  917. * 构建歌单tab内容
  918. */
  919. @Builder
  920. buildPlaylistTab() {
  921. // 创建歌单按钮
  922. ListItem() {
  923. Button({ type: ButtonType.Capsule, stateEffect: true }) {
  924. Row() {
  925. SymbolGlyph($r('sys.symbol.plus_circle'))
  926. .fontSize(22)
  927. .fontColor([this.themeColor])
  928. .effectStrategy(SymbolEffectStrategy.HIERARCHICAL)
  929. .alignSelf(ItemAlign.Center)
  930. .margin({ left: 25 })
  931. Text('创建歌单')
  932. .margin({ left: 10, right: 20 })
  933. .fontSize(15)
  934. .fontColor($r('app.color.index_tab_font_color'))
  935. .fontWeight(480)
  936. Blank()
  937. Image($r('app.media.arrow_right'))
  938. .width(22)
  939. .height(22)
  940. .margin({ left: 20, right: 0 })
  941. .align(Alignment.Center)
  942. }
  943. .width('100%')
  944. .height(55)
  945. }
  946. .backgroundColor(Color.Transparent)
  947. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
  948. .onClick(() => {
  949. this.showCreatePlaylistDialog()
  950. })
  951. }
  952. // 歌单列表
  953. ForEach(this.playlistList, (playlist: Playlist) => {
  954. ListItem() {
  955. Button({ type: ButtonType.Capsule, stateEffect: true }) {
  956. Row() {
  957. Image(playlist.coverPath || $r('app.media.hm_playlist'))
  958. .width(22)
  959. .height(22)
  960. .margin({ left: 25 })
  961. .borderRadius(4)
  962. .fillColor(this.themeColor)
  963. .clip(true)
  964. Column() {
  965. Text(playlist.name)
  966. .margin({ left: 10, right: 20 })
  967. .fontSize(15)
  968. .fontColor($r('app.color.index_tab_font_color'))
  969. .fontWeight(480)
  970. .maxLines(1)
  971. .textOverflow({ overflow: TextOverflow.Ellipsis })
  972. Text(`${playlist.songCount}首`)
  973. .margin({ left: 10, right: 20 })
  974. .fontSize(12)
  975. .fontColor($r('app.color.index_tab_font_color'))
  976. .opacity(0.7)
  977. }
  978. .alignItems(HorizontalAlign.Start)
  979. Blank()
  980. Image($r('app.media.arrow_right'))
  981. .width(22)
  982. .height(22)
  983. .margin({ left: 20, right: 0 })
  984. .align(Alignment.Center)
  985. }
  986. .width('100%')
  987. .height(55)
  988. }
  989. .backgroundColor(Color.Transparent)
  990. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
  991. .onClick(async () => {
  992. if (!this.playlistTable) {
  993. ToastUtil.showToast('歌单功能初始化中,请稍后再试')
  994. return
  995. }
  996. // 加载歌单歌曲
  997. const playlistSongs = await this.playlistTable.queryPlaylistSongs(playlist.id)
  998. this.currentSongList = await convertPlaylistSongsToVideoItems(this.context,playlistSongs)
  999. this.currentSongListName= playlist.name
  1000. this.currentSongListID = playlist.id
  1001. this.modeType = 4//切换到歌单模式
  1002. this.doShowDrawer()
  1003. })
  1004. .bindContextMenu(this.MenuBuilder(playlist), ResponseType.LongPress,
  1005. {
  1006. preview: MenuPreviewMode.IMAGE,
  1007. previewAnimationOptions: { scale: [0.8, 1.0] },
  1008. })
  1009. .bindContextMenu(this.MenuBuilder(playlist), ResponseType.RightClick,
  1010. {
  1011. preview: MenuPreviewMode.IMAGE,
  1012. previewAnimationOptions: { scale: [0.8, 1.0] },
  1013. })
  1014. }
  1015. })
  1016. }
  1017. @Builder
  1018. MenuBuilder(playlist: Playlist) {
  1019. Menu(){
  1020. MenuItem({
  1021. symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.square_and_pencil')),
  1022. content: '编辑歌单'
  1023. })
  1024. .onClick(async() => {
  1025. this.openPlaylist(playlist)
  1026. })
  1027. MenuItem({
  1028. symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.trash')),
  1029. content: '删除歌单'
  1030. })
  1031. .onClick(async() => {
  1032. this.deletePlaylist(playlist)
  1033. })
  1034. }
  1035. }
  1036. /**
  1037. * 删除歌单
  1038. */
  1039. async deletePlaylist(playlist: Playlist) {
  1040. if (playlist&&this.playlistTable) {
  1041. // 显示确认对话框
  1042. AlertDialog.show({
  1043. title: '删除歌单',
  1044. message: `确定要删除歌单"${playlist.name}"吗?此操作不可撤销。`,
  1045. primaryButton: {
  1046. value: '取消',
  1047. action: () => {}
  1048. },
  1049. secondaryButton: {
  1050. value: '删除',
  1051. fontColor: Color.Red,
  1052. action: async () => {
  1053. if (this.playlistTable) {
  1054. const success = await this.playlistTable.deletePlaylist(playlist.id)
  1055. if (success) {
  1056. ToastUtil.showToast('歌单删除成功')
  1057. // 发送刷新事件
  1058. emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH }, {})
  1059. } else {
  1060. ToastUtil.showToast('歌单删除失败')
  1061. }
  1062. }
  1063. }
  1064. }
  1065. })
  1066. }
  1067. }
  1068. /**
  1069. * 显示创建歌单对话框
  1070. */
  1071. showCreatePlaylistDialog() {
  1072. if (!this.playlistTable) {
  1073. ToastUtil.showToast('歌单功能初始化中,请稍后再试')
  1074. return
  1075. }
  1076. DialogHelper.showTextInputDialog({
  1077. title: '创建歌单',
  1078. maskColor: Color.Transparent,
  1079. text: '',
  1080. placeholder: '请输入歌单名称',
  1081. onAction: async (action, dialogId, content) => {
  1082. if (action === DialogAction.TWO && content.trim()) {
  1083. // 创建歌单
  1084. const success = await this.playlistTable!.createPlaylist(content.trim())
  1085. if (success) {
  1086. ToastUtil.showToast('歌单创建成功')
  1087. // 刷新歌单列表
  1088. await this.loadPlaylistList()
  1089. // 发送歌单刷新事件
  1090. emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH }, {})
  1091. } else {
  1092. ToastUtil.showToast('歌单创建失败')
  1093. }
  1094. }
  1095. }
  1096. })
  1097. }
  1098. /**
  1099. * 打开歌单详情
  1100. */
  1101. openPlaylist(playlist: Playlist) {
  1102. try {
  1103. // 跳转到歌单详情页面
  1104. router.pushUrl({
  1105. url: 'pages/PlaylistDetailPage',
  1106. params: {
  1107. playlist: playlist
  1108. }
  1109. }).catch((err: Error) => {
  1110. console.error('跳转到歌单详情页面失败:', err.message);
  1111. ToastUtil.showToast('打开歌单失败');
  1112. });
  1113. } catch (error) {
  1114. console.error('打开歌单失败:', error);
  1115. ToastUtil.showToast('打开歌单失败');
  1116. }
  1117. }
  1118. /**
  1119. * 初始化歌单数据库
  1120. */
  1121. async initPlaylistTable() {
  1122. try {
  1123. this.playlistTable = new PlaylistTable(this.context)
  1124. console.info('歌单数据库初始化成功')
  1125. // 等待数据库初始化完成后再加载数据
  1126. setTimeout(async () => {
  1127. await this.loadPlaylistList()
  1128. }, 500) // 延迟500ms确保数据库初始化完成
  1129. } catch (error) {
  1130. console.error('初始化歌单数据库失败:', error)
  1131. }
  1132. }
  1133. /**
  1134. * 加载歌单列表
  1135. */
  1136. async loadPlaylistList() {
  1137. try {
  1138. if (this.playlistTable) {
  1139. const playlists = await this.playlistTable.queryAllPlaylists()
  1140. this.playlistList = playlists
  1141. console.info(`成功加载 ${playlists.length} 个歌单`)
  1142. } else {
  1143. console.warn('歌单表未初始化')
  1144. }
  1145. } catch (error) {
  1146. console.error('加载歌单列表失败:', error)
  1147. }
  1148. }
  1149. }
  1150. // 1. 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体
  1151. function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string {
  1152. if (isDarkMode) {
  1153. // 深色模式下返回更深的灰色或半透明黑色
  1154. return `rgba(34,34,34,${alpha + 0.2 > 1 ? 1 : alpha + 0.2})`;
  1155. }
  1156. const color = themeColor.replace('#', '');
  1157. const r = parseInt(color.substring(0, 2), 16);
  1158. const g = parseInt(color.substring(2, 4), 16);
  1159. const b = parseInt(color.substring(4, 6), 16);
  1160. return `rgba(${r},${g},${b},${alpha})`;
  1161. }
  1162. // 定义接口
  1163. interface HiCarAspectRatio {
  1164. ratio: number;
  1165. name: string;
  1166. }