ソースを参照

feat(playlist): 添加歌单封面选择功能

chendeben 9 ヶ月 前
コミット
56c90deb91

+ 126 - 9
entry/src/main/ets/dialog/PlaylistDialog.ets

@@ -2,6 +2,9 @@ import { DialogHelper } from '@pura/harmony-dialog';
 import { Playlist } from '../viewmodel/Playlist';
 import { ToastUtil } from '@pura/harmony-utils';
 import { CommonConstants } from '../common/constants/CommonConstants';
+import { ImagePickerUtil } from '../common/util/ImagePickerUtil';
+import Logger from '../common/util/Logger';
+import { Context } from '@kit.AbilityKit';
 
 /**
  * 歌单对话框内容组件
@@ -10,12 +13,13 @@ import { CommonConstants } from '../common/constants/CommonConstants';
 struct PlaylistDialogContent {
   @State playlistName: string = ''
   @State playlistDescription: string = ''
+  @State coverPath: string = ''
   @State isEditMode: boolean = false
   @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
   @State originalPlaylist: Playlist | null = null
-  
+
   // 回调函数
-  onConfirm?: (name: string, description: string) => void
+  onConfirm?: (name: string, description: string, coverPath?: string) => void
   onCancel?: () => void
 
   aboutToAppear() {
@@ -23,6 +27,11 @@ struct PlaylistDialogContent {
       this.isEditMode = true
       this.playlistName = this.originalPlaylist.name
       this.playlistDescription = this.originalPlaylist.description || ''
+      this.coverPath = this.originalPlaylist.coverPath || ''
+      Logger.info('heanup PlaylistDialog', `编辑模式加载歌单: ${this.playlistName}, 封面路径: ${this.coverPath}`)
+      if (this.coverPath) {
+        Logger.info('heanup PlaylistDialog', `封面文件是否存在: ${ImagePickerUtil.imageExists(this.coverPath)}`)
+      }
     }
   }
 
@@ -36,6 +45,73 @@ struct PlaylistDialogContent {
         .visibility(this.isEditMode?Visibility.None:Visibility.Visible)
         .margin({ top: 20 })
 
+      // 歌单封面选择
+      Column({ space: 8 }) {
+        Text('歌单封面(可选)')
+          .fontSize(14)
+          .fontColor($r('app.color.text_color'))
+          .alignSelf(ItemAlign.Start)
+
+        // 封面选择区域
+        Row({ space: 12 }) {
+          // 封面预览
+          Stack() {
+            if (this.coverPath) {
+              Image(this.coverPath)
+                .width(80)
+                .height(80).borderRadius(40)
+                .objectFit(ImageFit.Cover)
+            } else {
+              // 默认封面图标
+              Column() {
+                Image($r('app.media.hm_music'))
+                  .width(40)
+                  .height(40)
+                  .fillColor($r('app.color.text_color'))
+              }
+              .width(80)
+              .height(80)
+              .backgroundColor($r('app.color.input_background'))
+              .borderRadius(40)
+              .justifyContent(FlexAlign.Center)
+            }
+          }
+          .onClick(() => {
+            this.handleSelectCover()
+          })
+
+          // 操作按钮
+          Column({ space: 8 }) {
+            Button('选择封面')
+              .width(120)
+              .height(36)
+              .fontSize(12)
+              .fontColor($r('app.color.text_color'))
+              .backgroundColor($r('app.color.input_background'))
+              .onClick(() => {
+                this.handleSelectCover()
+              })
+
+            if (this.coverPath) {
+              Button('移除封面')
+                .width(120)
+                .height(36)
+                .fontSize(12)
+                .fontColor(Color.Red)
+                .backgroundColor('#FFE5E5')
+                .onClick(() => {
+                  this.handleRemoveCover()
+                })
+            }
+          }
+          .alignItems(HorizontalAlign.Start)
+        }
+        .width('100%')
+        .justifyContent(FlexAlign.Start)
+      }
+      .width('100%')
+      .alignItems(HorizontalAlign.Start)
+
       // 歌单名称输入框
       Column({ space: 8 }) {
         Text('歌单名称')
@@ -135,10 +211,51 @@ struct PlaylistDialogContent {
       return
     }
 
-    this.onConfirm?.(this.playlistName.trim(), this.playlistDescription.trim())
+    this.onConfirm?.(this.playlistName.trim(), this.playlistDescription.trim(), this.coverPath)
     // 关闭对话框
     DialogHelper.closeDialog(this.isEditMode ? 'editPlaylistDialog' : 'createPlaylistDialog')
   }
+
+  /**
+   * 处理选择封面
+   */
+  private async handleSelectCover() {
+    try {
+      // 需要获取上下文,这里通过全局上下文获取
+      const context = getContext(this) as Context
+      if (!context) {
+        ToastUtil.showToast('获取应用上下文失败')
+        return
+      }
+
+      const selectedPath = await ImagePickerUtil.selectSingleImage(context)
+      if (selectedPath) {
+        // 如果之前有封面,删除旧封面
+        if (this.coverPath && this.coverPath !== this.originalPlaylist?.coverPath) {
+          ImagePickerUtil.deleteImage(this.coverPath)
+        }
+        this.coverPath = selectedPath
+        Logger.info('heanup PlaylistDialog', `选择封面成功: ${selectedPath}`)
+      }
+    } catch (error) {
+        Logger.error('heanup PlaylistDialog', `选择封面失败: ${(error as Error).message}`)
+        ToastUtil.showToast('选择封面失败')
+    }
+  }
+
+  /**
+   * 处理移除封面
+   */
+  private handleRemoveCover() {
+    if (this.coverPath) {
+      // 只有当封面不是原来的封面时才删除文件
+      if (this.coverPath !== this.originalPlaylist?.coverPath) {
+        ImagePickerUtil.deleteImage(this.coverPath)
+      }
+      this.coverPath = ''
+      Logger.info('heanup PlaylistDialog', '移除封面成功')
+    }
+  }
 }
 
 /**
@@ -150,7 +267,7 @@ export struct PlaylistDialogManager {
    * 创建歌单对话框构建器
    */
   @Builder
-  buildCreatePlaylistDialog(onConfirm: (name: string, description: string) => void, onCancel?: () => void) {
+  buildCreatePlaylistDialog(onConfirm: (name: string, description: string, coverPath?: string) => void, onCancel?: () => void) {
     PlaylistDialogContent({
       onConfirm: onConfirm,
       onCancel: onCancel
@@ -161,7 +278,7 @@ export struct PlaylistDialogManager {
    * 编辑歌单对话框构建器
    */
   @Builder
-  buildEditPlaylistDialog(playlist: Playlist, onConfirm: (name: string, description: string) => void, onCancel?: () => void) {
+  buildEditPlaylistDialog(playlist: Playlist, onConfirm: (name: string, description: string, coverPath?: string) => void, onCancel?: () => void) {
     PlaylistDialogContent({
       originalPlaylist: playlist,
       onConfirm: onConfirm,
@@ -173,7 +290,7 @@ export struct PlaylistDialogManager {
    * 显示创建歌单对话框
    */
   showCreatePlaylistDialog(
-    onConfirm: (name: string, description: string) => void,
+    onConfirm: (name: string, description: string, coverPath?: string) => void,
     onCancel?: () => void
   ) {
     DialogHelper.showCustomContentDialog({
@@ -192,7 +309,7 @@ export struct PlaylistDialogManager {
    */
   showEditPlaylistDialog(
     playlist: Playlist,
-    onConfirm: (name: string, description: string) => void,
+    onConfirm: (name: string, description: string, coverPath?: string) => void,
     onCancel?: () => void
   ) {
     DialogHelper.showCustomContentDialog({
@@ -217,7 +334,7 @@ const dialogManager = new PlaylistDialogManager()
  * 显示创建歌单对话框
  */
 export function showCreatePlaylistDialog(
-  onConfirm: (name: string, description: string) => void,
+  onConfirm: (name: string, description: string, coverPath?: string) => void,
   onCancel?: () => void
 ) {
   dialogManager.showCreatePlaylistDialog(onConfirm, onCancel)
@@ -228,7 +345,7 @@ export function showCreatePlaylistDialog(
  */
 export function showEditPlaylistDialog(
   playlist: Playlist,
-  onConfirm: (name: string, description: string) => void,
+  onConfirm: (name: string, description: string, coverPath?: string) => void,
   onCancel?: () => void
 ) {
   dialogManager.showEditPlaylistDialog(playlist, onConfirm, onCancel)

+ 20 - 20
entry/src/main/ets/pages/NewIndex.ets

@@ -38,6 +38,8 @@ import { Playlist, PlaylistSong } from '../viewmodel/Playlist';
 import PlaylistTable from '../common/util/PlaylistTable';
 import { convertPlaylistSongsToVideoItems } from './PlaylistDetailPage';
 import MediaTable from '../common/util/MediaTable';
+import { showCreatePlaylistDialog } from '../dialog/PlaylistDialog';
+import { ImagePickerUtil } from '../common/util/ImagePickerUtil';
 
 // import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg';
 
@@ -1050,7 +1052,7 @@ struct NewIndex {
               .width(22)
               .height(22)
               .margin({ left: 25 })
-              .borderRadius(4)
+              .borderRadius(11)
               .fillColor(this.themeColor)
               .clip(true)
             
@@ -1178,27 +1180,25 @@ struct NewIndex {
       return
     }
 
-    DialogHelper.showTextInputDialog({
-      title: '创建歌单',
-      maskColor: Color.Transparent,
-      text: '',
-      placeholder: '请输入歌单名称',
-      onAction: async (action, dialogId, content) => {
-        if (action === DialogAction.TWO && content.trim()) {
-          // 创建歌单
-          const success = await this.playlistTable!.createPlaylist(content.trim())
-          if (success) {
-            ToastUtil.showToast('歌单创建成功')
-            // 刷新歌单列表
-            await this.loadPlaylistList()
-            // 发送歌单刷新事件
-            emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH }, {})
-          } else {
-            ToastUtil.showToast('歌单创建失败')
-          }
+    // 使用新的歌单对话框,支持自定义封面
+    showCreatePlaylistDialog(
+      async (name: string, description: string, coverPath?: string) => {
+        // 创建歌单
+        const success = await this.playlistTable!.createPlaylist(name, description, coverPath)
+        if (success) {
+          ToastUtil.showToast('歌单创建成功')
+          // 刷新歌单列表
+          await this.loadPlaylistList()
+          // 发送歌单刷新事件
+          emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH }, {})
+        } else {
+          ToastUtil.showToast('歌单创建失败')
         }
+      },
+      () => {
+        // 取消创建歌单
       }
-    })
+    )
   }
 
   /**

+ 8 - 3
entry/src/main/ets/pages/PlaylistDetailPage.ets

@@ -11,6 +11,7 @@ import { GlobalContext } from '../common/util/GlobalContext';
 import { CommonConstants } from '../common/constants/CommonConstants';
 import { EventConstants } from '../common/constants/EventConstants';
 import { common } from '@kit.AbilityKit';
+import { ImagePickerUtil } from '../common/util/ImagePickerUtil';
 
 /**
  * 歌单播放事件数据
@@ -323,11 +324,15 @@ export struct PlaylistDetailPage {
     if (this.playlist) {
       showEditPlaylistDialog(
         this.playlist,
-        async (name: string, description: string) => {
+        async (name: string, description: string, coverPath?: string) => {
           if (this.playlist) {
             this.playlist.name = name
             this.playlist.description = description
-            const success = await this.playlistTable.updatePlaylist(this.playlist.id, name, description)
+            // 如果 coverPath 发生变化,更新歌单封面
+            if (coverPath !== undefined) {
+              this.playlist.coverPath = coverPath
+            }
+            const success = await this.playlistTable.updatePlaylist(this.playlist.id, name, description, coverPath)
             if (success) {
               ToastUtil.showToast('歌单更新成功')
               // 发送刷新事件
@@ -474,7 +479,7 @@ export struct PlaylistDetailPage {
               Image(this.playlist.coverPath || $r('app.media.hm_playlist'))
                 .width(100)
                 .height(100)
-                .borderRadius(12)
+                .borderRadius(50)
                 .clip(true)
                 .interpolation(ImageInterpolation.High)
                 .autoResize(true)

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

@@ -2729,8 +2729,8 @@ export struct LocalMusic {
       () => {
         // 创建新歌单
         showCreatePlaylistDialog(
-          async (name: string, description: string) => {
-            const success = await this.playlistTable?.createPlaylist(name, description)
+          async (name: string, description: string, coverPath?: string) => {
+            const success = await this.playlistTable?.createPlaylist(name, description, coverPath)
             if (success) {
               ToastUtil.showToast('歌单创建成功')
               // 重新加载歌单列表并打开添加对话框
@@ -2739,6 +2739,9 @@ export struct LocalMusic {
             } else {
               ToastUtil.showToast('歌单创建失败')
             }
+          },
+          () => {
+            // 取消创建歌单
           }
         )
       }