NewIndex.ets 55 KB

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