Procházet zdrojové kódy

实现NavidromPage的搜索和排序功能

onecold před 9 měsíci
rodič
revize
750f0998b9
1 změnil soubory, kde provedl 255 přidání a 38 odebrání
  1. 255 38
      entry/src/main/ets/view/NavidromePage.ets

+ 255 - 38
entry/src/main/ets/view/NavidromePage.ets

@@ -1,7 +1,8 @@
 import { VideoItem } from '../viewmodel/VideoItem';
 import { VideoItem } from '../viewmodel/VideoItem';
-import { LengthMetrics, SegmentButton, SegmentButtonItemTuple, SegmentButtonOptions } from '@kit.ArkUI';
+import { LengthMetrics, SegmentButton, SegmentButtonItemTuple, SegmentButtonOptions,
+  SymbolGlyphModifier } from '@kit.ArkUI';
 import {
 import {
-  PreferencesUtil, ToastUtil
+  PreferencesUtil, ToastUtil, StrUtil, ArrayUtil
 } from '@pura/harmony-utils';
 } from '@pura/harmony-utils';
 import {
 import {
   ButtonFancyModifier,
   ButtonFancyModifier,
@@ -59,6 +60,12 @@ export struct NavidromePage {
   @State filterType: NavFilterType = NavFilterType.None;
   @State filterType: NavFilterType = NavFilterType.None;
   @State filterLabel: string = '';
   @State filterLabel: string = '';
   @State filterId: string = '';
   @State filterId: string = '';
+  
+  // 搜索和排序相关状态
+  @State isSearchMode: boolean = false;
+  @State searchText: string = ''; // 用户输入内容
+  @State filteredList: Array<VideoItem> = []; // 过滤后的歌曲结果
+  @State sortType: number = 0; // 排序类型 0:名称升序 1:名称降序 2:时间升序 3:时间降序 4:大小升序 5:大小降序
 
 
   // SegmentButton选项
   // SegmentButton选项
   @State tabOptions: SegmentButtonOptions = SegmentButtonOptions.capsule({
   @State tabOptions: SegmentButtonOptions = SegmentButtonOptions.capsule({
@@ -89,6 +96,7 @@ export struct NavidromePage {
 
 
   aboutToAppear() {
   aboutToAppear() {
     this.isNoJumpToHome = PreferencesUtil.getBooleanSync('isNoJumpToHome', true)
     this.isNoJumpToHome = PreferencesUtil.getBooleanSync('isNoJumpToHome', true)
+    this.sortType = PreferencesUtil.getNumberSync('navidromeSortType', 0);
     this.refreshNavidromeData();
     this.refreshNavidromeData();
   }
   }
 
 
@@ -102,6 +110,7 @@ export struct NavidromePage {
       return;
       return;
     }
     }
     await this.loadNavidromeLibrary(account);
     await this.loadNavidromeLibrary(account);
+    this.doSortType(this.sortType)
   }
   }
 
 
   private resolveActiveAccount(): WebDavAccount | undefined {
   private resolveActiveAccount(): WebDavAccount | undefined {
@@ -200,41 +209,118 @@ export struct NavidromePage {
   topTitleBar() {
   topTitleBar() {
     Column() {
     Column() {
       Row({ space: 6 }) {
       Row({ space: 6 }) {
-        // 左侧返回按钮
-        Button({ type: ButtonType.Circle, stateEffect: true }) {
-          SymbolGlyph($r('sys.symbol.sort'))
-            .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
+        // 标题或搜索框
+        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('Navidrome音乐')
+            .margin({ left: 3, right: 10 })
+            .fontColor($r('app.color.text_color'))
+            .fontSize(18)
+            .maxLines(1)
+            .textOverflow({ overflow: TextOverflow.MARQUEE })
+            .layoutWeight(1)
+            .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
+            .onClick(() => {
+              // 可以添加标题点击事件
+            })
+            .animation({ duration: 300, curve: Curve.Ease })
+        } 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)
+
         }
         }
-        .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
+
+        // 搜索框
+        Search({ 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')
+            }
           })
           })
-        })
-        .attributeModifier(new ShadowModifier())
-        .zIndex(0)
-
-        // 标题
-        Text('Navidrome音乐')
-          .margin({ left: 3, right: 10 })
-          .fontColor($r('app.color.text_color'))
-          .fontSize(18)
-          .maxLines(1)
-          .textOverflow({ overflow: TextOverflow.MARQUEE })
           .layoutWeight(1)
           .layoutWeight(1)
+          .height(35)
+          .maxLength(20)
+          .backgroundColor(Color.White)
+          .placeholderColor(Color.Grey)
+          .placeholderFont({ size: 14, weight: 400 })
+          .textFont({ size: 14, weight: 400 })
+          .onSubmit((value: string) => {
+            this.onSearchInput(value);
+          })
+          .onChange((value: string) => {
+            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 })
           .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
+          .attributeModifier(new ShadowModifier())
+          .zIndex(0)
+          .visibility(this.selectedTab==0?Visibility.Visible:Visibility.None)
           .onClick(() => {
           .onClick(() => {
-            // 可以添加标题点击事件
+            this.isSearchMode = true;
+            if(ArrayUtil.isEmpty(this.filteredList)){
+              this.filteredList =  [...this.allVideos];
+            }
           })
           })
+
+          // 排序按钮
+          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 })
           .animation({ duration: 300, curve: Curve.Ease })
+          .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
+          .bindMenu(this.SortMenuBuilder)
+          .attributeModifier(new ShadowModifier())
+          .zIndex(0)
+        }
       }
       }
       .width('100%')
       .width('100%')
       .height(55)
       .height(55)
-      .padding({left: 15, right: 15})
+      .padding({ left: 15, right: 15 })
       .justifyContent(FlexAlign.SpaceBetween)
       .justifyContent(FlexAlign.SpaceBetween)
       .alignItems(VerticalAlign.Center)
       .alignItems(VerticalAlign.Center)
 
 
@@ -247,10 +333,137 @@ export struct NavidromePage {
         .padding({ left: 25, right: 25, top: 5, bottom: 5 })
         .padding({ left: 25, right: 25, top: 5, bottom: 5 })
     }
     }
     .width('100%')
     .width('100%')
-    .padding({ top: this.topRectHeight+5})
+    .padding({ top: this.topRectHeight + 5 })
     .backgroundColor($r('app.color.start_window_background'))
     .backgroundColor($r('app.color.start_window_background'))
   }
   }
 
 
+  @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("navidromeSortType", 0);
+        })
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_down')),
+        content: '按名称降序'
+      })
+        .onClick(async () => {
+          this.doSortType(1);
+          PreferencesUtil.put("navidromeSortType", 1);
+        })
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.clock')),
+        content: '按艺术家升序'
+      })
+        .onClick(async () => {
+          this.doSortType(2);
+          PreferencesUtil.put("navidromeSortType", 2);
+        })
+
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.timer')),
+        content: '按艺术家降序'
+      })
+        .onClick(async () => {
+          this.doSortType(3);
+          PreferencesUtil.put("navidromeSortType", 3);
+        })
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_up')),
+        content: '按专辑升序'
+      })
+        .onClick(async () => {
+          this.doSortType(4);
+          PreferencesUtil.put("navidromeSortType", 4);
+        })
+
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_down')),
+        content: '按专辑降序'
+      })
+        .onClick(async () => {
+          this.doSortType(5);
+          PreferencesUtil.put("navidromeSortType", 5);
+        })
+    }
+  }
+
+  doSortType(index: number) {
+    this.sortType = index;
+    const songs = this.isSearchMode ? this.filteredList :this.allVideos;
+    
+    // 对歌曲列表进行排序
+    switch (index) {
+      case 0: // 名称升序
+        Utility.doSortListAscending(songs,false)
+        break;
+      case 1: // 名称降序
+        Utility.doSortListDescending(songs,true)
+        break;
+      case 2: // 艺术家升序
+        songs.sort((a, b) => {
+          // 处理艺术家可能为undefined的字符串比较
+          const artistA = a.artist?.trim() || ''; // 可选添加 trim() 处理空格
+          const artistB = b.artist?.trim() || '';
+          return artistA.localeCompare(artistB);
+        });
+        break;
+      case 3: // 艺术家降序
+        songs.sort((a, b) => {
+          // 处理艺术家可能为undefined的字符串比较
+          const artistA = a.artist?.trim() || ''; // 可选添加 trim() 处理空格
+          const artistB = b.artist?.trim() || '';
+          return artistB.localeCompare(artistA);
+        });
+        break;
+      case 4: // 专辑升序
+        songs.sort((a, b) => {
+          const albumA = a.album?.trim() || ''; // 可选添加 trim() 处理空格
+          const albumB = b.album?.trim() || '';
+          return albumA.localeCompare(albumB);
+        });
+        break;
+      case 5: // 专辑降序
+        songs.sort((a, b) => {
+          const albumA = a.album?.trim() || ''; // 可选添加 trim() 处理空格
+          const albumB = b.album?.trim() || '';
+          return albumB.localeCompare(albumA);
+        });
+        break;
+    }
+
+    // 更新显示列表
+    if (this.isSearchMode) {
+      this.filteredList = [...songs];
+    }
+  }
+
+  // 实时搜索逻辑
+  private onSearchInput(value: string) {
+    this.searchText = value.trim();
+    let mSearchList: Array<VideoItem> = [...this.allVideos];
+
+    // 新增条件判断:空输入时显示所有数据
+    if (this.searchText === '') {
+      this.filteredList = [...mSearchList]; // 创建新数组以触发状态更新
+    } 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() ?? "")
+      });
+    }
+
+  }
+
   private getCurrentCount(): number {
   private getCurrentCount(): number {
     switch (this.selectedTab) {
     switch (this.selectedTab) {
       case 1:
       case 1:
@@ -293,7 +506,7 @@ export struct NavidromePage {
           .width(48)
           .width(48)
           .height(48)
           .height(48)
           .borderRadius(10)
           .borderRadius(10)
-          .sourceSize({width:38, height:38})
+          .sourceSize({ width: 38, height: 38 })
           .alt($r('app.media.music_red'))
           .alt($r('app.media.music_red'))
           .fillColor(this.themeColor)
           .fillColor(this.themeColor)
           .objectFit(ImageFit.Cover)
           .objectFit(ImageFit.Cover)
@@ -306,21 +519,21 @@ export struct NavidromePage {
         Column({ space: 4 }) {
         Column({ space: 4 }) {
           Text(song.name)
           Text(song.name)
             .fontSize(15)
             .fontSize(15)
-            .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:$r('app.color.index_tab_font_color'))
+            .fontColor(this.currentSong?.filePath == song.filePath ? this.themeColor : $r('app.color.index_tab_font_color'))
             .maxLines(1)
             .maxLines(1)
             .textOverflow({ overflow: TextOverflow.Ellipsis })
             .textOverflow({ overflow: TextOverflow.Ellipsis })
 
 
-          Row(){
+          Row() {
             Text((song.artist ?? '') + "  ")
             Text((song.artist ?? '') + "  ")
               .fontSize(13)
               .fontSize(13)
-              .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:$r('app.color.index_tab_font_color'))
+              .fontColor(this.currentSong?.filePath == song.filePath ? this.themeColor : $r('app.color.index_tab_font_color'))
               .opacity(0.6)
               .opacity(0.6)
               .maxLines(1)
               .maxLines(1)
-              .visibility(song.artist?Visibility.Visible:Visibility.None)
+              .visibility(song.artist ? Visibility.Visible : Visibility.None)
               .textOverflow({ overflow: TextOverflow.Ellipsis })
               .textOverflow({ overflow: TextOverflow.Ellipsis })
             Text(this.buildSongMetaLine(song))
             Text(this.buildSongMetaLine(song))
               .fontSize(13)
               .fontSize(13)
-              .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:$r('app.color.index_tab_font_color'))
+              .fontColor(this.currentSong?.filePath == song.filePath ? this.themeColor : $r('app.color.index_tab_font_color'))
               .opacity(0.6)
               .opacity(0.6)
               .maxLines(1)
               .maxLines(1)
               .textOverflow({ overflow: TextOverflow.Ellipsis })
               .textOverflow({ overflow: TextOverflow.Ellipsis })
@@ -337,7 +550,7 @@ export struct NavidromePage {
             .duration(1000)// 持续
             .duration(1000)// 持续
             .state(AnimationStatus.Running)// 动画状态
             .state(AnimationStatus.Running)// 动画状态
             .fillMode(FillMode.Forwards)
             .fillMode(FillMode.Forwards)
-            .visibility(this.currentSong?.filePath==song.filePath ?  Visibility.Visible :
+            .visibility(this.currentSong?.filePath == song.filePath ? Visibility.Visible :
               Visibility.None)
               Visibility.None)
             .width(18)
             .width(18)
             .margin({ right: 12, top: 8, bottom: 8 })
             .margin({ right: 12, top: 8, bottom: 8 })
@@ -412,7 +625,7 @@ export struct NavidromePage {
             .maxLines(1)
             .maxLines(1)
             .textAlign(TextAlign.Start)
             .textAlign(TextAlign.Start)
             .textOverflow({ overflow: TextOverflow.Ellipsis })
             .textOverflow({ overflow: TextOverflow.Ellipsis })
-          Row(){
+          Row() {
             Text(album.artist ?? Constants.UNKNOWN_ARTIST)
             Text(album.artist ?? Constants.UNKNOWN_ARTIST)
               .fontSize(13)
               .fontSize(13)
               .fontColor($r('app.color.index_tab_font_color'))
               .fontColor($r('app.color.index_tab_font_color'))
@@ -482,12 +695,16 @@ export struct NavidromePage {
   }
   }
 
 
   private getVisibleSongs(): VideoItem[] {
   private getVisibleSongs(): VideoItem[] {
+    if(this.isSearchMode)
+      return this.filteredList;
+
     if (this.filterType === NavFilterType.Artist && this.filterId.length > 0) {
     if (this.filterType === NavFilterType.Artist && this.filterId.length > 0) {
       return this.allVideos.filter(item => item.navArtistId === this.filterId || item.artist === this.filterLabel);
       return this.allVideos.filter(item => item.navArtistId === this.filterId || item.artist === this.filterLabel);
     }
     }
     if (this.filterType === NavFilterType.Album && this.filterId.length > 0) {
     if (this.filterType === NavFilterType.Album && this.filterId.length > 0) {
       return this.allVideos.filter(item => item.navAlbumId === this.filterId || item.album === this.filterLabel);
       return this.allVideos.filter(item => item.navAlbumId === this.filterId || item.album === this.filterLabel);
     }
     }
+
     return this.allVideos;
     return this.allVideos;
   }
   }
 
 
@@ -522,7 +739,7 @@ export struct NavidromePage {
     this.filterLabel = '';
     this.filterLabel = '';
   }
   }
 
 
-  private playSong(song: VideoItem, index: number,isJump:boolean = false): void {
+  private playSong(song: VideoItem, index: number, isJump: boolean = false): void {
     try {
     try {
       if (!this.allVideos || this.allVideos.length === 0) {
       if (!this.allVideos || this.allVideos.length === 0) {
         ToastUtil.showToast('暂无可播放的歌曲');
         ToastUtil.showToast('暂无可播放的歌曲');
@@ -654,4 +871,4 @@ export struct NavidromePage {
     .height('100%')
     .height('100%')
     .backgroundColor($r('app.color.start_window_background'))
     .backgroundColor($r('app.color.start_window_background'))
   }
   }
-}
+}