Jelajahi Sumber

继续美化发现页UI2

onecold 4 bulan lalu
induk
melakukan
963a699921

+ 2 - 2
entry/src/main/ets/view/ConfigTitle.ets

@@ -14,14 +14,14 @@ export struct ConfigTitle {
         .fontSize(16)
         .fontWeight(FontWeight.Bold)
         .lineHeight(25)
-        .fontColor('#E6000000')
+        .fontColor($r('app.color.text_color'))
 
       if (this.showAction) {
         Row({ space: 5 }) {
           Text(this.actionText)
             .fontSize(14)
             .lineHeight(16)
-            .fontColor('#99000000')
+            .fontColor($r('app.color.find_secondary_text'))
           SymbolGlyph(this.actionSymbol)
             .attributeModifier(new SymbolGlyphFancyModifier(18, '', ''))
         }

+ 586 - 41
entry/src/main/ets/view/FindView.ets

@@ -28,6 +28,8 @@ const RECENT_SECTION_COUNT = 6
 const POPULAR_SECTION_COUNT = 8
 const FAVORITE_SECTION_COUNT = 6
 const TOP_PLAYED_COUNT = 30
+const FEATURED_ALBUM_COUNT = 6
+const CLOUD_ALBUM_COUNT = 6
 
 function getFindSongKey(item: VideoItem, index: number): string {
   if (StrUtil.isNotEmpty(item.filePath)) {
@@ -51,6 +53,24 @@ interface FindPlaylistEventData {
   songFilePaths: string[]
 }
 
+interface FindAlbumGroup {
+  id: string
+  title: string
+  artist: string
+  coverPath: string
+  songCount: number
+  songs: VideoItem[]
+  isRemote: boolean
+  sourceLabel: string
+}
+
+function getFindAlbumKey(item: FindAlbumGroup, index: number): string {
+  if (StrUtil.isNotEmpty(item.id)) {
+    return item.id
+  }
+  return `find_album_${index}`
+}
+
 @Component
 export struct FindView {
   @StorageProp('isLandscape')  isLandscape: boolean = false;
@@ -71,7 +91,16 @@ export struct FindView {
   @State private recentSongs: VideoItem[] = []
   @State private popularSongs: VideoItem[] = []
   @State private favoriteSongs: VideoItem[] = []
+  @State private featuredAlbums: FindAlbumGroup[] = []
+  @State private cloudAlbums: FindAlbumGroup[] = []
   @State private isSearchMode: boolean = false
+  @State private isAlbumMode: boolean = false
+  @State private currentAlbumId: string = ''
+  @State private currentAlbumTitle: string = ''
+  @State private currentAlbumArtist: string = ''
+  @State private currentAlbumCoverPath: string = ''
+  @State private currentAlbumSourceLabel: string = ''
+  @State private currentAlbumSongs: VideoItem[] = []
   @State private searchText: string = ''
   @State private searchResults: VideoItem[] = []
   @State private searchHistoryItems: string[] = []
@@ -98,6 +127,8 @@ export struct FindView {
   private recentSongsPool: VideoItem[] = []
   private topPlayedSongsPool: VideoItem[] = []
   private favoriteSongsPool: VideoItem[] = []
+  private featuredAlbumsPool: FindAlbumGroup[] = []
+  private cloudAlbumsPool: FindAlbumGroup[] = []
   private searchRemoteSongsPool: VideoItem[] = []
   private readonly searchHistoryScope: string = 'find_music'
   private searchTicket: number = 0
@@ -176,6 +207,8 @@ export struct FindView {
       this.recentSongsPool = uniqueRecentSongs
       this.topPlayedSongsPool = uniqueTopPlayedSongs
       this.favoriteSongsPool = uniqueFavoriteSongs
+      this.featuredAlbumsPool = this.buildAlbumGroups(this.localSongsPool, false)
+      this.cloudAlbumsPool = this.buildAlbumGroups(this.remoteSongsPool, true)
       this.searchRemoteSongsPool = []
       this.swiperSongs = this.pickRandomSongs(this.coveredSongsPool, SWIPER_MAX_COUNT)
       this.hotSongs = this.pickPreferredSongs(this.localSongsPool, HOT_SECTION_COUNT)
@@ -183,6 +216,8 @@ export struct FindView {
       this.recentSongs = this.pickPreferredSongs(this.recentSongsPool, RECENT_SECTION_COUNT)
       this.popularSongs = this.pickPreferredSongs(this.topPlayedSongsPool, POPULAR_SECTION_COUNT)
       this.favoriteSongs = this.pickPreferredSongs(this.favoriteSongsPool, FAVORITE_SECTION_COUNT)
+      this.featuredAlbums = this.pickAlbumGroups(this.featuredAlbumsPool, FEATURED_ALBUM_COUNT)
+      this.cloudAlbums = this.pickAlbumGroups(this.cloudAlbumsPool, CLOUD_ALBUM_COUNT)
       this.swiperIndex = 0
       this.refreshText = ''
 
@@ -205,7 +240,11 @@ export struct FindView {
       this.recentSongsPool = []
       this.topPlayedSongsPool = []
       this.favoriteSongsPool = []
+      this.featuredAlbumsPool = []
+      this.cloudAlbumsPool = []
       this.searchRemoteSongsPool = []
+      this.featuredAlbums = []
+      this.cloudAlbums = []
       this.refreshText = '推荐加载失败,请下拉重试'
     } finally {
       this.isRefreshing = false
@@ -305,6 +344,89 @@ export struct FindView {
     return result
   }
 
+  private pickAlbumGroups(items: FindAlbumGroup[], count: number): FindAlbumGroup[] {
+    if (items.length <= count) {
+      return items.slice()
+    }
+
+    const copy: FindAlbumGroup[] = items.slice()
+    for (let i = copy.length - 1; i > 0; i--) {
+      const randomIndex = Math.floor(Math.random() * (i + 1))
+      const current = copy[i]
+      copy[i] = copy[randomIndex]
+      copy[randomIndex] = current
+    }
+    return copy.slice(0, Math.min(count, copy.length))
+  }
+
+  private buildAlbumGroups(items: VideoItem[], isRemote: boolean): FindAlbumGroup[] {
+    const albumMap: Map<string, VideoItem[]> = new Map<string, VideoItem[]>()
+    for (let i = 0; i < items.length; i++) {
+      const item = items[i]
+      const albumName = item.album?.trim() ?? ''
+      if (albumName.length === 0) {
+        continue
+      }
+      const artistName = item.artist?.trim() ?? ''
+      const groupKey = `${isRemote ? item.type : CommonConstants.TYPE_LOCAL}_${albumName}_${artistName}`
+      const groupSongs = albumMap.get(groupKey)
+      if (groupSongs) {
+        groupSongs.push(item)
+      } else {
+        albumMap.set(groupKey, [item])
+      }
+    }
+
+    const result: FindAlbumGroup[] = []
+    albumMap.forEach((songs: VideoItem[], key: string) => {
+      const sortedSongs = this.pickPreferredSongs(songs, songs.length)
+      const coverSong = this.pickAlbumCoverSong(sortedSongs)
+      const firstSong = songs[0]
+      const title = firstSong.album?.trim() ?? ''
+      if (title.length === 0) {
+        return
+      }
+      const artist = this.resolveAlbumArtist(songs)
+      result.push({
+        id: key,
+        title: title,
+        artist: artist,
+        coverPath: coverSong?.pixelMapPath ?? '',
+        songCount: songs.length,
+        songs: songs.slice(),
+        isRemote: isRemote,
+        sourceLabel: isRemote ? this.getCloudTypeLabel(firstSong) : '本地'
+      })
+    })
+
+    result.sort((left: FindAlbumGroup, right: FindAlbumGroup) => {
+      if (right.songCount !== left.songCount) {
+        return right.songCount - left.songCount
+      }
+      return left.title.localeCompare(right.title)
+    })
+    return result
+  }
+
+  private pickAlbumCoverSong(items: VideoItem[]): VideoItem | undefined {
+    for (let i = 0; i < items.length; i++) {
+      if (StrUtil.isNotEmpty(items[i].pixelMapPath)) {
+        return items[i]
+      }
+    }
+    return items[0]
+  }
+
+  private resolveAlbumArtist(items: VideoItem[]): string {
+    for (let i = 0; i < items.length; i++) {
+      const artistName = items[i].artist?.trim() ?? ''
+      if (artistName.length > 0) {
+        return artistName
+      }
+    }
+    return '未知歌手'
+  }
+
   private emitPlaylistPlay(playlistId: string, playlistName: string, songs: VideoItem[], startIndex: number): void {
     if (songs.length === 0) {
       ToastUtil.showToast('暂无可播放歌曲')
@@ -380,6 +502,14 @@ export struct FindView {
     this.popularSongs = this.pickPreferredSongs(this.topPlayedSongsPool, POPULAR_SECTION_COUNT)
   }
 
+  private refreshFeaturedAlbums(): void {
+    this.featuredAlbums = this.pickAlbumGroups(this.featuredAlbumsPool, FEATURED_ALBUM_COUNT)
+  }
+
+  private refreshCloudAlbums(): void {
+    this.cloudAlbums = this.pickAlbumGroups(this.cloudAlbumsPool, CLOUD_ALBUM_COUNT)
+  }
+
   private openLocalSpecialList(target: string): void {
     this.mType = 0
     this.isShowDrawer = false
@@ -391,6 +521,57 @@ export struct FindView {
     })
   }
 
+  private openAlbumDetail(album: FindAlbumGroup): void {
+    this.currentAlbumId = album.id
+    this.currentAlbumTitle = album.title
+    this.currentAlbumArtist = album.artist
+    this.currentAlbumCoverPath = album.coverPath
+    this.currentAlbumSourceLabel = album.isRemote ? '云端专辑' : '精选专辑'
+    this.currentAlbumSongs = album.songs.slice()
+    this.isAlbumMode = true
+    this.isSearchMode = false
+  }
+
+  private exitAlbumMode(): void {
+    this.isAlbumMode = false
+    this.currentAlbumId = ''
+    this.currentAlbumTitle = ''
+    this.currentAlbumArtist = ''
+    this.currentAlbumCoverPath = ''
+    this.currentAlbumSourceLabel = ''
+    this.currentAlbumSongs = []
+  }
+
+  private playCurrentAlbum(startIndex: number): void {
+    if (this.currentAlbumSongs.length === 0) {
+      ToastUtil.showToast('专辑里还没有歌曲')
+      return
+    }
+    this.emitPlaylistPlay(
+      this.currentAlbumId.length > 0 ? this.currentAlbumId : 'find-album',
+      this.currentAlbumTitle.length > 0 ? this.currentAlbumTitle : '专辑',
+      this.currentAlbumSongs,
+      startIndex
+    )
+  }
+
+  private handleAlbumPlayAll(): void {
+    this.playCurrentAlbum(0)
+  }
+
+  private handleAlbumRandomPlay(): void {
+    if (this.currentAlbumSongs.length === 0) {
+      ToastUtil.showToast('专辑里还没有歌曲')
+      return
+    }
+    const startIndex = Math.floor(Math.random() * this.currentAlbumSongs.length)
+    this.playCurrentAlbum(startIndex)
+  }
+
+  private handleAlbumSongTap(index: number): void {
+    this.playCurrentAlbum(index)
+  }
+
   private loadSearchHistory(): void {
     this.searchHistoryItems = SearchHistoryUtil.load(this.searchHistoryScope)
   }
@@ -535,6 +716,10 @@ export struct FindView {
     return StrUtil.isNotEmpty(item.pixelMapPath) ? item.pixelMapPath as string : $r('app.media.alt')
   }
 
+  private getAlbumCover(coverPath: string): string | Resource {
+    return StrUtil.isNotEmpty(coverPath) ? coverPath : $r('app.media.alt')
+  }
+
   private getCloudTypeLabel(item: VideoItem): string {
     switch (item.type) {
       case CommonConstants.TYPE_WEBDAV:
@@ -644,7 +829,7 @@ export struct FindView {
   private topTitleBar() {
     Column() {
       Row({ space: 12 }) {
-        if (!this.isSearchMode) {
+        if (!this.isSearchMode && !this.isAlbumMode) {
           Button({ type: ButtonType.Circle, stateEffect: true }) {
             SymbolGlyph($r('sys.symbol.sort'))
               .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
@@ -678,42 +863,69 @@ export struct FindView {
           .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
           .animation({ duration: 300, curve: Curve.Ease })
           .onClick(() => {
-            this.exitSearchMode()
+            if (this.isSearchMode) {
+              this.exitSearchMode()
+              return
+            }
+            this.exitAlbumMode()
           })
           .attributeModifier(new ShadowModifier())
           .zIndex(0)
-        }
 
-        Search({ controller: this.searchController, 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')
+          if (this.isAlbumMode) {
+            Column({ space: 2 }) {
+              Text(this.currentAlbumTitle)
+                .fontColor($r('app.color.text_color'))
+                .fontSize(18)
+                .fontWeight(FontWeight.Bold)
+                .maxLines(1)
+                .textOverflow({ overflow: TextOverflow.Ellipsis })
+                .width('100%')
+
+              Text(this.currentAlbumArtist + ' · ' + this.currentAlbumSongs.length + ' 首')
+                .fontColor(this.getSecondaryTextColor())
+                .fontSize(11)
+                .maxLines(1)
+                .textOverflow({ overflow: TextOverflow.Ellipsis })
+                .width('100%')
             }
-          })
-          .layoutWeight(1)
-          .height(35)
-          .maxLength(40)
-          .backgroundColor($r('app.color.input_background'))
-          .placeholderColor(this.getSecondaryTextColor())
-          .placeholderFont({ size: 14, weight: 400 })
-          .textFont({ size: 14, weight: 400 })
-          .onSubmit((value: string) => {
-            this.searchController.stopEditing()
-            this.commitSearchHistory(value)
-            void this.onSearchInput(value)
-          })
-          .onChange((value: string) => {
-            void this.onSearchInput(value)
-          })
-          .visibility(this.isSearchMode ? Visibility.Visible : Visibility.None)
-          .animation({ duration: 300, curve: Curve.Ease })
+            .layoutWeight(1)
+            .alignItems(HorizontalAlign.Start)
+            .margin({ right: 10 })
+          }
+        }
 
-        if (!this.isSearchMode) {
+        if (this.isSearchMode) {
+          Search({ controller: this.searchController, 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')
+              }
+            })
+            .layoutWeight(1)
+            .height(35)
+            .maxLength(40)
+            .backgroundColor($r('app.color.input_background'))
+            .placeholderColor(this.getSecondaryTextColor())
+            .placeholderFont({ size: 14, weight: 400 })
+            .textFont({ size: 14, weight: 400 })
+            .onSubmit((value: string) => {
+              this.searchController.stopEditing()
+              this.commitSearchHistory(value)
+              void this.onSearchInput(value)
+            })
+            .onChange((value: string) => {
+              void this.onSearchInput(value)
+            })
+            .animation({ duration: 300, curve: Curve.Ease })
+        }
+
+        if (!this.isSearchMode && !this.isAlbumMode) {
           Button({ type: ButtonType.Circle, stateEffect: true }) {
             SymbolGlyph($r('sys.symbol.magnifyingglass'))
               .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
@@ -725,6 +937,7 @@ export struct FindView {
           .zIndex(0)
           .onClick(() => {
             this.isSearchMode = true
+            this.isAlbumMode = false
             this.loadSearchHistory()
             void this.ensureSearchSourceReady()
           })
@@ -733,6 +946,7 @@ export struct FindView {
     }
     .padding({ top: this.topSafeHeight + 10, left: 10, right: 10, bottom: 6 })
     .width('100%')
+    .backgroundColor(this.isSearchMode || this.isAlbumMode ? $r('app.color.start_window_background_blur') : Color.Transparent)
   }
 
   @Builder
@@ -874,7 +1088,9 @@ export struct FindView {
               buttonRadius: 18,
               pointLightHeight: 132,
               useShadow: true,
-              builder: this.buildSearchResultCardContent(item)
+              builder: () => {
+                this.buildSearchResultCardContent(item)
+              }
             })
               .width('100%')
               .onClick(() => {
@@ -945,7 +1161,9 @@ export struct FindView {
             pointColor: this.themeColor,
             buttonRadius: 24,
             pointLightHeight: 180,
-            builder: this.buildSwiperCardContent(item)
+            builder: () => {
+              this.buildSwiperCardContent(item)
+            }
           })
           .width('100%')
           .onClick(() => {
@@ -1077,7 +1295,9 @@ export struct FindView {
             PointLightContentButton({
               pointColor: this.themeColor,
               buttonRadius: 16,
-              builder: this.buildCloudSongCardContent(item)
+              builder: () => {
+                this.buildCloudSongCardContent(item)
+              }
             })
             .width('100%')
             .onClick(() => {
@@ -1089,6 +1309,319 @@ export struct FindView {
     }
   }
 
+
+  @Builder
+  private buildAlbumCardContent(album: FindAlbumGroup) {
+    Column({ space: 6 }) {
+      Stack({ alignContent: Alignment.Bottom }) {
+        Image(this.getAlbumCover(album.coverPath))
+          .width('100%')
+          .aspectRatio(3 / 4)
+          .borderRadius(18)
+          .objectFit(ImageFit.Cover)
+
+        Row() {
+          Text(album.sourceLabel)
+            .fontColor($r('app.color.white'))
+            .fontWeight(FontWeight.Medium)
+            .padding({ left: 6, right: 6, top: 3, bottom: 3 })
+            .fontSize(9)
+            .backgroundColor('#7A000000')
+            .borderRadius(999)
+
+          Text(album.songCount + ' 首')
+            .fontColor($r('app.color.white'))
+            .fontSize(9)
+            .padding({ left: 6, right: 6, top: 3, bottom: 3 })
+            .backgroundColor('#5C000000')
+            .borderRadius(999)
+        }
+        .width('100%')
+        .padding({ left: 8, right: 8, bottom: 8 })
+        .justifyContent(FlexAlign.SpaceBetween)
+      }
+
+      Text(album.title)
+        .fontSize(14)
+        .fontWeight(FontWeight.Bold)
+        .lineHeight(18)
+        .fontColor(this.getPrimaryTextColor())
+        .maxLines(1)
+        .textOverflow({ overflow: TextOverflow.Ellipsis })
+        .width('100%')
+
+      Text(album.artist)
+        .fontSize(11)
+        .lineHeight(14)
+        .fontColor(this.getSecondaryTextColor())
+        .maxLines(1)
+        .textOverflow({ overflow: TextOverflow.Ellipsis })
+        .width('100%')
+    }
+    .width('100%')
+  }
+
+  @Builder
+  private buildFeaturedAlbumSection() {
+    ConfigTitle({
+      text: '精选专辑',
+      onActionClick: () => {
+        this.refreshFeaturedAlbums()
+      }
+    })
+    if (this.featuredAlbums.length === 0) {
+      this.buildSectionEmptyState('还没有可展示的专辑', '带专辑信息的本地歌曲会自动整理到这里', false)
+    } else {
+      GridRow({
+        columns: this.currentBreakpoint === BreakpointTypeEnum.SM ? 3 : 5,
+        gutter: 8
+      }) {
+        ForEach(this.featuredAlbums, (album: FindAlbumGroup, index: number) => {
+          GridCol() {
+            PointLightContentButton({
+              pointColor: this.themeColor,
+              buttonRadius: 18,
+              builder: () => {
+                this.buildAlbumCardContent(album)
+              }
+            })
+            .width('100%')
+            .onClick(() => {
+              this.openAlbumDetail(album)
+            })
+          }
+        }, getFindAlbumKey)
+      }
+    }
+  }
+
+  @Builder
+  private buildCloudAlbumSection() {
+    ConfigTitle({
+      text: '云端专辑',
+      onActionClick: () => {
+        this.refreshCloudAlbums()
+      }
+    })
+    if (this.cloudAlbums.length === 0) {
+      this.buildSectionEmptyState('云端歌曲还没有专辑信息', '网盘歌曲带有专辑标签后,这里会自动汇总', false)
+    } else {
+      GridRow({
+        columns: this.currentBreakpoint === BreakpointTypeEnum.SM ? 3 : 5,
+        gutter: 8
+      }) {
+        ForEach(this.cloudAlbums, (album: FindAlbumGroup, index: number) => {
+          GridCol() {
+            PointLightContentButton({
+              pointColor: this.themeColor,
+              buttonRadius: 18,
+              builder: () => {
+                this.buildAlbumCardContent(album)
+              }
+            })
+            .width('100%')
+            .onClick(() => {
+              this.openAlbumDetail(album)
+            })
+          }
+        }, getFindAlbumKey)
+      }
+    }
+  }
+
+  @Builder
+  private buildAlbumHeroHeader() {
+    Stack({ alignContent: Alignment.BottomStart }) {
+      Column() {
+        Row({ space: 14 }) {
+          Image(this.getAlbumCover(this.currentAlbumCoverPath))
+            .width(112)
+            .aspectRatio(1)
+            .borderRadius(24)
+            .objectFit(ImageFit.Cover)
+            .shadow({ radius: 24, color: '#40000000', offsetY: 10 })
+
+          Column({ space: 8 }) {
+            Row() {
+              Text(this.currentAlbumSourceLabel)
+                .fontSize(10)
+                .fontWeight(FontWeight.Medium)
+                .fontColor($r('app.color.white'))
+                .padding({ left: 8, right: 8, top: 4, bottom: 4 })
+                .backgroundColor('#42000000')
+                .borderRadius(999)
+            }
+
+            Text(this.currentAlbumTitle)
+              .fontSize(24)
+              .fontWeight(FontWeight.Bold)
+              .fontColor($r('app.color.white'))
+              .maxLines(2)
+              .textOverflow({ overflow: TextOverflow.Ellipsis })
+              .width('100%')
+
+            Text(this.currentAlbumArtist + ' · ' + this.currentAlbumSongs.length + ' 首歌曲')
+              .fontSize(12)
+              .lineHeight(16)
+              .fontColor('#E6FFFFFF')
+              .maxLines(2)
+              .textOverflow({ overflow: TextOverflow.Ellipsis })
+              .width('100%')
+          }
+          .layoutWeight(1)
+          .alignItems(HorizontalAlign.Start)
+        }
+        .alignItems(VerticalAlign.Bottom)
+
+        Row({ space: 12 }) {
+          PointLightActionButton({
+            text: '播放全部',
+            iconResource: $r('sys.symbol.play_fill'),
+            pointColor: this.themeColor,
+            textColor: $r('app.color.text_color'),
+            buttonColor: $r('app.color.start_window_background_blur')
+          })
+            .layoutWeight(1)
+            .onClick(() => {
+              this.handleAlbumPlayAll()
+            })
+
+          PointLightActionButton({
+            text: '随机播放',
+            iconResource: $r('sys.symbol.arrow_clockwise'),
+            pointColor: this.themeColor,
+            textColor: $r('app.color.text_color'),
+            buttonColor: $r('app.color.start_window_background_blur')
+          })
+            .layoutWeight(1)
+            .onClick(() => {
+              this.handleAlbumRandomPlay()
+            })
+        }
+        .width('88%')
+      }
+      .width('100%')
+      .padding({ left: 18, right: 18, bottom: 18 })
+    }
+    .width('100%')
+    .clip(true)
+    .backgroundImage(this.getAlbumCover(this.currentAlbumCoverPath))
+    .backgroundImageSize( { height: '150%', width: '100%' })
+    .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK)
+  }
+
+  @Builder
+  private buildAlbumSongCardContent(item: VideoItem, index: number) {
+    Row({ space: 12 }) {
+      Stack({ alignContent: Alignment.BottomStart }) {
+        Image(this.getSongCover(item))
+          .width(58)
+          .height(58)
+          .borderRadius(16)
+          .objectFit(ImageFit.Cover)
+
+        Row() {
+          Text(String(index + 1))
+            .fontColor($r('app.color.white'))
+            .fontSize(9)
+            .fontWeight(FontWeight.Medium)
+        }
+        .padding({ left: 7, right: 7, top: 4, bottom: 4 })
+        .backgroundColor('#7A000000')
+        .borderRadius(999)
+        .margin({ left: 6, bottom: 6 })
+      }
+
+      Column({ space: 4 }) {
+        Text(this.getSongTitle(item))
+          .fontSize(14)
+          .fontWeight(FontWeight.Bold)
+          .fontColor(this.getPrimaryTextColor())
+          .maxLines(1)
+          .textOverflow({ overflow: TextOverflow.Ellipsis })
+          .width('100%')
+
+        Text(this.getSongSubtitle(item))
+          .fontSize(11)
+          .lineHeight(14)
+          .fontColor(this.getSecondaryTextColor())
+          .maxLines(1)
+          .textOverflow({ overflow: TextOverflow.Ellipsis })
+          .width('100%')
+      }
+      .layoutWeight(1)
+      .alignItems(HorizontalAlign.Start)
+
+      Column({ space: 4 }) {
+        Text(item.duration ? item.duration : '')
+          .fontSize(10)
+          .fontColor(this.getSecondaryTextColor())
+          .visibility(item.duration ? Visibility.Visible : Visibility.None)
+
+        Text(this.currentAlbumSourceLabel)
+          .fontSize(9)
+          .fontColor(this.themeColor)
+      }
+      .alignItems(HorizontalAlign.End)
+    }
+    .width('100%')
+    .padding(10)
+  }
+
+  @Builder
+  private buildAlbumDetailContent() {
+    List() {
+      ListItem() {
+        this.buildAlbumHeroHeader()
+      }
+
+      if (this.currentAlbumSongs.length === 0) {
+        ListItem() {
+          this.buildSectionEmptyState('专辑里还没有歌曲', '等歌曲扫描完成后,这里会自动列出专辑曲目', false)
+        }
+      } else {
+        ListItem() {
+          Row() {
+            Text('歌曲列表')
+              .fontSize(16)
+              .fontWeight(FontWeight.Bold)
+              .fontColor(this.getPrimaryTextColor())
+            Blank()
+            Text(this.currentAlbumSongs.length + ' 首')
+              .fontSize(12)
+              .fontColor(this.getSecondaryTextColor())
+          }
+          .width('100%')
+          .padding({ left: 4, right: 4, top: 2, bottom: 2 })
+        }
+
+        ForEach(this.currentAlbumSongs, (item: VideoItem, index: number) => {
+          ListItem() {
+            PointLightContentButton({
+              pointColor: this.themeColor,
+              buttonColor: this.getCardBackgroundColor(),
+              buttonRadius: 18,
+              pointLightHeight: 132,
+              useShadow: true,
+              builder: () => {
+                this.buildAlbumSongCardContent(item, index)
+              }
+            })
+            .width('100%')
+            .onClick(() => {
+              this.handleAlbumSongTap(index)
+            })
+          }
+        }, getFindSongKey)
+      }
+    }
+    .width('100%')
+    .padding({ left: 12, right: 12, top: this.topSafeHeight + 66, bottom: this.bottomSafeHeight + 96 })
+    .scrollBar(BarState.Off)
+    .edgeEffect(EdgeEffect.Spring)
+    .layoutWeight(1)
+  }
+
   @Builder
   private buildHotSongCardContent(item: VideoItem) {
     Column({ space: 5 }) {
@@ -1149,7 +1682,9 @@ export struct FindView {
             PointLightContentButton({
               pointColor: this.themeColor,
               buttonRadius: 16,
-              builder: this.buildHotSongCardContent(item)
+              builder: () => {
+                this.buildHotSongCardContent(item)
+              }
             })
             .onClick(() => {
               this.handleHotSongTap(index)
@@ -1238,7 +1773,9 @@ export struct FindView {
             PointLightContentButton({
               pointColor: this.themeColor,
               buttonRadius: 16,
-              builder: this.buildRecentSongCardContent(item)
+              builder: () => {
+                this.buildRecentSongCardContent(item)
+              }
             })
             .width('100%')
             .onClick(() => {
@@ -1322,7 +1859,9 @@ export struct FindView {
             PointLightContentButton({
               pointColor: this.themeColor,
               buttonRadius: 16,
-              builder: this.buildPopularSongCardContent(item)
+              builder: () => {
+                this.buildPopularSongCardContent(item)
+              }
             })
             .width('100%')
             .onClick(() => {
@@ -1405,7 +1944,9 @@ export struct FindView {
             PointLightContentButton({
               pointColor: this.themeColor,
               buttonRadius: 16,
-              builder: this.buildFavoriteSongCardContent(item)
+              builder: () => {
+                this.buildFavoriteSongCardContent(item)
+              }
             })
             .width('100%')
             .onClick(() => {
@@ -1443,7 +1984,7 @@ export struct FindView {
   @Builder
   private buildDiscoveryContent() {
     Scroll() {
-      Column(){
+      Column() {
         this.buildTopErrorBanner()
         this.buildSwiperSection()
         Column({ space: 14 }) {
@@ -1451,9 +1992,12 @@ export struct FindView {
           this.buildLocalRandomSection()
           this.buildFavoriteSection()
           this.buildCloudSection()
+          this.buildFeaturedAlbumSection()
+          if (this.remoteSongsPool.length > 0 || this.cloudAlbumsPool.length > 0 || this.cloudAlbums.length > 0) {
+            this.buildCloudAlbumSection()
+          }
           this.buildRecentSection()
           this.buildPopularSection()
-
         }
         .width('100%')
         .padding({
@@ -1464,7 +2008,6 @@ export struct FindView {
         })
         .alignItems(HorizontalAlign.Start)
       }
-
     }
     .scrollBar(BarState.Off)
     .edgeEffect(EdgeEffect.Spring)
@@ -1510,6 +2053,8 @@ export struct FindView {
     Column() {
       if (this.isSearchMode) {
         this.buildSearchContent()
+      } else if (this.isAlbumMode) {
+        this.buildAlbumDetailContent()
       } else {
         Refresh({ refreshing: $$this.isRefreshing }) {
           Column() {

+ 53 - 0
entry/src/main/ets/view/PointLight/PointLightContentButton.ets

@@ -0,0 +1,53 @@
+import { hdsEffect } from '@kit.UIDesignKit'
+import { deviceInfo } from '@kit.BasicServicesKit'
+
+@Component
+export struct PointLightContentButton {
+  @BuilderParam builder: () => void
+  @Prop pointColor: ResourceColor = Color.White
+  @Prop buttonColor: ResourceColor = Color.Transparent
+  @Prop buttonRadius: number = 18
+  @Prop pressScale: number = 0.97
+  @Prop pointLightHeight: number = 120
+  @Prop useShadow: boolean = false
+  @Prop usePointLight: boolean = true
+
+  @StorageProp('EnablePointLight') enablePointLight: boolean = true
+  @StorageProp('EnableShadow') enableShadow: boolean = true
+  @StorageProp('SdkApiVersion') sdkApiVersion: number = 17
+
+  @State private pointLightOptions: hdsEffect.PointLightOptions | undefined = undefined
+
+  aboutToAppear(): void {
+    this.sdkApiVersion = deviceInfo.sdkApiVersion
+  }
+
+  build() {
+    Stack() {
+      this.builder()
+    }
+    .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: this.pressScale })
+    .backgroundColor(this.buttonColor)
+    .borderRadius(this.buttonRadius)
+    .onTouch((event: TouchEvent) => {
+      if (event.type === TouchType.Down) {
+        this.pointLightOptions = {
+          color: this.pointColor,
+          intensity: 1,
+          height: this.pointLightHeight
+        }
+      } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
+        this.pointLightOptions = undefined
+      }
+    })
+    .shadow(this.useShadow && this.enableShadow ? ShadowStyle.OUTER_DEFAULT_XS : undefined)
+    .visualEffect(this.usePointLight && this.enablePointLight && this.sdkApiVersion >= 20
+      ? new hdsEffect.HdsEffectBuilder()
+        .pointLight({
+          options: this.pointLightOptions,
+          illuminatedType: hdsEffect.PointLightIlluminatedType.BORDER_CONTENT
+        })
+        .buildEffect()
+      : undefined)
+  }
+}

+ 0 - 24
oh_modules/.ohpm/@pura+harmony-dialog@1.0.6/oh_modules/@pura/harmony-dialog/src/main/resources/base/element/color.json

@@ -1,24 +0,0 @@
-{
-  "color": [
-    {
-      "name": "color_33",
-      "value": "#333333"
-    },
-    {
-      "name": "color_66",
-      "value": "#666666"
-    },
-    {
-      "name": "color_99",
-      "value": "#999999"
-    },
-    {
-      "name": "color_line",
-      "value": "#EEEEEE"
-    },
-    {
-      "name": "color_cancel",
-      "value": "#0A59F7"
-    }
-  ]
-}