WebDavMainPage.ets 23 KB

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