WebDavMainPage.ets 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. import { WebdavManager } from '../common/util/WebdavManager';
  2. import { WebDavAccount } from '../viewmodel/WebDavAccount';
  3. import { Song } from '../viewmodel/Song';
  4. import { WebdavManagerStates } from '../common/enums/WebdavManagerStates';
  5. import Logger from '../common/util/Logger';
  6. import { promptAction, router, window } from '@kit.ArkUI';
  7. import { CommonConstants } from '../common/constants/CommonConstants';
  8. import { VideoItem } from '../viewmodel/VideoItem';
  9. import { GlobalContext } from '../common/util/GlobalContext';
  10. import { display } from '@kit.ArkUI';
  11. import { FileInfo } from '../viewmodel/FileInfo';
  12. import { emitter } from '@kit.BasicServicesKit';
  13. import { EventConstants } from '../common/constants/EventConstants';
  14. import { LazyDataSource } from '../common/util/LazyDataSource';
  15. /**
  16. * 歌单播放事件数据
  17. */
  18. interface PlaylistEventData {
  19. playlistId: string;
  20. playlistName: string;
  21. songCount: number;
  22. startIndex: number;
  23. songFilePaths: string[];
  24. // 移除webDavAuthInfo,现在直接使用videoItems中的webdav_account_id
  25. }
  26. const TAG = 'heanup WebDavMainPage';
  27. // URL解码函数
  28. function decodeUrlEncodedString(encodedStr: string): string {
  29. try {
  30. return decodeURIComponent(encodedStr);
  31. } catch (error) {
  32. // 如果解码失败,返回原始字符串
  33. return encodedStr;
  34. }
  35. }
  36. @Preview
  37. @Entry
  38. @Component
  39. export struct WebDavMainPage {
  40. @State webdavManager: WebdavManager = WebdavManager.getInstance();
  41. @State accounts: WebDavAccount[] = [];
  42. @Prop @Watch('onSwitchAccount') selectedAccount: WebDavAccount;
  43. @State songs: VideoItem[] = [];
  44. @State dataSource:LazyDataSource<VideoItem> = new LazyDataSource(this.songs)
  45. @Link mType: number;
  46. @Link offsetX: number;
  47. @Link isShowDrawer: boolean;
  48. @State isLoading: boolean = false;
  49. @State webDavFiles: FileInfo[] = []; // 直接在页面中保存文件列表副本
  50. @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
  51. @StorageProp('isDarkMode') isDarkMode: boolean = false;
  52. @State topRectHeight: number = 0; // 顶部安全区高度
  53. @State breadcrumbs:string[] = []//面包屑导航
  54. @State visibleFoldersState: FileInfo[] = []; // 改为普通状态变量
  55. async onSwitchAccount(){
  56. console.log('onecold 切换账户:', this.selectedAccount.name);
  57. this.songs = [];
  58. this.visibleFoldersState = [];
  59. this.updateListData(this.songs)
  60. // 注意:不再需要清空全局上下文,因为LocalMusic已改用实例变量缓存
  61. // 当新账户加载时,新的认证信息会自动覆盖旧的
  62. Logger.info(TAG, 'heanup 切换账户,准备加载新账户文件');
  63. this.isLoading = true;
  64. await this.webdavManager.loadFilesInfoFromAccount(this.selectedAccount)
  65. .catch((error: Error) => {
  66. Logger.error(TAG, '加载文件失败: ' + error.message);
  67. this.isLoading = false;
  68. });
  69. this.breadcrumbs = this.webdavManager.getBreadcrumbs();
  70. }
  71. updateListData(mList:Array<VideoItem>){
  72. this.dataSource.pushArrayData(mList)
  73. }
  74. // 更新可见文件夹列表
  75. private updateVisibleFolders(): void {
  76. try {
  77. // 安全检查webDavFiles
  78. if (!this.webDavFiles || !Array.isArray(this.webDavFiles)) {
  79. this.visibleFoldersState = [];
  80. return;
  81. }
  82. const allFolders = this.webDavFiles.filter(f => f.isDirectory);
  83. const visible: FileInfo[] = [];
  84. for (let i = 0; i < allFolders.length; i++) {
  85. const folder = allFolders[i];
  86. // 安全检查folder对象
  87. if (!folder || typeof folder.fileName !== 'string') {
  88. continue;
  89. }
  90. let shouldShow = this.isDirectChildOfCurrentPath(folder);
  91. if (shouldShow) {
  92. visible.push(folder);
  93. }
  94. }
  95. console.log('更新文件夹列表:', visible);
  96. this.visibleFoldersState = visible;
  97. } catch (error) {
  98. Logger.error(TAG, '更新文件夹列表失败:', error.toString());
  99. this.visibleFoldersState = [];
  100. }
  101. }
  102. // 对话框控制器
  103. private accountDialogController: CustomDialogController | null = null;
  104. // 保存事件处理器引用,用于取消订阅
  105. private eventHandler: (event: string) => void = (event: string) => {
  106. this.handleWebdavEvent(event);
  107. };
  108. aboutToAppear(): void {
  109. // 获取顶部安全区高度
  110. this.getTopRectHeight();
  111. // 加载账户列表
  112. this.loadAccounts();
  113. this.loadFiles()
  114. this.breadcrumbs = this.webdavManager.getBreadcrumbs();
  115. // 订阅WebDAV状态变化
  116. this.webdavManager.subscribe(this.eventHandler);
  117. }
  118. // 获取顶部安全区高度
  119. private getTopRectHeight(): void {
  120. window.getLastWindow(getContext(this), (err, data) => {
  121. if (err.code) {
  122. Logger.error(TAG, '获取窗口失败: ' + JSON.stringify(err));
  123. return;
  124. }
  125. const area = data.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
  126. this.topRectHeight = px2vp(area.topRect.height);
  127. Logger.info(TAG, '顶部安全区高度: ' + this.topRectHeight);
  128. });
  129. }
  130. aboutToDisappear(): void {
  131. // 取消订阅
  132. this.webdavManager.unsubscribe(this.eventHandler);
  133. }
  134. // 处理WebDAV事件
  135. private handleWebdavEvent(event: string): void {
  136. switch (event) {
  137. case WebdavManagerStates.LoadFilesInfoSucceed:
  138. this.songs = this.webdavManager.webDavSongs;
  139. this.updateListData(this.songs)
  140. // 直接引用webdavManager的数组,避免@Observed序列化问题
  141. this.webDavFiles = this.webdavManager.webDavFiles;
  142. this.isLoading = false;
  143. // 更新可见文件夹列表
  144. this.updateVisibleFolders();
  145. promptAction.showToast({
  146. message: '加载成功: ' + this.webDavFiles.filter(f => f.isDirectory).length + '个文件夹, ' + this.songs.length + '首歌曲'
  147. });
  148. break;
  149. case WebdavManagerStates.LoadFilesInfoFailed:
  150. this.isLoading = false;
  151. this.getUIContext().getPromptAction().showToast({ message: '加载失败' });
  152. break;
  153. case WebdavManagerStates.InsertAccountSucceed:
  154. case WebdavManagerStates.EditAccountSucceed:
  155. case WebdavManagerStates.RemoveAccountSucceed:
  156. this.loadAccounts();
  157. break;
  158. }
  159. }
  160. // 加载账户列表
  161. private loadAccounts(): void {
  162. this.accounts = this.webdavManager.getAllWebDavAccounts();
  163. }
  164. // 加载文件列表
  165. private loadFiles(): void {
  166. if (!this.selectedAccount) {
  167. this.getUIContext().getPromptAction().showToast({ message: '请先选择账户' });
  168. return;
  169. }
  170. this.isLoading = true;
  171. this.webdavManager.loadFilesInfoFromWebdav()
  172. .catch((error: Error) => {
  173. Logger.error(TAG, '加载文件失败: ' + error.message);
  174. this.isLoading = false;
  175. });
  176. }
  177. // 进入文件夹
  178. private enterFolder(folder: FileInfo): void {
  179. this.isLoading = true;
  180. this.webdavManager.enterFolder(folder)
  181. .catch((error: Error) => {
  182. Logger.error(TAG, '进入文件夹失败: ' + error.message);
  183. this.isLoading = false;
  184. });
  185. }
  186. // 检查是否为当前目录的直接子项
  187. private isDirectChildOfCurrentPath(folder: FileInfo): boolean {
  188. const currentPath = this.webdavManager.currentPath || '';
  189. // 如果在根目录(currentPath为空或'/'),显示所有第一级文件夹
  190. if (currentPath === '' || currentPath === '/') {
  191. const folderPath = folder.href.replace(/\/$/, ''); // 去掉尾部斜杠
  192. return folder.href.startsWith('/') &&
  193. folder.href !== '/' &&
  194. !folderPath.substring(1).includes('/');
  195. }
  196. // 非根目录情况,计算相对路径
  197. let relativePath = folder.href;
  198. if (currentPath !== '/') {
  199. relativePath = folder.href.replace(currentPath, '');
  200. }
  201. relativePath = relativePath.replace(/^\//, '').replace(/\/$/, '');
  202. // 只有相对路径不为空且不包含/时才认为是直接子项
  203. return relativePath !== '' && !relativePath.includes('/');
  204. }
  205. // 返回上级目录
  206. private goBack(): void {
  207. if(this.webdavManager.canGoBack()){
  208. this.isLoading = true;
  209. this.webdavManager.goBack()
  210. .catch((error: Error) => {
  211. Logger.error(TAG, '返回失败: ' + error.message);
  212. this.isLoading = false;
  213. });
  214. this.breadcrumbs = this.webdavManager.getBreadcrumbs();
  215. }
  216. }
  217. // 切换账户
  218. private switchAccount(account: WebDavAccount): void {
  219. this.selectedAccount = account;
  220. this.songs = [];
  221. this.updateListData(this.songs)
  222. }
  223. // 播放WebDAV歌曲
  224. private playSong(song: VideoItem, index: number): void {
  225. try {
  226. Logger.info(TAG, `heanup === WebDAV playSong 方法被调用 ===`);
  227. Logger.info(TAG, 'heanup 播放指定歌曲: ' + song.name + ', 索引: ' + index);
  228. Logger.info(TAG, 'heanup 歌曲列表长度: ' + this.songs.length);
  229. // 检查歌曲是否有webdav_account_id
  230. Logger.info(TAG, `heanup 播放歌曲的webdav_account_id: ${song.webdav_account_id || '未设置'}`);
  231. // 确保所有歌曲都设置了正确的webdav_account_id
  232. if (this.selectedAccount && this.selectedAccount.id) {
  233. const accountIds = this.songs.map(s => s.webdav_account_id).filter(id => id);
  234. Logger.info(TAG, `heanup 当前歌曲列表中有 ${accountIds.length}/${this.songs.length} 首歌曲设置了webdav_account_id`);
  235. // 如果发现歌曲缺少webdav_account_id,立即设置
  236. this.songs.forEach((item, idx) => {
  237. if (!item.webdav_account_id) {
  238. item.webdav_account_id = this.selectedAccount!.id.toString();
  239. Logger.info(TAG, `heanup 为歌曲 "${item.name}" 设置webdav_account_id: ${item.webdav_account_id}`);
  240. }
  241. });
  242. }
  243. // 直接使用当前的VideoItem数组
  244. const videoItems: VideoItem[] = this.songs;
  245. const songFilePaths: string[] = [];
  246. for (let i = 0; i < this.songs.length; i++) {
  247. const item = this.songs[i];
  248. songFilePaths.push(item.filePath); // 使用filePath作为文件路径
  249. }
  250. // 保存WebDAV歌曲数据到全局上下文
  251. const globalContext = GlobalContext.getContext();
  252. globalContext.setObject('videoItems', videoItems);
  253. globalContext.setObject('currentPlayIndex', index);
  254. Logger.info(TAG, 'heanup 已将WebDAV歌曲列表保存到全局上下文,长度:' + videoItems.length);
  255. // 发送播放事件,类似歌单播放的方式
  256. const eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY };
  257. const playlistData: PlaylistEventData = {
  258. playlistId: 'webdav-playlist', // 使用特殊的ID标识WebDAV播放列表
  259. playlistName: 'WebDAV - ' + (this.selectedAccount?.name || '未知账户'),
  260. songCount: this.songs.length,
  261. startIndex: index,
  262. songFilePaths: songFilePaths
  263. // 移除webDavAuthInfo,现在直接使用videoItems中的webdav_account_id
  264. };
  265. Logger.info(TAG, 'heanup 准备发送WebDAV播放事件,eventId: ' + eventPlaylistPlay.eventId);
  266. Logger.info(TAG, 'heanup 发送WebDAV播放事件数据: ' + JSON.stringify(playlistData));
  267. const eventData: emitter.EventData = {
  268. data: playlistData
  269. };
  270. emitter.emit(eventPlaylistPlay, eventData);
  271. // 跳转到首页播放器
  272. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  273. this.mType =0
  274. })
  275. } catch (error) {
  276. const err = error as Error;
  277. Logger.error(TAG, '播放歌曲失败: ' + err.message);
  278. this.getUIContext().getPromptAction().showToast({ message: '播放失败' });
  279. }
  280. }
  281. // 导航到指定层级的面包屑路径
  282. private navigateToBreadcrumb(breadcrumbIndex: number): void {
  283. try {
  284. this.isLoading = true;
  285. this.webdavManager.navigateToBreadcrumb(breadcrumbIndex).then((path: string) => {
  286. this.webdavManager.enterFolderFromPath(path)
  287. this.breadcrumbs = this.webdavManager.getBreadcrumbs();
  288. })
  289. .catch((error: Error) => {
  290. Logger.error(TAG, '导航到面包屑路径失败: ' + error.message);
  291. this.isLoading = false;
  292. });
  293. } catch (error) {
  294. Logger.error(TAG, '导航到面包屑路径异常: ' + (error as Error).message);
  295. this.isLoading = false;
  296. }
  297. }
  298. build() {
  299. Column() {
  300. // 顶部安全区和标题栏
  301. Column() {
  302. Blank()
  303. .height(this.topRectHeight + 5)
  304. .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)
  305. .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
  306. // 标题栏
  307. Row() {
  308. Image($r('app.media.menu'))
  309. .width(24)
  310. .height(24)
  311. .margin({ left: 12, right: 8 })
  312. .onClick(() => {
  313. this.getUIContext()?.animateTo({ duration: 555 }, () => {
  314. this.isShowDrawer = !this.isShowDrawer
  315. this.offsetX = 0
  316. })
  317. });
  318. Text(this.selectedAccount.name || 'WebDav')
  319. .fontSize(18)
  320. .fontColor(Color.White)
  321. .fontWeight(FontWeight.Medium)
  322. .textAlign(TextAlign.Center)
  323. }
  324. .height(48)
  325. .width('100%')
  326. .alignItems(VerticalAlign.Center)
  327. .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)
  328. }
  329. // 内容区域
  330. if (this.accounts.length === 0) {
  331. this.buildEmptyView();
  332. } else {
  333. this.buildContentView();
  334. }
  335. }
  336. .width('100%')
  337. .height('100%')
  338. .backgroundColor($r('app.color.start_window_background'))
  339. }
  340. // 空状态视图
  341. @Builder
  342. buildEmptyView() {
  343. Column({ space: 20 }) {
  344. Image($r('app.media.cloudDisk'))
  345. .width(120)
  346. .height(120)
  347. .opacity(0.3)
  348. Text('暂无WebDAV账户')
  349. .fontSize(16)
  350. .fontColor($r('app.color.index_tab_font_color'))
  351. .opacity(0.6)
  352. }
  353. .justifyContent(FlexAlign.Center)
  354. .width('100%')
  355. .layoutWeight(1)
  356. }
  357. // 内容视图
  358. @Builder
  359. buildContentView() {
  360. Column() {
  361. // 加载按钮和面包屑导航
  362. Column({ space: 8 }) {
  363. // 账户信息显示
  364. // if (this.selectedAccount) {
  365. // Row({ space: 12 }) {
  366. // // 账户封面
  367. // Stack() {
  368. // if (this.selectedAccount.coverPath) {
  369. // Image(this.selectedAccount.coverPath)
  370. // .width(20)
  371. // .height(20)
  372. // .borderRadius(10)
  373. // .objectFit(ImageFit.Cover)
  374. // .border({ width: 2, color: this.themeColor })
  375. // } else {
  376. //
  377. // Image($r('app.media.cloudDisk'))
  378. // .width(20)
  379. // .height(20)
  380. // .fillColor(this.themeColor)
  381. // }
  382. // }
  383. //
  384. // // 账户信息
  385. // Column({ space: 4 }) {
  386. // Text(this.selectedAccount.name || '未知账户')
  387. // .fontSize(16)
  388. // .fontWeight(FontWeight.Medium)
  389. // .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color'))
  390. // .maxLines(1)
  391. // .textOverflow({ overflow: TextOverflow.Ellipsis })
  392. //
  393. // Text(`${this.selectedAccount.host}:${this.selectedAccount.port}`)
  394. // .fontSize(12)
  395. // .fontColor(this.isDarkMode ? '#8E8E93' : $r('app.color.index_tab_font_color'))
  396. // .opacity(0.7)
  397. // .maxLines(1)
  398. // .textOverflow({ overflow: TextOverflow.Ellipsis })
  399. // }
  400. // .alignItems(HorizontalAlign.Start)
  401. // .layoutWeight(1)
  402. //
  403. // }
  404. // .width('100%')
  405. // .padding({ left: 4, right: 4, top: 8, bottom: 8 })
  406. // .backgroundColor(this.isDarkMode ? 'rgba(44,44,46,0.8)' : 'rgba(248,248,248,0.8)')
  407. // .borderRadius(8)
  408. // .margin({ bottom: 8 })
  409. // }
  410. //
  411. // 面包屑导航
  412. if (this.webdavManager.currentPath !== '') {
  413. Row({ space: 8 }) {
  414. Button({ type: ButtonType.Circle }) {
  415. Image(this.webdavManager.canGoBack()?$r('app.media.back'):$r('app.media.cloudDisk'))
  416. .width(15)
  417. .height(15)
  418. .fillColor(Color.White)
  419. }
  420. .width(20)
  421. .height(20)
  422. .backgroundColor(this.themeColor)
  423. .onClick(() => this.goBack())
  424. Row({ space: 4 }) {
  425. ForEach(this.breadcrumbs, (crumb: string, index: number) => {
  426. Row() {
  427. Text(crumb)
  428. .fontSize(15)
  429. .fontColor(this.themeColor)
  430. .maxLines(1)
  431. .textOverflow({ overflow: TextOverflow.Ellipsis })
  432. }
  433. .onClick(() => {
  434. this.navigateToBreadcrumb(index);
  435. })
  436. // 添加分隔符(除了最后一个元素)
  437. if (index < this.breadcrumbs.length - 1) {
  438. Text('/')
  439. .fontSize(15)
  440. .fontColor($r('app.color.index_tab_font_color'))
  441. .opacity(0.6)
  442. }
  443. })
  444. }
  445. .layoutWeight(1)
  446. }
  447. .width('100%')
  448. .padding({ left: 4, right: 4 })
  449. }
  450. // 统计信息
  451. if (this.webDavFiles.length > 0) {
  452. Row() {
  453. Text(this.visibleFoldersState.length + ' 个文件夹, ' + this.songs.length + ' 首歌曲')
  454. .fontSize(13)
  455. .fontColor($r('app.color.index_tab_font_color'))
  456. .opacity(0.6)
  457. .layoutWeight(1)
  458. .textAlign(TextAlign.Start)
  459. Blank()
  460. }
  461. .padding({ left: 4, right: 4 })
  462. }
  463. }
  464. .width('100%')
  465. .padding(12)
  466. .margin({ top: 8 })
  467. // 加载状态
  468. Row() {
  469. LoadingProgress()
  470. .width(30)
  471. .height(30)
  472. .color(this.themeColor)
  473. Text('加载中...')
  474. .fontSize(14)
  475. .fontColor($r('app.color.index_tab_font_color'))
  476. .margin({ left: 12 })
  477. }
  478. .padding(20)
  479. .visibility(this.isLoading?Visibility.Visible:Visibility.None)
  480. .opacity(this.isLoading ? 1 : 0)
  481. .animation({
  482. duration: 500,
  483. curve: 'ease-in-out' // 可选动画曲线
  484. })
  485. // 文件列表(文件夹 + 歌曲)
  486. if (this.webDavFiles.length > 0) {
  487. List({ space: 0 }) {
  488. // 显示文件夹 - 只显示当前目录下的直接子文件夹
  489. ForEach(this.visibleFoldersState, (folder: FileInfo) => {
  490. ListItem() {
  491. this.buildFolderItem(folder)
  492. }
  493. .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 600 }),
  494. TransitionEffect.scale({ x: 0, y: 0 })))
  495. .clickEffect({ level: ClickEffectLevel.MIDDLE })
  496. })
  497. // 显示歌曲
  498. LazyForEach(this.dataSource, (song: VideoItem, index: number) => {
  499. ListItem() {
  500. this.buildSongItem(song, index)
  501. }
  502. .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 600 }),
  503. TransitionEffect.scale({ x: 0, y: 0 })))
  504. .clickEffect({ level: ClickEffectLevel.MIDDLE })
  505. })
  506. }
  507. .layoutWeight(1)
  508. .divider({ strokeWidth: 1, color: this.isDarkMode ? '#333333' :'#EEEEEE' })
  509. .margin({ top: 4 })
  510. } else if (!this.isLoading) {
  511. Column() {
  512. Text('暂无内容')
  513. .fontSize(14)
  514. .fontColor($r('app.color.index_tab_font_color'))
  515. .opacity(0.6)
  516. Text('点击"加载文件列表"按钮加载')
  517. .fontSize(12)
  518. .fontColor($r('app.color.index_tab_font_color'))
  519. .opacity(0.4)
  520. .margin({ top: 8 })
  521. }
  522. .justifyContent(FlexAlign.Center)
  523. .layoutWeight(1)
  524. }
  525. }
  526. .layoutWeight(1)
  527. }
  528. // 文件夹列表项
  529. @Builder
  530. buildFolderItem(folder: FileInfo) {
  531. Button({ type: ButtonType.Normal, stateEffect: false }) {
  532. Row({ space: 2 }) {
  533. SymbolGlyph($r('sys.symbol.folder'))
  534. .fontSize(48)
  535. .fontColor([this.themeColor])
  536. .alignSelf(ItemAlign.Center)
  537. .margin({ left: 8, right: 6 })
  538. // 文件夹信息
  539. Column({ space: 4 }) {
  540. Text(decodeUrlEncodedString(folder.fileName.replace('/', '')))
  541. .fontSize(15)
  542. .fontColor($r('app.color.index_tab_font_color'))
  543. .maxLines(1)
  544. .textOverflow({ overflow: TextOverflow.Ellipsis })
  545. Text('文件夹')
  546. .fontSize(13)
  547. .fontColor($r('app.color.index_tab_font_color'))
  548. .opacity(0.6)
  549. }
  550. .alignItems(HorizontalAlign.Start)
  551. .layoutWeight(1)
  552. }
  553. }
  554. .reuseId('dir_item')
  555. .width('100%')
  556. .padding(12)
  557. .backgroundColor(Color.Transparent)
  558. // .backgroundColor($r('app.color.start_window_background'))
  559. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
  560. .onClick(() => {
  561. this.enterFolder(folder);
  562. this.breadcrumbs = this.webdavManager.getBreadcrumbs();
  563. })
  564. }
  565. // 歌曲列表项
  566. @Builder
  567. buildSongItem(song: VideoItem, index: number) {
  568. Button({ type: ButtonType.Normal, stateEffect: false }) {
  569. Row({ space: 12 }) {
  570. // 序号
  571. // 歌曲封面
  572. Image(song.pixelMap)
  573. .width(48)
  574. .height(48)
  575. .borderRadius(4)
  576. .alt($r('app.media.music_red'))
  577. .fillColor(this.themeColor)
  578. .objectFit(ImageFit.Cover)
  579. .margin({ left: 8 })
  580. // 歌曲信息
  581. Column({ space: 4 }) {
  582. Text(decodeUrlEncodedString(song.name))
  583. .fontSize(15)
  584. .fontColor($r('app.color.index_tab_font_color'))
  585. .maxLines(1)
  586. .textOverflow({ overflow: TextOverflow.Ellipsis })
  587. Row(){
  588. Text(song.artist)
  589. .fontSize(13)
  590. .fontColor($r('app.color.index_tab_font_color'))
  591. .opacity(0.6)
  592. .maxLines(1)
  593. .visibility(song.artist?Visibility.Visible:Visibility.None)
  594. .textOverflow({ overflow: TextOverflow.Ellipsis })
  595. Text(decodeUrlEncodedString(song.size||"")+' '+song.cTime)
  596. .fontSize(13)
  597. .fontColor($r('app.color.index_tab_font_color'))
  598. .opacity(0.6)
  599. .maxLines(1)
  600. .textOverflow({ overflow: TextOverflow.Ellipsis })
  601. }
  602. }
  603. .alignItems(HorizontalAlign.Start)
  604. .layoutWeight(1)
  605. // 播放图标
  606. Image($r('app.media.ic_play'))
  607. .width(20)
  608. .height(20)
  609. .fillColor($r('app.color.index_tab_font_color'))
  610. .opacity(0.4)
  611. }
  612. }
  613. .width('100%')
  614. .padding(12)
  615. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
  616. .backgroundColor(Color.Transparent)
  617. .onClick(() => {
  618. this.playSong(song, index);
  619. })
  620. }
  621. }