Selaa lähdekoodia

编辑歌单的添加歌曲的 歌曲列表分页加载 搜索功能报错也修复了

onecold 10 kuukautta sitten
vanhempi
sitoutus
ca1eb87283

+ 143 - 35
entry/src/main/ets/dialog/AddSongsToPlaylistDialog.ets

@@ -23,23 +23,38 @@ struct AddSongsToPlaylistDialogContent {
   private listScroller: ListScroller = new ListScroller()
   @State isSearchMode: boolean = false
   @Prop playlist: Playlist
-  @Prop mediaKuList:  Array<VideoItem>
-  @StorageProp('mediaKuList')  mediaKuList2: Array<VideoItem> = []; //媒体库文件
+  @StorageProp('mediaKuList') mediaKuList: Array<VideoItem> = []; //媒体库文件
   private mediaTable: MediaTable = new MediaTable(getContext(this))
   private playlistTable: PlaylistTable = new PlaylistTable(getContext(this))
-  @State dataSource: LazyDataSource<VideoItem> = new LazyDataSource(this.filteredSongs)
+  @State dataSource: LazyDataSource<VideoItem> = 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() {
-    LogUtil.info('heanup AddSongsToPlaylistDialogContent aboutToAppear 开始')
-    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.updateListData(this.mediaKuList2)
-
+    LogUtil.info('heanup  AddSongsToPlaylistDialogContent aboutToAppear 开始')
+    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)
+  }
 
   /**
    * 搜索过滤歌曲
@@ -47,37 +62,85 @@ struct AddSongsToPlaylistDialogContent {
   filterSongs() {
     this.isSearchMode = true
     if (!this.searchText.trim()) {
-      this.filteredSongs = [...this.mediaKuList]
+      this.filteredSongs = [...this.allSongs]
     } else {
-      this.filteredSongs = this.mediaKuList.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()  ?? "")
-      })
+      // 添加空值检查以防止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.updateListData(this.filteredSongs)
+    // 重置分页状态并重新加载数据
+    this.loadInitialData()
   }
 
-  updateListData(mList: Array<VideoItem>) {
+  // 修改 updateListData 方法,使其更清晰
+  updateListData(append: boolean = false) {
     this.getUIContext().animateTo({ duration: 666 }, () => {
       this.opacityItem = 0;
     })
-    //只展示100条,不然会报错
+
     setTimeout(() => {
-      if (ArrayUtil.isNotEmpty(mList)&&mList.length  > 100) {
-        this.dataSource.pushArrayData(mList.slice(0,  100))
-      }else{
-        this.dataSource.pushArrayData(mList)
+      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);
+        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)
+    }, 300)
   }
 
   build() {
@@ -111,9 +174,9 @@ struct AddSongsToPlaylistDialogContent {
       // 已选择歌曲数量
       if (this.selectedSongs.length > 0) {
         Row() {
-          Text(`已选择 ${this.selectedSongs.length} 首歌曲`)
+          Text(`已选择 ${this.selectedSongs.length}  首歌曲`)
             .fontSize(14)
-            .fontColor($r('app.color.theme_color'))
+            .fontColor(this.themeColor)
             .fontWeight(FontWeight.Medium)
 
           Blank()
@@ -133,7 +196,7 @@ struct AddSongsToPlaylistDialogContent {
       }
 
       // 歌曲列表
-     if (this.filteredSongs.length === 0&&this.isSearchMode) {
+      if (this.dataSource.dataArray.length === 0 && this.isSearchMode) {
         Column({ space: 12 }) {
           Image($r('app.media.music_red'))
             .width(64)
@@ -168,7 +231,7 @@ struct AddSongsToPlaylistDialogContent {
         Button(`添加${this.selectedSongs.length > 0 ? `(${this.selectedSongs.length})` : ''}`)
           .width('45%')
           .height(40)
-          .backgroundColor($r('app.color.theme_color'))
+          .backgroundColor(this.themeColor)
           .borderRadius(8)
           .fontSize(14)
           .fontColor(Color.White)
@@ -194,7 +257,7 @@ struct AddSongsToPlaylistDialogContent {
    */
   private handleConfirm() {
     if (this.selectedSongs.length === 0) {
-      ToastUtil.showToast('请选择至少一首歌曲')
+      ToastUtil.showToast(' 请选择至少一首歌曲')
       return
     }
 
@@ -221,7 +284,7 @@ struct AddSongsToPlaylistDialogContent {
                   .draggable(false)
                   .interpolation(ImageInterpolation.High)// 用于重采样后的抗锯齿
                   .autoResize(true) // 重采样,可减少内存占用
-                  .opacity(this.opacityItem)// 绑定透明度
+                  .opacity(this.opacityItem) //  绑定透明度
                 // 歌曲信息
                 Column({ space: 4 }) {
                   Text(song.name || '未知歌曲')
@@ -233,7 +296,7 @@ struct AddSongsToPlaylistDialogContent {
 
                   Row() {
                     if (song.artist) {
-                      Text(song.artist)
+                      Text(song.artist+'  '+song.duration)
                         .fontSize(12)
                         .fontColor('#999999')
                         .maxLines(1)
@@ -286,17 +349,60 @@ struct AddSongsToPlaylistDialogContent {
             .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('#999999')
+            .margin({ left: 8 })
+        }
+        .width('100%')
+        .height(40)
+        .justifyContent(FlexAlign.Center)
+      } else if (this.hasMoreData) {
+        Row() {
+          Text('上拉加载更多')
+            .fontSize(12)
+            .fontColor('#999999')
+        }
+        .width('100%')
+        .height(40)
+        .justifyContent(FlexAlign.Center)
+      }
+    }
+    .width('100%')
+  }
 }
 
 /**
@@ -356,3 +462,5 @@ export function showAddSongsToPlaylistDialog(
 ) {
   dialogManager.showAddSongsToPlaylistDialog(playlist, onConfirm, onCancel)
 }
+
+

+ 9 - 7
entry/src/main/ets/pages/NewIndex.ets

@@ -1088,13 +1088,15 @@ struct NewIndex {
         .backgroundColor(Color.Transparent)
         .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
         .onClick(async () => {
-          this.doShowDrawer();
-          this.mType = 0
-          this.modeType = 4
-          this.currentSongListName = playlist.name;
-          this.currentSongListID = playlist.id;
-
-
+          if(playlist.songCount==0){
+            this.openPlaylist(playlist)
+          }else{
+            this.doShowDrawer();
+            this.mType = 0
+            this.modeType = 4
+            this.currentSongListName = playlist.name;
+            this.currentSongListID = playlist.id;
+          }
 
 
         })

+ 31 - 73
entry/src/main/ets/pages/PlaylistDetailPage.ets

@@ -3,7 +3,7 @@ import { VideoItem } from '../viewmodel/VideoItem';
 import PlaylistTable from '../common/util/PlaylistTable';
 import MediaTable from '../common/util/MediaTable';
 import { emitter } from '@kit.BasicServicesKit';
-import { ToastUtil, AppUtil, LogUtil, PreferencesUtil } from '@pura/harmony-utils';
+import { ToastUtil, AppUtil, LogUtil, PreferencesUtil, StrUtil } from '@pura/harmony-utils';
 import { showEditPlaylistDialog } from '../dialog/PlaylistDialog';
 import { showAddSongsToPlaylistDialog } from '../dialog/AddSongsToPlaylistDialog';
 import { router } from '@kit.ArkUI';
@@ -31,9 +31,10 @@ interface PlaylistEventData {
 @Component
 export struct PlaylistDetailPage {
   context = this.getUIContext().getHostContext() as common.UIAbilityContext
+  @StorageProp('topRectHeight') topRectHeight: number = 0;
   @State playlist: Playlist | null = null
   @State songList: VideoItem[] = []
-  @State isLoading: boolean = true
+  @State isLoading: boolean = false
   @State isShowEditDialog: boolean = false
   @State isPlaying: boolean = false
   @State curIndex: number = -1
@@ -48,9 +49,7 @@ export struct PlaylistDetailPage {
   @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
 
   aboutToAppear() {
-    let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
-    AppStorage.setOrCreate('themeColor', themeColor);
-    this.themeColor = themeColor
+
     // 获取传入的歌单对象
     const params = router.getParams() as Record<string, Object>
     if (params && params['playlist']) {
@@ -72,8 +71,14 @@ export struct PlaylistDetailPage {
    */
   async loadPlaylistDetail() {
     try {
-      this.isLoading = true
-
+      //先获取缓存
+      const cacheSongs:string =PreferencesUtil.getStringSync(this.playlistId+'currentSongList', '')
+      if(StrUtil.isNotEmpty(cacheSongs)){
+        console.log('onecold loadPlaylistDetail 获取缓存成功')
+        this.songList = JSON.parse(cacheSongs) as VideoItem[]
+      }else{
+        console.log('onecold loadPlaylistDetail 歌曲数据为空')
+      }
       if (!this.playlist) {
         ToastUtil.showToast('歌单不存在')
         router.back()
@@ -399,7 +404,7 @@ export struct PlaylistDetailPage {
       // 顶部安全区和标题栏
       Column() {
         Blank()
-          .height(px2vp(AppUtil.getStatusBarHeight()))
+          .height(this.topRectHeight+5)
           .backgroundColor($r('app.color.title_bar_bg'))
           .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
         
@@ -679,24 +684,25 @@ export struct PlaylistDetailPage {
         // 歌曲列表标题 - 简化设计,与LocalMusic保持一致
         if (this.songList.length > 0) {
           Row() {
+
             Text('歌曲列表')
               .fontSize(16)
               .fontWeight(FontWeight.Medium)
               .fontColor($r('app.color.text_color'))
               .opacity(0.9)
-              .layoutWeight(1)
 
             Text(`${this.songList.length}首`)
               .fontSize(14)
               .fontColor($r('app.color.text_color'))
+              .margin({ left: 8 })
               .opacity(0.6)
-
+            Blank()
             // 添加歌曲按钮
             Button() {
               Row({ space: 6 }) {
                 SymbolGlyph($r('sys.symbol.plus'))
-                  .fontSize(22)
-                  .fontColor([this.themeColor])
+                  .fontSize(20)
+                  .fontColor([Color.White])
                   .effectStrategy(SymbolEffectStrategy.HIERARCHICAL)
                   .alignSelf(ItemAlign.Center)
 
@@ -752,7 +758,7 @@ export struct PlaylistDetailPage {
                         .objectFit(ImageFit.Cover)
                         .interpolation(ImageInterpolation.High)
                         .autoResize(true)
-                        .margin({ left: 16 })
+                        .margin({ left: 20 })
                         .onClick(() => {
                           this.playSong(song, index)
                         })
@@ -772,6 +778,12 @@ export struct PlaylistDetailPage {
                           .layoutWeight(1)
                           .margin({ top: 8, left: 12 })
 
+
+                      }
+                      .width('100%')
+
+                      // 歌手和专辑信息 - 参考LocalMusic的第二行布局
+                      Row() {
                         // 音质标签
                         if (song.md5Str) {
                           Text(song.md5Str?.includes('Lossless') ? '无损' : song.md5Str)
@@ -780,41 +792,25 @@ export struct PlaylistDetailPage {
                             .fontWeight(500)
                             .padding({ top: 2, right: 6, left: 6, bottom: 2 })
                             .borderRadius(4)
-                            .margin({ top: 8, left: 8 })
                             .backgroundColor( '#FFC107')
                             .visibility(song.md5Str ? Visibility.Visible : Visibility.None)
                         }
-                      }
-                      .width('100%')
-
-                      // 歌手和专辑信息 - 参考LocalMusic的第二行布局
-                      Row() {
                           Text(song.artist)
                             .fontSize(13)
                             .fontColor(this.isPlaying && this.curIndex === index ?this.themeColor : $r('app.color.text_color'))
                             .fontWeight(this.isPlaying && this.curIndex === index ? FontWeight.Medium : FontWeight.Normal)
                             .maxLines(1)
+                            .margin({ left: 8 })
                             .textOverflow({ overflow: TextOverflow.Ellipsis })
-                            .layoutWeight(1)
                             .opacity(this.isPlaying && this.curIndex === index ? 1.0 : 0.8)
-                          Text(song.album)
+                          Text(song.duration)
                             .fontSize(13)
                             .fontColor(this.isPlaying && this.curIndex === index ? this.themeColor  : $r('app.color.text_color'))
                             .fontWeight(this.isPlaying && this.curIndex === index ? FontWeight.Medium : FontWeight.Normal)
                             .maxLines(1)
+                            .margin({ left: 8 })
                             .textOverflow({ overflow: TextOverflow.Ellipsis })
-                            .layoutWeight(1)
                             .opacity(this.isPlaying && this.curIndex === index ? 1.0 : 0.8)
-
-                        // 时长显示
-                        if (song.duration && typeof song.duration === 'number' && song.duration > 0) {
-                          Text(this.formatDuration(song.duration))
-                            .fontSize(13)
-                            .fontColor(this.isPlaying && this.curIndex === index ? this.themeColor  : $r('app.color.text_color'))
-                            .fontWeight(this.isPlaying && this.curIndex === index ? FontWeight.Medium : FontWeight.Normal)
-                            .opacity(this.isPlaying && this.curIndex === index ? 1.0 : 0.7)
-                            .margin({ right: 20 })
-                        }
                       }
                       .width('100%')
                       .margin({ left: 12, top: 4 })
@@ -936,52 +932,14 @@ export struct PlaylistDetailPage {
             Column() {
               // 空状态图标容器
               emptyView(this.themeColor)
-              // Stack() {
-              //   // 背景圆圈
-              //   Circle({ width: 160, height: 160 })
-              //     .fill('#0a59f715')
-              //     .border({
-              //       width: 2,
-              //       color: '#0a59f71a'
-              //     })
-              //
-              //   // 中间圆圈
-              //   Circle({ width: 120, height: 120 })
-              //     .fill('#0a59f722')
-              //
-              //   // 音符图标
-              //   Image($r('app.media.music_red'))
-              //     .width(64)
-              //     .height(64)
-              //     .opacity(0.6)
-              //     .fillColor(this.themeColor )
-              // }
-              // .margin({ bottom: 32 })
-              //
-              // // 空状态标题
-              // Text('歌单还是空的')
-              //   .fontSize(22)
-              //   .fontColor($r('app.color.text_color'))
-              //   .fontWeight(FontWeight.Bold)
-              //   .margin({ bottom: 12 })
-              //   .letterSpacing(0.5)
-              //
-              // // 空状态描述
-              // Text('快来添加你喜欢的音乐吧\n让这个歌单充满美妙的旋律')
-              //   .fontSize(15)
-              //   .fontColor($r('app.color.text_color'))
-              //   .opacity(0.8)
-              //   .margin({ bottom: 40 })
-              //   .textAlign(TextAlign.Center)
-              //   .lineHeight(24)
-              //   .maxLines(2)
 
               // 添加歌曲按钮
               Button() {
                 Row({ space: 8 }) {
-                  Text('+')
+                  SymbolGlyph($r('sys.symbol.plus'))
                     .fontSize(20)
-                    .fontColor(Color.White)
+                    .fontColor([Color.White])
+                    .alignSelf(ItemAlign.Center)
 
                   Text('添加歌曲')
                     .fontSize(16)

+ 1 - 2
entry/src/main/ets/pages/SettingPage.ets

@@ -155,7 +155,7 @@ export struct SettingPage {
   @State isThemeSheet: boolean = false // 主题设置弹窗显示状态
   @State colorRows: Array<Array<ThemeColorItem>> = chunkArray(SettingPage.THEME_COLOR_LIST, 2);
   @State colorGroup: string = "group"
-  @StorageProp('topRectHeight') topRectHeight: number = px2vp(AppUtil.getStatusBarHeight());
+  @StorageProp('topRectHeight') topRectHeight: number = 0;
   apiDialogController: CustomDialogController = new CustomDialogController({
     builder: CustomContentDialog({
       primaryTitle: this.apiDialogType === 'lyric' ? '修改歌词API地址' : '修改封面API地址',
@@ -219,7 +219,6 @@ export struct SettingPage {
   // 组件生命周期
   aboutToAppear() {
     this.geIDD()
-    this.topRectHeight = px2vp(AppUtil.getStatusBarHeight());
     this.isBgPlayOpen = PreferencesUtil.getBooleanSync(SettingPage.IS_BGPLAY_OPEN, true)
     this.isAutoRatate = PreferencesUtil.getBooleanSync(SettingPage.IS_AUTO_RATATE, true)
     this.isMemoryPlay = PreferencesUtil.getBooleanSync(SettingPage.iS_MEMORY_PLAY, true)

+ 0 - 5
entry/src/main/ets/view/LocalMusic.ets

@@ -308,13 +308,11 @@ export struct LocalMusic {
     if(this.modeType==4){
       this.titleBarModel.setTitleName(this.currentSongListName)
       this.titleBarModel.setLeftIconMainNoAnimate($r('app.media.menu'))
-      console.log('onecold 歌单id发生变化获取缓存1')
       //获取缓存
       const cacheSongs:string =PreferencesUtil.getStringSync(this.currentSongListID+'currentSongList', '')
       console.log('onecold 歌单id发生变化获取缓存2  cacheSongs = '+cacheSongs)
       if(StrUtil.isNotEmpty(cacheSongs)){
         this.currentSongList = JSON.parse(cacheSongs) as VideoItem[]
-        console.log('onecold 歌单id发生变化获取缓存3 this.currentSongList = '+this.currentSongList.length)
         this.updateListData(this.currentSongList, true)
         this.isShowTitleBar = false
       }
@@ -326,16 +324,13 @@ export struct LocalMusic {
   async  doSongListTask() {
     // 初始化歌单数据库
     const playlistTable = new PlaylistTable(this.context);
-    console.log('onecold 歌单id发生变化获取缓存6')
     // 查询歌单中的所有歌曲
     const playlistSongs: PlaylistSong[] = await playlistTable.queryPlaylistSongs(this.currentSongListID);
     console.log('onecold 歌单id发生变化获取缓存7 playlistSongs='+playlistSongs.length)
     if (ArrayUtil.isEmpty(playlistSongs)) {
-      ToastUtil.showToast('当前歌单为空')
       this.updateListData([], true)
     } else {
       this.currentSongList = await convertPlaylistSongsToVideoItems(this.context, playlistSongs);
-      console.log('onecold 从数据库读取的歌曲为 playlistSongs='+JSON.stringify(this.currentSongList))
       console.log('onecold 从数据库读取的歌曲为 playlistSongs='+this.currentSongList.length)
       this.updateListData(this.currentSongList, true)