import { DialogHelper } from '@pura/harmony-dialog'; import { Playlist } from '../viewmodel/Playlist'; import { ToastUtil, LogUtil, StrUtil } from '@pura/harmony-utils'; import { VideoItem } from '../viewmodel/VideoItem'; import MediaTable from '../common/util/MediaTable'; import PlaylistTable from '../common/util/PlaylistTable'; import { LazyDataSource } from '../common/util/LazyDataSource'; import { ConfigurationConstant, common } from '@kit.AbilityKit'; import { CommonConstants } from '../common/constants/CommonConstants'; // 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体 function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string { if (isDarkMode) { // 深色模式下返回更深的灰色或半透明黑色 return `rgba(34,34,34,${alpha + 0.2 > 1 ? 1 : alpha + 0.2})`; } const color = themeColor.replace('#', ''); const r = parseInt(color.substring(0, 2), 16); const g = parseInt(color.substring(2, 4), 16); const b = parseInt(color.substring(4, 6), 16); return `rgba(${r},${g},${b},${alpha})`; } /** * 添加歌曲到歌单对话框内容组件 */ @Component struct AddSongsToPlaylistDialogContent { context = this.getUIContext().getHostContext() as common.UIAbilityContext @State opacityItem: number = 1; // 控制透明度的状态变量 @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR; @State isDarkMode: boolean = false @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT; onColorModeChange() { this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK } @State selectedSongs: VideoItem[] = [] @State isLoading: boolean = false @State searchText: string = '' @State filteredSongs: VideoItem[] = [] private listScroller: ListScroller = new ListScroller() @State isSearchMode: boolean = false @Prop playlist: Playlist @StorageProp('mediaKuList') mediaKuList: Array = []; //媒体库文件 private mediaTable: MediaTable = new MediaTable(getContext(this)) private playlistTable: PlaylistTable = new PlaylistTable(getContext(this)) @State dataSource: LazyDataSource = new LazyDataSource([]) // 分页相关状态 @State currentPage: number = 0 @State hasMoreData: boolean = true private readonly PAGE_SIZE: number = 50 // 用于存储完整数据的引用 private allSongs: VideoItem[] = [] // 回调函数 onConfirm?: (songs: VideoItem[]) => void onCancel?: () => void aboutToAppear() { // 初始化深色模式状态 this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK LogUtil.info('heanup AddSongsToPlaylistDialogContent aboutToAppear 开始') LogUtil.info('heanup 初始化深色模式状态: ' + this.isDarkMode + ', currentMode: ' + this.currentMode) LogUtil.info('heanup playlist: ' + (this.playlist ? JSON.stringify({ id: this.playlist.id, name: this.playlist.name }) : 'null')) console.info('onecold mediaKuList 对话框 aboutToAppear=' + this.mediaKuList.length) this.allSongs = [...this.mediaKuList] this.filteredSongs = [...this.mediaKuList] this.loadInitialData() } /** * 加载初始数据 */ private loadInitialData() { this.currentPage = 0 this.hasMoreData = true this.updateListData(false) } /** * 搜索过滤歌曲 */ filterSongs() { this.isSearchMode = true if (!this.searchText.trim()) { this.filteredSongs = [...this.allSongs] } else { // 添加空值检查以防止TypeError if (!this.allSongs || !Array.isArray(this.allSongs)) { this.filteredSongs = [] } else { this.filteredSongs = this.allSongs.filter(item => { //支持模糊匹配和艺术家 专辑匹配 const regex = new RegExp(this.searchText.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.loadInitialData() } // 修改 updateListData 方法,使其更清晰 updateListData(append: boolean = false) { this.getUIContext().animateTo({ duration: 666 }, () => { this.opacityItem = 0; }) setTimeout(() => { const sourceData = this.filteredSongs let dataToShow: VideoItem[] = [] if (append) { // 追加数据模式 const currentData = this.dataSource.dataArray const startIndex = this.currentPage * this.PAGE_SIZE const endIndex = Math.min(startIndex + this.PAGE_SIZE, sourceData.length) const newData = sourceData.slice(startIndex, endIndex) if (newData.length > 0) { dataToShow = [...currentData, ...newData] this.currentPage++ } } else { // 初始加载模式 this.currentPage = 1 const endIndex = Math.min(this.PAGE_SIZE, sourceData.length) dataToShow = sourceData.slice(0, endIndex) } // 更新数据源 this.dataSource.pushArrayData(dataToShow) // 检查是否还有更多数据 const totalLoaded = dataToShow.length this.hasMoreData = totalLoaded < sourceData.length this.getUIContext().animateTo({ duration: 666 }, () => { this.opacityItem = 1 }) }, 200) } loadMoreData() { console.info('loadMoreData called, hasMoreData:', this.hasMoreData, 'isLoading:', this.isLoading) if (!this.hasMoreData || this.isLoading) { console.info('loadMoreData skipped - no more data or already loading') return } console.info('loadMoreData executing') this.isLoading = true // 使用 setTimeout 模拟异步加载 setTimeout(() => { this.updateListData(true) // append mode this.isLoading = false console.info('loadMoreData completed, current data length:', this.dataSource.dataArray.length) }, 50) } build() { Column({ space: 16 }) { // 搜索框 Row({ space: 8 }) { Image($r('app.media.ic_action_search')) .width(20) .height(20) .fillColor(this.isDarkMode ? '#8E8E93' : $r('app.color.text_color')) .opacity(0.6) TextInput({ placeholder: '搜索歌曲、歌手或专辑', text: this.searchText }) .layoutWeight(1) .height(35) .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background')) .borderRadius(8) .padding({ left: 8, right: 8 }) .fontSize(14) .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color')) .placeholderColor(this.isDarkMode ? '#8E8E93' : '#999999') .onChange((value: string) => { this.searchText = value this.filterSongs() }) } .width('100%') .padding(12) .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background')) .borderRadius(20) // 已选择歌曲数量 if (this.selectedSongs.length > 0) { Row() { Text(`已选择 ${this.selectedSongs.length} 首歌曲`) .fontSize(14) .fontColor(this.isDarkMode ? '#FFFFFF' : this.themeColor) .fontWeight(FontWeight.Medium) Blank() Button(this.selectedSongs.length === this.dataSource.dataArray.length && this.dataSource.dataArray.length > 0 ? '全不选' : '全选') .fontSize(12) .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color')) .backgroundColor(Color.Transparent) .height(30) .padding({ left: 8, right: 8 }) .onClick(() => { // 判断是否已经全选 if (this.selectedSongs.length === this.dataSource.dataArray.length && this.dataSource.dataArray.length > 0) { // 如果已经全选,则清空选择 this.selectedSongs = [] } else { // 如果没有全选,则选择当前显示的所有歌曲 this.selectedSongs = [...this.dataSource.dataArray] } }) Button('清空') .fontSize(12) .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color')) .backgroundColor(Color.Transparent) .height(30) .padding({ left: 8, right: 8 }) .onClick(() => { this.selectedSongs = [] }) } .width('100%') .padding({ left: 4, right: 4 }) } // 歌曲列表 if (this.dataSource.dataArray.length === 0 && this.isSearchMode) { Column({ space: 12 }) { Image($r('app.media.music_red')) .width(64) .height(64) .opacity(0.3) Text(this.searchText ? '没有找到匹配的歌曲' : '没有可添加的歌曲') .fontSize(14) .fontColor(this.isDarkMode ? '#8E8E93' : '#999999') } .width('100%') .height(300) .justifyContent(FlexAlign.Center) } else { this.getListView() } // 按钮区域 Row({ space: 12 }) { Button('取消') .width('45%') .height(40) .backgroundColor($r('app.color.cancel_button_background')) .borderRadius(8) .fontSize(14) .fontColor($r('app.color.cancel_button_text')) .onClick(() => { this.onCancel?.() DialogHelper.closeDialog('addSongsToPlaylistDialog') }) Button(`添加${this.selectedSongs.length > 0 ? `(${this.selectedSongs.length})` : ''}`) .width('45%') .height(40) .backgroundColor(this.isDarkMode ? themeColorWithAlpha(this.themeColor, 0.8, this.isDarkMode) : this.themeColor) .borderRadius(8) .fontSize(14) .fontColor(Color.White) .enabled(this.selectedSongs.length > 0) .opacity(this.selectedSongs.length > 0 ? 1 : 0.5) .onClick(() => { this.handleConfirm() }) } .width('100%') .justifyContent(FlexAlign.SpaceBetween) .margin({ top: 10, bottom: 10 }) } .width('100%') .constraintSize({ maxWidth: 400 }) .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.dialog_background')) .borderRadius(12) .padding({ left: 20, right: 20 }) } /** * 处理确认操作 */ private handleConfirm() { if (this.selectedSongs.length === 0) { ToastUtil.showToast(' 请选择至少一首歌曲') return } this.onConfirm?.(this.selectedSongs) DialogHelper.closeDialog('addSongsToPlaylistDialog') } @Builder getListView() { Scroll() { Column({ space: 0 }) { List({ scroller: this.listScroller }) { LazyForEach(this.dataSource, (song: VideoItem, index: number) => { ListItem() { Row({ space: 12 }) { Image(StrUtil.isEmpty(song.pixelMapPath) ? $r('app.media.music_red') : song.pixelMapPath) .fillColor(StrUtil.isEmpty(song.pixelMapPath) ? (this.isDarkMode ? Color.White : this.themeColor) : undefined) .height(40) .width(40) .alt($r('app.media.music_red')) .borderRadius('100%') .clip(true) .draggable(false) .interpolation(ImageInterpolation.High)// 用于重采样后的抗锯齿 .autoResize(true) // 重采样,可减少内存占用 .opacity(this.opacityItem) // 绑定透明度 // 歌曲信息 Column({ space: 4 }) { Text(song.name || '未知歌曲') .fontSize(14) .fontColor(this.isDarkMode ? '#FFFFFF' : $r('app.color.text_color')) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) .width('100%') Row() { if (song.artist) { Text(song.artist+' '+song.duration) .fontSize(12) .fontColor(this.isDarkMode ? '#8E8E93' : '#999999') .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) .layoutWeight(1) } } .width('100%') } .alignItems(HorizontalAlign.Start) .layoutWeight(1) // 选择框 Checkbox({ name: 'song_' + song.id }) .select(this.selectedSongs.some(s => s.id === song.id)) .selectedColor($r('app.color.theme_color')) .shape(CheckBoxShape.ROUNDED_SQUARE) .onChange((checked: boolean) => { if (checked) { if (!this.selectedSongs.some(s => s.id === song.id)) { this.selectedSongs.push(song) } } else { const index = this.selectedSongs.findIndex(s => s.id === song.id) if (index > -1) { this.selectedSongs.splice(index, 1) } } }) } .width('100%') .padding(12) .borderRadius(10) .onClick(() => { const isSelected = this.selectedSongs.some(s => s.id === song.id) if (isSelected) { const index = this.selectedSongs.findIndex(s => s.id === song.id) if (index > -1) { this.selectedSongs.splice(index, 1) } } else { this.selectedSongs.push(song) } }) } .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }) .animation({ duration: 500 }), TransitionEffect.scale({ x: 0, y: 0 }))) .clickEffect({ level: ClickEffectLevel.LIGHT }) }, (song: VideoItem) => song.id) // 添加 footer 来显示加载状态 ListItem() { this.footer() } .visibility(this.hasMoreData ? Visibility.Visible : Visibility.None) } .cachedCount(6) .height('100%') .scrollBar(BarState.Off) .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true }) .onReachEnd(() => { // 滚动到底部时加载更多数据 console.info('List onReachEnd triggered') this.loadMoreData() }) } } .scrollBar(BarState.Auto) .scrollable(ScrollDirection.Vertical) .height(300) } // 改进的 footer Builder @Builder footer() { Column() { if (this.isLoading) { Row() { LoadingProgress() .height(20) .width(20) Text('加载中...') .fontSize(12) .fontColor(this.isDarkMode ? '#8E8E93' : '#999999') .margin({ left: 8 }) } .width('100%') .height(40) .justifyContent(FlexAlign.Center) } else if (this.hasMoreData) { Row() { Text('上拉加载更多') .fontSize(12) .fontColor(this.isDarkMode ? '#8E8E93' : '#999999') } .width('100%') .height(40) .justifyContent(FlexAlign.Center) } } .width('100%') } } /** * 添加歌曲到歌单对话框管理器 */ @Component export struct AddSongsToPlaylistDialogManager { /** * 添加歌曲到歌单对话框构建器 */ @Builder buildAddSongsToPlaylistDialog( playlist: Playlist, onConfirm: (songs: VideoItem[]) => void, onCancel?: () => void, ) { AddSongsToPlaylistDialogContent({ playlist: playlist, onConfirm: onConfirm, onCancel: onCancel }) } /** * 显示添加歌曲到歌单对话框 */ showAddSongsToPlaylistDialog( playlist: Playlist, onConfirm: (songs: VideoItem[]) => void, onCancel?: () => void, ) { DialogHelper.showCustomContentDialog({ dialogId: 'addSongsToPlaylistDialog', title: '添加歌曲到歌单', autoCancel: true, contentBuilder: () => { this.buildAddSongsToPlaylistDialog(playlist, onConfirm,onCancel) }, buttons: [] }) } build() { } } // 创建全局实例 const dialogManager = new AddSongsToPlaylistDialogManager() /** * 显示添加歌曲到歌单对话框 */ export function showAddSongsToPlaylistDialog( playlist: Playlist, onConfirm: (songs: VideoItem[]) => void, onCancel?: () => void, ) { dialogManager.showAddSongsToPlaylistDialog(playlist, onConfirm, onCancel) }