| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260 |
- import { Playlist, PlaylistSong } from '../viewmodel/Playlist';
- import { VideoItem } from '../viewmodel/VideoItem';
- import PlaylistTable from '../common/util/PlaylistTable';
- import MediaTable from '../common/util/MediaTable';
- import { emitter } from '@kit.BasicServicesKit';
- import { ToastUtil, AppUtil, LogUtil, PreferencesUtil } from '@pura/harmony-utils';
- import { showEditPlaylistDialog } from '../dialog/PlaylistDialog';
- import { showAddSongsToPlaylistDialog } from '../dialog/AddSongsToPlaylistDialog';
- import { router } from '@kit.ArkUI';
- import { GlobalContext } from '../common/util/GlobalContext';
- import { CommonConstants } from '../common/constants/CommonConstants';
- import { EventConstants } from '../common/constants/EventConstants';
- import { common } from '@kit.AbilityKit';
- /**
- * 歌单播放事件数据
- */
- interface PlaylistEventData {
- playlistId: string;
- playlistName: string;
- songCount: number;
- startIndex: number;
- songFilePaths: string[];
- }
- /**
- * 歌单详情页面
- * 展示歌单信息和歌曲列表
- */
- @Entry
- @Component
- export struct PlaylistDetailPage {
- context = this.getUIContext().getHostContext() as common.UIAbilityContext
- @State playlist: Playlist | null = null
- @State songList: VideoItem[] = []
- @State isLoading: boolean = true
- @State isShowEditDialog: boolean = false
- @State isPlaying: boolean = false
- @State curIndex: number = -1
- @State pageOpacity: number = 0
- @State contentScale: number = 0.95
- @State showContent: boolean = false
- @State isSortMode: boolean = false // 是否处于排序模式
- private playlistTable: PlaylistTable = new PlaylistTable(getContext(this))
- private mediaTable: MediaTable = new MediaTable(getContext(this))
- private playlistId: string = ''
- @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
- aboutToAppear() {
- let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
- AppStorage.setOrCreate('themeColor', themeColor);
- this.themeColor = themeColor
- // 获取传入的歌单对象
- const params = router.getParams() as Record<string, Object>
- if (params && params['playlist']) {
- this.playlist = params['playlist'] as Playlist
- this.playlistId = this.playlist.id
- this.loadPlaylistDetail()
- }
- // 监听播放状态变化
- this.setupPlaybackStatusListener()
- }
- aboutToDisappear() {
- // 移除事件监听
- this.removePlaybackStatusListener()
- }
- /**
- * 加载歌单详情
- */
- async loadPlaylistDetail() {
- try {
- this.isLoading = true
- if (!this.playlist) {
- ToastUtil.showToast('歌单不存在')
- router.back()
- return
- }
- // 加载歌单歌曲
- const playlistSongs = await this.playlistTable.queryPlaylistSongs(this.playlistId)
- // 将 PlaylistSong 转换为 VideoItem
- this.songList = await convertPlaylistSongsToVideoItems(this.context,playlistSongs)
- // 歌单加载完成后,加载当前播放状态
- this.loadCurrentPlaybackStatus()
- } catch (error) {
- LogUtil.error('heanup 加载歌单详情失败: ' + error)
- ToastUtil.showToast('加载歌单详情失败')
- } finally {
- this.isLoading = false
- // 启动页面进入动画
- this.animatePageEntry()
- }
- }
- /**
- * 格式化时长显示
- */
- formatDuration(duration?: number): string {
- if (!duration || duration <= 0) {
- return ''
- }
- const minutes = Math.floor(duration / 60)
- const seconds = Math.floor(duration % 60)
- return `${minutes}:${seconds.toString().padStart(2, '0')}`
- }
- /**
- * 计算歌单总时长
- */
- calculateTotalDuration(): number {
- let total = 0
- for (const song of this.songList) {
- const duration = song.duration || 0
- total = total + (typeof duration === 'number' ? duration : 0)
- }
- return total
- }
- /**
- * 格式化总时长显示
- */
- formatTotalDuration(totalSeconds: number): string {
- if (totalSeconds <= 0) {
- return ''
- }
- const hours = Math.floor(totalSeconds / 3600)
- const minutes = Math.floor((totalSeconds % 3600) / 60)
- if (hours > 0) {
- return `${hours}小时${minutes}分钟`
- } else {
- return `${minutes}分钟`
- }
- }
- /**
- * 页面入场动画
- */
- animatePageEntry() {
- animateTo({
- duration: 600,
- curve: Curve.EaseOut,
- delay: 100,
- onFinish: () => {
- this.showContent = true
- }
- }, () => {
- this.pageOpacity = 1
- this.contentScale = 1
- })
- }
- /**
- * 设置播放状态监听
- */
- setupPlaybackStatusListener() {
- try {
- // 监听播放状态变化事件
- const eventPlaybackStatus: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYBACK_STATUS }
- emitter.on(eventPlaybackStatus, (eventData: emitter.EventData) => {
- LogUtil.info('heanup PlaylistDetailPage 收到播放状态变化事件:'+ JSON.stringify(eventData))
- if (eventData.data) {
- const data = eventData.data as Record<string, Object>
- const oldIsPlaying = this.isPlaying
- const oldCurIndex = this.curIndex
- this.isPlaying = data['isPlaying'] as boolean
- const currentFilePath = data['currentFilePath'] as string
- LogUtil.info(`heanup 原始播放状态: isPlaying=${this.isPlaying}, currentFilePath=${currentFilePath}`)
- // 通过filePath在当前歌单中查找对应的索引
- if (currentFilePath) {
- const matchedIndex = this.songList.findIndex(song => song.filePath === currentFilePath)
- if (matchedIndex !== -1) {
- this.curIndex = matchedIndex
- LogUtil.info(`heanup 在歌单中找到匹配的歌曲: ${this.songList[matchedIndex].name}, 索引: ${matchedIndex}`)
- } else {
- // 当前播放的歌曲不在本歌单中,重置索引
- this.curIndex = -1
- LogUtil.info(`heanup 当前播放的歌曲不在本歌单中: ${currentFilePath}`)
- }
- } else {
- this.curIndex = -1
- LogUtil.info(`heanup 当前没有播放歌曲`)
- }
- LogUtil.info(`heanup 播放状态更新: isPlaying=${oldIsPlaying}->${this.isPlaying}, curIndex=${oldCurIndex}->${this.curIndex}`)
- }
- })
- } catch (error) {
- LogUtil.error('heanup 设置播放状态监听失败: ' + error)
- }
- }
- /**
- * 移除播放状态监听
- */
- removePlaybackStatusListener() {
- try {
- emitter.off(EventConstants.EVENT_PLAYBACK_STATUS)
- } catch (error) {
- LogUtil.error('heanup 移除播放状态监听失败: ' + error)
- }
- }
- /**
- * 加载当前播放状态
- */
- loadCurrentPlaybackStatus() {
- try {
- // 从AppStorage获取当前播放的歌曲
- const currentSong = AppStorage.get<VideoItem>('currentSong')
- if (currentSong && currentSong.filePath) {
- LogUtil.info(`heanup 获取到当前播放歌曲: ${currentSong.name}, filePath: ${currentSong.filePath}`)
- // 在歌单中查找匹配的歌曲
- const matchedIndex = this.songList.findIndex(song => song.filePath === currentSong.filePath)
- if (matchedIndex !== -1) {
- this.curIndex = matchedIndex
- // 假设如果有currentSong说明正在播放
- this.isPlaying = true
- LogUtil.info(`heanup 在歌单中找到当前播放的歌曲: ${this.songList[matchedIndex].name}, 索引: ${matchedIndex}`)
- } else {
- LogUtil.info(`heanup 当前播放的歌曲不在本歌单中`)
- }
- } else {
- LogUtil.info(`heanup 当前没有播放歌曲`)
- }
- } catch (error) {
- LogUtil.error('heanup 加载当前播放状态失败: ' + error)
- }
- }
- /**
- * 播放歌单
- */
- playPlaylist() {
- if (this.songList.length === 0) {
- ToastUtil.showToast('歌单为空')
- return
- }
- // 发送播放歌单事件
- const eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY }
- LogUtil.info(`heanup 准备发送播放事件,playlist: ${this.playlist ? '存在' : '不存在'}, songs: ${this.songList.length}首`)
- // 构建歌单播放事件数据
- const playlistData: PlaylistEventData = {
- playlistId: this.playlist?.id || '',
- playlistName: this.playlist?.name || '',
- songCount: this.songList.length,
- startIndex: 0,
- // 只发送歌曲的必要信息
- songFilePaths: this.songList.map(song => song.filePath)
- };
- LogUtil.info(`heanup 发送歌单播放事件数据: ${JSON.stringify(playlistData)}`)
- const eventData: emitter.EventData = {
- data: playlistData
- };
- emitter.emit(eventPlaylistPlay, eventData)
-
- ToastUtil.showToast('开始播放歌单')
- }
- /**
- * 撌放指定歌曲
- */
- playSong(song: VideoItem, index: number) {
- LogUtil.info(`heanup === playSong 方法被调用 ===`)
- LogUtil.info(`heanup 播放指定歌曲: ${song.name}, 索引: ${index}`)
- LogUtil.info(`heanup 歌曲列表长度: ${this.songList.length}`)
- LogUtil.info(`heanup 歌单ID: ${this.playlist?.id}, 歌单名称: ${this.playlist?.name}`)
- // 检查歌曲文件路径
- LogUtil.info(`heanup 所有歌曲文件路径: ${JSON.stringify(this.songList.map(s => s.filePath))}`)
- // 发送播放歌单事件
- const eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY }
- const playlistData: PlaylistEventData = {
- playlistId: this.playlist?.id || '',
- playlistName: this.playlist?.name || '',
- songCount: this.songList.length,
- startIndex: index,
- // 只发送所有歌曲的文件路径
- songFilePaths: this.songList.map(s => s.filePath)
- };
- LogUtil.info(`heanup 准备发送事件,eventId: ${eventPlaylistPlay.eventId}`)
- LogUtil.info(`heanup 发送歌单播放事件数据: ${JSON.stringify(playlistData)}`)
- const eventData: emitter.EventData = {
- data: playlistData
- };
- LogUtil.info(`heanup eventData对象: ${JSON.stringify(eventData)}`)
- emitter.emit(eventPlaylistPlay, eventData)
- }
- /**
- * 编辑歌单
- */
- editPlaylist() {
- if (this.playlist) {
- showEditPlaylistDialog(
- this.playlist,
- async (name: string, description: string) => {
- if (this.playlist) {
- this.playlist.name = name
- this.playlist.description = description
- const success = await this.playlistTable.updatePlaylist(this.playlist.id, name, description)
- if (success) {
- ToastUtil.showToast('歌单更新成功')
- // 发送刷新事件
- const eventRefresh: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH }
- emitter.emit(eventRefresh, {})
- } else {
- ToastUtil.showToast('歌单更新失败')
- }
- }
- }
- )
- }
- }
- /**
- * 删除歌单
- */
- async deletePlaylist() {
- if (this.playlist) {
- // 显示确认对话框
- AlertDialog.show({
- title: '删除歌单',
- message: `确定要删除歌单"${this.playlist.name}"吗?此操作不可撤销。`,
- primaryButton: {
- value: '取消',
- action: () => {}
- },
- secondaryButton: {
- value: '删除',
- fontColor: Color.Red,
- action: async () => {
- const success = await this.playlistTable.deletePlaylist(this.playlistId)
- if (success) {
- ToastUtil.showToast('歌单删除成功')
- // 发送刷新事件
- emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH }, {})
- router.back()
- } else {
- ToastUtil.showToast('歌单删除失败')
- }
- }
- }
- })
- }
- }
- /**
- * 从歌单移除歌曲
- */
- async removeSongFromPlaylist(song: VideoItem) {
- if (this.playlist) {
- const success = await this.playlistTable.removeSongFromPlaylist(this.playlistId, song.filePath)
- if (success) {
- // 从本地列表移除
- const index = this.songList.findIndex(s => s.filePath === song.filePath)
- if (index !== -1) {
- this.songList.splice(index, 1)
- this.songList = [...this.songList] // 触发UI更新
- }
-
- // 更新歌单信息
- this.playlist.songCount = this.songList.length
- await this.playlistTable.updatePlaylist(this.playlist.id, this.playlist.name, this.playlist.description)
-
- ToastUtil.showToast('已从歌单移除')
- } else {
- ToastUtil.showToast('移除失败')
- }
- }
- }
- build() {
- Column() {
- // 顶部安全区和标题栏
- Column() {
- Blank()
- .height(px2vp(AppUtil.getStatusBarHeight()))
- .backgroundColor($r('app.color.title_bar_bg'))
- .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
-
- // 标题栏
- Row() {
- Image($r('app.media.back'))
- .width(24)
- .height(24)
- .margin({ left: 12, right: 8 })
- .onClick(() => {
- // 如果在排序模式,先退出排序模式
- if (this.isSortMode) {
- this.exitSortMode()
- } else {
- router.back()
- }
- })
- Text(this.isSortMode ? '排序模式' : (this.playlist?.name || '歌单详情'))
- .fontSize(18)
- .fontColor(Color.White)
- .fontWeight(FontWeight.Medium)
- .layoutWeight(1)
- .textAlign(TextAlign.Center)
- // 排序/完成按钮
- Text(this.isSortMode ? '完成' : '排序')
- .fontSize(16)
- .fontColor(Color.White)
- .margin({ right: 12 })
- .onClick(() => {
- if (this.isSortMode) {
- this.exitSortMode()
- } else {
- this.enterSortMode()
- }
- })
- }
- .height(48)
- .width('100%')
- .alignItems(VerticalAlign.Center)
- .backgroundColor($r('app.color.title_bar_bg'))
- }
- if (this.isLoading) {
- // 加载状态
- Column() {
- LoadingProgress()
- .width(40)
- .height(40)
- .color(this.themeColor )
-
- Text('加载中...')
- .fontSize(14)
- .fontColor($r('app.color.text_color'))
- .margin({ top: 12 })
- }
- .layoutWeight(1)
- .justifyContent(FlexAlign.Center)
- } else if (this.playlist) {
- // 歌单信息区域 - 参考LocalMusic的封面标题区域设计
- Column() {
- // 背景区域
- Row() {
- Column() {
- // 歌单封面
- Image(this.playlist.coverPath || $r('app.media.hm_playlist'))
- .width(100)
- .height(100)
- .borderRadius(12)
- .clip(true)
- .interpolation(ImageInterpolation.High)
- .autoResize(true)
- .shadow({
- radius: 15,
- color: '#0000001a',
- offsetX: 0,
- offsetY: 6
- })
- }
- .alignItems(HorizontalAlign.Start)
- .margin({ left: 24, top: 16, bottom: 16 })
- Column() {
- // 歌单名称
- Text(this.playlist.name)
- .fontSize(20)
- .fontWeight(FontWeight.Bold)
- .fontColor($r('app.color.text_color'))
- .maxLines(2)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- .margin({ bottom: 8 })
- // 歌单描述
- if (this.playlist.description) {
- Text(this.playlist.description)
- .fontSize(14)
- .fontColor($r('app.color.text_color'))
- .opacity(0.8)
- .maxLines(2)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- .margin({ bottom: 12 })
- .lineHeight(18)
- }
- // 歌单统计信息
- Row() {
- Text(`共${this.playlist.songCount}首歌`)
- .fontSize(13)
- .fontWeight(FontWeight.Bold)
- .fontColor($r('app.color.text_color'))
- .opacity(0.8)
- if (this.calculateTotalDuration() > 0) {
- Text(` · ${this.formatTotalDuration(this.calculateTotalDuration())}`)
- .fontSize(13)
- .fontColor($r('app.color.text_color'))
- .opacity(0.7)
- .margin({ left: 4 })
- }
- }
- .margin({ top: 8 })
- }
- .height('100%')
- .layoutWeight(1)
- .margin({ left: 16 })
- .justifyContent(FlexAlign.Center)
- .alignItems(HorizontalAlign.Start)
- }
- .width('100%')
- .height(140)
- .justifyContent(FlexAlign.SpaceBetween)
- .padding({ right: 24 })
- // 操作按钮区域
- Row({ space: 12 }) {
- // 播放全部按钮 - 参考LocalMusic的按钮样式
- Button() {
- Row({ space: 8 }) {
- Image($r('app.media.ic_play'))
- .width(16)
- .height(16)
- .fillColor(Color.White)
- Text('播放全部')
- .fontSize(14)
- .fontColor(Color.White)
- .fontWeight(FontWeight.Medium)
- }
- .justifyContent(FlexAlign.Center)
- }
- .layoutWeight(1)
- .height(44)
- .backgroundColor(this.themeColor )
- .borderRadius(22)
- .shadow({
- radius: 8,
- color: '#0a59f740',
- offsetX: 0,
- offsetY: 4
- })
- .onClick(() => {
- this.playPlaylist()
- })
- .stateStyles({
- normal: {
- .backgroundColor(this.themeColor )
- .scale({ x: 1, y: 1 })
- },
- pressed: {
- .backgroundColor('#0a59f7cc')
- .scale({ x: 0.96, y: 0.96 })
- }
- })
- .animation({
- duration: 150,
- curve: Curve.EaseInOut
- })
- // 编辑歌单按钮
- Button() {
- Row({ space: 6 }) {
- Text('✏️')
- .fontSize(16)
- .fontColor(this.themeColor )
- Text('编辑')
- .fontSize(14)
- .fontColor(this.themeColor )
- .fontWeight(FontWeight.Medium)
- }
- .justifyContent(FlexAlign.Center)
- }
- .layoutWeight(1)
- .height(44)
- .backgroundColor(Color.Transparent)
- .borderRadius(22)
- .border({
- width: 1.5,
- color: this.themeColor
- })
- .onClick(() => {
- this.editPlaylist()
- })
- .stateStyles({
- normal: {
- .backgroundColor(Color.Transparent)
- .scale({ x: 1, y: 1 })
- },
- pressed: {
- .backgroundColor('#0a59f715')
- .scale({ x: 0.96, y: 0.96 })
- }
- })
- .animation({
- duration: 150,
- curve: Curve.EaseInOut
- })
- // 删除按钮
- Button() {
- Row({ space: 6 }) {
- Text('🗑️')
- .fontSize(16)
- Text('删除')
- .fontSize(14)
- .fontColor(Color.Red)
- .fontWeight(FontWeight.Medium)
- }
- .justifyContent(FlexAlign.Center)
- }
- .layoutWeight(1)
- .height(44)
- .backgroundColor(Color.Transparent)
- .borderRadius(22)
- .border({
- width: 1.5,
- color: Color.Red
- })
- .onClick(() => {
- this.deletePlaylist()
- })
- .stateStyles({
- normal: {
- .backgroundColor(Color.Transparent)
- .scale({ x: 1, y: 1 })
- },
- pressed: {
- .backgroundColor('#ff000015')
- .scale({ x: 0.96, y: 0.96 })
- }
- })
- .animation({
- duration: 150,
- curve: Curve.EaseInOut
- })
- }
- .width('100%')
- .padding({ left: 24, right: 24, bottom: 20 })
- .justifyContent(FlexAlign.Start)
- }
- .backgroundColor($r('app.color.bg_card'))
- .margin({ left: 16, right: 16, top: 16 })
- .borderRadius(16)
- .shadow({
- radius: 12,
- color: '#00000014',
- offsetX: 0,
- offsetY: 4
- })
- .scale({ x: this.contentScale, y: this.contentScale })
- .opacity(this.pageOpacity)
- .transition(TransitionEffect.OPACITY.animation({ duration: 600, curve: Curve.EaseOut }))
- .transition(TransitionEffect.scale({ x: 0.95, y: 0.95 }).animation({ duration: 600, curve: Curve.EaseOut }))
- // 歌曲列表标题 - 简化设计,与LocalMusic保持一致
- if (this.songList.length > 0) {
- Row() {
- Text('歌曲列表')
- .fontSize(16)
- .fontWeight(FontWeight.Medium)
- .fontColor($r('app.color.text_color'))
- .opacity(0.9)
- .layoutWeight(1)
- Text(`${this.songList.length}首`)
- .fontSize(14)
- .fontColor($r('app.color.text_color'))
- .opacity(0.6)
- // 添加歌曲按钮
- Button() {
- Row({ space: 6 }) {
- Text('+')
- .fontSize(18)
- .fontColor(Color.White)
- Text('添加歌曲')
- .fontSize(14)
- .fontColor(Color.White)
- .fontWeight(FontWeight.Medium)
- }
- .justifyContent(FlexAlign.Center)
- .padding({ left: 16, right: 16, top: 8, bottom: 8 })
- }
- .height(36)
- .backgroundColor(this.themeColor)
- .borderRadius(18)
- .margin({ left: 12 })
- .onClick(() => {
- this.addSongsToPlaylist()
- })
- .stateStyles({
- normal: {
- .backgroundColor(this.themeColor)
- .scale({ x: 1, y: 1 })
- },
- pressed: {
- .backgroundColor('#0a59f7cc')
- .scale({ x: 0.96, y: 0.96 })
- }
- })
- .animation({
- duration: 150,
- curve: Curve.EaseInOut
- })
- }
- .width('100%')
- .padding({ left: 24, right: 24, top: 16, bottom: 12 })
- .opacity(this.pageOpacity)
- .transition(TransitionEffect.OPACITY.animation({ duration: 800, curve: Curve.EaseOut, delay: 200 }))
- }
- // 歌曲列表 - 参考LocalMusic的MusicItem样式
- if (this.songList.length > 0) {
- List({ space: 0 }) {
- ForEach(this.songList, (song: VideoItem, index: number) => {
- ListItem() {
- Button({ type: ButtonType.Normal, stateEffect: true }) {
- Row() {
- // 歌曲封面 - 改为圆形
- Stack() {
- Image(song.pixelMapPath || $r('app.media.music_red'))
- .width(48)
- .height(48)
- .borderRadius(24) // 改为圆形
- .objectFit(ImageFit.Cover)
- .interpolation(ImageInterpolation.High)
- .autoResize(true)
- .margin({ left: 16 })
- .onClick(() => {
- this.playSong(song, index)
- })
- }
- .width(64)
- // 歌曲信息 - 参考LocalMusic的布局
- Column() {
- Row() {
- // 歌曲名称
- Text(song.name || song.fileName || '')
- .fontSize(16)
- .fontColor(this.isPlaying && this.curIndex === index ? this.themeColor : $r('app.color.text_color'))
- .fontWeight(this.isPlaying && this.curIndex === index ? FontWeight.Bold : FontWeight.Normal)
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- .layoutWeight(1)
- .margin({ top: 8, left: 12 })
- // 音质标签
- if (song.md5Str) {
- Text(song.md5Str?.includes('Lossless') ? '无损' : song.md5Str)
- .fontSize(10)
- .fontColor($r('app.color.text_color'))
- .fontWeight(500)
- .padding({ top: 2, right: 6, left: 6, bottom: 2 })
- .borderRadius(4)
- .margin({ top: 8, left: 8 })
- .backgroundColor( '#FFC107')
- .visibility(song.md5Str ? Visibility.Visible : Visibility.None)
- }
- }
- .width('100%')
- // 歌手和专辑信息 - 参考LocalMusic的第二行布局
- Row() {
- Text(song.artist)
- .fontSize(13)
- .fontColor(this.isPlaying && this.curIndex === index ?this.themeColor : $r('app.color.text_color'))
- .fontWeight(this.isPlaying && this.curIndex === index ? FontWeight.Medium : FontWeight.Normal)
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- .layoutWeight(1)
- .opacity(this.isPlaying && this.curIndex === index ? 1.0 : 0.8)
- Text(song.album)
- .fontSize(13)
- .fontColor(this.isPlaying && this.curIndex === index ? this.themeColor : $r('app.color.text_color'))
- .fontWeight(this.isPlaying && this.curIndex === index ? FontWeight.Medium : FontWeight.Normal)
- .maxLines(1)
- .textOverflow({ overflow: TextOverflow.Ellipsis })
- .layoutWeight(1)
- .opacity(this.isPlaying && this.curIndex === index ? 1.0 : 0.8)
- // 时长显示
- if (song.duration && typeof song.duration === 'number' && song.duration > 0) {
- Text(this.formatDuration(song.duration))
- .fontSize(13)
- .fontColor(this.isPlaying && this.curIndex === index ? this.themeColor : $r('app.color.text_color'))
- .fontWeight(this.isPlaying && this.curIndex === index ? FontWeight.Medium : FontWeight.Normal)
- .opacity(this.isPlaying && this.curIndex === index ? 1.0 : 0.7)
- .margin({ right: 20 })
- }
- }
- .width('100%')
- .margin({ left: 12, top: 4 })
- .alignItems(VerticalAlign.Center)
- }
- .layoutWeight(1)
- .alignItems(HorizontalAlign.Start)
- .justifyContent(FlexAlign.Center)
- // 排序模式下显示上下移动按钮,否则显示更多按钮
- if (this.isSortMode) {
- Row({ space: 8 }) {
- // 上移按钮
- Text('▲')
- .fontSize(16)
- .fontColor(index === 0 ? '#cccccc' : this.themeColor)
- .onClick(() => {
- if (index > 0) {
- this.moveSong(index, index - 1)
- }
- })
- .enabled(index > 0)
- // 下移按钮
- Text('▼')
- .fontSize(16)
- .fontColor(index === this.songList.length - 1 ? '#cccccc' : this.themeColor)
- .onClick(() => {
- if (index < this.songList.length - 1) {
- this.moveSong(index, index + 1)
- }
- })
- .enabled(index < this.songList.length - 1)
- }
- .margin({ right: 15 })
- } else {
- Text('⋯')
- .fontSize(20)
- .fontColor($r('app.color.text_color'))
- .opacity(0.6)
- .margin({ right: 15 })
- .onClick(() => {
- this.showSongMenu(song)
- })
- }
- }
- .width('100%')
- .height(70)
- .justifyContent(FlexAlign.Start)
- .alignItems(VerticalAlign.Center)
- }
- .backgroundColor(Color.Transparent)
- .height(70)
- .width('100%')
- .onClick(() => {
- // 排序模式下禁用点击播放
- if (!this.isSortMode) {
- this.playSong(song, index)
- }
- })
- .gesture(
- LongPressGesture()
- .onAction(() => {
- // 排序模式下不显示菜单
- if (!this.isSortMode) {
- this.showSongMenu(song)
- }
- })
- )
- .stateStyles({
- normal: {
- .backgroundColor(Color.Transparent)
- },
- pressed: {
- .backgroundColor(this.isSortMode ? Color.Transparent : '#f1f3f5')
- }
- })
- // 当前播放歌曲的背景高亮
- .backgroundColor(this.isPlaying && this.curIndex === index ? '#f0f8ff' : Color.Transparent)
- .border({
- width: { left: this.isPlaying && this.curIndex === index ? 3 : 0 },
- color: this.themeColor
- })
- .opacity(this.pageOpacity)
- .translate({ x: 0, y: this.showContent ? 0 : 20 })
- .transition(TransitionEffect.OPACITY.animation({
- duration: 600,
- curve: Curve.EaseOut,
- delay: 300 + index * 30
- }))
- .transition(TransitionEffect.translate({ y: 20 }).animation({
- duration: 600,
- curve: Curve.EaseOut,
- delay: 300 + index * 30
- }))
- }
- })
- }
- .width('100%')
- .layoutWeight(1)
- .backgroundColor($r('app.color.bg_card'))
- .margin({ left: 16, right: 16 })
- .borderRadius(12)
- .divider({
- strokeWidth: 0.5,
- color: '#0000001a',
- startMargin: 80,
- endMargin: 20
- })
- .opacity(this.pageOpacity)
- .scale({ x: this.contentScale, y: this.contentScale })
- .transition(TransitionEffect.OPACITY.animation({ duration: 800, curve: Curve.EaseOut, delay: 200 }))
- .transition(TransitionEffect.scale({ x: 0.95, y: 0.95 }).animation({ duration: 800, curve: Curve.EaseOut, delay: 200 }))
- } else {
- // 空状态
- Column() {
- // 空状态容器
- Column() {
- // 空状态图标容器
- Stack() {
- // 背景圆圈
- Circle({ width: 160, height: 160 })
- .fill('#0a59f715')
- .border({
- width: 2,
- color: '#0a59f71a'
- })
- // 中间圆圈
- Circle({ width: 120, height: 120 })
- .fill('#0a59f722')
- // 音符图标
- Image($r('app.media.music_red'))
- .width(64)
- .height(64)
- .opacity(0.6)
- .fillColor(this.themeColor )
- }
- .margin({ bottom: 32 })
- // 空状态标题
- Text('歌单还是空的')
- .fontSize(22)
- .fontColor($r('app.color.text_color'))
- .fontWeight(FontWeight.Bold)
- .margin({ bottom: 12 })
- .letterSpacing(0.5)
- // 空状态描述
- Text('快来添加你喜欢的音乐吧\n让这个歌单充满美妙的旋律')
- .fontSize(15)
- .fontColor($r('app.color.text_color'))
- .opacity(0.8)
- .margin({ bottom: 40 })
- .textAlign(TextAlign.Center)
- .lineHeight(24)
- .maxLines(2)
- // 添加歌曲按钮
- Button() {
- Row({ space: 8 }) {
- Text('+')
- .fontSize(20)
- .fontColor(Color.White)
- Text('添加歌曲')
- .fontSize(16)
- .fontColor(Color.White)
- .fontWeight(FontWeight.Medium)
- }
- .justifyContent(FlexAlign.Center)
- }
- .width(160)
- .height(48)
- .backgroundColor(this.themeColor )
- .borderRadius(24)
- .shadow({
- radius: 12,
- color: '#0a59f74d',
- offsetX: 0,
- offsetY: 6
- })
- .onClick(() => {
- showAddSongsToPlaylistDialog(
- this.playlist!,
- async (songs: VideoItem[]) => {
- // 添加选中的歌曲到歌单
- const success = await this.playlistTable.addSongsToPlaylist(this.playlistId, songs.map(song => song.filePath))
-
- if (success) {
- ToastUtil.showToast(`成功添加 ${songs.length} 首歌曲`)
- // 重新加载歌单详情
- this.loadPlaylistDetail()
- } else {
- ToastUtil.showToast('添加歌曲失败')
- }
- }
- )
- })
- .stateStyles({
- normal: {
- .backgroundColor(this.themeColor )
- .scale({ x: 1, y: 1 })
- },
- pressed: {
- .backgroundColor('#0a59f7cc')
- .scale({ x: 0.96, y: 0.96 })
- }
- })
- .animation({
- duration: 200,
- curve: Curve.EaseInOut
- })
- // 快速操作提示
- Text('或长按歌单选择更多操作')
- .fontSize(13)
- .fontColor($r('app.color.text_color'))
- .margin({ top: 16 })
- .opacity(0.7)
- }
- .width('100%')
- .padding(32)
- .backgroundColor($r('app.color.bg_card'))
- .borderRadius(20)
- .margin({ left: 16, right: 16 })
- .shadow({
- radius: 16,
- color: '#0000000f',
- offsetX: 0,
- offsetY: 8
- })
- .opacity(this.pageOpacity)
- .scale({ x: this.contentScale, y: this.contentScale })
- .transition(TransitionEffect.OPACITY.animation({ duration: 800, curve: Curve.EaseOut, delay: 200 }))
- .transition(TransitionEffect.scale({ x: 0.95, y: 0.95 }).animation({ duration: 800, curve: Curve.EaseOut, delay: 200 }))
- }
- .layoutWeight(1)
- .margin({ left: 16, right: 16 })
- .justifyContent(FlexAlign.Center)
- .padding({ top: 20, bottom: 20 })
- }
- }
- }
- .width('100%')
- .height('100%')
- .backgroundColor($r('app.color.index_background'))
- }
- /**
- * 显示更多菜单
- */
- showMoreMenu() {
- // 简单直接的二选一,避免复杂的多级菜单
- AlertDialog.show({
- title: '歌单操作',
- message: '🎵 添加歌曲:向歌单添加新音乐\n✏️ 编辑歌单:修改歌单信息\n\n点击"确定"添加歌曲,点击"取消"编辑歌单',
- primaryButton: {
- value: '取消 (编辑)',
- action: () => {
- this.editPlaylist()
- }
- },
- secondaryButton: {
- value: '确定 (添加)',
- action: () => {
- this.addSongsToPlaylist()
- }
- }
- })
- }
- /**
- * 添加歌曲到歌单
- */
- addSongsToPlaylist() {
- if (this.playlist) {
- showAddSongsToPlaylistDialog(
- this.playlist,
- async (songs: VideoItem[]) => {
- // 添加选中的歌曲到歌单
- const success = await this.playlistTable.addSongsToPlaylist(this.playlistId, songs.map(song => song.filePath))
- if (success) {
- ToastUtil.showToast(`成功添加 ${songs.length} 首歌曲`)
- // 重新加载歌单详情
- this.loadPlaylistDetail()
- } else {
- ToastUtil.showToast('添加歌曲失败')
- }
- }
- )
- }
- }
- /**
- * 显示歌曲菜单
- */
- showSongMenu(song: VideoItem) {
- AlertDialog.show({
- title: song.name,
- message: '',
- primaryButton: {
- value: '取消',
- action: () => {}
- },
- secondaryButton: {
- value: '从歌单移除',
- fontColor: Color.Red,
- action: () => {
- this.removeSongFromPlaylist(song)
- }
- }
- })
- }
- /**
- * 进入排序模式
- */
- enterSortMode() {
- this.isSortMode = true
- ToastUtil.showToast('点击上下箭头调整歌曲顺序')
- LogUtil.info('heanup 进入排序模式')
- }
- /**
- * 退出排序模式
- */
- exitSortMode() {
- this.isSortMode = false
- ToastUtil.showToast('排序已保存')
- LogUtil.info('heanup 退出排序模式')
- }
- /**
- * 移动歌曲位置
- */
- async moveSong(fromIndex: number, toIndex: number) {
- if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 ||
- fromIndex >= this.songList.length || toIndex >= this.songList.length) {
- return
- }
- LogUtil.info(`heanup 移动歌曲: from ${fromIndex} to ${toIndex}`)
- // 1. 在本地数组中移动
- const movedSong = this.songList.splice(fromIndex, 1)[0]
- this.songList.splice(toIndex, 0, movedSong)
- // 2. 触发UI更新
- this.songList = [...this.songList]
- // 3. 更新数据库中的sortOrder
- await this.updateAllSongsSortOrder()
- }
- /**
- * 更新所有歌曲的sortOrder到数据库
- */
- async updateAllSongsSortOrder() {
- try {
- LogUtil.info('heanup 开始更新所有歌曲的sortOrder')
- for (let i = 0; i < this.songList.length; i++) {
- const song = this.songList[i]
- const success = await this.playlistTable.updatePlaylistSongSortOrder(
- this.playlistId,
- song.filePath,
- i
- )
- if (success) {
- LogUtil.info(`heanup 更新歌曲[${i}] ${song.name} sortOrder成功`)
- } else {
- LogUtil.error(`heanup 更新歌曲[${i}] ${song.name} sortOrder失败`)
- }
- }
- LogUtil.info('heanup 所有歌曲sortOrder更新完成')
- } catch (error) {
- LogUtil.error('heanup 更新歌曲sortOrder失败: ' + error)
- }
- }
- }
- /**
- * 将 PlaylistSong 转换为 VideoItem
- */
- export async function convertPlaylistSongsToVideoItems(context: Context,playlistSongs: PlaylistSong[]): Promise<VideoItem[]> {
- const videoItems: VideoItem[] = []
- LogUtil.info(`heanup 开始转换歌曲,共 ${playlistSongs.length} 首`)
- const mediaTable: MediaTable = new MediaTable(context)
- await new Promise<void>((resolve, reject) => {
- mediaTable.getRdbStore(context, (err:Error) => {
- err ? reject(err) : resolve();
- });
- });
- for (const playlistSong of playlistSongs) {
- try {
- LogUtil.info(`heanup 查询歌曲: ${playlistSong.songFilePath}`)
- // 从数据库查询完整的歌曲信息
- const videoItem = await mediaTable.queryVideoByFilePath(playlistSong.songFilePath)
- if (videoItem) {
- LogUtil.info(`heanup 找到歌曲: ${videoItem.name}`)
- videoItems.push(videoItem)
- } else {
- LogUtil.warn(`heanup 数据库中未找到歌曲: ${playlistSong.songFilePath}`)
- // 如果数据库中没有,创建一个基本的 VideoItem
- const fileName = playlistSong.songFilePath.split('/').pop() || ''
- const name = fileName.replace(/\.[^/.]+$/, "") // 移除文件扩展名
- const basicVideoItem = new VideoItem(
- name, // name
- Date.now().toString() + Math.random(), // id
- playlistSong.songFilePath, // filePath
- 0, // type (音乐类型)
- 0, // videoSize
- playlistSong.addTime, // cTime
- undefined, // pixelMap
- undefined, // size
- undefined, // pixelMapPath
- undefined, // artist
- undefined, // album
- fileName, // fileName
- undefined // lastPlayed
- )
- videoItems.push(basicVideoItem)
- }
- } catch (error) {
- LogUtil.error('heanup 转换歌曲失败: ' + error)
- }
- }
- LogUtil.info(`heanup 转换完成,共 ${videoItems.length} 首歌曲`)
- return videoItems
- }
|