Эх сурвалжийг харах

初步实现右侧的字母索引

onecold 9 сар өмнө
parent
commit
4ac950d0cb

+ 1 - 1
AppScope/app.json5

@@ -2,7 +2,7 @@
   "app": {
     "bundleName": "com.xgplayer.ttmusic.hm",
     "vendor": "example",
-    "versionCode": 20251115,
+    "versionCode": 20251116,
     "versionName": "1.7.0",
     "icon": "$media:app_icon",
     "label": "$string:app_name",

+ 4 - 0
entry/src/main/ets/Constants.ets

@@ -33,4 +33,8 @@ export class Constants {
 
   // 默认封面
   public static readonly COMMON_SONG_DEFAULT_IMAGE: ResourceStr = $r('app.media.music_red')
+
+  public static readonly  COMMON_BORDER_RADIUS: number = 8
+  public static readonly  COMMON_HIGHLIGHT_COLOR: string = '#e45050'
+  public static readonly  COMMON_HIGHLIGHT_LIGHT_COLOR: string = '#f9d7d7'
 }

+ 92 - 0
entry/src/main/ets/Styels/GlobalStyles.ets

@@ -0,0 +1,92 @@
+import { Constants } from "../Constants"
+
+
+// 字母表样式
+export class GlobalAlphabetIndexerModifier implements AttributeModifier<AlphabetIndexerAttribute> {
+  applyNormalAttribute(instance: AlphabetIndexerAttribute): void {
+    instance
+      .itemSize(17)
+      .usingPopup(true)
+      .popupItemBorderRadius(24)
+      .popupColor(Constants.COMMON_HIGHLIGHT_COLOR)
+      .popupBackground($r('app.color.alphabet_background_color_opacity'))
+      .alignStyle(IndexerAlign.Right)
+      .autoCollapse(true)
+      .enableHapticFeedback(true)
+      .selectedColor(Constants.COMMON_HIGHLIGHT_COLOR)
+      .selectedBackgroundColor(Constants.COMMON_HIGHLIGHT_LIGHT_COLOR)
+      .itemBorderRadius(14)
+      .layoutWeight(1)
+      .selectedFont({weight: FontWeight.Bolder })
+  }
+}
+
+// 矩形样式
+export class GlobalRectModifier implements AttributeModifier<RectAttribute> {
+  applyNormalAttribute(instance: RectAttribute): void {
+    instance
+      .borderColor(Color.Transparent)
+      .fill(Constants.COMMON_HIGHLIGHT_COLOR)
+      .animation({
+        curve: Curve.EaseOut,
+        iterations: 1,
+        playMode: PlayMode.Normal
+      })
+      .width(2)
+  }
+}
+
+
+// 显示图片样式
+export class GlobalImageModifier implements AttributeModifier<ImageAttribute> {
+  applyNormalAttribute(instance: ImageAttribute): void {
+    instance
+      .shadow(ShadowStyle.OUTER_DEFAULT_MD)
+      .objectFit(ImageFit.None)
+  }
+}
+
+// 选择框样式
+export class GlobalSelectModifier implements AttributeModifier<SelectAttribute> {
+  applyNormalAttribute(instance: SelectAttribute): void {
+    instance
+      .font({ size: 16, weight: 500 })
+      .fontColor($r('app.color.common_fill_color'))
+      .selectedOptionFont({ size: 16, weight: 400 })
+      .optionFont({ size: 16, weight: 400 })
+      .arrowPosition(ArrowPosition.END)
+      .menuAlign(MenuAlignType.START, {dx:0, dy:0})
+      .optionWidth(200)
+      .optionHeight(500)
+  }
+}
+
+
+
+
+// 文本输入框样式
+export class GlobalTextInputModifier implements AttributeModifier<TextInputAttribute>{
+  applyNormalAttribute(instance: TextInputAttribute): void {
+    instance
+      .borderRadius(Constants.COMMON_BORDER_RADIUS)
+  }
+}
+
+// 搜索框样式
+export class GlobalSearchModifier implements AttributeModifier<SearchAttribute>{
+  applyNormalAttribute(instance: SearchAttribute): void {
+    instance
+      .borderRadius(Constants.COMMON_BORDER_RADIUS)
+  }
+}
+
+// 选择框样式
+export class GlobalToggleModifier implements AttributeModifier<ToggleAttribute>{
+  applyNormalAttribute(instance: ToggleAttribute): void {
+    instance
+      .selectedColor(Constants.COMMON_HIGHLIGHT_COLOR)
+  }
+}
+
+
+

+ 186 - 56
entry/src/main/ets/view/LocalMusic.ets

@@ -121,7 +121,7 @@ import {
   HdsSceneType,
   HdsVisualComponentAttribute,
 } from '@kit.UIDesignKit';
-
+import { GlobalAlphabetIndexerModifier } from '../Styels/GlobalStyles'
 
 const TAG = 'LocalMusic';
 
@@ -566,6 +566,16 @@ const ITEM_HEIGHT_BIG: number = 78; // 列表项大高度
 @Preview
 @Component
 export struct LocalMusic {
+  // 字母表
+  @State alphaBet: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
+    'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+  @State alphaBetStyle: GlobalAlphabetIndexerModifier = new GlobalAlphabetIndexerModifier()
+  @State selectAlphaBetIndex: number = 0
+  @State isShowAlphaBet: boolean = true
+  @State isScrolling: boolean = false
+  @State isTouchingAlphaBet: boolean = false
+  @State alphabetHeight: number = 0
+  public enableShowAlphabet: boolean = true  // 是否显示字母表
   @State isShowPrecious: boolean = false //播控条显示上一首按钮
   //背景两侧流光控制器
   @State sceneBgController: HdsSceneController|undefined = deviceInfo.sdkApiVersion>=20
@@ -2110,6 +2120,8 @@ export struct LocalMusic {
       animateTo({ duration: 666 }, () => {
         this.opacityItem = 1;
       });
+      //添加到字母索引表
+      this.setAlphaBet()
       this.setButtonStatus()
     }, 200);
 
@@ -2494,7 +2506,7 @@ export struct LocalMusic {
         message:'用系统自带的文件管理器将:\n\n1、音频文件放入' + '\n\n我的设备/Download/'
           + this.appName + '\n\n2、无需繁琐的勾选和分类步骤。\n\n3、每个子文件夹将被组成一个分组。' +
           '\n\n4、歌词文件的文件名必须和目标歌曲文件名相同(不包含后缀名)。\n\n5、歌词文件和目标歌曲文件必须同个目录。' +
-          '\n\n6、电脑上Download的目录是\nDownload/' + this.packName + '。\n\n7、放好文件后如果没显示点击刷新按钮。\n',
+          '\n\n6、电脑上Download的目录是\nDownload/' + this.packName + '。\n\n7、放好文件后去文件扫描一下。\n',
         buttons: [
           {
             text: '知道了',
@@ -3154,31 +3166,9 @@ export struct LocalMusic {
           })
 
           Stack() {
-            Row() {
-              Blank()
-                .layoutWeight(1)
-              IndexerView(
-                {
-                  indexArray: DEFAULT_INDEX,
-                  selectedIndex: this.selectedIndex,
-                  textSize: 12,
-                  selectedColor: "#ff419ee5",
-                  normalColor: "#ffa0a0a0",
-                  floatSelectedTextColor: "#ffffff",
-                  floatSelectedViewColor: "#ff419ee5",
-                  onIndexChanged: (index) => {
-                    this.listScroller.scrollToIndex(index)
-                  }
-                })
-                .width(24)
-                .height("50%")
-            }.visibility(Visibility.None)
 
-            Column() {
 
-              // if (!this.isShowCoverHeader()) {
-              //   this.tabTitle()
-              // }
+            Column() {
 
               if(this.twoFingerType==4){
                 this.getWaterView()
@@ -3212,6 +3202,8 @@ export struct LocalMusic {
             }
             .layoutWeight(1)
             .height('100%')
+            // 右侧居中的字母表索引
+            this.indexBuilder()
 
             this.topTitleBar()
           }
@@ -3392,6 +3384,131 @@ export struct LocalMusic {
 
   }
 
+  //字母表索引
+  @Builder
+  indexBuilder(){
+    if(this.enableShowAlphabet){
+      Column({space: 5}){
+        if(this.isShowAlphaBet){
+          AlphabetIndexer({arrayValue: this.alphaBet,selected: this.selectAlphaBetIndex})
+            .attributeModifier(this.alphaBetStyle)
+            .usingPopup(this.isTouchingAlphaBet)
+            .onAreaChange((old_value,new_value)=>{
+              this.alphabetHeight = new_value.height as number
+            })
+            .popupPosition({
+              x: 40,
+              y: this.alphabetHeight / 2
+            })
+            .onSelect((index: number) => {
+              this.onSelectIndexItem(this.alphaBet[index])
+            })
+            .onTouch((event: TouchEvent) =>{
+              this.isShowAlphaBet = true
+              if(event.type === TouchType.Up || event.type === TouchType.Cancel){
+                this.isTouchingAlphaBet = false
+              }else{
+                this.isTouchingAlphaBet = true
+              }
+            })
+            .transition(
+              TransitionEffect.translate({x: 10})
+                .combine(TransitionEffect.opacity(0.1))
+                .animation({
+                  duration: 150,
+                  curve: Curve.EaseInOut,
+                })
+            )
+        }else{
+          Blank()
+            .layoutWeight(1)
+        }
+
+        // ListLocateButton()
+        //   .onClick(() =>{
+        //     this.locateSong()
+        //   })
+        //   .clickEffect({level:ClickEffectLevel.LIGHT,scale: 0.5})
+        //   .margin({
+        //     bottom: 10
+        //   })
+      }
+      .padding({
+        bottom: 0
+      })
+      .position({x:'92%'})
+      .height('100%')
+      .justifyContent(FlexAlign.Center)
+    }
+  }
+
+  onSelectIndexItem(letter: string) {
+    let songs = this.videoLocalList
+    let targetIndex = -1;
+    for (let i = 0; i < songs.length; i++) {
+      let song = songs[i]
+      let text = song.name
+      const pyStr = song.pyStr
+      if(pyStr){
+        let firstPinyin = pyStr[0]
+        if (firstPinyin&&firstPinyin === letter) {
+          targetIndex = i;
+          break;
+        }
+      }
+
+    }
+    if (targetIndex !== -1) {
+      this.listScroller.scrollToIndex(targetIndex);
+    }
+  }
+
+  async setAlphaBet(){
+    return new Promise<void>((resolve,reject) => {
+      try {
+        let songs = this.videoLocalList
+        if(songs.length > 40000){
+          this.alphaBet = []
+          resolve()
+          return
+        }
+        let alphabetSet = new Set<string>();
+        for (let song of songs) {
+          let text = song.name
+          // if(this.sortMode === SortMode.ByArtist){
+          //   text = song.artist
+          // }else if(this.sortMode === SortMode.ByTitle){
+          //   text = song.title
+          // }else if(this.sortMode === SortMode.ByRoot){
+          //   text = song.webFilePath.replace('/','')
+          // }
+          const pyStr = song.pyStr
+          if(pyStr){
+            if(!alphabetSet.has(pyStr)){
+              alphabetSet.add(pyStr)
+            }
+          }
+
+        }
+        // this.alphaBet = Array.from(alphabetSet).sort((a,b)=>{
+        //   if(this.upOrDown === SortModeUpOrDown.Down){
+        //     return b.localeCompare(a)
+        //   }else{
+        //     return a.localeCompare(b)
+        //   }
+        // })
+        if(this.videoLocalList.length > 0){
+          this.listScroller.scrollToIndex(0)
+          this.selectAlphaBetIndex = 0
+        }
+        resolve()
+      }catch (e) {
+        console.error('testTag','排序字母表出错',JSON.stringify(e))
+        resolve()
+      }
+    })
+  }
+
 
   @Builder
   topTitleBar(){
@@ -4518,16 +4635,16 @@ export struct LocalMusic {
         Blank()
         Image($r('app.media.arrow_right'))
           .height(25)
-          .margin({ right: 10 })
           .alignSelf(ItemAlign.Center)
           .id(item.filePath)
-
+          .visibility(this.isShowAlphaBet?Visibility.None:Visibility.Visible)
+          .animation({ duration: 300, curve: Curve.Ease })
 
       }
     }
     .backgroundColor(Color.Transparent)
     .height(this.twoFingerType == 3 ? ITEM_HEIGHT_BIG : this.twoFingerType == 2 ? ITEM_HEIGHT : ITEM_HEIGHT_SMALL)
-    .width('100%')
+    .width('98%')
     .reuseId('dir_item')
     .visibility(this.isShowDir(item.name) ? Visibility.Visible : Visibility.None)
     .opacity(this.opacityItem) // 绑定透明度
@@ -6085,37 +6202,40 @@ export struct LocalMusic {
   }
   @Builder
   getList(){
-    Refresh({ refreshing: $$this.isRefreshing, refreshingContent: this.contentNode }) {
-      this.getListView()
-    }
-    .pullDownRatio(this.ratioL)
-    .pullToRefresh(true)
-    .refreshOffset(0)
-    .onOffsetChange((offset: number) => {
-      // 越接近最大距离,下拉跟手系数越小。
-      this.ratioL = 1 - Math.pow((offset / this.maxRefreshingHeight), 3);
-    })
-    .onStateChange((refreshStatus: RefreshStatus) => {
-      console.info('onecold Refresh onStatueChange state is ' + refreshStatus);
-    })
-    .onRefreshing(async () => {
-      if(this.modeType==0){
-        this.getHistoryList(false)
-        this.asyncCurrentPathOnlyFile()
-      }else if(this.modeType == 1){
-        workerInstance.postMessage({ code: 2, data: this.context,data2:this.packName });
-      }else if(this.modeType == 2){
-        workerInstance.postMessage({ code: 3, data: this.context });
-      }else if(this.modeType == 3){
-        workerInstance.postMessage({ code: 4, data: this.context });
-      }else if(this.modeType == 4){
-        this.doSongListTask()
-      }
+    Stack() {
+      Refresh({ refreshing: $$this.isRefreshing, refreshingContent: this.contentNode }) {
+        this.getListView()
+      }
+      .pullDownRatio(this.ratioL)
+      .pullToRefresh(true)
+      .refreshOffset(0)
+      .onOffsetChange((offset: number) => {
+        // 越接近最大距离,下拉跟手系数越小。
+        this.ratioL = 1 - Math.pow((offset / this.maxRefreshingHeight), 3);
+      })
+      .onStateChange((refreshStatus: RefreshStatus) => {
+        console.info('onecold Refresh onStatueChange state is ' + refreshStatus);
+      })
+      .onRefreshing(async () => {
+        if(this.modeType==0){
+          this.getHistoryList(false)
+          this.asyncCurrentPathOnlyFile()
+        }else if(this.modeType == 1){
+          workerInstance.postMessage({ code: 2, data: this.context,data2:this.packName });
+        }else if(this.modeType == 2){
+          workerInstance.postMessage({ code: 3, data: this.context });
+        }else if(this.modeType == 3){
+          workerInstance.postMessage({ code: 4, data: this.context });
+        }else if(this.modeType == 4){
+          this.doSongListTask()
+        }
 
 
-      console.log('onRefreshing test');
-    })
+        console.log('onRefreshing test');
+      })
+
 
+    }
   }
 
 
@@ -6301,6 +6421,16 @@ export struct LocalMusic {
       }, (item: VideoItem) => item.filePath + '_' + this.listRefreshKey)
       // }
     }
+    .onScrollStart(() => {
+      this.isScrolling = true
+      this.isShowAlphaBet = true
+    })
+    .onScrollStop(() => {
+      if(this.isScrolling){
+        this.isScrolling = false
+      }
+      this.isShowAlphaBet = false
+    })
     .scrollBar(BarState.Off)
     .scale({ x: this.scaleValue, y: this.scaleValue, z: 1 })
     .gesture(PinchGesture({ fingers: 2 })

+ 1 - 1
entry/src/main/ets/view/TagsContentCover.ets

@@ -146,7 +146,7 @@ export struct TagsContentCover {
           .fontColor([this.themeColor])
           .alignSelf(ItemAlign.Center)
           .margin({ left: 15 })
-        Text('说明:不是本app路径下的歌曲将在[DownLoad/天天静听/批量内嵌]标签文件夹中生成新的内嵌歌曲。如果失败可以多试几次。')
+        Text('说明:不是本app路径下的歌曲将在[DownLoad/天天静听/批量内嵌]文件夹中生成新的内嵌歌曲。如果失败可以多试几次。')
           .margin({ left: 5 ,right: 5})
           .fontSize(14)
           .padding({top:4,bottom:4})

+ 8 - 0
entry/src/main/resources/base/element/color.json

@@ -219,6 +219,14 @@
     {
       "name": "cancel_button_text",
       "value": "#666666"
+    },
+    {
+      "name": "alphabet_background_color_opacity",
+      "value": "#99AAAAAA"
+    },
+    {
+      "name": "common_fill_color",
+      "value": "#FFFFFF"
     }
   ]
 }

+ 8 - 0
entry/src/main/resources/dark/element/color.json

@@ -208,6 +208,14 @@
     {
       "name": "start_window_background_blur",
       "value": "#333333"
+    },
+    {
+      "name": "alphabet_background_color_opacity",
+      "value": "#99A4A49A"
+    },
+    {
+      "name": "common_fill_color",
+      "value": "#000000"
     }
   ]
 }