Forráskód Böngészése

继续优化发现页

onecold 4 hónapja
szülő
commit
544ba11500

+ 3 - 0
entry/src/main/ets/common/constants/EventConstants.ets

@@ -61,6 +61,9 @@ export class EventConstants {
   // 打开本地特殊列表(最近播放/我的收藏)
   static readonly EVENT_OPEN_LOCAL_SPECIAL_LIST: number = 2007;
 
+  // 发现页内部返回(搜索态/专辑详情态)
+  static readonly EVENT_FIND_VIEW_BACK: number = 2008;
+
   /**
    * WebDAV 元数据同步事件
    */

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

@@ -59,6 +59,7 @@ import { ServerLogUtil } from '../common/util/ServerLogUtil';
 import { PlayStatus } from '../common/PlayStatus';
 import { PointLightDefaultButton } from '../view/PointLight/PointLightDeFaultButton';
 import { FindView } from '../view/FindView';
+import { hdsEffect } from '@kit.UIDesignKit';
 
 // import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg';
 
@@ -72,6 +73,10 @@ const TAG = 'NewIndex'; // 日志标签
 @Entry
 @Component
 struct NewIndex {
+  //背景流光控制器
+  @State bgController: hdsEffect.ShaderEffectController|undefined = deviceInfo.sdkApiVersion>=20
+    &&canIUse("SystemCapability.UIDesign.HDSComponent.Core")?
+    new hdsEffect.ShaderEffectController():undefined;
   @State defalut_home_type:number = 0//首页默认类型 可支持首页 媒体库 歌单 网盘
   @State isDetailView: boolean = false; // 是否在艺术家/专辑详情视图
   @Provide isShowPlay: boolean = false;
@@ -236,7 +241,7 @@ struct NewIndex {
         // 动画闭包内控制Image组件的出现和消失
         // this.isShowDrawer = !this.isShowDrawer
         // this.offsetX = 0
-        this.mType =0
+        this.mType = 8
       })
     } else {
       console.info('onecold 返回键处理 关闭');
@@ -529,7 +534,7 @@ struct NewIndex {
     })
   }
 
-
+  @State private pointLightOptions: hdsEffect.PointLightOptions | undefined = undefined
 
   build() {
     SideBarContainer(SideBarContainerType.AUTO) {
@@ -540,6 +545,7 @@ struct NewIndex {
       Stack() {
         this.ContentBuild()
         Stack() {
+
           this.PlayController()
         }
         .width('90%')
@@ -547,9 +553,23 @@ struct NewIndex {
         .margin({ bottom:DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_2IN1
           ? 30 :this.bottomSafeHeight })
         .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK)
-        .backgroundImage(StrUtil.isEmpty(this.cover) ?$r('app.media.alt'):this.cover)
+        .backgroundImage(StrUtil.isEmpty(this.cover) ?undefined:this.cover)
         .visibility(this.isMultiSelect||(this.mType>0&&this.mType<6) ? Visibility.None : Visibility.Visible)
-
+        .visualEffect(deviceInfo.sdkApiVersion>=20&&this.bgController&&StrUtil.isEmpty(this.cover)?
+          new hdsEffect.HdsEffectBuilder()
+            .shaderEffect({
+              effectType: hdsEffect.EffectType.UV_BACKGROUND_FLOW_LIGHT,
+              animation: {
+                duration: 10000,
+                iterations: -1,
+                autoPlay: true,
+                onFinish: ()=> {
+                  console.info('Succeeded in finishing');
+                }
+              },
+              controller: this.bgController,
+            })
+            .buildEffect():null)
         .backgroundImageSize( {  width: '100%' })
         .animation({
           duration: 500,
@@ -641,6 +661,25 @@ struct NewIndex {
     .height(this.bottomBarHeight)
     .hitTestBehavior(HitTestMode.Transparent)
     .zIndex(2)
+    .onTouch((event: TouchEvent) => {
+      if (event.type === TouchType.Down) {
+        this.pointLightOptions = {
+          color: this.themeColor,
+          intensity: 1,
+          height: 100
+        }
+      } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
+        this.pointLightOptions = undefined
+      }
+    })
+    .visualEffect( deviceInfo.sdkApiVersion >= 20
+      ? new hdsEffect.HdsEffectBuilder()
+        .pointLight({
+          options: this.pointLightOptions,
+          illuminatedType: hdsEffect.PointLightIlluminatedType.BORDER_CONTENT
+        })
+        .buildEffect()
+      : undefined)
     .padding({
       left: 16,
       right: 16
@@ -648,7 +687,6 @@ struct NewIndex {
 
 
   }
-
   @Builder
   playConLeft(isLeft: boolean = true) {
     Row() {
@@ -701,6 +739,7 @@ struct NewIndex {
     .onClick(() => {
       this.setShowPlayTrue()
     })
+
   }
 
 
@@ -1232,10 +1271,10 @@ struct NewIndex {
 
   isTextSelected(index:number):boolean{
     if(this.mType == 0){
-      return this.modeType == index+1
+      return this.modeType == index
     }
     if (this.mType == 8) {
-      return index == 0
+      return index == 4
     }
     if (this.mType == 2) {
       return index == 5

+ 51 - 12
entry/src/main/ets/view/ConfigTitle.ets

@@ -1,13 +1,32 @@
 import { SymbolGlyphFancyModifier } from "../common/util/AttributeModifierUtil"
+import Logger from '../common/util/Logger'
+
+const TAG = 'ConfigTitle'
+
+function getConfigTitleIndicatorKey(item: number): string {
+  return 'config_indicator_' + item
+}
 
 @Component
 export struct ConfigTitle {
   @Require @Prop text: string
   @Prop actionText: string = '换一换'
   @Prop showAction: boolean = true
-  @Prop actionSymbol: Resource = $r('sys.symbol.arrow_clockwise')
+  @Prop actionSymbol: Resource = $r('sys.symbol.chevron_right')
+  @Prop indicatorCount: number = 0
+  @Prop indicatorIndex: number = 0
+  @Prop indicatorActiveColor: ResourceColor = $r('app.color.text_color')
+  @Prop indicatorInactiveColor: ResourceColor = $r('app.color.find_secondary_text')
   onActionClick: () => void = () => {}
 
+  private getIndicatorItems(): number[] {
+    const items: number[] = []
+    for (let i = 0; i < this.indicatorCount; i++) {
+      items.push(i)
+    }
+    return items
+  }
+
   build() {
     Row() {
       Text(this.text)
@@ -16,19 +35,39 @@ export struct ConfigTitle {
         .lineHeight(25)
         .fontColor($r('app.color.text_color'))
 
-      if (this.showAction) {
-        Row({ space: 5 }) {
-          Text(this.actionText)
-            .fontSize(14)
-            .lineHeight(16)
-            .fontColor($r('app.color.find_secondary_text'))
-          SymbolGlyph(this.actionSymbol)
-            .attributeModifier(new SymbolGlyphFancyModifier(18, '', ''))
+      Row({ space: 10 }) {
+        if (this.indicatorCount > 1) {
+          Row({ space: 6 }) {
+            ForEach(this.getIndicatorItems(), (item: number) => {
+              Row()
+                .width(item === this.indicatorIndex ? 14 : 6)
+                .height(6)
+                .borderRadius(999)
+                .backgroundColor(item === this.indicatorIndex ? this.indicatorActiveColor : this.indicatorInactiveColor)
+                .opacity(item === this.indicatorIndex ? 1 : 0.24)
+            }, getConfigTitleIndicatorKey)
+          }
+        }
+
+        if (this.showAction) {
+          Row({ space: 5 }) {
+            Text(this.actionText)
+              .fontSize(13)
+              .lineHeight(16)
+              .fontColor($r('app.color.find_secondary_text'))
+            SymbolGlyph(this.actionSymbol)
+              .attributeModifier(new SymbolGlyphFancyModifier(16, '', ''))
+          }
+          .padding({ left: 8, right: 8, top: 6, bottom: 6 })
+          .borderRadius(14)
+          .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.92 })
+          .onClick(() => {
+            Logger.info(TAG, `FindView ConfigTitle actionClick text=${this.text}, indicatorCount=${this.indicatorCount}, indicatorIndex=${this.indicatorIndex}`)
+            this.onActionClick()
+          })
         }
-        .onClick(() => {
-          this.onActionClick()
-        })
       }
+      .alignItems(VerticalAlign.Center)
     }
     .width('100%')
     .justifyContent(FlexAlign.SpaceBetween)

+ 287 - 35
entry/src/main/ets/view/FindAlbumDetail.ets

@@ -1,10 +1,21 @@
+import { SymbolGlyphModifier } from '@kit.ArkUI'
 import { StrUtil } from '@pura/harmony-utils'
 import { CommonConstants } from '../common/constants/CommonConstants'
-import { ButtonFancyModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil'
+import { ButtonFancyModifier, MenuModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil'
 import { VideoItem } from '../viewmodel/VideoItem'
 import { PlayingIndicator } from './PlayingIndicator'
 import { PointLightContentButton } from './PointLight/PointLightContentButton'
 
+class AlbumSortSongItem {
+  song: VideoItem
+  index: number
+
+  constructor(song: VideoItem, index: number) {
+    this.song = song
+    this.index = index
+  }
+}
+
 function getFindAlbumSongKey(item: VideoItem, index: number): string {
   if (StrUtil.isNotEmpty(item.filePath)) {
     return item.filePath
@@ -24,21 +35,106 @@ export struct FindAlbumDetail {
   @Require @Prop albumArtist: string
   @Require @Prop albumCoverPath: string
   @Require @Prop albumSourceLabel: string
-  @Prop songs: VideoItem[] = []
+  @Prop @Watch('onSongsChange') songs: VideoItem[] = []
   @Prop topSafeHeight: number = 0
   @Prop bottomSafeHeight: number = 0
   @Prop themeColor: string = CommonConstants.DEFAULT_THEME_COLOR
+  @StorageProp('isDarkMode') isDarkMode: boolean = false
+  @State private displaySongs: VideoItem[] = []
+  @State private sortType: number = 0
   @StorageLink('currentSong') currentSong: VideoItem | undefined = undefined
   @StorageLink('isPlaying') isPlaying: boolean = false
-  onPlayAll: () => void = () => {}
-  onRandomPlay: () => void = () => {}
+  onPlayAll: (songs?: VideoItem[]) => void = (_songs?: VideoItem[]) => {}
+  onRandomPlay: (songs?: VideoItem[]) => void = (_songs?: VideoItem[]) => {}
   onBack: () => void = () => {}
-  onSongTap: (index: number) => void = (_index: number) => {}
+  onSongTap: (index: number, songs?: VideoItem[]) => void = (_index: number, _songs?: VideoItem[]) => {}
+
+  aboutToAppear(): void {
+    this.applySortType(this.sortType)
+  }
+
+  private onSongsChange(): void {
+    this.applySortType(this.sortType)
+  }
 
   private getThemeAccent(): ResourceColor {
     return this.themeColor
   }
 
+  private getActiveSongs(): VideoItem[] {
+    if (this.displaySongs.length > 0) {
+      return this.displaySongs.slice()
+    }
+    return this.songs.slice()
+  }
+
+  private parseTrackNumber(item: VideoItem): number {
+    if (StrUtil.isEmpty(item.track)) {
+      return Number.MAX_SAFE_INTEGER
+    }
+    const normalizedTrack = (item.track as string).split('/')[0].trim()
+    const parsedTrack = Number.parseInt(normalizedTrack, 10)
+    return Number.isNaN(parsedTrack) ? Number.MAX_SAFE_INTEGER : parsedTrack
+  }
+
+  private parseSongTime(item: VideoItem): number {
+    if (StrUtil.isEmpty(item.cTime)) {
+      return 0
+    }
+    const parsedTime = Date.parse(item.cTime as string)
+    return Number.isNaN(parsedTime) ? 0 : parsedTime
+  }
+
+  private compareTrackNumber(left: VideoItem, right: VideoItem, descending: boolean): number {
+    const leftTrack = this.parseTrackNumber(left)
+    const rightTrack = this.parseTrackNumber(right)
+    const leftMissing = leftTrack === Number.MAX_SAFE_INTEGER
+    const rightMissing = rightTrack === Number.MAX_SAFE_INTEGER
+    if (leftMissing && rightMissing) {
+      return 0
+    }
+    if (leftMissing) {
+      return 1
+    }
+    if (rightMissing) {
+      return -1
+    }
+    return descending ? rightTrack - leftTrack : leftTrack - rightTrack
+  }
+
+  private compareSongsBySortType(left: VideoItem, right: VideoItem, sortType: number): number {
+    switch (sortType) {
+      case 0:
+        return this.compareTrackNumber(left, right, false)
+      case 1:
+        return this.compareTrackNumber(left, right, true)
+      case 2:
+        return this.getSongTitle(left).localeCompare(this.getSongTitle(right))
+      case 3:
+        return this.getSongTitle(right).localeCompare(this.getSongTitle(left))
+      case 4:
+        return this.parseSongTime(left) - this.parseSongTime(right)
+      case 5:
+        return this.parseSongTime(right) - this.parseSongTime(left)
+      default:
+        return 0
+    }
+  }
+
+  private applySortType(sortType: number): void {
+    this.sortType = sortType
+    const indexedSongs: AlbumSortSongItem[] = this.songs.map((song: VideoItem, index: number) =>
+      new AlbumSortSongItem(song, index))
+    indexedSongs.sort((left, right) => {
+      const compareResult = this.compareSongsBySortType(left.song, right.song, sortType)
+      if (compareResult !== 0) {
+        return compareResult
+      }
+      return left.index - right.index
+    })
+    this.displaySongs = indexedSongs.map((item) => item.song)
+  }
+
   private getSongTitle(item: VideoItem): string {
     if (StrUtil.isNotEmpty(item.name)) {
       return item.name
@@ -50,18 +146,46 @@ export struct FindAlbumDetail {
   }
 
   private getSongSubtitle(item: VideoItem): string {
+    const parts: string[] = []
     if (StrUtil.isNotEmpty(item.artist)) {
-      return `${item.artist} · ${item.duration}`
+      parts.push(item.artist as string)
     }
-    if (StrUtil.isNotEmpty(item.artist)) {
-      return item.artist as string
+    if (StrUtil.isNotEmpty(item.duration)) {
+      parts.push(item.duration as string)
+    }
+    if (parts.length > 0) {
+      return parts.join(' · ')
     }
     if (StrUtil.isNotEmpty(item.fileName)) {
       return item.fileName as string
     }
+    if (StrUtil.isNotEmpty(item.duration)) {
+      return item.duration as string
+    }
     return '本地音乐'
   }
 
+  private getSongQualityText(item: VideoItem): string {
+    if (StrUtil.isNotEmpty(item.md5Str)) {
+      const quality = item.md5Str as string
+      return quality.includes('Lossless') ? '无损' : quality
+    }
+
+    const sampleRate = Number(item.sampleRate ?? '0')
+    const bitDepth = Number(item.bits_per_raw_sample ?? '0')
+    if (sampleRate >= 88200 || bitDepth >= 24) {
+      return 'Hi-Res'
+    }
+
+    const mimeType = (item.mimeType ?? '').toLowerCase()
+    const fileName = (item.fileName ?? item.name ?? '').toLowerCase()
+    const isLosslessFormat = mimeType.includes('flac') || mimeType.includes('wav') || mimeType.includes('ape') ||
+      mimeType.includes('alac') || mimeType.includes('dsf') || mimeType.includes('dff') ||
+      fileName.endsWith('.flac') || fileName.endsWith('.wav') || fileName.endsWith('.ape') ||
+      fileName.endsWith('.alac') || fileName.endsWith('.dsf') || fileName.endsWith('.dff')
+    return isLosslessFormat ? '无损' : ''
+  }
+
   private getSongCover(item: VideoItem): string | Resource {
     return StrUtil.isNotEmpty(item.pixelMapPath) ? item.pixelMapPath as string : $r('app.media.alt')
   }
@@ -82,22 +206,121 @@ export struct FindAlbumDetail {
     return $r('app.color.find_card_background')
   }
 
+  private getSongQualityBadgeTextColor(): ResourceColor {
+    return $r('app.color.album_detail_song_quality_text')
+  }
+
+  private getSongQualityBadgeBackgroundColor(): ResourceColor {
+    return $r('app.color.album_detail_song_quality_background')
+  }
+
+  private getHeroTagBackgroundColor(): ResourceColor {
+    return $r('app.color.album_detail_hero_tag_background')
+  }
+
+  private getHeaderActionButtonColor(): ResourceColor {
+    return $r('app.color.album_detail_header_action_background')
+  }
+
+  private getHeaderActionPrimaryBorderColor(): ResourceColor {
+    return $r('app.color.album_detail_header_action_primary_border')
+  }
+
+  private getHeaderActionSecondaryBorderColor(): ResourceColor {
+    return $r('app.color.album_detail_header_action_secondary_border')
+  }
+
   private getAlbumArtistText(): string {
     if (StrUtil.isNotEmpty(this.albumArtist)) {
       return this.albumArtist
     }
+    if (StrUtil.isNotEmpty(this.albumSourceLabel)) {
+      return this.albumSourceLabel
+    }
     return '未知歌手'
   }
 
+  private getSortItemBackground(sortType: number): ResourceColor {
+    if (this.sortType !== sortType) {
+      return Color.Transparent
+    }
+    const isAsc: boolean = sortType % 2 === 0
+    if (isAsc) {
+      return this.isDarkMode ? '#295B8A' : '#DCEEFF'
+    }
+    return this.isDarkMode ? '#7A4D22' : '#FFE9D5'
+  }
+
+  @Builder
+  private SortMenuBuilder() {
+    Menu() {
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.list_number')),
+        content: '按音轨号升序',
+        symbolEndIcon: this.sortType === 0 ? new SymbolGlyphModifier($r('sys.symbol.checkmark')) : undefined
+      })
+        .backgroundColor(this.getSortItemBackground(0))
+        .onClick(() => {
+          this.applySortType(0)
+        })
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.list_number')),
+        content: '按音轨号降序',
+        symbolEndIcon: this.sortType === 1 ? new SymbolGlyphModifier($r('sys.symbol.checkmark')) : undefined
+      })
+        .backgroundColor(this.getSortItemBackground(1))
+        .onClick(() => {
+          this.applySortType(1)
+        })
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_up')),
+        content: '按名称升序',
+        symbolEndIcon: this.sortType === 2 ? new SymbolGlyphModifier($r('sys.symbol.checkmark')) : undefined
+      })
+        .backgroundColor(this.getSortItemBackground(2))
+        .onClick(() => {
+          this.applySortType(2)
+        })
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_down')),
+        content: '按名称降序',
+        symbolEndIcon: this.sortType === 3 ? new SymbolGlyphModifier($r('sys.symbol.checkmark')) : undefined
+      })
+        .backgroundColor(this.getSortItemBackground(3))
+        .onClick(() => {
+          this.applySortType(3)
+        })
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.clock')),
+        content: '按时间升序',
+        symbolEndIcon: this.sortType === 4 ? new SymbolGlyphModifier($r('sys.symbol.checkmark')) : undefined
+      })
+        .backgroundColor(this.getSortItemBackground(4))
+        .onClick(() => {
+          this.applySortType(4)
+        })
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.timer')),
+        content: '按时间降序',
+        symbolEndIcon: this.sortType === 5 ? new SymbolGlyphModifier($r('sys.symbol.checkmark')) : undefined
+      })
+        .backgroundColor(this.getSortItemBackground(5))
+        .onClick(() => {
+          this.applySortType(5)
+        })
+    }
+    .attributeModifier(new MenuModifier())
+  }
+
 
   @Builder
   private buildHeroTag(text: string) {
     Text(text)
       .fontSize(15)
       .fontWeight(FontWeight.Bold)
-      .fontColor('#F8FFFFFF')
+      .fontColor($r('app.color.text_color'))
       .padding({ left: 10, right: 10, top: 6, bottom: 6 })
-      .backgroundColor('#24FFFFFF')
+      .backgroundColor(this.getHeroTagBackgroundColor())
       .borderRadius(100)
   }
 
@@ -126,13 +349,14 @@ export struct FindAlbumDetail {
     Row({ space: 8 }) {
       SymbolGlyph($r('sys.symbol.shuffle'))
         .fontSize(18)
-        .fontColor([Color.White])
+        .fontSize(18)
+        .fontColor([this.getThemeAccent()])
         .effectStrategy(SymbolEffectStrategy.HIERARCHICAL)
 
       Text('随机播放')
         .fontSize(14)
         .fontWeight(FontWeight.Medium)
-        .fontColor('#FFFFFFFF')
+        .fontColor(this.getPrimaryTextColor())
         .maxLines(1)
     }
     .width('100%')
@@ -148,7 +372,7 @@ export struct FindAlbumDetail {
     Row({ space: 10 }) {
       PointLightContentButton({
         pointColor: this.getThemeAccent(),
-        buttonColor: '#1FFFFFFF',
+        buttonColor: this.getHeaderActionButtonColor(),
         buttonRadius: 20,
         pointLightHeight: 48,
         useShadow: true,
@@ -159,14 +383,14 @@ export struct FindAlbumDetail {
       .width(132)
       .height(48)
       .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.9 })
-      .border({ width: 1, color: '#10FFFFFF', radius: 20 })
+      .border({ width: 1, color: this.getHeaderActionPrimaryBorderColor(), radius: 20 })
       .onClick(() => {
-        this.onPlayAll()
+        this.onPlayAll(this.getActiveSongs())
       })
 
       PointLightContentButton({
-        pointColor: Color.White,
-        buttonColor: '#1FFFFFFF',
+        pointColor: $r('app.color.white'),
+        buttonColor: this.getHeaderActionButtonColor(),
         buttonRadius: 20,
         pointLightHeight: 48,
         useShadow: true,
@@ -176,9 +400,9 @@ export struct FindAlbumDetail {
       })
       .width(132)
       .height(48).clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.9 })
-      .border({ width: 1, color: '#30FFFFFF', radius: 20 })
+      .border({ width: 1, color: this.getHeaderActionSecondaryBorderColor(), radius: 20 })
       .onClick(() => {
-        this.onRandomPlay()
+        this.onRandomPlay(this.getActiveSongs())
       })
     }
     .width('100%')
@@ -201,7 +425,7 @@ export struct FindAlbumDetail {
             .fontSize(22)
             .fontWeight(FontWeight.Bold)
             .textAlign(TextAlign.Center)
-            .fontColor('#FFFFFFFF')
+            .fontColor($r('app.color.text_color'))
             .maxLines(2)
             .textOverflow({ overflow: TextOverflow.Ellipsis })
             .width('100%')
@@ -211,7 +435,7 @@ export struct FindAlbumDetail {
             .lineHeight(21)
             .textAlign(TextAlign.Center)
             .fontWeight(FontWeight.Bold)
-            .fontColor('#D8FFFFFF')
+            .fontColor($r('app.color.text_color'))
             .maxLines(1)
             .textOverflow({ overflow: TextOverflow.Ellipsis })
             .width('100%')
@@ -252,14 +476,30 @@ export struct FindAlbumDetail {
           .textOverflow({ overflow: TextOverflow.Ellipsis })
           .width('100%')
 
-        Text(this.getSongSubtitle(item))
-          .fontSize(13)
-          .lineHeight(17)
-          .fontColor(this.getSecondaryTextColor())
-          .maxLines(1)
-          .textOverflow({ overflow: TextOverflow.Ellipsis })
-          .width('100%')
+        Row({ space: 6 }) {
+          if (StrUtil.isNotEmpty(this.getSongQualityText(item))) {
+            Text(this.getSongQualityText(item))
+              .fontSize(10)
+              .fontWeight(FontWeight.Medium)
+              .fontColor(this.getSongQualityBadgeTextColor())
+              .padding({ left: 6, right: 6, top: 2, bottom: 2 })
+              .backgroundColor(this.getSongQualityBadgeBackgroundColor())
+              .borderRadius(4)
+          }
+
+          Text(this.getSongSubtitle(item))
+            .fontSize(13)
+            .lineHeight(17)
+            .fontColor(this.getSecondaryTextColor())
+            .maxLines(1)
+            .textOverflow({ overflow: TextOverflow.Ellipsis })
+            .layoutWeight(1)
+        }
+        .width('100%')
+        .alignItems(VerticalAlign.Center)
+        .justifyContent(FlexAlign.Start)
       }
+      .width('100%')
       .layoutWeight(1)
       .alignItems(HorizontalAlign.Start)
 
@@ -276,7 +516,7 @@ export struct FindAlbumDetail {
     }
     .width('100%')
     .padding({ left: 14, right: 14, top: 14, bottom: 14 })
-    .backgroundColor('#FBFAFD')
+    .backgroundColor(this.getCardBackgroundColor())
   }
 
   @Builder
@@ -334,7 +574,7 @@ export struct FindAlbumDetail {
   @Builder
   private topTitleBar() {
     Column() {
-      Row({ space: 12 }) {
+      Row() {
         Button({ type: ButtonType.Circle, stateEffect: true }) {
           SymbolGlyph($r('sys.symbol.chevron_left'))
             .attributeModifier(new SymbolGlyphFancyModifier(24, '', ''))
@@ -347,10 +587,22 @@ export struct FindAlbumDetail {
         })
         .attributeModifier(new ShadowModifier())
         .zIndex(0)
+        Blank()
+        Button({ type: ButtonType.Circle, stateEffect: true }) {
+          SymbolGlyph($r('sys.symbol.list_number'))
+            .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
+        }
+        .attributeModifier(new ButtonFancyModifier(40, 40))
+        .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
+        .animation({ duration: 300, curve: Curve.Ease })
+        .bindMenu(this.SortMenuBuilder)
+        .attributeModifier(new ShadowModifier())
+        .zIndex(0)
     }
     .padding({ top: this.topSafeHeight + 10, left: 10, right: 10, bottom: 6 })
     .width('100%')
-    .backgroundColor(Color.Transparent)
+    .alignItems(VerticalAlign.Center)
+    .backgroundColor($r('app.color.ban_touming'))
     }
   }
 
@@ -361,17 +613,17 @@ export struct FindAlbumDetail {
         this.buildHeroHeader()
       }
 
-      if (this.songs.length === 0) {
+      if (this.displaySongs.length === 0) {
         ListItem() {
           this.buildEmptyState()
         }
         .padding({ left: 16, right: 16 })
       } else {
-        ForEach(this.songs, (item: VideoItem, index: number) => {
+        ForEach(this.displaySongs, (item: VideoItem, index: number) => {
           ListItem() {
             PointLightContentButton({
               pointColor: this.getThemeAccent(),
-              buttonColor: '#00FFFFFF',
+              buttonColor: $r('app.color.touming'),
               buttonRadius: 15,
               pointLightHeight: 132,
               useShadow: true,
@@ -381,7 +633,7 @@ export struct FindAlbumDetail {
             })
             .width('100%')
             .onClick(() => {
-              this.onSongTap(index)
+              this.onSongTap(index, this.getActiveSongs())
             })
           }
           .padding({ left: 16, right: 16 })

+ 277 - 66
entry/src/main/ets/view/FindView.ets

@@ -54,6 +54,7 @@ interface FindPlaylistEventData {
   startIndex: number
   isJump: boolean
   songFilePaths: string[]
+  playType?: number
 }
 
 interface FindAlbumGroup {
@@ -115,11 +116,16 @@ export struct FindView {
   @State private swiperSongs: VideoItem[] = []
   @State private hotSongs: VideoItem[] = []
   @State private remoteSongs: VideoItem[] = []
+  @State private cloudSectionPages: FindSongPage[] = []
   @State private recentSongs: VideoItem[] = []
   @State private popularSongs: VideoItem[] = []
+  @State private popularSectionPages: FindSongPage[] = []
   @State private favoriteSongs: VideoItem[] = []
+  @State private cloudMoodSongs: VideoItem[] = []
   @State private featuredAlbums: FindAlbumGroup[] = []
+  @State private featuredAlbumPages: FindAlbumPage[] = []
   @State private cloudAlbums: FindAlbumGroup[] = []
+  @State private cloudAlbumPages: FindAlbumPage[] = []
   @State @Watch('syncFindCanBackState') private isSearchMode: boolean = false
   @State @Watch('syncFindCanBackState') private isAlbumMode: boolean = false
   @State private currentAlbumId: string = ''
@@ -146,7 +152,7 @@ export struct FindView {
   @State private maxRefreshingHeight: number = 100
 
   searchController: SearchController = new SearchController()
-  @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD
+  @StorageProp('currentBreakpoint') @Watch('onDiscoverBreakpointChange') currentBreakpoint: string = BreakpointTypeEnum.MD
   @StorageProp('windowWidth') windowWidth: number = 0
   @StorageProp('topSafeHeight') topSafeHeight: number = 0
   @StorageProp('bottomSafeHeight') bottomSafeHeight: number = 0
@@ -165,6 +171,10 @@ export struct FindView {
   private searchRemoteSongsPool: VideoItem[] = []
   private readonly searchHistoryScope: string = 'find_music'
   private searchTicket: number = 0
+  private cloudSectionPageVersion: number = 0
+  private featuredAlbumPageVersion: number = 0
+  private cloudAlbumPageVersion: number = 0
+  private popularSectionPageVersion: number = 0
 
   aboutToAppear(): void {
     this.initSetting()
@@ -194,6 +204,11 @@ export struct FindView {
     this.autoHideTitle = PreferencesUtil.getBooleanSync('autoHideTitle', true)
   }
 
+  private onDiscoverBreakpointChange(): void {
+    Logger.info(TAG, `FindView onDiscoverBreakpointChange breakpoint=${this.currentBreakpoint}, columns=${this.getDiscoverSectionColumns()}`)
+    this.rebuildPagedSectionSources()
+  }
+
   private ensureMediaTable(): void {
     if (this.mediaTable) {
       void this.loadDiscoveryContent(false)
@@ -254,6 +269,7 @@ export struct FindView {
       this.searchRemoteSongsPool = []
       this.swiperSongs = this.pickRandomSongs(this.coveredSongsPool, SWIPER_MAX_COUNT)
       this.hotSongs = this.pickPreferredSongs(this.localSongsPool, HOT_SECTION_COUNT)
+      this.cloudMoodSongs = this.pickPreferredSongs(this.remoteSongsPool, HOT_SECTION_COUNT)
       this.remoteSongs = this.pickPreferredSongs(this.remoteSongsPool, CLOUD_SECTION_COUNT)
       this.recentSongs = this.pickPreferredSongs(this.recentSongsPool, RECENT_SECTION_COUNT)
       this.popularSongs = this.pickPreferredSongs(this.topPlayedSongsPool, POPULAR_SECTION_COUNT)
@@ -262,6 +278,7 @@ export struct FindView {
       this.cloudAlbums = this.pickAlbumGroups(this.cloudAlbumsPool, CLOUD_ALBUM_COUNT)
       this.swiperIndex = 0
       this.resetPagedSectionIndices()
+      this.rebuildPagedSectionSources()
       this.refreshText = ''
 
       Logger.info(
@@ -273,9 +290,12 @@ export struct FindView {
       Logger.error(TAG, `发现页加载失败: ${message}`)
       this.swiperSongs = []
       this.hotSongs = []
+      this.cloudMoodSongs = []
       this.remoteSongs = []
+      this.cloudSectionPages = []
       this.recentSongs = []
       this.popularSongs = []
+      this.popularSectionPages = []
       this.favoriteSongs = []
       this.localSongsPool = []
       this.coveredSongsPool = []
@@ -287,7 +307,9 @@ export struct FindView {
       this.cloudAlbumsPool = []
       this.searchRemoteSongsPool = []
       this.featuredAlbums = []
+      this.featuredAlbumPages = []
       this.cloudAlbums = []
+      this.cloudAlbumPages = []
       this.refreshText = '推荐加载失败,请下拉重试'
     } finally {
       this.isRefreshing = false
@@ -718,7 +740,8 @@ export struct FindView {
     return false
   }
 
-  private emitPlaylistPlay(playlistId: string, playlistName: string, songs: VideoItem[], startIndex: number): void {
+  private emitPlaylistPlay(playlistId: string, playlistName: string, songs: VideoItem[], startIndex: number,
+    playType?: number): void {
     if (songs.length === 0) {
       ToastUtil.showToast('暂无可播放歌曲')
       return
@@ -734,31 +757,58 @@ export struct FindView {
       songCount: songs.length,
       startIndex: safeIndex,
       isJump: false,
-      songFilePaths: songs.map((item: VideoItem): string => item.filePath)
+      songFilePaths: songs.map((item: VideoItem): string => item.filePath),
+      playType
     }
     emitter.emit(eventPlaylistPlay, { data: playlistData })
   }
 
-  private handleActionButtonTap(type: string): void {
+  private async getFullLocalSongsPool(): Promise<VideoItem[]> {
+    if (this.localSongsPool.length > 0) {
+      return this.localSongsPool
+    }
+    if (!this.mediaTable) {
+      return []
+    }
+    const localSongs = await this.mediaTable.queryAllVideos()
+    this.localSongsPool = this.filterUniqueSongs(localSongs)
+    return this.localSongsPool
+  }
+
+  private async getFullRemoteSongsPool(): Promise<VideoItem[]> {
+    if (this.searchRemoteSongsPool.length > 0) {
+      return this.searchRemoteSongsPool
+    }
+    if (!this.mediaTable) {
+      return []
+    }
+    const remoteSongs = await this.mediaTable.queryRemoteSongsAsync()
+    this.searchRemoteSongsPool = this.filterUniqueSongs(remoteSongs)
+    return this.searchRemoteSongsPool
+  }
+
+  private async handleActionButtonTap(type: string): Promise<void> {
     if (type === 'top') {
       this.emitPlaylistPlay('find-top-played', '最近爱听', this.topPlayedSongsPool, 0)
       return
     }
     if (type === 'local-random') {
-      if (this.localSongsPool.length === 0) {
+      const allLocalSongs = await this.getFullLocalSongsPool()
+      if (allLocalSongs.length === 0) {
         ToastUtil.showToast('本地歌曲为空')
         return
       }
-      const startIndex = Math.floor(Math.random() * this.localSongsPool.length)
-      this.emitPlaylistPlay('find-local-random-all', '随心所欲', this.localSongsPool, startIndex)
+      const startIndex = Math.floor(Math.random() * allLocalSongs.length)
+      this.emitPlaylistPlay('find-local-random-all', '随心所欲', allLocalSongs, startIndex, 3)
       return
     }
-    if (this.remoteSongsPool.length === 0) {
+    const allRemoteSongs = await this.getFullRemoteSongsPool()
+    if (allRemoteSongs.length === 0) {
       ToastUtil.showToast('需要先配置网盘并加载歌曲后才能播放')
       return
     }
-    const startIndex = Math.floor(Math.random() * this.remoteSongsPool.length)
-    this.emitPlaylistPlay('find-random-cloud', '云端漫游', this.remoteSongsPool, startIndex)
+    const startIndex = Math.floor(Math.random() * allRemoteSongs.length)
+    this.emitPlaylistPlay('find-random-cloud', '云端漫游', allRemoteSongs, startIndex, 3)
   }
 
   private handleSwiperTap(index: number): void {
@@ -773,6 +823,10 @@ export struct FindView {
     this.emitPlaylistPlay('find-local-random', '本地随机', this.hotSongs, index)
   }
 
+  private handleCloudMoodSongTap(index: number): void {
+    this.emitPlaylistPlay('find-cloud-mood', '云卷云舒', this.cloudMoodSongs, index)
+  }
+
   private handleRecentSongTap(index: number): void {
     this.emitPlaylistPlay('find-recent', '最近播放', this.recentSongs, index)
   }
@@ -789,10 +843,21 @@ export struct FindView {
     this.hotSongs = this.pickDifferentPreferredSongs(this.localSongsPool, HOT_SECTION_COUNT, this.hotSongs)
   }
 
+  private refreshCloudMoodSongs(): void {
+    this.cloudMoodSongs = this.pickDifferentPreferredSongs(this.remoteSongsPool, HOT_SECTION_COUNT, this.cloudMoodSongs)
+  }
+
   private refreshCloudSongs(): void {
+    const current = this.remoteSongs.slice()
     const next = this.pickDifferentPreferredSongs(this.remoteSongsPool, CLOUD_SECTION_COUNT, this.remoteSongs)
-    this.remoteSongs = this.ensureDifferentLeadingSongs(next, this.remoteSongs)
+    const updated = this.ensureDifferentLeadingSongs(next, this.remoteSongs)
+    Logger.info(
+      TAG,
+      `FindView refreshCloudSongs pool=${this.remoteSongsPool.length}, current=${this.buildSongSelectionLog(current)}, next=${this.buildSongSelectionLog(next)}, updated=${this.buildSongSelectionLog(updated)}`
+    )
+    this.remoteSongs = updated
     this.cloudSectionPageIndex = 0
+    this.updateCloudSectionPages()
   }
 
   private refreshRecentSongs(): void {
@@ -801,32 +866,69 @@ export struct FindView {
   }
 
   private refreshPopularSongs(): void {
+    const current = this.popularSongs.slice()
     const next = this.pickDifferentPreferredSongs(this.topPlayedSongsPool, POPULAR_SECTION_COUNT, this.popularSongs)
-    this.popularSongs = this.ensureDifferentLeadingSongs(next, this.popularSongs)
+    const updated = this.ensureDifferentLeadingSongs(next, this.popularSongs)
+    Logger.info(
+      TAG,
+      `FindView refreshPopularSongs pool=${this.topPlayedSongsPool.length}, current=${this.buildSongSelectionLog(current)}, next=${this.buildSongSelectionLog(next)}, updated=${this.buildSongSelectionLog(updated)}`
+    )
+    this.popularSongs = updated
     this.popularSectionPageIndex = 0
+    this.updatePopularSectionPages()
   }
 
   private refreshFeaturedAlbums(): void {
+    const current = this.featuredAlbums.slice()
     const next = this.pickDifferentAlbumGroups(this.featuredAlbumsPool, FEATURED_ALBUM_COUNT, this.featuredAlbums)
-    this.featuredAlbums = this.ensureDifferentLeadingAlbums(next, this.featuredAlbums)
+    const updated = this.ensureDifferentLeadingAlbums(next, this.featuredAlbums)
+    Logger.info(
+      TAG,
+      `FindView refreshFeaturedAlbums pool=${this.featuredAlbumsPool.length}, current=${this.buildAlbumSelectionLog(current)}, next=${this.buildAlbumSelectionLog(next)}, updated=${this.buildAlbumSelectionLog(updated)}`
+    )
+    this.featuredAlbums = updated
     this.featuredAlbumPageIndex = 0
+    this.updateFeaturedAlbumPages()
   }
 
   private refreshCloudAlbums(): void {
+    const current = this.cloudAlbums.slice()
     const next = this.pickDifferentAlbumGroups(this.cloudAlbumsPool, CLOUD_ALBUM_COUNT, this.cloudAlbums)
-    this.cloudAlbums = this.ensureDifferentLeadingAlbums(next, this.cloudAlbums)
+    const updated = this.ensureDifferentLeadingAlbums(next, this.cloudAlbums)
+    Logger.info(
+      TAG,
+      `FindView refreshCloudAlbums pool=${this.cloudAlbumsPool.length}, current=${this.buildAlbumSelectionLog(current)}, next=${this.buildAlbumSelectionLog(next)}, updated=${this.buildAlbumSelectionLog(updated)}`
+    )
+    this.cloudAlbums = updated
     this.cloudAlbumPageIndex = 0
+    this.updateCloudAlbumPages()
   }
 
   private openLocalSpecialList(target: string): void {
-    this.mType = 0
-    this.isShowDrawer = false
-    this.offsetX = 0
-    emitter.emit({ eventId: EventConstants.EVENT_OPEN_LOCAL_SPECIAL_LIST }, {
-      data: {
-        target: target
-      }
-    })
+    if (target === 'recent') {
+      this.openSongCollectionDetail('find-recent-collection', '最近播放', '', this.recentSongsPool)
+      return
+    }
+    if (target === 'favorite') {
+      this.openSongCollectionDetail('find-favorite-collection', '我的收藏', '', this.favoriteSongsPool)
+      return
+    }
+  }
+
+  private openSongCollectionDetail(id: string, title: string, sourceLabel: string, songs: VideoItem[]): void {
+    if (songs.length === 0) {
+      ToastUtil.showToast('暂无可展示歌曲')
+      return
+    }
+    const coverSong = this.pickAlbumCoverSong(songs)
+    this.currentAlbumId = id
+    this.currentAlbumTitle = title
+    this.currentAlbumArtist = ''
+    this.currentAlbumCoverPath = coverSong?.pixelMapPath ?? ''
+    this.currentAlbumSourceLabel = sourceLabel
+    this.currentAlbumSongs = songs.slice()
+    this.isAlbumMode = true
+    this.isSearchMode = false
   }
 
   private openAlbumDetail(album: FindAlbumGroup): void {
@@ -850,34 +952,37 @@ export struct FindView {
     this.currentAlbumSongs = []
   }
 
-  private playCurrentAlbum(startIndex: number): void {
-    if (this.currentAlbumSongs.length === 0) {
+  private playCurrentAlbum(startIndex: number, songs: VideoItem[] = this.currentAlbumSongs): void {
+    if (songs.length === 0) {
       ToastUtil.showToast('专辑里还没有歌曲')
       return
     }
     this.emitPlaylistPlay(
       'find-album-playlist',
       this.currentAlbumTitle.length > 0 ? this.currentAlbumTitle : '专辑',
-      this.currentAlbumSongs,
+      songs,
       startIndex
     )
   }
 
-  private handleAlbumPlayAll(): void {
-    this.playCurrentAlbum(0)
+  private handleAlbumPlayAll(songs?: VideoItem[]): void {
+    const targetSongs = songs && songs.length > 0 ? songs : this.currentAlbumSongs
+    this.playCurrentAlbum(0, targetSongs)
   }
 
-  private handleAlbumRandomPlay(): void {
-    if (this.currentAlbumSongs.length === 0) {
+  private handleAlbumRandomPlay(songs?: VideoItem[]): void {
+    const targetSongs = songs && songs.length > 0 ? songs : this.currentAlbumSongs
+    if (targetSongs.length === 0) {
       ToastUtil.showToast('专辑里还没有歌曲')
       return
     }
-    const startIndex = Math.floor(Math.random() * this.currentAlbumSongs.length)
-    this.playCurrentAlbum(startIndex)
+    const startIndex = Math.floor(Math.random() * targetSongs.length)
+    this.playCurrentAlbum(startIndex, targetSongs)
   }
 
-  private handleAlbumSongTap(index: number): void {
-    this.playCurrentAlbum(index)
+  private handleAlbumSongTap(index: number, songs?: VideoItem[]): void {
+    const targetSongs = songs && songs.length > 0 ? songs : this.currentAlbumSongs
+    this.playCurrentAlbum(index, targetSongs)
   }
 
   private loadSearchHistory(): void {
@@ -1021,6 +1126,30 @@ export struct FindView {
     return Math.min(Math.max(currentIndex, 0), pageCount - 1)
   }
 
+  private buildSongSelectionLog(items: VideoItem[], limit: number = 4): string {
+    if (items.length === 0) {
+      return '[]'
+    }
+    const parts: string[] = []
+    const maxCount = Math.min(limit, items.length)
+    for (let i = 0; i < maxCount; i++) {
+      parts.push(this.getSongTitle(items[i]))
+    }
+    return `[${parts.join(', ')}](${items.length})`
+  }
+
+  private buildAlbumSelectionLog(items: FindAlbumGroup[], limit: number = 4): string {
+    if (items.length === 0) {
+      return '[]'
+    }
+    const parts: string[] = []
+    const maxCount = Math.min(limit, items.length)
+    for (let i = 0; i < maxCount; i++) {
+      parts.push(items[i].title)
+    }
+    return `[${parts.join(', ')}](${items.length})`
+  }
+
   private buildSongPages(prefix: string, items: VideoItem[], columns: number): FindSongPage[] {
     const pages: FindSongPage[] = []
     const pageSize = this.getSectionPageSize(columns)
@@ -1045,24 +1174,64 @@ export struct FindView {
     return pages
   }
 
-  private getCloudSectionPages(): FindSongPage[] {
-    return this.buildSongPages('find_cloud_section', this.remoteSongs, this.getDiscoverSectionColumns())
+  private rebuildPagedSectionSources(): void {
+    this.updateCloudSectionPages()
+    this.updateFeaturedAlbumPages()
+    this.updateCloudAlbumPages()
+    this.updatePopularSectionPages()
+    Logger.info(
+      TAG,
+      `FindView rebuildPagedSectionSources columns=${this.getDiscoverSectionColumns()}, cloudPages=${this.cloudSectionPages.length}, featuredAlbumPages=${this.featuredAlbumPages.length}, cloudAlbumPages=${this.cloudAlbumPages.length}, popularPages=${this.popularSectionPages.length}`
+    )
   }
 
-  private getFeaturedAlbumPages(): FindAlbumPage[] {
-    return this.buildAlbumPages('find_featured_album_section', this.featuredAlbums, this.getDiscoverSectionColumns())
+  private updateCloudSectionPages(): void {
+    this.cloudSectionPageVersion++
+    this.cloudSectionPages =
+      this.buildSongPages(`find_cloud_section_${this.cloudSectionPageVersion}`, this.remoteSongs, this.getDiscoverSectionColumns())
+    this.cloudSectionPageIndex = this.getSafeSectionPageIndex(this.cloudSectionPages.length, this.cloudSectionPageIndex)
+    Logger.info(
+      TAG,
+      `FindView updateCloudSectionPages version=${this.cloudSectionPageVersion}, pageIndex=${this.cloudSectionPageIndex}, pages=${this.cloudSectionPages.length}, songs=${this.buildSongSelectionLog(this.remoteSongs)}`
+    )
   }
 
-  private getCloudAlbumPages(): FindAlbumPage[] {
-    return this.buildAlbumPages('find_cloud_album_section', this.cloudAlbums, this.getDiscoverSectionColumns())
+  private updateFeaturedAlbumPages(): void {
+    this.featuredAlbumPageVersion++
+    this.featuredAlbumPages =
+      this.buildAlbumPages(`find_featured_album_section_${this.featuredAlbumPageVersion}`, this.featuredAlbums,
+        this.getDiscoverSectionColumns())
+    this.featuredAlbumPageIndex = this.getSafeSectionPageIndex(this.featuredAlbumPages.length, this.featuredAlbumPageIndex)
+    Logger.info(
+      TAG,
+      `FindView updateFeaturedAlbumPages version=${this.featuredAlbumPageVersion}, pageIndex=${this.featuredAlbumPageIndex}, pages=${this.featuredAlbumPages.length}, albums=${this.buildAlbumSelectionLog(this.featuredAlbums)}`
+    )
   }
 
-  private getRecentSectionPages(): FindSongPage[] {
-    return this.buildSongPages('find_recent_section', this.recentSongs, this.getDiscoverSectionColumns())
+  private updateCloudAlbumPages(): void {
+    this.cloudAlbumPageVersion++
+    this.cloudAlbumPages =
+      this.buildAlbumPages(`find_cloud_album_section_${this.cloudAlbumPageVersion}`, this.cloudAlbums, this.getDiscoverSectionColumns())
+    this.cloudAlbumPageIndex = this.getSafeSectionPageIndex(this.cloudAlbumPages.length, this.cloudAlbumPageIndex)
+    Logger.info(
+      TAG,
+      `FindView updateCloudAlbumPages version=${this.cloudAlbumPageVersion}, pageIndex=${this.cloudAlbumPageIndex}, pages=${this.cloudAlbumPages.length}, albums=${this.buildAlbumSelectionLog(this.cloudAlbums)}`
+    )
+  }
+
+  private updatePopularSectionPages(): void {
+    this.popularSectionPageVersion++
+    this.popularSectionPages =
+      this.buildSongPages(`find_popular_section_${this.popularSectionPageVersion}`, this.popularSongs, this.getDiscoverSectionColumns())
+    this.popularSectionPageIndex = this.getSafeSectionPageIndex(this.popularSectionPages.length, this.popularSectionPageIndex)
+    Logger.info(
+      TAG,
+      `FindView updatePopularSectionPages version=${this.popularSectionPageVersion}, pageIndex=${this.popularSectionPageIndex}, pages=${this.popularSectionPages.length}, songs=${this.buildSongSelectionLog(this.popularSongs)}`
+    )
   }
 
-  private getPopularSectionPages(): FindSongPage[] {
-    return this.buildSongPages('find_popular_section', this.popularSongs, this.getDiscoverSectionColumns())
+  private getRecentSectionPages(): FindSongPage[] {
+    return this.buildSongPages('find_recent_section', this.recentSongs, this.getDiscoverSectionColumns())
   }
 
   private getFavoriteSectionPages(): FindSongPage[] {
@@ -1539,7 +1708,7 @@ export struct FindView {
         ForEach(this.swiperSongs, (item: VideoItem, index: number) => {
           PointLightContentButton({
             pointColor: this.themeColor,
-            buttonRadius: 24,
+            buttonRadius: 0,
             pointLightHeight: 180,
             builder: () => {
               this.buildSwiperCardContent(item)
@@ -1581,7 +1750,7 @@ export struct FindView {
       })
         .layoutWeight(1)
         .onClick(() => {
-          this.handleActionButtonTap('top')
+          void this.handleActionButtonTap('top')
         })
 
       PointLightActionButton({
@@ -1593,7 +1762,7 @@ export struct FindView {
       })
         .layoutWeight(1)
         .onClick(() => {
-          this.handleActionButtonTap('local-random')
+          void this.handleActionButtonTap('local-random')
         })
 
       PointLightActionButton({
@@ -1605,7 +1774,7 @@ export struct FindView {
       })
         .layoutWeight(1)
         .onClick(() => {
-          this.handleActionButtonTap('cloud-random')
+          void this.handleActionButtonTap('cloud-random')
         })
     }
     .width('100%')
@@ -1659,9 +1828,10 @@ export struct FindView {
   private buildCloudSection() {
     ConfigTitle({
       text: '漫步云端',
-      indicatorCount: this.getCloudSectionPages().length,
-      indicatorIndex: this.getSafeSectionPageIndex(this.getCloudSectionPages().length, this.cloudSectionPageIndex),
+      indicatorCount: this.cloudSectionPages.length,
+      indicatorIndex: this.getSafeSectionPageIndex(this.cloudSectionPages.length, this.cloudSectionPageIndex),
       onActionClick: () => {
+        Logger.info(TAG, `FindView clickChange cloudSection current=${this.buildSongSelectionLog(this.remoteSongs)}`)
         this.refreshCloudSongs()
       }
     })
@@ -1669,7 +1839,7 @@ export struct FindView {
       this.buildSectionEmptyState('还没有网盘歌曲', '先去网盘页或远程音乐页加载歌曲,这里会自动展示', false)
     } else {
       Swiper() {
-        ForEach(this.getCloudSectionPages(), (page: FindSongPage, pageIndex: number) => {
+        ForEach(this.cloudSectionPages, (page: FindSongPage, pageIndex: number) => {
           GridRow({ columns: this.getDiscoverSectionColumns(), gutter: 8 }) {
             ForEach(page.items, (item: VideoItem, itemIndex: number) => {
               GridCol() {
@@ -1756,9 +1926,10 @@ export struct FindView {
   private buildFeaturedAlbumSection() {
     ConfigTitle({
       text: '精选专辑',
-      indicatorCount: this.getFeaturedAlbumPages().length,
-      indicatorIndex: this.getSafeSectionPageIndex(this.getFeaturedAlbumPages().length, this.featuredAlbumPageIndex),
+      indicatorCount: this.featuredAlbumPages.length,
+      indicatorIndex: this.getSafeSectionPageIndex(this.featuredAlbumPages.length, this.featuredAlbumPageIndex),
       onActionClick: () => {
+        Logger.info(TAG, `FindView clickChange featuredAlbums current=${this.buildAlbumSelectionLog(this.featuredAlbums)}`)
         this.refreshFeaturedAlbums()
       }
     })
@@ -1766,7 +1937,7 @@ export struct FindView {
       this.buildSectionEmptyState('还没有可展示的专辑', '带专辑信息的本地歌曲会自动整理到这里', false)
     } else {
       Swiper() {
-        ForEach(this.getFeaturedAlbumPages(), (page: FindAlbumPage) => {
+        ForEach(this.featuredAlbumPages, (page: FindAlbumPage) => {
           GridRow({ columns: this.getDiscoverSectionColumns(), gutter: 8 }) {
             ForEach(page.items, (album: FindAlbumGroup) => {
               GridCol() {
@@ -1800,9 +1971,10 @@ export struct FindView {
   private buildCloudAlbumSection() {
     ConfigTitle({
       text: '云端专辑',
-      indicatorCount: this.getCloudAlbumPages().length,
-      indicatorIndex: this.getSafeSectionPageIndex(this.getCloudAlbumPages().length, this.cloudAlbumPageIndex),
+      indicatorCount: this.cloudAlbumPages.length,
+      indicatorIndex: this.getSafeSectionPageIndex(this.cloudAlbumPages.length, this.cloudAlbumPageIndex),
       onActionClick: () => {
+        Logger.info(TAG, `FindView clickChange cloudAlbums current=${this.buildAlbumSelectionLog(this.cloudAlbums)}`)
         this.refreshCloudAlbums()
       }
     })
@@ -1810,7 +1982,7 @@ export struct FindView {
       this.buildSectionEmptyState('云端歌曲还没有专辑信息', '网盘歌曲带有专辑标签后,这里会自动汇总', false)
     } else {
       Swiper() {
-        ForEach(this.getCloudAlbumPages(), (page: FindAlbumPage) => {
+        ForEach(this.cloudAlbumPages, (page: FindAlbumPage) => {
           GridRow({ columns: this.getDiscoverSectionColumns(), gutter: 8 }) {
             ForEach(page.items, (album: FindAlbumGroup) => {
               GridCol() {
@@ -1886,7 +2058,7 @@ export struct FindView {
   @Builder
   private buildLocalRandomSection() {
     ConfigTitle({
-      text: '本地随机',
+      text: '今日夜曲',
       onActionClick: () => {
         this.refreshLocalRandomSongs()
       }
@@ -1920,6 +2092,43 @@ export struct FindView {
     }
   }
 
+  @Builder
+  private buildCloudMoodSection() {
+    ConfigTitle({
+      text: '云卷云舒',
+      onActionClick: () => {
+        this.refreshCloudMoodSongs()
+      }
+    })
+    if (this.cloudMoodSongs.length === 0) {
+      this.buildSectionEmptyState('网盘随机还没有内容', '网盘歌曲加载完成后,这里会随机展示歌曲', false)
+    } else {
+      List({ space: 8 }) {
+        ForEach(this.cloudMoodSongs, (item: VideoItem, index: number) => {
+          ListItem() {
+            PointLightContentButton({
+              pointColor: this.themeColor,
+              buttonRadius: 16,
+              builder: () => {
+                this.buildHotSongCardContent(item)
+              }
+            })
+            .onClick(() => {
+              this.handleCloudMoodSongTap(index)
+            })
+          }
+          .margin({
+            left: index === 0 ? 2 : 0,
+            right: index === this.cloudMoodSongs.length - 1 ? 2 : 0
+          })
+        }, getFindSongKey)
+      }
+      .listDirection(Axis.Horizontal)
+      .scrollBar(BarState.Off)
+      .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
+    }
+  }
+
   @Builder
   private buildRecentSongCardContent(item: VideoItem) {
     Column({ space: 6 }) {
@@ -2054,9 +2263,10 @@ export struct FindView {
   private buildPopularSection() {
     ConfigTitle({
       text: '热门歌曲',
-      indicatorCount: this.getPopularSectionPages().length,
-      indicatorIndex: this.getSafeSectionPageIndex(this.getPopularSectionPages().length, this.popularSectionPageIndex),
+      indicatorCount: this.popularSectionPages.length,
+      indicatorIndex: this.getSafeSectionPageIndex(this.popularSectionPages.length, this.popularSectionPageIndex),
       onActionClick: () => {
+        Logger.info(TAG, `FindView clickChange popularSongs current=${this.buildSongSelectionLog(this.popularSongs)}`)
         this.refreshPopularSongs()
       }
     })
@@ -2064,7 +2274,7 @@ export struct FindView {
       this.buildSectionEmptyState('热门歌曲还没生成', '播放次数高的歌曲会优先展示在这里', false)
     } else {
       Swiper() {
-        ForEach(this.getPopularSectionPages(), (page: FindSongPage, pageIndex: number) => {
+        ForEach(this.popularSectionPages, (page: FindSongPage, pageIndex: number) => {
           GridRow({ columns: this.getDiscoverSectionColumns(), gutter: 8 }) {
             ForEach(page.items, (item: VideoItem, itemIndex: number) => {
               GridCol() {
@@ -2214,6 +2424,7 @@ export struct FindView {
           this.buildActionButtons()
           this.buildLocalRandomSection()
           this.buildFavoriteSection()
+          this.buildCloudMoodSection()
           this.buildCloudSection()
           this.buildFeaturedAlbumSection()
           if (this.remoteSongsPool.length > 0 || this.cloudAlbumsPool.length > 0 || this.cloudAlbums.length > 0) {
@@ -2289,14 +2500,14 @@ export struct FindView {
           topSafeHeight: this.topSafeHeight,
           bottomSafeHeight: this.bottomSafeHeight,
           themeColor: this.themeColor,
-          onPlayAll: () => {
-            this.handleAlbumPlayAll()
+          onPlayAll: (songs?: VideoItem[]) => {
+            this.handleAlbumPlayAll(songs)
           },
-          onRandomPlay: () => {
-            this.handleAlbumRandomPlay()
+          onRandomPlay: (songs?: VideoItem[]) => {
+            this.handleAlbumRandomPlay(songs)
           },
-          onSongTap: (index: number) => {
-            this.handleAlbumSongTap(index)
+          onSongTap: (index: number, songs?: VideoItem[]) => {
+            this.handleAlbumSongTap(index, songs)
           }
         })
       } else {

+ 17 - 2
entry/src/main/ets/view/LocalMusic.ets

@@ -304,6 +304,7 @@ interface PlaylistEventData {
   startIndex: number;
   songFilePaths: string[];
   isJump: boolean;
+  playType?: number;
   webDavAuthInfo?: WebDavAuthItem; // 新增WebDAV认证信息
 }
 
@@ -490,7 +491,7 @@ export struct LocalMusic {
   @StorageProp('themeColor') themeColor: string =
     PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
   themeMode: number = 0;
-  @State titleName: string = '首页'
+  @State titleName: string = '文件夹'//首页就是文件夹
   @State textStr: string = ''
   // static readonly STR_MUSIC_VIDEO: string = 'Audios';
   // private download_path: string = '';
@@ -1073,7 +1074,12 @@ export struct LocalMusic {
           songCount: data.songCount as number,
           startIndex: data.startIndex as number,
           isJump: data.isJump as boolean,
-          songFilePaths: data.songFilePaths as string[]
+          songFilePaths: data.songFilePaths as string[],
+          playType: data.playType as number
+        }
+
+        if (playlistData.playType !== undefined) {
+          this.applyRequestedPlayType(playlistData.playType)
         }
 
         await this.handlePlaylistPlayRequest(
@@ -17952,6 +17958,15 @@ export struct LocalMusic {
     });
   }
 
+  private applyRequestedPlayType(playType: number): void {
+    if (playType < 0 || playType > 4 || this.playType === playType) {
+      return
+    }
+    this.playType = playType
+    PreferencesUtil.putSync('musicPlayType', this.playType)
+    this.setCurrentPlayMode()
+  }
+
   private sessionToggleFavoriteCallback = (assetId: string) => {
     if (this.mIjkMediaPlayer != null && this.currentSong) {
       console.info(`on toggleFavorite `);

+ 1 - 0
entry/src/main/ets/view/PointLight/PointLightActionButton.ets

@@ -31,6 +31,7 @@ export struct PointLightActionButton {
           .fontSize(18)
           .fontColor([this.pointColor])
           .effectStrategy(SymbolEffectStrategy.HIERARCHICAL)
+          .margin({left:6})
         Text(this.text)
           .layoutWeight(1)
           .fontSize(12)

+ 2 - 2
entry/src/main/ets/viewmodel/MainViewModel.ets

@@ -46,12 +46,12 @@ export  class  MainViewModel{
   //测滑菜单的数据
   getDrawerData(): Array<ItemData> {
     let drawerGridData: ItemData[] = [
-      new ItemData($r('app.string.find_music'), { type: 'symbol', value: $r('sys.symbol.music_note_circle') }, MainViewModel.MENU_FIND, false),
+
       new ItemData($r('app.string.dir'), { type: 'symbol', value: $r('sys.symbol.music') }, MainViewModel.MENU_MUSIC, false),
       new ItemData($r('app.string.media_ku'), $r('app.media.hm_playlist'),MainViewModel.MENU_MIEDIA_KU,false),
       new ItemData($r('app.string.artist'), $r('app.media.kp_music'),MainViewModel.MENU_MIEDIA_ARTIST,false),
       new ItemData($r('app.string.album'), $r('app.media.llq'),MainViewModel.MENU_MIEDIA_ALBUM,false),
-
+      new ItemData($r('app.string.find_music'), { type: 'symbol', value: $r('sys.symbol.music_note_circle') }, MainViewModel.MENU_FIND, false),
       // new ItemData($r('app.string.user_center'), { type: 'symbol', value: $r('sys.symbol.person') }, MainViewModel.MENU_USER, false),
       new ItemData($r('app.string.file_scan'), { type: 'symbol', value: $r('sys.symbol.doc_text_badge_magnifyingglass') },MainViewModel.MENU_FILE_SCAN,false),
 

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

@@ -247,6 +247,42 @@
     {
       "name": "find_history_background",
       "value": "#F2F3F5"
+    },
+    {
+      "name": "album_detail_hero_primary_text",
+      "value": "#FFFFFFFF"
+    },
+    {
+      "name": "album_detail_hero_secondary_text",
+      "value": "#D8FFFFFF"
+    },
+    {
+      "name": "album_detail_hero_tag_text",
+      "value": "#F8FFFFFF"
+    },
+    {
+      "name": "album_detail_hero_tag_background",
+      "value": "#24FFFFFF"
+    },
+    {
+      "name": "album_detail_header_action_background",
+      "value": "#1FFFFFFF"
+    },
+    {
+      "name": "album_detail_header_action_primary_border",
+      "value": "#10FFFFFF"
+    },
+    {
+      "name": "album_detail_header_action_secondary_border",
+      "value": "#30FFFFFF"
+    },
+    {
+      "name": "album_detail_song_quality_text",
+      "value": "#3B2B00"
+    },
+    {
+      "name": "album_detail_song_quality_background",
+      "value": "#FFC107"
     }
   ]
 }

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

@@ -236,6 +236,42 @@
     {
       "name": "find_history_background",
       "value": "#222222"
+    },
+    {
+      "name": "album_detail_hero_primary_text",
+      "value": "#FFFFFFFF"
+    },
+    {
+      "name": "album_detail_hero_secondary_text",
+      "value": "#E0FFFFFF"
+    },
+    {
+      "name": "album_detail_hero_tag_text",
+      "value": "#FFFFFFFF"
+    },
+    {
+      "name": "album_detail_hero_tag_background",
+      "value": "#2EFFFFFF"
+    },
+    {
+      "name": "album_detail_header_action_background",
+      "value": "#24FFFFFF"
+    },
+    {
+      "name": "album_detail_header_action_primary_border",
+      "value": "#22FFFFFF"
+    },
+    {
+      "name": "album_detail_header_action_secondary_border",
+      "value": "#42FFFFFF"
+    },
+    {
+      "name": "album_detail_song_quality_text",
+      "value": "#2A1E00"
+    },
+    {
+      "name": "album_detail_song_quality_background",
+      "value": "#FFD54F"
     }
   ]
 }