|
|
@@ -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() {
|