| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671 |
- import { BreadcrumbItem, RemoteDriveManager } from '../common/util/RemoteDriveManager';
- import { WebDavAccount } from '../viewmodel/WebDavAccount';
- import { Song } from '../viewmodel/Song';
- import { RemoteDriveManagerStates } from '../common/enums/RemoteDriveManagerStates';
- import Logger from '../common/util/Logger';
- import { promptAction, SymbolGlyphModifier, router, window } from '@kit.ArkUI';
- import { CommonConstants } from '../common/constants/CommonConstants';
- import { VideoItem } from '../viewmodel/VideoItem';
- import { GlobalContext } from '../common/util/GlobalContext';
- import { display } from '@kit.ArkUI';
- import { FileInfo } from '../viewmodel/FileInfo';
- import { emitter } from '@kit.BasicServicesKit';
- import { EventConstants } from '../common/constants/EventConstants';
- import { LazyDataSource } from '../common/util/LazyDataSource';
- import { ArrayUtil, PreferencesUtil, StrUtil } from '@pura/harmony-utils';
- import { DialogHelper } from '@pura/harmony-dialog';
- import { ButtonFancyModifier,
- MenuModifier,
- ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil';
- import { getRemoteDriveAccountLabel, getRemoteDriveDisplayLabel, getRemoteDrivePlaylistPrefix } from '../common/util/RemoteDriveLabel';
- import { RemoteDriveType } from '../common/enums/RemoteDriveType';
- import { Utility } from '../common/util/Utility';
- import { SettingPage } from './SettingPage';
- import { getCloudDiskIcon } from '../dialog/RemoteDriveAccountDialog';
- import { CreateFolderDialog } from '../dialog/CreateFolderDialog';
- /**
- * 歌单播放事件数据
- */
- interface PlaylistEventData {
- playlistId: string;
- playlistName: string;
- songCount: number;
- startIndex: number;
- isJump: boolean;
- songFilePaths: string[];
- // 移除webDavAuthInfo,现在直接使用videoItems中的webdav_account_id
- }
- interface WebDavMetadataUpdatePayload {
- filePath: string;
- pixelMapPath?: string;
- name?: string;
- artist?: string;
- }
- const TAG = 'heanup WebDavMainPage';
- // WebDAV歌曲数据全局内存存储
- let globalWebdavVideoItems: VideoItem[] = [];
- let globalWebdavCurrentPlayIndex: number = 0;
- // 导出函数供LocalMusic访问
- export function getWebdavVideoItems(): VideoItem[] {
- return globalWebdavVideoItems;
- }
- export function getWebdavCurrentPlayIndex(): number {
- return globalWebdavCurrentPlayIndex;
- }
- export function clearWebdavVideoItems(): void {
- globalWebdavVideoItems = [];
- globalWebdavCurrentPlayIndex = 0;
- }
- // URL解码函数
- function decodeUrlEncodedString(encodedStr: string): string {
- try {
- return decodeURIComponent(encodedStr);
- } catch (error) {
- // 如果解码失败,返回原始字符串
- return encodedStr;
- }
- }
- @Preview
- @Entry
- @Component
- export struct WebDavMainPage {
- @State isShowTitleBar: boolean = true //是否显示分类导航条
- private prevOffsetY: number = 0; // 记录上一次的Y轴偏移量
- @State autoHideTitle: boolean = true //滚动自动隐藏标题栏
- @State isCustomizeBg: boolean = false //自定义背景界面
- @StorageProp('isLandscape') isLandscape: boolean = false;
- @State blurValue: number = 0 //背景模糊
- @State bgBrightness: number = 0 //背景亮度
- @State customizeBgPath: string | undefined = '';
- private listScroller: ListScroller = new ListScroller()
- @StorageProp('bottomSafeHeight') bottomSafeHeight: number = 0;
- searchController: SearchController = new SearchController()
- @StorageProp('animationState') animationState: AnimationStatus= AnimationStatus.Running
- @State isNoJumpToHome: boolean = false //网盘播放不跳转首页
- @State isSearchMode: boolean = false
- @StorageLink('currentSong') currentSong: VideoItem | undefined = undefined;
- @StorageProp('topSafeHeight') topSafeHeight: number = 0;
- @State webdavManager: RemoteDriveManager = RemoteDriveManager.getInstance();
- @State accounts: WebDavAccount[] = [];
- @Prop @Watch('onSwitchAccount') selectedAccount: WebDavAccount;
- @State songs: VideoItem[] = [];
- @State dataSource:LazyDataSource<VideoItem> = new LazyDataSource(this.songs)
- @Link mType: number;
- @Link offsetX: number;
- @Link isShowDrawer: boolean;
- @State isLoading: boolean = false;
- @State webDavFiles: FileInfo[] = []; // 直接在页面中保存文件列表副本
- @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
- @StorageProp('isDarkMode') isDarkMode: boolean = false;
- @State breadcrumbs:BreadcrumbItem[] = []//面包屑导航
- @State visibleFoldersState: FileInfo[] = []; // 改为普通状态变量
- @State isShowFileName: boolean = false//是否显示文件名
- @State isLongNameRoLL: boolean = true//长歌名滚动
- @State sortType: number = 0 //默认排序方式
- @State listRefreshKey: number = 0 // 列表刷新标识
- @Consume isMultiSelect: boolean
- @State selectedSongs: VideoItem[] = []
- @State isAllSelected: boolean = false
- @State isDeletingSelection: boolean = false
- private readonly metadataUpdateEvent: emitter.InnerEvent = { eventId: EventConstants.EVENT_WEBDAV_METADATA_UPDATED };
- async onSwitchAccount(){
- console.log('onecold 切换账户:', this.selectedAccount.name);
- this.songs = [];
- this.visibleFoldersState = [];
- this.updateListData(this.songs)
- this.exitMultiSelect();
- // 注意:不再需要清空全局上下文,因为LocalMusic已改用实例变量缓存
- // 当新账户加载时,新的认证信息会自动覆盖旧的
- Logger.info(TAG, 'heanup 切换账户,准备加载新账户文件');
-
- this.isLoading = true;
- await this.webdavManager.loadFilesInfoFromAccount(this.selectedAccount)
- .catch((error: Error) => {
- Logger.error(TAG, '加载文件失败: ' + error.message);
- this.isLoading = false;
- });
- this.breadcrumbs = this.webdavManager.getBreadcrumbs();
- }
- updateListData(mList:Array<VideoItem>, noSort?: boolean){
- if (!noSort) {
- this.sortType = PreferencesUtil.getNumberSync('webDavSortType', 0)
- this.doSortType(this.sortType)
- }
- this.dataSource.pushArrayData(mList)
- if(mList.length > 0){
- setTimeout(() => {
- this.listScroller.scrollToIndex(0)
- },200)
- }
- }
- // 更新可见文件夹列表
- private updateVisibleFolders(): void {
- try {
- // 安全检查webDavFiles
- if (!this.webDavFiles || !Array.isArray(this.webDavFiles)) {
- this.visibleFoldersState = [];
- return;
- }
- const allFolders = this.webDavFiles.filter(f => f.isDirectory);
- const isNavAccount = this.selectedAccount?.webType === RemoteDriveType.Navidrome;
- if (isNavAccount) {
- const sortedNavFolders = allFolders.sort((a, b) => a.fileName.localeCompare(b.fileName));
- this.visibleFoldersState = sortedNavFolders;
- return;
- }
- const visible: FileInfo[] = [];
- for (let i = 0; i < allFolders.length; i++) {
- const folder = allFolders[i];
- // 安全检查folder对象
- if (!folder || typeof folder.fileName !== 'string') {
- continue;
- }
- let shouldShow = this.isDirectChildOfCurrentPath(folder);
- if (shouldShow) {
- visible.push(folder);
- }
- }
- console.log('更新文件夹列表:', visible);
- this.visibleFoldersState = visible;
- } catch (error) {
- Logger.error(TAG, '更新文件夹列表失败:', error.toString());
- this.visibleFoldersState = [];
- }
- this.isShowTitleBar = true
- }
- private isSongSelected(song: VideoItem): boolean {
- return this.selectedSongs.some(item => item.filePath === song.filePath);
- }
- private enterMultiSelect(song: VideoItem): void {
- if (!this.isMultiSelect) {
- this.isMultiSelect = true;
- this.selectedSongs = [song];
- this.isAllSelected = this.selectedSongs.length === this.songs.length && this.songs.length > 0;
- return;
- }
- if (!this.isSongSelected(song)) {
- this.toggleSongSelection(song);
- }
- }
- private toggleSongSelection(song: VideoItem): void {
- if (!this.isMultiSelect) {
- this.isMultiSelect = true;
- }
- const exists = this.isSongSelected(song);
- if (exists) {
- const next = this.selectedSongs.filter(item => item.filePath !== song.filePath);
- this.selectedSongs = next;
- this.isAllSelected = next.length > 0 && next.length === this.songs.length;
- if (next.length === 0) {
- this.exitMultiSelect();
- }
- } else {
- const next = [...this.selectedSongs, song];
- this.selectedSongs = next;
- this.isAllSelected = next.length === this.songs.length && this.songs.length > 0;
- }
- }
- private handleCheckboxSelection(song: VideoItem, checked: boolean): void {
- if (!this.isMultiSelect) {
- this.isMultiSelect = true;
- }
- const exists = this.isSongSelected(song);
- if (checked && !exists) {
- this.toggleSongSelection(song);
- } else if (!checked && exists) {
- this.toggleSongSelection(song);
- }
- }
- private toggleSelectAll(): void {
- if (this.isAllSelected) {
- this.selectedSongs = [];
- this.isAllSelected = false;
- return;
- }
- if (!this.isMultiSelect) {
- this.isMultiSelect = true;
- }
- this.selectedSongs = this.songs.slice();
- this.isAllSelected = this.selectedSongs.length > 0 && this.selectedSongs.length === this.songs.length;
- }
- private exitMultiSelect(): void {
- this.isMultiSelect = false;
- this.selectedSongs = [];
- this.isAllSelected = false;
- console.info('onecold this.isMultiSelect '+this.isMultiSelect)
- }
- private syncSelectionAfterRefresh(): void {
- if (!this.isMultiSelect) {
- return;
- }
- const currentKeys: Set<string> = new Set(this.songs.map(item => item.filePath));
- const filtered = this.selectedSongs.filter(item => currentKeys.has(item.filePath));
- if (filtered.length !== this.selectedSongs.length) {
- this.selectedSongs = filtered;
- }
- this.isAllSelected = filtered.length > 0 && filtered.length === this.songs.length;
- if (filtered.length === 0) {
- this.exitMultiSelect();
- }
- }
- private confirmDeleteSelected(): void {
- if (!this.selectedAccount) {
- this.getUIContext().getPromptAction().showToast({ message: `请先选择${getRemoteDriveAccountLabel()}` });
- return;
- }
- if (!this.isMultiSelect || this.selectedSongs.length === 0) {
- this.getUIContext().getPromptAction().showToast({ message: '请先选择要删除的文件' });
- return;
- }
- this.getUIContext().showAlertDialog({
- title: '删除文件',
- message: `确定删除选中的${this.selectedSongs.length}个文件吗?`,
- primaryButton: {
- value: '取消',
- action: () => {}
- },
- secondaryButton: {
- value: '删除',
- fontColor: Color.Red,
- action: () => {
- void this.performDeleteSelected();
- }
- }
- });
- }
- private async performDeleteSelected(): Promise<void> {
- if (!this.selectedAccount || this.selectedSongs.length === 0) {
- return;
- }
- this.isDeletingSelection = true;
- try {
- await this.webdavManager.deleteRemoteSongs(this.selectedAccount, this.selectedSongs.slice());
- await this.webdavManager.loadFilesInfoFromAccount(this.selectedAccount, this.webdavManager.currentPath);
- this.exitMultiSelect();
- this.getUIContext().getPromptAction().showToast({ message: '删除成功' });
- } catch (error) {
- const err = error as Error;
- this.getUIContext().getPromptAction().showToast({ message: `删除失败: ${err.message}` });
- } finally {
- this.isDeletingSelection = false;
- }
- }
- private buildSongMetaLine(song: VideoItem): string {
- const parts: string[] = [];
- if (StrUtil.isNotEmpty(song.duration)) {
- parts.push(song.duration as string);
- } else if (StrUtil.isNotEmpty(song.size)) {
- parts.push(decodeUrlEncodedString(song.size as string));
- }
- if (parts.length === 0 && StrUtil.isNotEmpty(song.cTime)) {
- parts.push(song.cTime as string);
- }
- return parts.join(' · ');
- }
- // 对话框控制器
- private accountDialogController: CustomDialogController | null = null;
- // 保存事件处理器引用,用于取消订阅
- private eventHandler: (event: string) => void = (event: string) => {
- this.handleWebdavEvent(event);
- };
- initSetting(){
- this.isCustomizeBg = PreferencesUtil.getBooleanSync(SettingPage.IS_CUSTOMIZE_BG, false)
- this.customizeBgPath = PreferencesUtil.getStringSync(SettingPage.IS_CUSTOMIZE_BG_PATH, '')
- this.blurValue = PreferencesUtil.getNumberSync(SettingPage.CUSTOMIZE_BG_BLUR, 0)
- this.bgBrightness = PreferencesUtil.getNumberSync(SettingPage.BG_BRIGHTNESS, 0)
- this.isShowFileName = PreferencesUtil.getBooleanSync('isShowFileName', false)
- this.isLongNameRoLL = PreferencesUtil.getBooleanSync('isLongNameRoLL', true)
- this.sortType = PreferencesUtil.getNumberSync('webDavSortType', 0)
- this.isNoJumpToHome = PreferencesUtil.getBooleanSync('isNoJumpToHome', true)
- this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true)
- this.autoHideTitle = PreferencesUtil.getBooleanSync('autoHideTitle', true)
- }
- aboutToAppear(): void {
- this.initSetting()
- let eventSetting: emitter.InnerEvent = { eventId: 333 }
- // 监听广播事件(通用设置配置更新)
- emitter.on(eventSetting, (eventData: emitter.EventData) => {
- this.initSetting()
- });
- // 加载账户列表
- this.loadAccounts();
- this.loadFiles()
- this.breadcrumbs = this.webdavManager.getBreadcrumbs();
- // 订阅WebDAV状态变化
- this.webdavManager.subscribe(this.eventHandler);
- this.subscribeWebDavMetadataUpdates();
- }
- aboutToDisappear(): void {
- // 取消订阅
- this.webdavManager.unsubscribe(this.eventHandler);
- emitter.off(EventConstants.EVENT_WEBDAV_METADATA_UPDATED);
- }
- // 处理WebDAV事件
- private handleWebdavEvent(event: string): void {
- switch (event) {
- case RemoteDriveManagerStates.LoadFilesInfoSucceed:
- this.songs = this.webdavManager.webDavSongs;
- this.updateListData(this.songs)
- // 直接引用webdavManager的数组,避免@Observed序列化问题
- this.webDavFiles = this.webdavManager.webDavFiles;
- this.isLoading = false;
- // 更新可见文件夹列表
- this.updateVisibleFolders();
- this.breadcrumbs = this.webdavManager.getBreadcrumbs();
- this.syncSelectionAfterRefresh();
- // promptAction.showToast({
- // message: '加载成功: ' + this.webDavFiles.filter(f => f.isDirectory).length + '个文件夹, ' + this.songs.length + '首歌曲'
- // });
- break;
- case RemoteDriveManagerStates.LoadFilesInfoFailed:
- this.isLoading = false;
- // this.getUIContext().getPromptAction().showToast({ message: '加载失败' });
- break;
- case RemoteDriveManagerStates.InsertAccountSucceed:
- case RemoteDriveManagerStates.EditAccountSucceed:
- case RemoteDriveManagerStates.RemoveAccountSucceed:
- this.loadAccounts();
- break;
- }
- }
- private subscribeWebDavMetadataUpdates(): void {
- emitter.on(this.metadataUpdateEvent, (eventData: emitter.EventData) => {
- const payloads = eventData?.data as WebDavMetadataUpdatePayload[] | undefined;
- if (!payloads || payloads.length === 0) {
- Logger.info(TAG, 'WebDav metadata event received but payload empty');
- return;
- }
- Logger.info(TAG, `WebDav metadata event payload count: ${payloads.length}`);
- this.handleWebDavMetadataUpdates(payloads);
- });
- }
- private handleWebDavMetadataUpdates(payloads: WebDavMetadataUpdatePayload[]): void {
- if (!payloads || payloads.length === 0) {
- return;
- }
- Logger.info(TAG, 'handleWebDavMetadataUpdates start');
- let hasChanges = false;
- for (let i = 0; i < payloads.length; i++) {
- const payload = payloads[i];
- if (!payload || !payload.filePath) {
- continue;
- }
- Logger.info(TAG, `WebDav metadata detail path=${payload.pixelMapPath ?? 'null'}`);
- const songUpdated = this.updateSongMetadata(payload);
- if (songUpdated) {
- hasChanges = true;
- Logger.info(TAG, `metadata updated for ${payload.filePath}`);
- }
- }
- if (!hasChanges) {
- Logger.info(TAG, 'handleWebDavMetadataUpdates no changes detected');
- return;
- }
- const clonedSongs: VideoItem[] = [];
- for (let i = 0; i < this.songs.length; i++) {
- clonedSongs.push(this.cloneSong(this.songs[i]));
- }
- this.songs = clonedSongs;
- this.dataSource.pushArrayData(clonedSongs);
- this.listRefreshKey++;
- Logger.info(TAG, 'handleWebDavMetadataUpdates trigger refresh');
- }
- private updateSongMetadata(payload: WebDavMetadataUpdatePayload): boolean {
- if (!payload.filePath) {
- return false;
- }
- const targetIndex = this.findSongIndex(payload.filePath);
- if (targetIndex < 0) {
- return false;
- }
- const targetSong = this.songs[targetIndex];
- let mutated = false;
- if (payload.pixelMapPath && payload.pixelMapPath.length > 0) {
- targetSong.pixelMapPath = payload.pixelMapPath;
- mutated = true;
- }
- if (payload.name && payload.name.length > 0) {
- targetSong.name = payload.name;
- mutated = true;
- }
- if (payload.artist && payload.artist.length > 0) {
- targetSong.artist = payload.artist;
- mutated = true;
- }
- return mutated;
- }
- private findSongIndex(filePath: string): number {
- for (let i = 0; i < this.songs.length; i++) {
- if (this.songs[i].filePath === filePath) {
- return i;
- }
- }
- return -1;
- }
- private cloneSong(item: VideoItem): VideoItem {
- const clone = new VideoItem(
- item.name,
- item.id,
- item.filePath,
- item.type,
- item.videoSize,
- item.cTime,
- item.size,
- item.pixelMapPath,
- item.artist,
- item.album,
- item.fileName,
- item.lastPlayed
- );
- clone.duration = item.duration;
- clone.mimeType = item.mimeType;
- clone.trackCount = item.trackCount;
- clone.sampleRate = item.sampleRate;
- clone.size = item.size;
- clone.webdav_account_id = item.webdav_account_id;
- clone.remote_rel_path = item.remote_rel_path;
- clone.artist = item.artist;
- clone.album = item.album;
- clone.lyricContent = item.lyricContent;
- clone.pixelMapPath = item.pixelMapPath;
- clone.cTime = item.cTime;
- clone.fileName = item.fileName;
- clone.md5Str = item.md5Str;
- clone.bit_rate = item.bit_rate;
- clone.probe_score = item.probe_score;
- clone.year = item.year;
- clone.nb_streams = item.nb_streams;
- clone.nb_programs = item.nb_programs;
- clone.genre = item.genre;
- clone.track = item.track;
- clone.disc = item.disc;
- clone.channels = item.channels;
- clone.channel_layout = item.channel_layout;
- clone.start_time = item.start_time;
- clone.ALBUMARTIST = item.ALBUMARTIST;
- clone.COMPOSER = item.COMPOSER;
- clone.COMMENT = item.COMMENT;
- clone.LYRICIST = item.LYRICIST;
- clone.pyStr = item.pyStr;
- clone.extra_json = item.extra_json;
- clone.parentPath = item.parentPath;
- clone.isFav = item.isFav;
- clone.playCount = item.playCount;
- clone.lastPlayed = item.lastPlayed;
- clone.videoSize = item.videoSize;
- clone.isFav = item.isFav;
- return clone;
- }
- // 加载账户列表
- private loadAccounts(): void {
- this.accounts = this.webdavManager.getAllWebDavAccounts();
- }
- // 加载文件列表
- private loadFiles(): void {
- if (!this.selectedAccount) {
- this.getUIContext().getPromptAction().showToast({ message: '请先选择账户' });
- return;
- }
- this.isLoading = true;
- this.webdavManager.loadFilesInfoFromWebdav()
- .catch((error: Error) => {
- Logger.error(TAG, '加载文件失败: ' + error.message);
- this.isLoading = false;
- });
- }
- // 进入文件夹
- private enterFolder(folder: FileInfo): void {
- this.isLoading = true;
- this.webdavManager.enterFolder(folder)
- .catch((error: Error) => {
- Logger.error(TAG, '进入文件夹失败: ' + error.message);
- this.isLoading = false;
- });
- }
- // 检查是否为当前目录的直接子项
- private isDirectChildOfCurrentPath(folder: FileInfo): boolean {
- const currentPath = this.webdavManager.currentPath || '';
- // 如果在根目录(currentPath为空或'/'),显示所有第一级文件夹
- if (currentPath === '' || currentPath === '/') {
- const folderPath = folder.href.replace(/\/$/, ''); // 去掉尾部斜杠
- return folder.href.startsWith('/') &&
- folder.href !== '/' &&
- !folderPath.substring(1).includes('/');
- }
- // 非根目录情况,计算相对路径
- let relativePath = folder.href;
- if (currentPath !== '/') {
- relativePath = folder.href.replace(currentPath, '');
- }
- relativePath = relativePath.replace(/^\//, '').replace(/\/$/, '');
- // 只有相对路径不为空且不包含/时才认为是直接子项
- return relativePath !== '' && !relativePath.includes('/');
- }
- // 返回上级目录
- private goBack(): void {
- if(this.webdavManager.canGoBack()){
- this.isLoading = true;
- this.webdavManager.goBack()
- .catch((error: Error) => {
- Logger.error(TAG, '返回失败: ' + error.message);
- this.isLoading = false;
- });
- this.breadcrumbs = this.webdavManager.getBreadcrumbs();
- }
- }
- // 切换账户
- private switchAccount(account: WebDavAccount): void {
- this.selectedAccount = account;
- this.songs = [];
- this.updateListData(this.songs)
- }
- // 播放WebDAV歌曲
- private playSong(song: VideoItem, index: number,isJump:boolean=false): void {
- try {
- Logger.info(TAG, `heanup === WebDAV playSong 方法被调用 ===`);
- Logger.info(TAG, 'heanup 播放指定歌曲: ' + song.name + ', 索引: ' + index);
- Logger.info(TAG, 'heanup 歌曲列表长度: ' + this.songs.length);
- // 检查歌曲是否有webdav_account_id
- Logger.info(TAG, `heanup 播放歌曲的webdav_account_id: ${song.webdav_account_id || '未设置'}`);
- // 确保所有歌曲都设置了正确的webdav_account_id
- if (this.selectedAccount && this.selectedAccount.id) {
- const accountIds = this.songs.map(s => s.webdav_account_id).filter(id => id);
- Logger.info(TAG, `heanup 当前歌曲列表中有 ${accountIds.length}/${this.songs.length} 首歌曲设置了webdav_account_id`);
- // 如果发现歌曲缺少webdav_account_id,立即设置
- this.songs.forEach((item, idx) => {
- if (!item.webdav_account_id) {
- item.webdav_account_id = this.selectedAccount!.id.toString();
- Logger.info(TAG, `heanup 为歌曲 "${item.name}" 设置webdav_account_id: ${item.webdav_account_id}`);
- }
- });
- }
- // 直接使用当前的VideoItem数组
- const videoItems: VideoItem[] = this.songs;
- const songFilePaths: string[] = [];
- for (let i = 0; i < this.songs.length; i++) {
- const item = this.songs[i];
- songFilePaths.push(item.filePath); // 使用filePath作为文件路径
- }
- // 直接通过事件传递videoItems数据,不使用GlobalContext
- const eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY };
- const playlistData: PlaylistEventData = {
- playlistId: 'webdav-playlist', // 使用特殊的ID标识网盘播放列表
- playlistName: `${getRemoteDriveDisplayLabel(this.selectedAccount?.webType)} - ${this.selectedAccount?.name || '未知账户'}`,
- songCount: this.songs.length,
- startIndex: index,
- isJump: isJump,//设置true会弹出播放页
- songFilePaths: songFilePaths
- };
- // 保存videoItems到全局内存
- globalWebdavVideoItems = videoItems;
- globalWebdavCurrentPlayIndex = index;
- Logger.info(TAG, `heanup 保存WebDAV歌曲到全局内存,长度: ${videoItems.length}, 索引: ${index}`);
- // 发送播放请求事件,只传递索引信息
- emitter.emit(eventPlaylistPlay, { data: playlistData });
- Logger.info(TAG, `heanup 发送WebDAV播放事件,只传递索引: ${index}`);
- if(!this.isNoJumpToHome){
- // 跳转到首页播放器
- this.getUIContext()?.animateTo({ duration: 555 }, () => {
- this.mType = 0
- })
- }
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, '播放歌曲失败: ' + err.message);
- this.getUIContext().getPromptAction().showToast({ message: '播放失败' });
- }
- }
- /**
- * 一键创建歌单:将当前WebDAV歌曲全部加入新歌单
- */
- private async createPlaylistFromCurrentWebDav(): Promise<void> {
- try {
- Logger.info(TAG, 'heanup 一键创建歌单开始');
- if (!this.selectedAccount || !this.selectedAccount.id) {
- this.getUIContext().getPromptAction().showToast({ message: `请先选择${getRemoteDriveAccountLabel()}` });
- return;
- }
- if (!this.songs || this.songs.length === 0) {
- // 回退到manager内的歌曲(可能还未复制到页面state)
- this.songs = this.webdavManager.webDavSongs;
- Logger.info(TAG, `heanup 页面songs为空,回退webdavManager.webDavSongs,长度=${this.songs.length}`);
- if (this.songs.length === 0) {
- this.getUIContext().getPromptAction().showToast({ message: '当前目录没有可添加的歌曲' });
- return;
- }
- }
- Logger.info(TAG, `heanup 歌单创建前歌曲数量: ${this.songs.length}`);
- // 确保每首歌都有webdav_account_id
- for (let i = 0; i < this.songs.length; i++) {
- if (!this.songs[i].webdav_account_id) {
- this.songs[i].webdav_account_id = this.selectedAccount.id.toString();
- }
- }
- // 构建歌单名称:账户名 + 当前路径(简化)
- const rawPath = this.webdavManager.currentPath || '/';
- const shortPath = rawPath === '/' ? '根目录' : rawPath.replace(/\/$/, '').split('/').slice(-1)[0];
- const playlistName = `${getRemoteDrivePlaylistPrefix(this.selectedAccount.webType)}-${this.selectedAccount.name}-${shortPath}`;
- // 创建歌单
- // 安全获取HostContext
- const uiContext = this.getUIContext();
- const hostCtx = uiContext ? uiContext.getHostContext() : undefined;
- if (!hostCtx) {
- this.getUIContext().getPromptAction().showToast({ message: '无法获取上下文' });
- return;
- }
- const playlistModule = await import('../common/util/PlaylistTable');
- const mediaModule = await import('../common/util/MediaTable');
- const playlistTable = new playlistModule.default(hostCtx);
- const mediaTable = new mediaModule.default(hostCtx);
- // 等待MediaTable底层RDB初始化完成
- await new Promise<void>((resolve) => {
- mediaTable.getRdbStore(hostCtx, () => {
- Logger.info(TAG, 'heanup mediaTable RDB 初始化完成');
- resolve();
- });
- });
- // 先将WebDAV歌曲入库(若不存在)
- let upsertSuccess = 0;
- for (let i = 0; i < this.songs.length; i++) {
- const v = this.songs[i];
- if (!v.id || v.id === '') {
- // 使用filePath作为唯一ID
- v.id = v.filePath;
- }
- if (!v.parentPath) {
- const idxp = v.filePath.lastIndexOf('/');
- if (idxp > 0) {
- v.parentPath = v.filePath.substring(0, idxp);
- }
- }
- const ok = await mediaTable.upsertWebDavVideoItem(v);
- Logger.info(TAG, `heanup upsert 第${i+1}/${this.songs.length}首: ${v.filePath} => ${ok}`);
- if (ok) {
- upsertSuccess++;
- }
- }
- Logger.info(TAG, `heanup WebDAV歌曲入库完成: 成功 ${upsertSuccess}/${this.songs.length}`);
- // 先查询是否已有同名歌单,避免重复创建导致混淆
- const existing = (await playlistTable.queryAllPlaylists()).find(p => p.name === playlistName);
- if (existing) {
- this.getUIContext().getPromptAction().showToast({ message: '歌单已存在,直接追加歌曲' });
- const filePathsExist: string[] = this.songs.map(s => s.filePath);
- await playlistTable.addSongsToPlaylist(existing.id, filePathsExist);
- router.pushUrl({ url: 'pages/PlaylistDetailPage', params: { playlist: existing } });
- return;
- }
- Logger.info(TAG, `heanup 准备创建歌单: ${playlistName}`);
- const created = await playlistTable.createPlaylist(playlistName, `来自${getRemoteDriveDisplayLabel(this.selectedAccount.webType)}: ${this.selectedAccount.name} 路径: ${rawPath}`);
- if (!created) {
- this.getUIContext().getPromptAction().showToast({ message: '歌单创建失败' });
- return;
- }
- // 查询刚创建的歌单ID
- const playlists = await playlistTable.queryAllPlaylists();
- const target = playlists.reverse().find(p => p.name === playlistName); // 取最近创建的同名歌单
- if (!target) {
- this.getUIContext().getPromptAction().showToast({ message: '无法找到新建歌单' });
- return;
- }
- // 批量添加歌曲
- const filePaths: string[] = this.songs.map(s => s.filePath);
- Logger.info(TAG, `heanup 开始批量添加歌曲到歌单: ${target.id}`);
- const addResult = await playlistTable.addSongsToPlaylist(target.id, filePaths);
- Logger.info(TAG, `heanup 批量添加结果: ${addResult}`);
- if (!addResult) {
- Logger.warn(TAG, '批量添加歌曲返回false,可能全部已存在或写入失败');
- }
- this.getUIContext().getPromptAction().showToast({ message: `歌单创建成功: ${playlistName}` });
- Logger.info(TAG, `heanup 一键创建歌单成功: ${playlistName}, 添加 ${filePaths.length} 首歌曲`);
- // 跳转到歌单详情页面
- router.pushUrl({ url: 'pages/PlaylistDetailPage', params: { playlist: target } });
- } catch (error) {
- Logger.error(TAG, '一键创建歌单失败: ' + (error as Error).message);
- this.getUIContext().getPromptAction().showToast({ message: '一键创建歌单失败' });
- }
- }
- // 导航到指定层级的面包屑路径
- private async navigateToBreadcrumb(crumb: BreadcrumbItem): Promise<void> {
- if (!crumb) {
- return;
- }
- try {
- this.isLoading = true;
- await this.webdavManager.enterFolderFromPath(crumb.path);
- } catch (error) {
- Logger.error(TAG, '导航到面包屑路径失败: ' + (error as Error).message);
- this.isLoading = false;
- }
- }
- // 导航到上传页面
- private navigateToUploadPage(): void {
- if (!this.selectedAccount || !this.selectedAccount.id) {
- this.getUIContext().getPromptAction().showToast({ message: '请先选择账户' });
- return;
- }
-
- Logger.info(TAG, '导航到上传页面,账户ID: ' + this.selectedAccount.id);
- this.getUIContext().getRouter().pushUrl({
- url: 'pages/UploadMusicPage',
- params: {
- accountId: this.selectedAccount.id
- }
- }).catch((error: Error) => {
- Logger.error(TAG, '导航到上传页面失败: ' + error.message);
- this.getUIContext().getPromptAction().showToast({ message: '打开上传页面失败' });
- });
- }
- @Builder
- MoreMenuBuilder() {
- Menu() {
- MenuItem({
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.music_note_list')),
- content: $r('app.string.onekey_add_playlist')
- })
- .onClick(async () => {
- this.createPlaylistFromCurrentWebDav();
- })
- MenuItem({
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.arrow_up_to_line')),
- content: $r('app.string.upload_music_file')
- })
- .onClick(async () => {
- this.navigateToUploadPage();
- })
- MenuItem({
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.folder_badge_plus')),
- content: $r('app.string.create_foder')
- })
- .onClick(async () => {
- this.showCreateFolderDialog();
- })
- }.attributeModifier(new MenuModifier())
- }
- @Builder
- SortMenuBuilder() {
- Menu() {
- MenuItem({
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_up')),
- content: $r('app.string.sort_by_name')
- })
- .onClick(async () => {
- this.doSortType(0)
- PreferencesUtil.put("webDavSortType", 0)
- })
- MenuItem({
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_down')),
- content: '按名称降序'
- })
- .onClick(async () => {
- this.doSortType(1)
- PreferencesUtil.put("webDavSortType", 1)
- })
- MenuItem({
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.clock')),
- content:'按时间升序'
- })
- .onClick(async () => {
- this.doSortType(2)
- PreferencesUtil.put("webDavSortType", 2)
- })
- MenuItem({
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.timer')),
- content: '按时间降序'
- })
- .onClick(async () => {
- this.doSortType(3)
- PreferencesUtil.put("webDavSortType", 3)
- })
- MenuItem({
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_up')),
- content:'按大小升序'
- })
- .onClick(async () => {
- this.doSortType(4)
- PreferencesUtil.put("webDavSortType", 4)
- })
- MenuItem({
- symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_down')),
- content: '按大小降序'
- })
- .onClick(async () => {
- this.doSortType(5)
- PreferencesUtil.put("webDavSortType", 5)
- })
- }.attributeModifier(new MenuModifier())
- }
- doSortType(index: number) {
- // 对歌曲列表进行排序
- switch (index) {
- case 0:
- Utility.doSortListAscending(this.songs,this.isShowFileName)
- this.visibleFoldersState.sort((a, b) => {
- return a.fileName.localeCompare(b.fileName);
- });
- break;
- case 1:
- Utility.doSortListDescending(this.songs,this.isShowFileName)
- this.visibleFoldersState.sort((a, b) => {
- return b.fileName.localeCompare(a.fileName);
- });
- break;
- case 2:
- this.songs.sort((a, b) => {
- return a.cTime.localeCompare(b.cTime);
- });
- this.visibleFoldersState.sort((a, b) => {
- return a.time-b.time;
- });
- break;
- case 3:
- this.songs.sort((a, b) => {
- return b.cTime.localeCompare(a.cTime);
- });
- this.visibleFoldersState.sort((a, b) => {
- return b.time-a.time;
- });
- break;
- case 4:
- this.songs.sort((a, b) => {
- return a.videoSize - b.videoSize;
- });
- break;
- case 5:
- this.songs.sort((a, b) => {
- return b.videoSize - a.videoSize;
- });
- break;
- }
-
- this.updateListData(this.songs,true)
- }
- @Builder
- topTitleBar(){
- Column() {
- Row({ space: 15 }) {
- if (!this.isSearchMode ) {
- //左侧滑动按钮
- Button({ type: ButtonType.Circle, stateEffect: true }) {
- SymbolGlyph($r('sys.symbol.sort'))
- .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
- }
- .attributeModifier(new ButtonFancyModifier(40, 40))
- .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
- .animation({ duration: 300, curve: Curve.Ease })
- .onClick(() => {
- this.getUIContext().animateTo({ duration: 555 }, () => {
- // 动画闭包内控制Image组件的出现和消失
- this.isShowDrawer = !this.isShowDrawer
- this.offsetX = 0
- })
- })
- .attributeModifier(new ShadowModifier())
- .zIndex(0)
- Text(this.selectedAccount.name)
- .margin({left:3,right:10})
- .fontColor($r('app.color.text_color'))
- .fontSize(19)
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.MARQUEE })//超长滚动
- .layoutWeight(1)
- .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
- }else{
- //左侧搜索返回按钮
- Button({ type: ButtonType.Circle, stateEffect: true }) {
- SymbolGlyph($r('sys.symbol.chevron_left'))
- .attributeModifier(new SymbolGlyphFancyModifier(24, '', ''))
- }
- .attributeModifier(new ButtonFancyModifier(40, 40))
- .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
- .animation({ duration: 300, curve: Curve.Ease })
- .onClick(() => {
- this.isSearchMode = false
- this.onSearchInput('')
- })
- .attributeModifier(new ShadowModifier())
- .zIndex(0)
- }
- //搜索框
- Search({ controller: this.searchController,value: this.searchText, placeholder: '搜索标题、艺术家...' })
- .searchButton('搜索',{fontColor:this.themeColor})
- .searchIcon({
- src: $r('sys.media.ohos_ic_public_search_filled')
- })
- .cancelButton({
- style: CancelButtonStyle.CONSTANT,
- icon: {
- src: $r('sys.media.ohos_ic_public_cancel_filled')
- }
- })
- .layoutWeight(1)
- .height(35)
- .maxLength(20)
- .backgroundColor(this.isDarkMode?Color.Black:'#F5F5F5')
- .placeholderColor(Color.Grey)
- .placeholderFont({ size: 14, weight: 400 })
- .textFont({ size: 14, weight: 400 })
- .onSubmit((value: string) => {
- console.log('onecold onSubmit ='+value)
- this.searchController.stopEditing()
- this.onSearchInput(this.searchText);
- })
- .onChange((value: string) => {
- console.log('onecold onChange ='+value)
- this.onSearchInput(value);
- })
- .visibility(this.isSearchMode?Visibility.Visible:Visibility.None)
- .animation({ duration: 300, curve: Curve.Ease })
- //搜索按钮
- if (!this.isSearchMode) {
- Button({ type: ButtonType.Circle, stateEffect: true }) {
- SymbolGlyph($r('sys.symbol.magnifyingglass'))
- .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
- }
- .attributeModifier(new ButtonFancyModifier(40, 40))
- .animation({ duration: 300, curve: Curve.Ease })
- .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
- .attributeModifier(new ShadowModifier())
- .zIndex(0)
- .onClick(()=>{
- this.filteredFolderList = [...this.visibleFoldersState]; // 显示所有文件夹
- this.isSearchMode = true
- })
- //排序按钮
- Button({ type: ButtonType.Circle, stateEffect: true }) {
- SymbolGlyph($r('sys.symbol.list_number'))
- .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
- }
- .attributeModifier(new ButtonFancyModifier(40, 40))
- .animation({ duration: 300, curve: Curve.Ease })
- .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
- .bindMenu(this.SortMenuBuilder)
- .attributeModifier(new ShadowModifier())
- .zIndex(0)
- //添加/上传综合按钮
- Button({ type: ButtonType.Circle, stateEffect: true }) {
- SymbolGlyph($r('sys.symbol.plus'))
- .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
- }
- .attributeModifier(new ButtonFancyModifier(40, 40))
- .animation({ duration: 300, curve: Curve.Ease })
- .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
- .bindMenu(this.MoreMenuBuilder)
- .attributeModifier(new ShadowModifier())
- .zIndex(0)
- }
- }
- }
- .padding({ top: this.topSafeHeight + 10, left: 10, right: 10, bottom: 2 })
- .width('100%')
- }
- //搜索功能的实现
- @State searchText: string = ''; // 用户输入内容
- @State filteredList: Array<VideoItem> = []; // 过滤后的歌曲结果
- @State filteredFolderList: Array<FileInfo> = []; // 过滤后的文件夹结果
- // 实时搜索逻辑(带防抖)
- // 实时搜索逻辑(带防抖)
- private onSearchInput(value: string) {
- this.searchText = value.trim();
- let mSearchList: Array<VideoItem> = []
- mSearchList = this.songs
- // 新增条件判断:空输入时显示所有数据
- if (this.searchText === '') {
- this.filteredList = [...mSearchList]; // 创建新数组以触发状态更新
- this.filteredFolderList = [...this.visibleFoldersState]; // 显示所有文件夹
- } else {
- this.filteredList = mSearchList.filter((item: VideoItem) => {
- //支持模糊匹配和艺术家 专辑匹配
- const regex = new RegExp(this.searchText.toLowerCase().replace(/\s+/g, '.*'), 'i');
- return regex.test(item.name.toLowerCase())||
- regex.test(item.fileName?.toLowerCase() ?? "") ||
- regex.test(item.artist?.toLowerCase() ?? "") ||
- regex.test(item.album?.toLowerCase() ?? "")
- });
-
- // 对文件夹进行过滤
- this.filteredFolderList = this.visibleFoldersState.filter((folder: FileInfo) => {
- const regex = new RegExp(this.searchText.toLowerCase().replace(/\s+/g, '.*'), 'i');
- return regex.test(folder.fileName?.toLowerCase() ?? "") ||
- regex.test(decodeUrlEncodedString(folder.fileName?.replace('/', '') ?? "").toLowerCase());
- });
- }
- this.updateListData(this.filteredList);
- }
- build() {
- Stack() {
- if (this.accounts.length === 0) {
- this.buildEmptyView();
- } else {
- this.buildContentView();
- }
- // 顶部区域:标题栏在上,面包屑在下
- Column() {
- this.topTitleBar()
- this.breaker()
- }
- .width('100%')
- .visibility(this.isShowTitleBar?Visibility.Visible:
- this.autoHideTitle?Visibility.None:Visibility.Visible)
- .animation({
- duration: 500,
- curve: Curve.Friction // 可选动画曲线
- })
- .position({ top: 0, left: 0 })
- // .backgroundColor($r('app.color.start_window_background'))
- // 多选操作条,位于播放条之上(仅在多选模式下显示)
- if (this.isMultiSelect || this.isDeletingSelection) {
- this.buildSelectionOverlay()
- }
- }
- .width('100%')
- .height('100%')
- .alignContent(Alignment.Bottom)
- .backgroundImage(this.isCustomizeBg?this.customizeBgPath:$r('app.color.start_window_background'))
- .backgroundImageSize(this.isLandscape?{width:'100%'}:{ height: '100%'})
- .backgroundImagePosition(Alignment.Center)
- .backdropBlur(this.blurValue)
- .backgroundBrightness({rate:this.isCustomizeBg?0.1:0,lightUpDegree:this.bgBrightness})
- .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
- }
- @Builder
- breaker() {
- // 面包屑导航
- Column({ space: 8 }) {
- // 面包屑导航
- if (this.webdavManager.currentPath !== '') {
- Row({ space: 8 }) {
- Button({ type: ButtonType.Circle }) {
- Image(this.webdavManager.canGoBack()?$r('app.media.back'):
- this.selectedAccount.coverPath?this.selectedAccount.coverPath:getCloudDiskIcon(this.selectedAccount.webType))
- .width(15)
- .height(15)
- .borderRadius(10)
- .alt($r('app.media.cloudDisk'))
- .fillColor(Color.White)
- }
- .width(20)
- .height(20)
- .margin({left:5})
- .backgroundColor(this.themeColor)
- .onClick(() => this.goBack())
- Row({ space: 4 }) {
- ForEach(this.breadcrumbs, (crumb: BreadcrumbItem, index: number) => {
- Row() {
- Text(crumb.label)
- .fontSize(15)
- .fontColor($r('app.color.text_color'))
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- }
- .onClick(() => {
- this.navigateToBreadcrumb(crumb);
- })
- // 添加分隔符(除了最后一个元素)
- if (index < this.breadcrumbs.length - 1) {
- Text('/')
- .fontSize(15)
- .fontColor($r('app.color.index_tab_font_color'))
- .opacity(0.6)
- }
- })
- }
- .layoutWeight(1)
- }
- .width('100%')
- .padding({ left: 4, right: 4 })
- }
- }
- .width('100%')
- .padding({ left: 12, right: 12, top: 8, bottom: 5 })
- }
- // 空状态视图
- @Builder
- buildEmptyView() {
- Column({ space: 20 }) {
- Image($r('app.media.cloudDisk'))
- .width(120)
- .height(120)
- .opacity(0.3)
- Text(`暂无${getRemoteDriveAccountLabel()}`)
- .fontSize(16)
- .fontColor($r('app.color.index_tab_font_color'))
- .opacity(0.6)
- }
- .justifyContent(FlexAlign.Center)
- .width('100%')
- .padding({top: this.topSafeHeight+160})
- .layoutWeight(1)
- }
- // 内容视图
- @Builder
- buildContentView() {
- Column() {
- // 加载状态
- Row() {
- LoadingProgress()
- .width(30)
- .height(30)
- .color(this.themeColor)
- Text('加载中...')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'))
- .margin({ left: 12 })
- }
- .padding({top: this.topSafeHeight + 90, left: 20, right: 20})
- .visibility(this.isLoading?Visibility.Visible:Visibility.None)
- .opacity(this.isLoading ? 1 : 0)
- .animation({
- duration: 800,
- curve: Curve.Smooth // 可选动画曲线
- })
- // 文件列表(文件夹 + 歌曲)
- if (this.webDavFiles.length > 0) {
- List({ scroller: this.listScroller ,space: 0 }) {
- // 显示文件夹 - 只显示当前目录下的直接子文件夹
- ForEach(this.isSearchMode ? this.filteredFolderList : this.visibleFoldersState, (folder: FileInfo) => {
- ListItem() {
- this.buildFolderItem(folder)
- }
- .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 600 }),
- TransitionEffect.scale({ x: 0, y: 0 })))
- .clickEffect({ level: ClickEffectLevel.MIDDLE })
- }, (folder: FileInfo) => folder.name+folder.fileName)
- // 显示歌曲
- LazyForEach(this.dataSource, (song: VideoItem, index: number) => {
- ListItem() {
- this.buildSongItem(song, index)
- }
- .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 600 }),
- TransitionEffect.scale({ x: 0, y: 0 })))
- .clickEffect({ level: ClickEffectLevel.MIDDLE })
- }, (item: VideoItem) => item.filePath + '_' + this.listRefreshKey)
- }
- .scrollBar(BarState.Off)
- .onScrollFrameBegin((offset: number) => {
- // 获取当前滚动偏移量
- if(this.autoHideTitle){
- const currentOffsetY = this.listScroller.currentOffset().yOffset;
- // 判断滚动方向
- if (currentOffsetY > this.prevOffsetY) {
- console.log("onecold 向上滚动");
- this.isShowTitleBar = false
- } else if (currentOffsetY < this.prevOffsetY) {
- console.log("onecold 向下滚动");
- this.isShowTitleBar = true
- }
- // 更新前一次偏移量
- this.prevOffsetY = currentOffsetY;
- }
- return { offsetRemain: offset };
- })
- .contentStartOffset(this.topSafeHeight + 85)
- .contentEndOffset(this.bottomSafeHeight+70)
- .layoutWeight(1)
- .margin({ top: 4 })
- } else if (!this.isLoading) {
- Column() {
- Text('暂无内容')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'))
- .opacity(0.6)
- Text('点击"左侧菜单"网盘加载')
- .fontSize(12)
- .fontColor($r('app.color.index_tab_font_color'))
- .opacity(0.4)
- .margin({ top: 8 })
- }
- .justifyContent(FlexAlign.Center)
- .layoutWeight(1)
- }
- }
- .layoutWeight(1)
- }
- @Builder
- private buildMultiSelectBar() {
- Row({ space: 12 }) {
- Button(this.isAllSelected ? '反选' : '全选', { type: ButtonType.Circle, stateEffect: true })
- .width(55)
- .height(55)
- .fontSize(13)
- .backgroundColor(this.themeColor)
- .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 })
- .onClick(() => this.toggleSelectAll())
- Button('删除', { type: ButtonType.Circle, stateEffect: true })
- .width(55)
- .height(55)
- .fontSize(13)
- .backgroundColor(this.themeColor)
- .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 })
- .enabled(this.selectedSongs.length > 0 && !this.isDeletingSelection)
- .onClick(() => this.confirmDeleteSelected())
- Button('取消', { type: ButtonType.Circle, stateEffect: true })
- .width(55)
- .height(55)
- .fontSize(13)
- .backgroundColor(this.themeColor)
- .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.5 })
- .onClick(() => this.exitMultiSelect())
- }
- .width('100%')
- .padding({ bottom: 10, top: 12 })
- .justifyContent(FlexAlign.Center)
- .visibility(this.isMultiSelect ? Visibility.Visible : Visibility.None)
- .opacity(this.isMultiSelect ? 1 : 0)
- .animation({ duration: 300, curve: 'ease-in-out' })
- }
- @Builder
- private buildSelectionOverlay() {
- Column() {
- Row({ space: 10 }) {
- LoadingProgress()
- .width(22)
- .height(22)
- .color(this.themeColor)
- Text('正在删除...')
- .fontSize(14)
- .fontColor($r('app.color.index_tab_font_color'))
- }
- .padding({ top: 12, bottom: 6 })
- .justifyContent(FlexAlign.Center)
- .visibility(this.isDeletingSelection ? Visibility.Visible : Visibility.None)
- this.buildMultiSelectBar()
- }
- .width('100%')
- .padding({ left: 12, right: 12, bottom: this.bottomSafeHeight })
- }
- // 文件夹列表项
- @Builder
- buildFolderItem(folder: FileInfo) {
- Button({ type: ButtonType.Capsule, stateEffect: true }) {
- Row({ space: 2 }) {
- SymbolGlyph($r('sys.symbol.folder'))
- .fontSize(48)
- .fontColor([this.themeColor])
- .alignSelf(ItemAlign.Center)
- .margin({ left: 8, right: 6 })
- // 文件夹信息
- Column({ space: 4 }) {
- Text(decodeUrlEncodedString(folder.fileName.replace('/', '')))
- .fontSize(15)
- .fontColor($r('app.color.index_tab_font_color'))
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- Text('文件夹')
- .fontSize(13)
- .fontColor($r('app.color.index_tab_font_color'))
- .opacity(0.6)
- }
- .alignItems(HorizontalAlign.Start)
- .layoutWeight(1)
- }
- }
- .reuseId('dir_item')
- .width('100%')
- .padding(12)
- .backgroundColor(Color.Transparent)
- // .backgroundColor($r('app.color.start_window_background'))
- .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
- .onClick(() => {
- this.enterFolder(folder);
- this.breadcrumbs = this.webdavManager.getBreadcrumbs();
- })
- }
- // 歌曲列表项
- @Builder
- buildSongItem(song: VideoItem, index: number) {
- Button({ type: ButtonType.Capsule, stateEffect: true }) {
- Row({ space: 12 }) {
- // 序号
- // 歌曲封面
- Image(song.pixelMapPath ? song.pixelMapPath : $r('app.media.alt'))
- .width(52)
- .height(52)
- .borderRadius(9)
- .sourceSize({width:38, height:38})
- .alt($r('app.media.alt'))
- .fillColor(this.themeColor)
- .objectFit(ImageFit.Cover)
- .shadow({
- radius: StrUtil.isEmpty(song.pixelMapPath) ?6:14,
- type: ShadowType.BLUR,
- color: 'on_primary'
- })
- .margin({ left: 8 })
- .onClick(() => {
- if (this.isMultiSelect) {
- this.toggleSongSelection(song);
- } else {
- this.playSong(song, index, true);
- }
- })
- // 歌曲信息
- Column({ space: 4 }) {
- Text(this.isShowFileName?song.fileName :song.name)
- .fontSize(15)
- .textOverflow({ overflow: this.isLongNameRoLL?TextOverflow.MARQUEE:TextOverflow.Ellipsis })//超长滚动
- .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:$r('app.color.index_tab_font_color'))
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- Row(){
- Text((song.artist ?? '') + " ")
- .fontSize(13)
- .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:$r('app.color.index_tab_font_color'))
- .opacity(0.6)
- .maxLines(1)
- .visibility(song.artist?Visibility.Visible:Visibility.None)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- Text(this.buildSongMetaLine(song))
- .fontSize(13)
- .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:
- $r('app.color.index_tab_font_color'))
- .opacity(0.6)
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- }
- .width('90%')
- }
- .alignItems(HorizontalAlign.Start)
- .layoutWeight(1)
- .padding({ right: 20 })
- Column() {
- Checkbox({ name: 'checkbox_' + index })
- .select(this.isSongSelected(song))
- .selectedColor(this.themeColor)
- .opacity(this.isMultiSelect ? 1 : 0)
- .animation({
- duration: 300,
- curve: 'Smooth'
- })
- .visibility(this.isMultiSelect ? Visibility.Visible : Visibility.None)
- .onChange((checked: boolean) => this.handleCheckboxSelection(song, checked))
- .margin({ left: 10, top: 8, bottom: 8, right: 12 })
- .width(22)
- .height(22)
- ImageAnimator()
- .images(CommonConstants.IMAGE_FRAME_INFO)// 动画数组
- .duration(1000)// 持续
- //.state(this.animationState)// 动画状态
- .state(AnimationStatus.Running)// 动画状态
- .fillMode(FillMode.Forwards)
- .width(18)
- .margin({ right: 12, top: 8, bottom: 8 })
- .visibility(this.currentSong?.filePath==song.filePath ? (this.isMultiSelect ? Visibility.None : Visibility.Visible) :
- Visibility.None)
- .height(18)
- .iterations(-1) // 播放次数
- }
- }
- }
- .width('100%')
- .padding(12)
- .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
- .backgroundColor(Color.Transparent)
- .onClick(() => {
- if (this.isMultiSelect) {
- this.toggleSongSelection(song);
- } else {
- this.playSong(song, index);
- }
- })
- .gesture(LongPressGesture().onAction(() => {
- this.enterMultiSelect(song);
- }))
- }
-
-
- // 对话框控制器
- private createFolderDialogController: CustomDialogController | null = null;
- /**
- * 显示创建文件夹对话框
- */
- private showCreateFolderDialog(): void {
- if (!this.selectedAccount) {
- this.getUIContext().getPromptAction().showToast({ message: `请先选择${getRemoteDriveAccountLabel()}` });
- return;
- }
- // 获取当前主题色
- const currentThemeColor = this.themeColor;
- // 创建自定义对话框
- this.createFolderDialogController = new CustomDialogController({
- builder: CreateFolderDialog({
- onConfirm: (folderName: string) => {
- if (folderName.trim().length > 0) {
- void this.createFolder(folderName.trim());
- } else {
- this.getUIContext().getPromptAction().showToast({ message: '文件夹名称不能为空' });
- }
- },
- onCancel: () => {
- // 用户取消创建
- },
- initialName: '',
- themeColor: currentThemeColor
- }),
- autoCancel: true,
- alignment: DialogAlignment.Center,
- customStyle: false
- });
- this.createFolderDialogController.open();
- }
- /**
- * 创建文件夹的实际方法
- * @param folderName 文件夹名称
- */
- private async createFolder(folderName: string): Promise<void> {
- if (!this.selectedAccount) {
- this.getUIContext().getPromptAction().showToast({ message: `请先选择${getRemoteDriveAccountLabel()}` });
- return;
- }
- if (folderName.length === 0) {
- this.getUIContext().getPromptAction().showToast({ message: '文件夹名称不能为空' });
- return;
- }
- try {
- Logger.info(TAG, `heanup 开始创建文件夹: ${folderName}`);
- // 显示加载状态
- this.isLoading = true;
- if(this.selectedAccount.webType==RemoteDriveType.Baidu){
- // 调用RemoteDriveManager的createBaiduFolder方法
- const createdPath = await this.webdavManager.createBaiduFolder(this.selectedAccount, folderName);
- // 创建成功后刷新当前目录
- await this.webdavManager.loadFilesInfoFromAccount(this.selectedAccount, this.webdavManager.currentPath);
- }else if(this.selectedAccount.webType==RemoteDriveType.WebDav){
- // 调用RemoteDriveManager的createWebDavFolder方法
- const createdPath = await this.webdavManager.createWebDavFolder(this.selectedAccount, folderName);
- // 创建成功后刷新当前目录
- await this.webdavManager.loadFilesInfoFromAccount(this.selectedAccount, this.webdavManager.currentPath);
- }else{
- throw new Error('当前账户类型不支持创建文件夹');
- }
- this.getUIContext().getPromptAction().showToast({
- message: `文件夹 "${folderName}" 创建成功`
- });
- Logger.info(TAG, `heanup 文件夹创建成功`);
- } catch (error) {
- const err = error as Error;
- Logger.error(TAG, `heanup 创建文件夹失败: ${err.message}`);
- // 显示具体的错误信息
- this.getUIContext().getPromptAction().showToast({
- message: `创建失败: ${err.message}`
- });
- } finally {
- this.isLoading = false;
- }
- }
- }
|