import { AppUtil, ArrayUtil, DeviceUtil, DisplayUtil, LogUtil, PreferencesUtil, StrUtil, ToastUtil } from '@pura/harmony-utils'; import TitleBar from '../view/TitleBar'; import { curves, ImmersiveMode, LengthMetrics, LevelMode, MenuModifier, router, SegmentButton, SegmentButtonOptions, SymbolGlyphModifier } from '@kit.ArkUI'; import mainViewModel, { MainViewModel } from '../viewmodel/MainViewModel'; import ItemData from '../viewmodel/ItemData'; import type { sysResource } from '../viewmodel/ItemData'; import { CommonConstants } from '../common/constants/CommonConstants'; import { EventConstants } from '../common/constants/EventConstants'; import { VideoItem } from '../viewmodel/VideoItem'; import ScreenUtil from '../common/util/ScreenUtil'; import { Utility } from '../common/util/Utility'; import { BusinessError, emitter } from '@kit.BasicServicesKit'; import { common } from '@kit.AbilityKit'; import { BreakpointSystem, BreakpointTypeEnum } from '../common/util/BreakpointSystem'; import dataStorage from '@ohos.data.storage'; import ConfigurationConstant from '@ohos.app.ability.ConfigurationConstant'; import { SettingPage } from './SettingPage'; import { deviceInfo } from '@kit.BasicServicesKit'; import { resourceManager } from '@kit.LocalizationKit'; import { systemShare } from '@kit.ShareKit'; import { uniformTypeDescriptor } from '@kit.ArkData'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { DialogHelper, DialogAction } from '@pura/harmony-dialog'; import UserUtil from '../common/util/UserUtil'; import json from '@ohos.util.json'; import { UserCenter } from './UserCenter'; import { ScanFilePage } from './ScanFilePage'; import { AboutPage, gotoShare } from './AboutPage'; import { smartMobilityCommon } from '@kit.CarKit'; import { UpdateLogManager } from '../common/util/UpdateLogManager'; import OnlineUpdateLogDialog from '../dialog/OnlineUpdateLog'; import OnlineUpdateLog from '../dialog/OnlineUpdateLog'; import { LocalMusic } from '../view/LocalMusic'; import { ChartsCount } from './ChartsCount'; import { image } from '@kit.ImageKit'; import { Playlist, PlaylistSong } from '../viewmodel/Playlist'; import PlaylistTable from '../common/util/PlaylistTable'; import { convertPlaylistSongsToVideoItems } from './PlaylistDetailPage'; import MediaTable from '../common/util/MediaTable'; import { showCreatePlaylistDialog } from '../dialog/PlaylistDialog'; import { ImagePickerUtil } from '../common/util/ImagePickerUtil'; import { WebDavMainPage } from './WebDavMainPage'; import { WebDavAccount } from '../viewmodel/WebDavAccount'; import { RemoteDriveManager } from '../common/util/RemoteDriveManager'; import { getCloudDiskIcon, RemoteDriveAccountDialog } from '../dialog/RemoteDriveAccountDialog'; import ReqPermissionUtil from '../common/util/ReqPermissionUtil'; import { getRemoteDriveAccountLabel, getRemoteDriveDisplayLabel, getRemoteDriveProtocolLabel } from '../common/util/RemoteDriveLabel'; import { RemoteDriveType } from '../common/enums/RemoteDriveType'; import { RemoteMusicPage } from '../view/RemoteMusicPage'; import { ServerLogUtil } from '../common/util/ServerLogUtil'; import { PlayStatus } from '../common/PlayStatus'; import { PointLightDefaultButton } from '../view/PointLight/PointLightDeFaultButton'; import { FindView } from '../view/FindView'; import { hdsEffect } from '@kit.UIDesignKit'; // import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg'; const TAG = 'NewIndex'; // 日志标签 /** * 首页主组件,包含顶部标题栏、主内容区(本地音乐/网络内容)、侧边抽屉菜单等。 * 支持多端适配、广告集成、导航、生命周期管理等功能。 */ @Preview @Entry @Component struct NewIndex { //背景流光控制器 @State bgController: hdsEffect.ShaderEffectController|undefined = deviceInfo.sdkApiVersion>=20 &&canIUse("SystemCapability.UIDesign.HDSComponent.Core")? new hdsEffect.ShaderEffectController():undefined; @State defalut_home_type:number = 0//首页默认类型 可支持首页 媒体库 歌单 网盘 @State isDetailView: boolean = false; // 是否在艺术家/专辑详情视图 @Provide isShowPlay: boolean = false; @Provide CONTROL_PlayStatus: number = PlayStatus.INIT; @Provide progressValue: number = 0; @State isShowPrecious: boolean = false //播控条显示上一首按钮 @State bottomBarHeight: number = 70; @StorageProp('bottomSafeHeight') bottomSafeHeight: number = 0; @Provide cover: string | undefined = ''; // @Provide currentSong: VideoItem | undefined = undefined; @StorageLink('currentSong') currentSong: VideoItem | undefined = undefined; @Provide isMultiSelect: boolean = false; /** 页面上下文 */ context = this.getUIContext().getHostContext() as common.UIAbilityContext @Provide currentSongListName:string = '' //当前歌单名称 @Provide currentSongListID:string='' //当前歌单ID @State isDarkMode: boolean = false /** 列表滚动器,用于抽屉菜单列表滚动 */ private scroller: Scroller = new Scroller(); /** 应用包名 */ @State bundleName: string = '' @State appName: string = '' /** 是否显示侧边抽屉菜单 */ @Provide isShowDrawer: boolean = false; /** 抽屉菜单 X 轴偏移量,用于手势滑动动画 */ @Provide offsetX: number = 0; @StorageProp('findCanBack') findCanBack: boolean = false /** 本地视频列表,页面参数传入 */ @Provide videoLocalList: Array = [] /** Y 轴偏移量(预留) */ @State offsetY: number = 0; /** 主内容类型(0:本地音乐,1:网络内容) */ @Provide mType: number = 0 /** 模式类型(如文件夹 媒体库 艺术家、专辑等) */ @Provide modeType: number = 0 /** 是否为零状态(预留) */ @Provide('isZero') isZero: boolean = false; /** 是否显示赞助入口 */ @State isShowSponsorship: boolean = false /** 音乐是否为零状态(预留) */ @Provide('isMusicZero') isMusicZero: boolean = false; @State isShowTitleBar: boolean = false //是否显示分类导航条 /** 本地音乐列表,持久化存储 */ @StorageLink('musicLocalList') musicLocalList: Array = [] @Provide isFavMusic: boolean = false @StorageProp('windowWidth') windowWidth: number = 0; @StorageProp('windowHeight') windowHeight: number = 0; @StorageProp('isHiCarStatus') isHiCarStatus: boolean = false; @StorageProp('curDisplayIsHiCar') curDisplayIsHiCar: boolean = false; /** * 是否显示更新日志开关 */ @State isShowUpdateDialog: boolean = false /** 应用版本号 */ @State versionName: string = '' /** 音频根目录路径 */ @Provide rootPath: string = '' /** 当前路径 */ @Provide currentPath: string = '' /** 是否为历史记录页面 */ @Provide isHistory: boolean = false /** 是否可以返回上一级 */ @Provide isCanBack: boolean = false @StorageProp('currentColorMode') currentMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT; @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR; /** * 一多界面适配断点系统 */ private breakpointSystem: BreakpointSystem = new BreakpointSystem(); /** 当前断点类型(如大屏/小屏) */ @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD; /** 记录上一次点击返回键的时间戳,用于双击退出 */ private backTime: number = 0; @State tabOptions: SegmentButtonOptions = SegmentButtonOptions.tab({ buttons: [{ text: '分类' }, { text: '歌单' }, { text: '网盘' }], direction: Direction.Ltr, backgroundColor: $r('app.color.index_background'), selectedBackgroundColor:$r('app.color.start_window_background'), selectedFontColor: $r('app.color.text_color'), localizedTextPadding: { end: LengthMetrics.vp(10), start: LengthMetrics.vp(10) } }) @State @Watch('tabSelectedIndexesChanged') tabSelectedIndexes: number[] = [0] //当胶囊按钮的选择发生变化时调用此函数 tabSelectedIndexesChanged() { if(this.tabSelectedIndexes[0]==1){ this.mType = 0 } else if(this.tabSelectedIndexes[0]==2){ // 网盘选项卡 if(this.webDavAccounts.length>0&&(this.webDavAccounts[0].webType===RemoteDriveType.Navidrome|| this.webDavAccounts[0].webType===RemoteDriveType.Jellyfin|| this.webDavAccounts[0].webType===RemoteDriveType.Emby|| this.webDavAccounts[0].webType===RemoteDriveType.DaoLiYu)){ this.mType = 7 }else{ this.mType = 6 } } } // 歌单相关状态变量 @State playlistList: Playlist[] = [] @State isShowCreatePlaylistDialog: boolean = false @State selectedPlaylist: Playlist | null = null private playlistTable: PlaylistTable | null = null // 网盘账户相关状态变量 @State webDavAccounts: WebDavAccount[] = [] private webdavManager: RemoteDriveManager = RemoteDriveManager.getInstance() private _webDavLoading: boolean = false // 防止重复加载网盘账户的标志 @State selectedAccount: WebDavAccount = new WebDavAccount() /** * 返回键处理逻辑: * - 如果不是根目录或有历史记录,发送广播通知更新列表 * - 如果是根目录,双击返回键退出应用,否则提示 */ onBackPress(): boolean | void { console.info('onecold onBackPress isDetailView = '+ this.isDetailView); console.info('onecold onBackPress mType = '+ this.mType); if (this.mType === 4) { this.returnToConfiguredHome() } else if (this.currentPath !== this.rootPath || this.isHistory || this.isFavMusic || (this.modeType !== 0 && this.isCanBack)) { console.info('onecold 返回键处理逻辑:发送音频广播通知更新列表'); const eventData: emitter.EventData = {}; emitter.emit({ eventId: EventConstants.EVENT_SWIPE_BACK_UPDATE }, eventData); // 发送音频广播通知更新doSwipBack } else if(this.mType === 7) { if(this.isDetailView){ console.info('onecold RemoteMusicPage 返回键处理:发送手势返回事件'); const eventData: emitter.EventData = {}; emitter.emit({ eventId: EventConstants.EVENT_SWIPE_BACK_NAVID}, eventData); }else{ if (this.isShowDrawer) { // 如果抽屉菜单打开,先关闭抽屉 this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = false }) } else { return false } } } else if(this.mType === 6) { console.info('onecold 网盘 返回键处理:发送手势返回事件'); const eventData: emitter.EventData = {}; emitter.emit({ eventId: EventConstants.EVENT_SWIPE_BACK_DISK }, eventData); } else if(this.mType === 8) { if (this.findCanBack) { const eventData: emitter.EventData = {} emitter.emit({ eventId: EventConstants.EVENT_FIND_VIEW_BACK }, eventData) } else if (this.isShowDrawer) { this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = false }) } else { return false } } else if(this.mType > 0){ this.getUIContext()?.animateTo({ duration: 555 }, () => { // 动画闭包内控制Image组件的出现和消失 // this.isShowDrawer = !this.isShowDrawer // this.offsetX = 0 this.mType = 8 }) } else { console.info('onecold 返回键处理 关闭'); if (this.isShowDrawer) { // 如果抽屉菜单打开,先关闭抽屉 this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = false }) } else { return false } } return true; } /** * 刷新@State用户信息变量,确保UI同步 */ refreshUserInfoState() { this.isLogin = PreferencesUtil.getBooleanSync('isLogin', false); this.userId = PreferencesUtil.getNumberSync('userId', 0); this.userName = PreferencesUtil.getStringSync('userName', '未登录用户'); this.userAvatarUrl = PreferencesUtil.getStringSync('userAvatarUrl', ''); this.subscriptionName = PreferencesUtil.getStringSync('subscriptionName', ''); this.subscriptionEndDate = PreferencesUtil.getStringSync('subscriptionEndDate', ''); this.hasActiveSubscription = PreferencesUtil.getBooleanSync('hasActiveSubscription', false); } onPageShow() { LogUtil.info('heanup NewIndex', 'onPageShow 被调用,刷新歌单列表和网盘账户列表') // 加载歌单列表 this.loadPlaylistList() // 加载网盘账户列表 this.loadWebDavAccounts() // 通知 LocalMusic 页面已显示,恢复旋转 AppStorage.setOrCreate('isPageVisible', true) } onPageHide() { // 通知 LocalMusic 页面已隐藏,停止旋转 AppStorage.setOrCreate('isPageVisible', false) } /** * 页面显示生命周期钩子 * - 注册断点系统 * - 获取页面参数 * - 设置状态栏样式 * - 初始化图片缓存 * - 判断是否显示赞助入口 */ async aboutToAppear() { this.defalut_home_type = PreferencesUtil.getNumberSync('defalut_home_type', 0) this.isShowPrecious = PreferencesUtil.getBooleanSync('isShowPrecious', false) ReqPermissionUtil.reqPermissionsFromUser(ReqPermissionUtil.permissions, this.context); this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, false) Utility.enableFullScreen() this.topRectHeight = px2vp(AppUtil.getStatusBarHeight()); Utility.getAppName(getContext(this)).then((appName: string) => { this.appName = appName }) this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK this.breakpointSystem.register(); let params = router.getParams() as Record; if (params && params.videoList) { this.videoLocalList = params.videoList as VideoItem[]; LogUtil.info('heanup NewIndex', '从路由参数获取到播放列表,长度:' + this.videoLocalList.length) } else { this.videoLocalList = [] LogUtil.info('heanup NewIndex', '路由参数中没有播放列表,使用空数组') } // 检查是否从WebDavPage返回 if (params && params.fromWebDavPage) { LogUtil.info('heanup NewIndex', '从WebDavPage返回,切换到网盘标签页') this.mType = 6 this.tabSelectedIndexes = [2] // 切换到网盘标签 } // Utility.setStatusBarLight() ScreenUtil.setScreenSize(); this.bundleName = AppUtil.getBundleName() this.versionName = AppUtil.getVersionName(); // await ImageKnife.getInstance().initFileCache(this.context, 256, 256 * 1024 * 1024) this.isShowSponsorship = Utility.isOpenTime() let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR); hilog.info(0x0000, 'Heanup', '当前 NewIndex themeColor:' + themeColor); AppStorage.setOrCreate('themeColor', themeColor); this.themeColor = themeColor // ====== 本地会员同步提醒逻辑 ====== // this.refreshUserInfoState(); // if (this.isLogin) { // void this.fetchUserInfo(); // } await UserUtil.fetchUserInfo(); this.refreshUserInfoState(); let changeUserState: emitter.InnerEvent = { eventId: EventConstants.EVENT_USER_STATE_CHANGE } emitter.on(changeUserState, () => { this.refreshUserInfoState(); }); let eventSetting: emitter.InnerEvent = { eventId: EventConstants.EVENT_SETTING_UPDATE } // 监听广播事件(通用设置配置更新) emitter.on(eventSetting, (eventData: emitter.EventData) => { this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true) }); emitter.on({ eventId: EventConstants.EVENT_SETTING_BACK_TO_HOME }, () => { this.returnToConfiguredHome() }); this.logHiCar('info', `getHiCarStatus isHiCarStatus=${this.isHiCarStatus}`) if(this.curDisplayIsHiCar||this.isBigScreen()){ this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = true this.offsetX = 0 }) this.isShowTitleBar = false } // 检查并显示更新日志 this.checkAndShowUpdateLog(); // 初始化歌单数据库 await this.initPlaylistTable(); // 初始化WebDAV管理器 await this.initWebDavManager(); // 监听歌单刷新事件 emitter.on({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH }, (eventData: emitter.EventData) => { LogUtil.info('heanup NewIndex', '收到歌单刷新事件,开始刷新歌单列表') this.loadPlaylistList() }); if(Utility.isOpenTime()&&!PreferencesUtil.getBooleanSync('isShowHaoPingDialog17', false)){ setTimeout(()=>{ Utility.showHaoPingDialog(this.getUIContext(),this.context,this.appName,this.bundleName) PreferencesUtil.putSync('isShowHaoPingDialog17', true) },25000) } this.initDefalutType() } private initDefalutType(){ this.applyDefaultHomeState() console.info('onecold 网盘 默认首页2 this.mType='+this.mType ) } private isRemoteMusicAccount(account: WebDavAccount | null | undefined): boolean { if (!account) { return false } return account.webType === RemoteDriveType.Navidrome || account.webType === RemoteDriveType.Jellyfin || account.webType === RemoteDriveType.Emby || account.webType === RemoteDriveType.AudioStation || account.webType === RemoteDriveType.Plex || account.webType === RemoteDriveType.DaoLiYu } private getConfiguredHomeAccount(): WebDavAccount | null { const activatedAccount = this.webdavManager.getActivatedWebDavAccount() if (activatedAccount) { return activatedAccount } if (this.selectedAccount && (this.selectedAccount.id > 0 || this.selectedAccount.host.length > 0)) { return this.selectedAccount } if (this.webDavAccounts.length > 0) { return this.webDavAccounts[0] } return null } private applyDefaultHomeState(): void { this.defalut_home_type = PreferencesUtil.getNumberSync('defalut_home_type', 0) const configuredAccount = this.getConfiguredHomeAccount() this.currentSongListID = '' this.currentSongListName = '' this.modeType = 0 this.tabSelectedIndexes = [0] if(this.defalut_home_type==1){ this.mType = 0 this.modeType = 1 }else if(this.defalut_home_type==2){//如果是网盘的话 this.tabSelectedIndexes = [2] if (configuredAccount) { this.selectedAccount = configuredAccount } this.mType = this.isRemoteMusicAccount(configuredAccount) ? 7 : 6 }else if(this.defalut_home_type==3){ this.mType = 0 this.modeType = 2 }else if(this.defalut_home_type==4){ this.mType = 0 this.modeType = 3 }else if(this.defalut_home_type==5){ this.mType = 8 } else { this.mType = 0 } } private returnToConfiguredHome(): void { this.getUIContext()?.animateTo({ duration: 555 }, () => { this.applyDefaultHomeState() }) } /** * 检查并显示更新日志 */ private async checkAndShowUpdateLog() { try { const shouldShow = await UpdateLogManager.checkAndShowUpdateLog(); if (shouldShow) { this.isShowUpdateDialog = !this.isShowUpdateDialog UpdateLogManager.markCurrentVersionShown() } // this.isShowUpdateDialog = true;//调试期间显示更新日志,测试完成后请删除 } catch (error) { console.error('NewIndex checkAndShowUpdateLog error:', error); } } /** * 页面消失生命周期钩子 * 注销断点系统 */ aboutToDisappear() { console.info('NewIndex aboutToDisappear'); this.breakpointSystem.unregister(); emitter.off(EventConstants.EVENT_SWIPE_BACK_UPDATE); emitter.off(EventConstants.EVENT_USER_STATE_CHANGE); emitter.off(EventConstants.EVENT_SETTING_BACK_TO_HOME); // 监听歌单刷新事件 emitter.off(EventConstants.EVENT_PLAYLIST_REFRESH); // 清理WebDAV管理器订阅 if (this.webdavManager) { this.webdavManager.observers = []; } } @Builder ContentBuild() { Stack() { LocalMusic() .visibility(this.mType === 0 ? Visibility.Visible : Visibility.None) // 本地音乐内容区 if(this.mType === 1 ){ UserCenter() }else if(this.mType === 2 ){ ScanFilePage() }else if(this.mType === 3 ){ ChartsCount({ isShowDrawer:this.isShowDrawer, offsetX:this.offsetX }) }else if(this.mType === 4 ){ SettingPage() }else if(this.mType === 5 ){ AboutPage() }else if(this.mType === 8 ){//发现页 FindView() } if(this.mType === 6){ WebDavMainPage({ offsetX:this.offsetX, isShowDrawer:this.isShowDrawer, mType:this.mType, selectedAccount:this.selectedAccount }) .visibility(this.mType === 6 ? Visibility.Visible : Visibility.None) } if(this.mType === 7){ RemoteMusicPage({ offsetX:this.offsetX, isDetailView:this.isDetailView, isShowDrawer:this.isShowDrawer, mType:this.mType, selectedAccount:this.selectedAccount }) .visibility(this.mType === 7 ? Visibility.Visible : Visibility.None) } // 抽屉打开时的遮罩层,用于拦截点击事件 这个只能正常尺寸的手机竖屏的才能生效 if (this.isShowDrawer&&this.isPhonePortrait()) { Column() .width('100%') .height('100%') .backgroundBlurStyle(BlurStyle.BACKGROUND_THIN) .transition(TransitionEffect.OPACITY.animation({ curve: curves.springMotion(0, 1) })) .onClick(() => { // 点击遮罩层关闭抽屉 this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = false }) }) } } .bindSheet($$this.isShowUpdateDialog, this.updateSheet(), { height: '90%', dragBar: true, preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM, showClose: true, blurStyle: BlurStyle.Regular, title: { title: '更新日志' } }) } @State private pointLightOptions: hdsEffect.PointLightOptions | undefined = undefined build() { SideBarContainer(SideBarContainerType.AUTO) { Column() { this.getLeftView() } .backgroundColor(Color.Transparent) Stack() { this.ContentBuild() Stack() { this.PlayController() } .width('90%') .borderRadius(15) .margin({ bottom:DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_2IN1||this.curDisplayIsHiCar ? 30 :this.bottomSafeHeight }) .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK) .backgroundImage(StrUtil.isEmpty(this.cover) ?undefined:this.cover) .visibility(this.isMultiSelect||(this.mType>0&&this.mType<6) ? Visibility.None : Visibility.Visible) .visualEffect(deviceInfo.sdkApiVersion>=20&&this.bgController&&StrUtil.isEmpty(this.cover)? new hdsEffect.HdsEffectBuilder() .shaderEffect({ effectType: hdsEffect.EffectType.UV_BACKGROUND_FLOW_LIGHT, animation: { duration: 10000, iterations: -1, autoPlay: true, onFinish: ()=> { console.info('Succeeded in finishing'); } }, controller: this.bgController, }) .buildEffect():null) .backgroundImageSize( { width: '100%' }) .animation({ duration: 500, curve: Curve.Friction // 可选动画曲线 }) .opacity(0.99) .clickEffect({ level: ClickEffectLevel.HEAVY }) } .alignContent(Alignment.Bottom) .width('100%') .height('100%') .gesture(SwipeGesture({ direction: SwipeDirection.Horizontal }).onAction((event: GestureEvent) => { if (event) {//手势返回 this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = !this.isShowDrawer if(this.isShowDrawer) this.offsetX = 0 }) } })) } .showControlButton(false) .minContentWidth(0) .sideBarWidth(260) .autoHide(true) .showSideBar($$this.isShowDrawer) .onChange((value: boolean) => { this.isShowDrawer = value }) } @Builder centerName() { Column() { Text(this.currentSong?.name) .fontSize(16) .textOverflow({ overflow: TextOverflow.MARQUEE }) //超长滚动 .maxLines(1) .fontColor(Color.White) .fontWeight(FontWeight.Bolder); Row() { Text(this.currentSong?.artist) .margin({ top: 2 }) .fontSize(13) .textAlign(TextAlign.Start) .maxLines(1) .fontWeight(FontWeight.Bold) .textOverflow({ overflow: TextOverflow.MARQUEE }) //超长滚动 .fontColor(Color.White) } .visibility(this.currentSong?.artist? Visibility.Visible:Visibility.None) } .alignItems(HorizontalAlign.Center) } setShowPlayTrue(){ if (this.isShowPlay === true) { this.isShowPlay = false } this.getUIContext()?.animateTo({ duration: 500, curve: Curve.Friction }, () => { this.isShowPlay = true; }); this.getUIContext().getHostContext()!.eventHub.emit('showPlayerView'); } @Builder PlayController() { Row() { this.playConLeft(!this.curDisplayIsHiCar) if(!this.curDisplayIsHiCar){ this.playConRigth() } if(this.curDisplayIsHiCar){ Column(){ this.centerName() } .layoutWeight(1) .onClick(()=>{ this.setShowPlayTrue() }) } } .width('100%') .height(this.bottomBarHeight) .hitTestBehavior(HitTestMode.Transparent) .zIndex(2) .onTouch((event: TouchEvent) => { if (event.type === TouchType.Down) { this.pointLightOptions = { color: this.themeColor, intensity: 1, height: 60 } } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) { this.pointLightOptions = undefined } }) .visualEffect( deviceInfo.sdkApiVersion >= 20 ? new hdsEffect.HdsEffectBuilder() .pointLight({ options: this.pointLightOptions, illuminatedType: hdsEffect.PointLightIlluminatedType.BORDER_CONTENT }) .buildEffect() : undefined) .padding({ left: 16, right: 16 }) } @Builder playConLeft(isLeft: boolean = true) { Row() { Image( StrUtil.isNotEmpty(this.currentSong?.pixelMapPath)? this.currentSong?.pixelMapPath:$r('app.media.alt')) .height(48) .objectFit(ImageFit.Contain) .alt( $r('app.media.alt')) .fillColor(this.themeColor) .borderRadius(8) .shadow({ radius: 15, type: ShadowType.BLUR, color: 'on_primary' }) .geometryTransition('cover', { follow: true }) // 绑定标识符 if(!this.curDisplayIsHiCar){ Column() { Text(this.currentSong?.name) .fontSize(16) .textOverflow({ overflow: TextOverflow.MARQUEE }) //超长滚动 .maxLines(1) .fontColor(Color.White) .fontWeight(FontWeight.Bolder); Row() { Text(this.currentSong?.artist) .margin({ top: 2 }) .fontSize(13) .textAlign(TextAlign.Start) .maxLines(1) .fontWeight(FontWeight.Bold) .textOverflow({ overflow: TextOverflow.MARQUEE }) //超长滚动 .fontColor(Color.White) } .visibility(this.currentSong?.artist? Visibility.Visible:Visibility.None) } .padding({left:8}) .alignItems(isLeft?HorizontalAlign.Start:HorizontalAlign.End) .margin({ right: isLeft?22:10 }) }else{ this.playConRigth() } } .padding({ right: 18 }) .layoutWeight(1) .alignItems(VerticalAlign.Center) .justifyContent(FlexAlign.Start) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 }) .onClick(() => { this.setShowPlayTrue() }) } @Builder playConRigth() { Row() { SymbolGlyph($r('sys.symbol.backward_end_fill')) .fontSize(28) .fontColor([Color.White]) .alignSelf(ItemAlign.Center) .visibility(this.isShowPrecious? Visibility.Visible:Visibility.None) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .margin({ left: 8, right: 14 }) .displayPriority(2) .onClick(() => { this.getUIContext().getHostContext()!.eventHub.emit('playPrevious'); }) //播放进度条 Stack() { Progress({ value: Math.floor(this.progressValue), type: ProgressType.Ring, }) .color(this.themeColor)// 进度条前景色为灰色 .height(38) .aspectRatio(CommonConstants.ASPECT_RATIO) Image(this.CONTROL_PlayStatus === PlayStatus.PLAY ? $r('app.media.hm_pause') : $r('app.media.hm_play2')) .height(36) .width(36) .displayPriority(3) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 }) .fillColor(Color.White) .onClick(() => { this.getUIContext().getHostContext()!.eventHub.emit('playOrPause'); }) } SymbolGlyph($r('sys.symbol.forward_end_fill')) .fontSize(28) .fontColor([Color.White]) .alignSelf(ItemAlign.Center) .margin({ right: 14, left: 14 }) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .displayPriority(2) .onClick(() => { this.getUIContext().getHostContext()!.eventHub.emit('playNext'); }) Image($r('app.media.hm_playlist')) .height(26) .width(26) .displayPriority(1) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .fillColor(Color.White) .onClick(() => { this.getUIContext().getHostContext()!.eventHub.emit('openPlayList'); }) } .margin({left:this.curDisplayIsHiCar?20:5}) .justifyContent(this.curDisplayIsHiCar?FlexAlign.Start:FlexAlign.End) } //是不是hicar的屏幕分辨率 isHiCar() { return this.curDisplayIsHiCar; } //是不是正常的手机竖屏 isPhonePortrait() { LogUtil.info('twocold this.currentHeightBreakpoint = '+this.currentHeightBreakpoint) LogUtil.info('twocold this.currentWidthBreakpoint = '+this.currentWidthBreakpoint) if(DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE &&this.currentHeightBreakpoint == HeightBreakpoint.HEIGHT_LG &&this.currentWidthBreakpoint==WidthBreakpoint.WIDTH_SM){//如果是手机横屏,返回false return true } return false } /** * 判断是否横屏 * @returns */ isBigScreen() { if (this.currentWidthBreakpoint == WidthBreakpoint.WIDTH_LG) { return true } if (DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE && this.currentWidthBreakpoint === WidthBreakpoint.WIDTH_SM && this.currentHeightBreakpoint == HeightBreakpoint.HEIGHT_LG) { return false } else { return true } return false } @Builder getLeftView() { Column() { Column() { this.getDrawerView() } .width('100%') .height('100%') .onClick((event: ClickEvent) => { // 点击抽屉内部不关闭,通过返回值阻止事件传播 return true }) } // .width(this.currentBreakpoint !== BreakpointTypeEnum.SM?'36%':'70%') .height('100%') .borderRadius({ topLeft: 0, topRight: 20, bottomLeft: 0, bottomRight: 20 }) // .backgroundColor($r('app.color.silvery')) // .backgroundColor($r('app.color.index_background')) .backgroundColor($r('app.color.user_center_card_background')) .alignItems(HorizontalAlign.Start) .translate({ x: this.offsetX, y: 0, z: 0 }) .transition({ type: TransitionType.Insert, translate: { x: -DisplayUtil.getWidth(), y: 0 } }) .transition({ type: TransitionType.Delete, translate: { x: -DisplayUtil.getWidth(), y: 0 } }) .gesture( PanGesture() .onActionUpdate((event: GestureEvent) => { // 手势滑动更新抽屉偏移 if (event.offsetX < 0) { this.offsetX = event.offsetX; } }) .onActionEnd(() => { // 手势结束判断是否关闭抽屉 if (this.offsetX < -DisplayUtil.getWidth() / 15) { this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = false }) } else { this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = true this.offsetX = 0 }) } }) ) } @State isLogin: boolean = false; @State userAvatar: Resource = $r('app.media.icon_person2'); @State userAvatarUrl: string = ''; @State isVip: boolean = false; @State userName: string = '未登录用户'; @State userId: number = 0; @State hasActiveSubscription: boolean = false; @State subscriptionName: string = ''; @State subscriptionEndDate: string = ''; // 用户信息卡片 @Builder buildUserInfoCard() { Column() { this.userInfoView() Column({ space: 20 }) { SegmentButton({ options: this.tabOptions, selectedIndexes: $tabSelectedIndexes }) } .padding({ bottom:10 }) .width('90%') } } @Builder userInfoView() { Stack({ alignContent: Alignment.BottomEnd }) { Row() { Stack({ alignContent: Alignment.BottomEnd }){ Image(this.userAvatarUrl && this.userAvatarUrl.length > 0 ? this.userAvatarUrl : this.userAvatar) .width(55) .height(55) .margin({ left: 12 }) .borderRadius('50%') .clip(true) // .fillColor(this.themeColor) // .backgroundColor(this.isDarkMode ? Color.Black : Color.White) Column() { Text('VIP') .fontSize(9) .padding(2) .textAlign(TextAlign.Center) .fontWeight(FontWeight.Bolder) .fontColor(Color.White) } .width(28) .height(16) .visibility(Utility.isNoble()?Visibility.Visible:Visibility.None) .borderRadius(15) .backgroundColor(this.themeColor) } Column() { Row() { Text(this.isLogin ? this.userName : '未登录用户') .fontSize(14) // .fontWeight(FontWeight.Bold) // .fontColor(this.isDarkMode ? Color.White : Color.Black) .textAlign(TextAlign.Start) .maxLines(1) .width(110) .margin({ right: 12 }) } .justifyContent(FlexAlign.Start) .margin({ top: 2 }) if (this.isLogin) { if (this.hasActiveSubscription) { // Text(this.subscriptionName) // .fontSize(13) // .fontColor(themeColorWithAlpha(this.themeColor, 0.8, false)) // .textAlign(TextAlign.Start) // .padding({top:5}) this.TimeTextBuilder() } else { Text('普通用户') .fontSize(13) .fontColor(this.isDarkMode ? Color.White : Color.Grey) .textAlign(TextAlign.Start) .padding({top:5}) this.TimeTextBuilder() } } else { this.TimeTextBuilder() } } .alignItems(HorizontalAlign.Start) .width(80) .margin({ left: 10 }) } .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7}) .onClick(() => { // if (!this.isLogin) { this.mType = 1 if (!this.isBigScreen()) { this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = false }) // } } }) .width('100%') .padding(10) PointLightDefaultButton({ isPx: false, isSysBol: true, pointColor: $r('app.color.text_color'), imageResource: $r('sys.symbol.gearshape'), builderHeight: 32, builderWidth: 32, buttonScale: 1, canShadow: false, }) .margin({ right: 2, bottom: 2 }) .onClick(() => { this.mType = 4 if (!this.isBigScreen()) { this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = false }) } }) } .backgroundColor($r('app.color.user_center_card_background')) .borderRadius(24) .width('86%') .padding(8) .margin({ left: 10, right: 10, top: this.topRectHeight + 10,bottom:10 }) .shadow({ radius: 8, color: 0x11000000, offsetX: 0, offsetY: 2 }) } @Builder TimeTextBuilder() { Text(Utility.getTimePeriod()) .fontSize(13) .margin({ top: 5 }) .fontColor($r('app.color.text_color')) .align(Alignment.Start) } @State ratioL: number = 1; @State isRefreshing: boolean = false; @State maxRefreshingHeight: number = 100.0; private contentNode?: ComponentContent = undefined; /** * 动态生成抽屉菜单内容 * 根据 isShowSponsorship 展示不同菜单项 */ @Builder getDrawerView() { Refresh({ refreshing: $$this.isRefreshing, refreshingContent: this.contentNode }) { List({ space: 0, scroller: this.scroller }) { ListItemGroup({ header: this.buildUserInfoCard() }) { if(this.tabSelectedIndexes[0]==0){//分类 this.buildTabCate() }else if(this.tabSelectedIndexes[0]==1){//歌单 this.buildPlaylistTab() }else if(this.tabSelectedIndexes[0]==2){//网盘 this.buildCloudStorageTab() } } } .scrollBar(BarState.Off) .width('86%') .borderRadius(20) .margin({ bottom: 20, left: 10, right: 10, top: 10 }) .alignListItem(ListItemAlign.Center) .layoutWeight(1) .backgroundColor($r('app.color.left_draw_bg')) .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果 } .pullDownRatio(this.ratioL) .pullToRefresh(true) .refreshOffset(0) .onOffsetChange((offset: number) => { // 越接近最大距离,下拉跟手系数越小。 this.ratioL = 1 - Math.pow((offset / this.maxRefreshingHeight), 3); }) .onStateChange((refreshStatus: RefreshStatus) => { console.info('onecold Refresh onStatueChange state is ' + refreshStatus); }) .onRefreshing(async () => { if(this.tabSelectedIndexes[0]==0){//分类 this.isRefreshing = false }else if(this.tabSelectedIndexes[0]==1){//歌单 this.loadPlaylistList() }else if(this.tabSelectedIndexes[0]==2){//网盘 this.loadWebDavAccounts() } }) } @Builder buildTabCate() { ForEach(mainViewModel.getDrawerData(), (item: ItemData, index: number) => { ListItem() { Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { // 菜单图标 if (typeof item.img === 'object' && item.img !== null && (item.img as sysResource).type === 'symbol') { SymbolGlyph((item.img as sysResource).value as Resource)// .size({ width: 22, height: 22 }) .fontSize(22) .fontColor([this.themeColor]) .effectStrategy(SymbolEffectStrategy.HIERARCHICAL) .alignSelf(ItemAlign.Center) .margin({ left: 15 }) } else { Image(item.img as Resource) .height(22) .alignSelf(ItemAlign.Center) .fillColor(this.themeColor) .margin({ left: 15 }) } // 菜单标题 Text(item.title) .margin({ left: 10, right: 20 }) .fontSize(15) .fontColor(this.isTextSelected(index) ? this.themeColor : $r('app.color.index_tab_font_color')) .fontWeight(480) Blank() // 右侧箭头 Image($r('app.media.arrow_right')) .width(22) .height(22) .margin({ left: 20, right: 0 }) .align(Alignment.Center) } .width('100%') .height(55) } .backgroundColor(Color.Transparent) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .onClick(async () => { // 根据 item.id 跳转或切换功能 switch (item.id) { case MainViewModel.MENU_MUSIC: this.mType = 0 this.modeType = 0 this.currentSongListID = '' this.doShowDrawer() break case MainViewModel.MENU_FIND: this.mType = 8 this.modeType = 0 this.currentSongListID = '' this.doShowDrawer() break case MainViewModel.MENU_FILE_SCAN: this.mType = 2 this.doShowDrawer() break case MainViewModel.MENU_CHARTS: this.mType = 3 this.modeType = 0 this.doShowDrawer() break case MainViewModel.MENU_MIEDIA_KU: this.modeType = 1 this.mType = 0 this.currentSongListID = '' this.doShowDrawer() break case MainViewModel.MENU_MIEDIA_ARTIST: this.modeType = 2 this.mType = 0 this.currentSongListID = '' this.doShowDrawer() break case MainViewModel.MENU_MIEDIA_ALBUM: this.modeType = 3 this.mType = 0 this.currentSongListID = '' this.doShowDrawer() break case MainViewModel.MENU_SETTING: this.mType = 4 this.doShowDrawer() break case MainViewModel.MENU_VIP: router.pushUrl({ url: 'pages/VipPage' }, router.RouterMode.Single); break case MainViewModel.MENU_USER: this.mType = 1 this.doShowDrawer() break case MainViewModel.MENU_NET_CONNECT: this.mType = 1 this.doShowDrawer() break case MainViewModel.MENU_SIMI: router.pushUrl({ url: 'pages/VerifyPage' }); break case MainViewModel.MENU_DUTY: router.pushUrl({ url: 'pages/WebIndex', params: { titleName: '用户协议', webUrl: CommonConstants.NEW_DUTY } }); break case MainViewModel.MENU_HAOPING: Utility.gotoMarket(getContext(this) as common.UIAbilityContext, this.bundleName) break case MainViewModel.MENU_ABOUT: this.mType = 5 this.doShowDrawer() break case MainViewModel.MENU_UPDATE: AlertDialog.show({ title: '版本更新', message: '当前版本为最新版本:' + this.versionName, autoCancel: true, alignment: DialogAlignment.Center, offset: { dx: 0, dy: -20 }, //在Y轴方向上的编译量 confirm: { value: '确定', fontColor: Color.White, backgroundColor: $r('app.color.title_bar_bg'), action: () => { } } }) break case MainViewModel.MENU_YSZC: router.pushUrl({ url: 'pages/WebIndex', params: { titleName: '隐私政策', webUrl: CommonConstants.NEW_YS_HW } }); break case MainViewModel.MENU_SHARE: gotoShare(this.context,this.appName,this.bundleName) break } }) } .transition(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 600, curve: Curve.Ease, delay: 60 * index })) .width('90%') .height(55) }) } isTextSelected(index:number):boolean{ if(this.mType == 0){ return this.modeType == index } if (this.mType == 8) { return index == 4 } if (this.mType == 2) { return index == 5 } if (this.mType == 3) { return index == 6 } if (this.mType == 5) { return index == 7 } return false } doShowDrawer(){ if (!this.isBigScreen()) { this.getUIContext()?.animateTo({ duration: 555 }, () => { this.isShowDrawer = false }) } } @Builder updateSheet() { Scroll() { Column() { OnlineUpdateLog() } } .width('100%') .height('100%') } // /** // * ================== 穿山甲广告相关 ================== // */ // /** Banner广告对象 */ // private declare bannerAd: CSJNativeExpressAd; // /** 广告加载状态 */ // @State private status: string = "未加载" // /** 是否展示广告 */ // @State private isShowAd: boolean = false // /** 广告位配置信息 */ // private declare mAdSlot: AdSlot; // /** 服务端bidding广告内容(可选) */ // private mBiddingAdm = '' // /** 广告加载监听器 */ // private expressLoadAdListener: NativeExpressAdListener = { // /** // * 广告加载失败回调 // */ // onError: (code: number, message: string) => { // console.error("加载广告失败,code=" + code + ",message=" + message); // this.status = "加载广告失败,code=" + code + ",message=" + message; // }, // /** // * 广告加载成功回调 // * @param ads 返回的广告列表 // */ // onNativeExpressAdLoad: (ads: ArrayList) => { // console.log("BannerExpressAdPage==onNativeExpressAdLoad......success"); // if (ads && ads.length > 0) { // ads.forEach((ad: CSJNativeExpressAd, idx: number) => { // // 设置广告交互监听 // ad.setExpressInteractionListener({ // /** 广告点击回调 */ // onAdClicked: (type: number) => { // console.log("BannerExpressAdPage==onAdClicked......"); // }, // /** 广告展示回调 */ // onAdShow: (type: number) => { // console.log("BannerExpressAdPage==onAdShow......"); // }, // /** 模板渲染失败回调 */ // onRenderFail: (code: number, msg: string) => { // console.log("BannerExpressAdPage==onRenderFail...code=" + code + ",msg=" + msg); // }, // /** 模板渲染成功回调 */ // onRenderSuccess: (width: number, height: number) => { // console.log("BannerExpressAdPage==onRenderSuccess......width=" + width + ",height=" + height); // this.bannerAd = ad; // this.status = "广告加载完成待展示"; // this.showBannerAd() // } // }) // // 设置广告轮播时间 // ad.setSlideIntervalTime(30 * 1000);//设置轮播时间长 // this.setDislikeCallback(ad); //设置dislike // ad.render(this.getUIContext()) // 开始渲染广告 // this.status = "广告加载中..."; // }); // } // } // } // // /** // * 设置广告 dislike 回调 // * @param ad 广告对象 // */ // private setDislikeCallback(ad: CSJNativeExpressAd) { // ad.setDislikeCallback({ // /** dislike 弹窗显示 */ // onShow: () => { // console.log("BannerExpressAdPage==dislike......show"); // }, // /** 选择 dislike 选项 */ // onSelected: (position: number, value: string, enforceRemove: boolean) => { // this.isShowAd = false; // console.log("BannerExpressAdPage==dislike......onSelected:position=" + position + ",value:" + value + ",enforce=" + enforceRemove); // }, // /** 点击取消 dislike */ // onCancel: () => { // console.log("BannerExpressAdPage==dislike......onCancel"); // } // }) // } // // /** // * 加载Banner广告 // * @param rit 广告位ID // */ // loadBannerAd(rit: string) { // // 仅在满足条件时加载广告 // if(!Utility.isNoble()){ // return // } // if(!Utility.isPassInstallTime(2)){ // return // } // this.isShowAd = false; // let adCreator:CSJAdCreator = CSJAdSdk.getAdCreator() // this.mAdSlot = new AdSlotBuilder() // .setCodeId(rit) // .setAcceptSize(CSJUtil.BANNER_WIGTH, CSJUtil.BANNER_HEIGHT) // .setAdCount(1) // .setBidAdm(this.mBiddingAdm) // .build() // PrintBiddingTokenUtils.printBiddingToken(this.mAdSlot, adCreator); // adCreator.loadBannerAd(this.mAdSlot, this.expressLoadAdListener) // this.status = "广告加载中..." // } @StorageProp('currentHeightBreakpoint') currentHeightBreakpoint: HeightBreakpoint | undefined = HeightBreakpoint.HEIGHT_LG; @StorageProp('currentWidthBreakpoint') currentWidthBreakpoint: WidthBreakpoint | undefined = WidthBreakpoint.WIDTH_SM; @StorageProp('topRectHeight') topRectHeight: number = px2vp(AppUtil.getStatusBarHeight()); //是否是Pura X外屏,或者小屏幕 isPuraWP() { return this.currentWidthBreakpoint === WidthBreakpoint.WIDTH_SM && this.currentHeightBreakpoint === HeightBreakpoint.HEIGHT_MD } //是否是Pura X外屏,或者小屏幕或者手机横屏 isCoverOpacity() { if (this.isPuraWP()) { return true } if (this.isPhoneLan()) { return true } if (deviceInfo.marketName.includes('Pura X') && this.currentHeightBreakpoint === HeightBreakpoint.HEIGHT_SM) { return true } return false } //是不是手机横屏 isPhoneLan() { if (DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE && this.currentBreakpoint !== BreakpointTypeEnum.SM) { if (DisplayUtil.getFoldStatus() === 0) { return true } } return false } isLoginChange() { this.refreshUserInfoState(); } /** * 构建歌单tab内容 */ @Builder buildPlaylistTab() { // 创建歌单按钮 ListItem() { Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { SymbolGlyph($r('sys.symbol.plus_circle')) .fontSize(22) .fontColor([this.themeColor]) .effectStrategy(SymbolEffectStrategy.HIERARCHICAL) .alignSelf(ItemAlign.Center) .margin({ left: 25 }) Text('创建歌单') .margin({ left: 10, right: 20 }) .fontSize(14) .fontColor($r('app.color.index_tab_font_color')) .fontWeight(480) Blank() Image($r('app.media.arrow_right')) .width(22) .height(22) .margin({ left: 20, right: 0 }) .align(Alignment.Center) } .width('100%') .height(55) } .backgroundColor(Color.Transparent) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(() => { this.showCreatePlaylistDialog() }) } // 歌单列表 ForEach(this.playlistList, (playlist: Playlist,index:number) => { ListItem() { Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { Image(playlist.coverPath || $r('app.media.hm_playlist')) .width(22) .height(22) .margin({ left: 25 }) .borderRadius(4) .fillColor(this.themeColor) .clip(true) Column() { Text(playlist.name) .margin({ left: 10, right: 20 }) .fontSize(15) .fontColor($r('app.color.index_tab_font_color')) .fontWeight(480) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Text(`${playlist.songCount}首`) .margin({ left: 10, right: 20 }) .fontSize(12) .fontColor($r('app.color.index_tab_font_color')) .opacity(0.7) } .alignItems(HorizontalAlign.Start) Blank() Image($r('app.media.arrow_right')) .width(22) .height(22) .margin({ left: 20, right: 0 }) .align(Alignment.Center) } .width('100%') .height(55) } .backgroundColor(Color.Transparent) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) .onClick(async () => { let targetPlaylist: Playlist = playlist; try { if (this.playlistTable) { const latestPlaylist = await this.playlistTable.queryPlaylistById(playlist.id); if (latestPlaylist) { targetPlaylist = latestPlaylist; const targetIndex = this.playlistList.findIndex((item: Playlist) => item.id === latestPlaylist.id); if (targetIndex >= 0) { this.playlistList[targetIndex] = latestPlaylist; this.playlistList = [...this.playlistList]; } } } } catch (error) { LogUtil.warn('heanup NewIndex', `点击歌单时刷新最新数据失败: ${(error as Error).message}`); } if(targetPlaylist.songCount==0){ this.openPlaylist(targetPlaylist) }else{ this.doShowDrawer(); this.mType = 0 this.modeType = 4 this.currentSongListName = targetPlaylist.name; this.currentSongListID = targetPlaylist.id; } }) .bindContextMenu(this.MenuBuilder(playlist), ResponseType.LongPress, { preview: MenuPreviewMode.IMAGE, previewAnimationOptions: { scale: [0.8, 1.0] }, }) .bindContextMenu(this.MenuBuilder(playlist), ResponseType.RightClick, { preview: MenuPreviewMode.IMAGE, previewAnimationOptions: { scale: [0.8, 1.0] }, }) } .transition(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 600, curve: Curve.Ease, delay: 60 * index })) }) } /** * 构建网盘tab内容 - 从数据库获取所有网盘账户 * 支持多种类型的网盘账户(目前支持WebDAV,将来可扩展其他类型) */ @Builder buildCloudStorageTab() { // 当前只支持网盘账户,将来可以在这里添加其他类型的网盘账户 // 例如:OneDrive, Google Drive, Dropbox等 // 添加新账户按钮 ListItem() { Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { SymbolGlyph($r('sys.symbol.plus_circle')) .fontSize(22) .fontColor([this.themeColor]) .effectStrategy(SymbolEffectStrategy.HIERARCHICAL) .alignSelf(ItemAlign.Center) .margin({ left: 25 }) Text(`添加${getRemoteDriveAccountLabel()}`) .margin({ left: 10, right: 20 }) .fontSize(14) .fontColor($r('app.color.index_tab_font_color')) .fontWeight(480) Blank() Image($r('app.media.arrow_right')) .width(22) .height(22) .margin({ left: 20, right: 0 }) .align(Alignment.Center) } .width('100%') .height(55) } .backgroundColor(Color.Transparent) .bindMenu(this.AddMenuBuilder) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 }) } // 显示所有网盘账户 ForEach(this.webDavAccounts, (account: WebDavAccount,index:number) => { ListItem() { Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { // 账户封面或默认图标 Stack() { Image(account.coverPath?account.coverPath:getCloudDiskIcon(account.webType)) .width(25) .height(25) .borderRadius(4) .objectFit(ImageFit.Cover) } .margin({ left: 20 }) Column() { // 账户名称 Text(account.name) .margin({ left: 8, right: 20 }) .fontSize(15) .fontColor(this.selectedAccount==account?this.themeColor:$r('app.color.text_color')) .fontWeight(480) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row() { // 账户类型标签 Text(getRemoteDriveDisplayLabel(account.webType)) .fontSize(10) .fontColor(this.selectedAccount==account?this.themeColor:$r('app.color.index_tab_font_color')) .opacity(0.8) .padding({ left: 8, top: 2, bottom: 2 }) .borderRadius(3) // 服务器地址 Text(`@${account.isUseLocalHost ? account.localHost : account.host}:${account.port}`) .fontSize(12) .fontColor(this.selectedAccount==account?this.themeColor:$r('app.color.index_tab_font_color')) .opacity(0.7) .maxLines(1) .padding({ right: 8, top: 2, bottom: 2 }) .textOverflow({ overflow: TextOverflow.Ellipsis }) } } .alignItems(HorizontalAlign.Start) Blank() } .width('100%') .height(60) } .backgroundColor(Color.Transparent) .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 }) .onClick(() => { console.info('heanup', '点击网盘账户:', account.name) this.selectWebDavAccount(account) }) .bindContextMenu(this.MenuDavBuilder(account), ResponseType.LongPress, { preview: MenuPreviewMode.IMAGE, previewAnimationOptions: { scale: [0.8, 1.0] }, }) .bindContextMenu(this.MenuDavBuilder(account), ResponseType.RightClick, { preview: MenuPreviewMode.IMAGE, previewAnimationOptions: { scale: [0.8, 1.0] }, }) } .transition(TransitionEffect.move(TransitionEdge.BOTTOM) .animation({ duration: 600, curve: Curve.Ease, delay: 60 * index })) }) // 如果没有账户,显示提示信息 if (this.webDavAccounts.length === 0) { ListItem() { Text(`暂无${getRemoteDriveAccountLabel()},点击上方按钮添加`) .fontSize(14) .fontColor($r('app.color.index_tab_font_color')) .opacity(0.6) .textAlign(TextAlign.Center) .width('100%') .padding(20) } } } @Builder AddMenuBuilder() { Menu() { MenuItem({ startIcon: $r('app.media.webdav'), content: $r('app.string.webdav') }) .onClick(async () => { this.showRemoteDriveAccountDialog(false,undefined,RemoteDriveType.WebDav) }) MenuItem({ startIcon: $r('app.media.smb'), content: $r('app.string.smb') }) .onClick(async () => { this.showRemoteDriveAccountDialog(false,undefined,RemoteDriveType.Smb) }) MenuItem({ startIcon: $r('app.media.ftp'), content: $r('app.string.ftp') }) .onClick(async () => { this.showRemoteDriveAccountDialog(false,undefined,RemoteDriveType.Ftp) }) MenuItem({ startIcon: $r('app.media.navidrome'), content: $r('app.string.navidrome') }) .onClick(async () => { this.showRemoteDriveAccountDialog(false,undefined,RemoteDriveType.Navidrome) }) MenuItem({ startIcon: $r('app.media.jellyfin'), content: 'Jellyfin' }) .onClick(async () => { this.showRemoteDriveAccountDialog(false, undefined, RemoteDriveType.Jellyfin) }) MenuItem({ startIcon: $r('app.media.emby'), content: $r('app.string.emby') }) .onClick(async () => { this.showRemoteDriveAccountDialog(false, undefined, RemoteDriveType.Emby) }) MenuItem({ startIcon: $r('app.media.audio_Station'), content: 'AudioStation' }) .onClick(async () => { this.showRemoteDriveAccountDialog(false, undefined, RemoteDriveType.AudioStation) }) MenuItem({ startIcon: $r('app.media.plex'), content: 'Plex' }) .onClick(async () => { this.showRemoteDriveAccountDialog(false, undefined, RemoteDriveType.Plex) }) MenuItem({ startIcon: $r('app.media.daoliyu'), content: '道理鱼' }) .onClick(async () => { this.showRemoteDriveAccountDialog(false, undefined, RemoteDriveType.DaoLiYu) }) MenuItem({ startIcon: $r('app.media.baiduwp'), content: '百度网盘' }) .onClick(async () => { this.showRemoteDriveAccountDialog(false, undefined, RemoteDriveType.Baidu) }) }.attributeModifier(new MenuModifier()) } @Builder MenuBuilder(playlist: Playlist) { Menu(){ MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.square_and_pencil')), content: '编辑歌单' }) .onClick(async() => { this.openPlaylist(playlist) }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.trash')), content: '删除歌单' }) .onClick(async() => { this.deletePlaylist(playlist) }) } } @Builder MenuDavBuilder(account: WebDavAccount) { Menu(){ MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.square_and_pencil')), content: '编辑' }) .onClick(async() => { this.showRemoteDriveAccountDialog(true,account) }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.arrow_up_to_line')), content: '置顶' }) .onClick(async() => { try { await this.webdavManager.pinAccount(account) // 重新加载网盘账户列表以更新UI排序 await this.loadWebDavAccounts() ToastUtil.showToast('置顶成功') LogUtil.info('heanup NewIndex', '网盘账户置顶成功:', account.name) this.logAccount('info', `网盘账户置顶成功: ${account.name}`) } catch (error) { LogUtil.error('heanup NewIndex', `置顶网盘账户失败: ${(error as Error).message}`) ToastUtil.showToast('置顶失败') this.logAccount('error', `置顶网盘账户失败: ${(error as Error).message}`) } }) MenuItem({ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.trash')), content: '删除' }) .onClick(async() => { this.deleteWebDavAccount(account) }) } } /** * 删除网盘账户 */ async deleteWebDavAccount(account: WebDavAccount) { try { // 显示确认对话框 this.getUIContext().showAlertDialog({ title: '删除账户', message: `确定要删除webdav账户"${account.name}"吗?`, primaryButton: { value: '取消', action: () => {} }, secondaryButton: { value: '删除', fontColor: Color.Red, action: async () => { try { await this.webdavManager.removeAccount(account) ToastUtil.showToast('网盘账户删除成功') // 重新加载网盘账户列表 await this.loadWebDavAccounts() LogUtil.info('heanup NewIndex', '网盘账户删除成功:', account.name) this.logAccount('info', `网盘账户删除成功: ${account.name}`) } catch (error) { LogUtil.error('heanup NewIndex', `删除网盘账户失败: ${(error as Error).message}`) ToastUtil.showToast('删除失败') this.logAccount('error', `删除网盘账户失败: ${(error as Error).message}`) } } } }) } catch (error) { LogUtil.error('heanup NewIndex', `删除网盘账户操作失败: ${(error as Error).message}`) ToastUtil.showToast('操作失败') this.logAccount('error', `删除网盘账户操作失败: ${(error as Error).message}`) } } /** * 删除歌单 */ async deletePlaylist(playlist: Playlist) { if (playlist&&this.playlistTable) { // 显示确认对话框 AlertDialog.show({ title: '删除歌单', message: `确定要删除歌单"${playlist.name}"吗?此操作不可撤销。`, primaryButton: { value: '取消', action: () => {} }, secondaryButton: { value: '删除', fontColor: Color.Red, action: async () => { if (this.playlistTable) { const success = await this.playlistTable.deletePlaylist(playlist.id) if (success) { ToastUtil.showToast('歌单删除成功') // 发送刷新事件 emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH }, {}) } else { ToastUtil.showToast('歌单删除失败') } } } } }) } } /** * 显示创建歌单对话框 */ showCreatePlaylistDialog() { if (!this.playlistTable) { ToastUtil.showToast('歌单功能初始化中,请稍后再试') return } // 使用新的歌单对话框,支持自定义封面 showCreatePlaylistDialog( async (name: string, description: string, coverPath?: string) => { // 创建歌单 const success = await this.playlistTable!.createPlaylist(name, description, coverPath) if (success) { ToastUtil.showToast('歌单创建成功') // 刷新歌单列表 await this.loadPlaylistList() // 发送歌单刷新事件 emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH }, {}) } else { ToastUtil.showToast('歌单创建失败') } }, () => { // 取消创建歌单 } ) } /** * 打开歌单详情 */ openPlaylist(playlist: Playlist) { try { // 跳转到歌单详情页面 router.pushUrl({ url: 'pages/PlaylistDetailPage', params: { playlist: playlist } }).catch((err: Error) => { console.error('跳转到歌单详情页面失败:', err.message); ToastUtil.showToast('打开歌单失败'); }); } catch (error) { console.error('打开歌单失败:', error); ToastUtil.showToast('打开歌单失败'); } } /** * 初始化歌单数据库 */ async initPlaylistTable() { try { this.playlistTable = new PlaylistTable(this.context) console.info('onecold 歌单数据库初始化成功') // 等待数据库初始化完成后再加载数据 setTimeout(async () => { await this.loadPlaylistList() }, 500) // 延迟500ms确保数据库初始化完成 } catch (error) { console.error('初始化歌单数据库失败:', error) } } /** * 加载歌单列表 */ async loadPlaylistList() { try { LogUtil.info('heanup NewIndex', '开始加载歌单列表') if (this.playlistTable) { const playlists = await this.playlistTable.queryAllPlaylists() // 强制触发UI更新 this.playlistList = playlists.slice() LogUtil.info('heanup NewIndex', `成功加载 ${playlists.length} 个歌单`) } else { LogUtil.warn('heanup NewIndex', '歌单表未初始化') } this.isRefreshing = false } catch (error) { LogUtil.error('heanup NewIndex', `加载歌单列表失败: ${(error as Error).message}`) this.isRefreshing = false } } /** * 初始化WebDAV管理器 */ private logAccount(level: 'info' | 'warn' | 'error', message: string) { const tag = 'AccountCenter'; switch (level) { case 'info': void ServerLogUtil.info(tag, message); break; case 'warn': void ServerLogUtil.warn(tag, message); break; case 'error': void ServerLogUtil.error(tag, message); break; default: break; } } private logHiCar(level: 'info' | 'warn' | 'error', message: string) { const tag = 'HiCarEntry'; switch (level) { case 'info': void ServerLogUtil.info(tag, message); break; case 'warn': void ServerLogUtil.warn(tag, message); break; case 'error': void ServerLogUtil.error(tag, message); break; default: break; } } async initWebDavManager() { try { // 设置WebDAV管理器的上下文 this.webdavManager.setContext(this.context) // 创建网盘账户表 await this.webdavManager.createWebDavTableInDB() // 订阅WebDAV管理器事件 this.webdavManager.subscribe((event: string) => { LogUtil.info('heanup NewIndex', '收到WebDAV事件:', event) this.logAccount('info', `收到WebDAV事件: ${event}`) if (event === 'QueryAccountsSucceed') { this.webDavAccounts = this.webdavManager.getAllWebDavAccounts().slice() } }) // 首页EntryAblility去加载网盘账户了。所以这边不加载 // await this.loadWebDavAccounts() LogUtil.info('heanup NewIndex', 'WebDAV管理器初始化成功') this.logAccount('info', 'WebDAV管理器初始化成功') } catch (error) { LogUtil.error('heanup NewIndex', `初始化WebDAV管理器失败: ${(error as Error).message}`) this.logAccount('error', `初始化WebDAV管理器失败: ${(error as Error).message}`) } } /** * 加载网盘账户列表 */ async loadWebDavAccounts() { try { LogUtil.info('heanup NewIndex', '开始加载网盘账户列表') this.logAccount('info', '开始加载网盘账户列表') await this.webdavManager.queryWebDavAccountsFromDB() // 强制触发UI更新 this.webDavAccounts = this.webdavManager.getAllWebDavAccounts().slice() this.isRefreshing = false LogUtil.info('heanup NewIndex', `成功加载 ${this.webDavAccounts.length} 个网盘账户`) this.logAccount('info', `成功加载 ${this.webDavAccounts.length} 个网盘账户`) } catch (error) { this.isRefreshing = false LogUtil.error('heanup NewIndex', `加载网盘账户列表失败: ${(error as Error).message}`) this.logAccount('error', `加载网盘账户列表失败: ${(error as Error).message}`) } } /** * 选择网盘账户 */ selectWebDavAccount(account: WebDavAccount) { try { LogUtil.info('heanup NewIndex', '选择网盘账户:', account.name) this.logAccount('info', `选择网盘账户: ${account.name} (id=${account.id ?? 'unknown'}, webType=${account.webType})`) // 关键修复:立即设置 selectedAccount,确保后续使用的是正确的账户 this.selectedAccount = account console.info('heanup selectWebDavAccount account.webType='+account.webType) // 如果账户未激活,先激活它 if (!account.isActivate) { // 先将所有账户设为未激活 this.webDavAccounts.forEach(acc => { acc.isActivate = false }) // 立即激活选中的账户(不等待数据库) account.isActivate = true // 异步更新数据库中的激活状态(不阻塞UI) this.webdavManager.editAccount(account).then(() => { LogUtil.info('heanup NewIndex', '网盘账户激活成功:', account.name) this.logAccount('info', `网盘账户激活成功: ${account.name}`) }).catch((error: Error) => { LogUtil.error('heanup NewIndex', `编辑网盘账户失败: ${error.message}`) // 不显示Toast,避免干扰用户 this.logAccount('error', `编辑网盘账户失败: ${error.message}`) }) } // 根据账户类型切换到对应页面 if (account.webType === RemoteDriveType.Navidrome || account.webType === RemoteDriveType.Jellyfin || account.webType === RemoteDriveType.Emby || account.webType === RemoteDriveType.AudioStation || account.webType === RemoteDriveType.Plex || account.webType === RemoteDriveType.DaoLiYu) { this.mType = 7 LogUtil.info('heanup NewIndex', `切换到 Navidrome/Jellyfin/Emby/AudioStation/Plex/道理鱼 页面,webType=${account.webType}`) } else { this.mType = 6 LogUtil.info('heanup NewIndex', `切换到 WebDAV 页面,webType=${account.webType}`) } this.doShowDrawer() // 打印最终确认 LogUtil.info('heanup NewIndex', `选择完成: selectedAccount=${this.selectedAccount.name}, webType=${this.selectedAccount.webType}, mType=${this.mType}`) } catch (error) { LogUtil.error('heanup NewIndex', `选择网盘账户失败: ${(error as Error).message}`) ToastUtil.showToast('选择账户失败') this.logAccount('error', `选择网盘账户失败: ${(error as Error).message}`) } } /** * 显示添加网盘账户对话框 */ @State addDavDialogId:number = 1 showRemoteDriveAccountDialog(isEditMode?: boolean,account?: WebDavAccount,driveType?:number) { const node: FrameNode | null = this.getUIContext().getFrameNodeById("test_text") || null; this.getUIContext().getPromptAction().openCustomDialog({ builder: () => { this.webDavAccountBuilder(isEditMode,account,driveType) }, levelMode: LevelMode.EMBEDDED, // 启用页面级弹出框 levelUniqueId: node?.getUniqueId(), // 设置页面级弹出框所在页面的任意节点ID immersiveMode: ImmersiveMode.EXTEND, // 设置页面级弹出框蒙层的显示模式 }).then((dialogId: number) => { this.addDavDialogId = dialogId; }) } @Builder webDavAccountBuilder(isEditMode?: boolean,account?: WebDavAccount,driveType?:number) { RemoteDriveAccountDialog({ isEditMode: isEditMode, account: account, initialDriveType: driveType, onCancel: () => { // 取消添加 this.getUIContext().getPromptAction().closeCustomDialog(this.addDavDialogId) }, onConfirm: (account: WebDavAccount) => { if(this.webDavAccounts.length>=1&&!Utility.isNoble()&&!isEditMode){ ToastUtil.showToast('普通用户只能添加一个网盘账户,请开通会员') return } this.getUIContext().getPromptAction().closeCustomDialog(this.addDavDialogId) if (isEditMode && account?.id) { // 编辑模式:更新现有账户 this.webdavManager.editAccount(account).then(() => { ToastUtil.showToast('修改成功') // 重新加载网盘账户列表,观察着模式去更新了,所以这里不需要更新,先注释掉 // this.loadWebDavAccounts() LogUtil.info('heanup NewIndex', '网盘账户修改成功:', account.name) this.logAccount('info', `网盘账户修改成功: ${account.name}`) }).catch((error: Error) => { LogUtil.error('heanup NewIndex', `修改网盘账户失败: ${error.message}`) ToastUtil.showToast('修改失败') this.logAccount('error', `修改网盘账户失败: ${error.message}`) }) } else { // 添加模式:创建新账户 this.webdavManager.insertAccount( account.name, account.host, account.localHost, account.isUseLocalHost, account.port, account.filepath, account.lyricFilePath, account.uploadFilePath, account.imageFilePath, account.account, account.password, account.enableHttps, account.coverPath, account.webType, account.smbShare, account.smbDomain, account.navidromeBasePath, account.jellyfinBasePath, account.embyBasePath, account.ftpEncoding, account.baiduAccessToken, account.baiduRefreshToken, account.baiduTokenExpiresAt ).then(() => { ToastUtil.showToast('添加成功') // 重新加载网盘账户列表 观察着模式去更新了,所以这里不需要更新,先注释掉 // this.loadWebDavAccounts() LogUtil.info('heanup NewIndex', '网盘账户添加成功:', account.name) this.logAccount('info', `网盘账户添加成功: ${account.name}`) }).catch((error: Error) => { LogUtil.error('heanup NewIndex', `添加网盘账户失败: ${error.message}`) ToastUtil.showToast('添加失败') this.logAccount('error', `添加网盘账户失败: ${error.message}`) }) } } }); } } // 1. 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体 function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string { if (isDarkMode) { // 深色模式下返回更深的灰色或半透明黑色 return `rgba(34,34,34,${alpha + 0.2 > 1 ? 1 : alpha + 0.2})`; } const color = themeColor.replace('#', ''); const r = parseInt(color.substring(0, 2), 16); const g = parseInt(color.substring(2, 4), 16); const b = parseInt(color.substring(4, 6), 16); return `rgba(${r},${g},${b},${alpha})`; } // 定义接口 interface HiCarAspectRatio { context: Context; playlistId: string; }