Bläddra i källkod

统一用TitleBarPointLightButton
专辑详情页歌曲增加长按事件和左滑删除

onecold 4 månader sedan
förälder
incheckning
428eda2920

+ 1 - 1
entry/src/main/ets/common/util/AttributeModifierUtil.ets

@@ -106,4 +106,4 @@ export class MenuModifier extends CommonModifier {
   applyNormalAttribute(instance: MenuAttribute): void {
     instance.font({ size: 15, weight: FontWeight.Normal }).radius(16)
   }
-}
+}

+ 12 - 17
entry/src/main/ets/pages/ChartsCount.ets

@@ -12,7 +12,7 @@ import { AudioQuality, FFMpegTags } from '../common/util/Utility';
 import MediaTable from '../common/util/MediaTable';
 import { McPieChart, Options } from '@mcui/mccharts'
 import { ComponentContent } from '@kit.ArkUI';
-import { ButtonFancyModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil';
+import { TitleBarPointLightButton } from '../view/PointLight/TitleBarPointLightButton';
 
 // 批量编辑标签
 @Component
@@ -169,23 +169,18 @@ export struct ChartsCount {
       Row({ space: 15 }) {
 
         //左侧滑动按钮
-        Button({ type: ButtonType.Circle, stateEffect: true }) {
-          SymbolGlyph($r('sys.symbol.sort'))
-            .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-        }
-        .attributeModifier(new ButtonFancyModifier(40, 40))
-        .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
-        .animation({ duration: 300, curve: Curve.Ease })
-        .onClick(() => {
-          this.getUIContext().animateTo({ duration: 555 }, () => {
-            // 动画闭包内控制Image组件的出现和消失
-            this.isShowDrawer = !this.isShowDrawer
-            this.offsetX = 0
-          })
-
+        TitleBarPointLightButton({
+          iconResource: $r('sys.symbol.sort'),
+          iconSize: 25,
+          pointColor: this.themeColor,
+          clickScale: 0.6,
+          clickHandler: () => {
+            this.getUIContext().animateTo({ duration: 555 }, () => {
+              this.isShowDrawer = !this.isShowDrawer
+              this.offsetX = 0
+            })
+          }
         })
-        .attributeModifier(new ShadowModifier())
-        .zIndex(0)
 
         Text($r('app.string.music_charts'))
           .margin({left:3,right:10})

+ 85 - 22
entry/src/main/ets/pages/NewIndex.ets

@@ -64,6 +64,9 @@ import { hdsEffect } from '@kit.UIDesignKit';
 // import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg';
 
 const TAG = 'NewIndex'; // 日志标签
+const FIND_DEFAULT_HOME_PROMPT_COUNT_KEY = 'find_default_home_prompt_count'
+const FIND_DEFAULT_HOME_PROMPT_HANDLED_KEY = 'find_default_home_prompt_handled'
+const FIND_DEFAULT_HOME_PROMPT_TARGET_COUNT = 5
 
 /**
  * 首页主组件,包含顶部标题栏、主内容区(本地音乐/网络内容)、侧边抽屉菜单等。
@@ -152,6 +155,7 @@ struct NewIndex {
   @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD;
   /** 记录上一次点击返回键的时间戳,用于双击退出 */
   private backTime: number = 0;
+  private isFindDefaultPromptShowing: boolean = false;
 
   @State tabOptions: SegmentButtonOptions = SegmentButtonOptions.tab({
     buttons: [{ text: '分类' }, { text: '歌单' }, { text: '网盘' }],
@@ -243,7 +247,7 @@ struct NewIndex {
         // 动画闭包内控制Image组件的出现和消失
         // this.isShowDrawer = !this.isShowDrawer
         // this.offsetX = 0
-        this.mType = 8
+        this.enterFindPage()
       })
     } else {
       console.info('onecold 返回键处理 关闭');
@@ -398,6 +402,63 @@ struct NewIndex {
     console.info('onecold 网盘 默认首页2 this.mType='+this.mType )
   }
 
+  private enterFindPage(): void {
+    const wasFindPage: boolean = this.mType === 8
+    this.mType = 8
+    if (!wasFindPage) {
+      this.handleFindPageEntry()
+    }
+  }
+
+  private handleFindPageEntry(): void {
+    if (this.defalut_home_type === 5 || PreferencesUtil.getBooleanSync(FIND_DEFAULT_HOME_PROMPT_HANDLED_KEY, false)) {
+      return
+    }
+    const nextCount: number = PreferencesUtil.getNumberSync(FIND_DEFAULT_HOME_PROMPT_COUNT_KEY, 0) + 1
+    PreferencesUtil.putSync(FIND_DEFAULT_HOME_PROMPT_COUNT_KEY, nextCount)
+    if (nextCount >= FIND_DEFAULT_HOME_PROMPT_TARGET_COUNT) {
+      setTimeout(() => {
+        this.showFindDefaultHomePrompt()
+      }, 360)
+    }
+  }
+
+  private showFindDefaultHomePrompt(): void {
+    if (this.mType !== 8 || this.defalut_home_type === 5 || this.isFindDefaultPromptShowing) {
+      return
+    }
+    if (PreferencesUtil.getBooleanSync(FIND_DEFAULT_HOME_PROMPT_HANDLED_KEY, false)) {
+      return
+    }
+    if (PreferencesUtil.getNumberSync(FIND_DEFAULT_HOME_PROMPT_COUNT_KEY, 0) < FIND_DEFAULT_HOME_PROMPT_TARGET_COUNT) {
+      return
+    }
+    this.isFindDefaultPromptShowing = true
+    AlertDialog.show({
+      title: '设为默认发现页',
+      message: '你已经多次进入发现页,是否将发现页设为启动默认页面?',
+      primaryButton: {
+        value: '取消',
+        action: () => {
+          PreferencesUtil.putSync(FIND_DEFAULT_HOME_PROMPT_HANDLED_KEY, true)
+          this.isFindDefaultPromptShowing = false
+        }
+      },
+      secondaryButton: {
+        value: '确定',
+        fontColor: this.themeColor,
+        action: () => {
+          this.defalut_home_type = 5
+          PreferencesUtil.putSync('defalut_home_type', 5)
+          PreferencesUtil.putSync(FIND_DEFAULT_HOME_PROMPT_HANDLED_KEY, true)
+          emitter.emit({ eventId: EventConstants.EVENT_SETTING_UPDATE }, {})
+          ToastUtil.showToast('已设为默认发现页')
+          this.isFindDefaultPromptShowing = false
+        }
+      }
+    })
+  }
+
   private isRemoteMusicAccount(account: WebDavAccount | null | undefined): boolean {
     if (!account) {
       return false
@@ -432,27 +493,29 @@ struct NewIndex {
     this.currentSongListName = ''
     this.modeType = 0
     this.tabSelectedIndexes = [0]
-
-    if(this.defalut_home_type==1){
-      this.mType = 0
-      this.modeType = 1
-    }else if(this.defalut_home_type==2){//如果是网盘的话
-      this.tabSelectedIndexes = [2]
-      if (configuredAccount) {
-        this.selectedAccount = configuredAccount
+    setTimeout(()=>{
+      if(this.defalut_home_type==1){
+        this.mType = 0
+        this.modeType = 1
+      }else if(this.defalut_home_type==2){//如果是网盘的话
+        this.tabSelectedIndexes = [2]
+        if (configuredAccount) {
+          this.selectedAccount = configuredAccount
+        }
+        this.mType = this.isRemoteMusicAccount(configuredAccount) ? 7 : 6
+      }else if(this.defalut_home_type==3){
+        this.mType = 0
+        this.modeType = 2
+      }else if(this.defalut_home_type==4){
+        this.mType = 0
+        this.modeType = 3
+      }else if(this.defalut_home_type==5){
+        this.enterFindPage()
+      } else {
+        this.mType = 0
       }
-      this.mType = this.isRemoteMusicAccount(configuredAccount) ? 7 : 6
-    }else if(this.defalut_home_type==3){
-      this.mType = 0
-      this.modeType = 2
-    }else if(this.defalut_home_type==4){
-      this.mType = 0
-      this.modeType = 3
-    }else if(this.defalut_home_type==5){
-      this.mType = 8
-    } else {
-      this.mType = 0
-    }
+    },200)
+
   }
 
   private returnToConfiguredHome(): void {
@@ -1200,7 +1263,7 @@ struct NewIndex {
               this.doShowDrawer()
               break
             case MainViewModel.MENU_FIND:
-              this.mType = 8
+              this.enterFindPage()
               this.modeType = 0
               this.currentSongListID = ''
               this.doShowDrawer()

+ 20 - 29
entry/src/main/ets/pages/ScanFilePage.ets

@@ -16,8 +16,8 @@ import { BreakpointTypeEnum } from '../common/util/BreakpointSystem'
 import { resourceManager } from '@kit.LocalizationKit'
 import { common, ConfigurationConstant } from '@kit.AbilityKit'
 import { DialogHelper } from '@pura/harmony-dialog'
-import { ButtonFancyModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil'
 import { getCueTrackSourceFilePath, isCueSplitItem } from '../common/util/CueUtils'
+import { TitleBarPointLightButton } from '../view/PointLight/TitleBarPointLightButton'
 
 
 
@@ -191,23 +191,18 @@ export struct ScanFilePage{
       Row({ space: 15 }) {
 
         //左侧滑动按钮
-        Button({ type: ButtonType.Circle, stateEffect: true }) {
-          SymbolGlyph($r('sys.symbol.sort'))
-            .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-        }
-        .attributeModifier(new ButtonFancyModifier(40, 40))
-        .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
-        .animation({ duration: 300, curve: Curve.Ease })
-        .onClick(() => {
-          this.getUIContext().animateTo({ duration: 555 }, () => {
-            // 动画闭包内控制Image组件的出现和消失
-            this.isShowDrawer = !this.isShowDrawer
-            this.offsetX = 0
-          })
-
+        TitleBarPointLightButton({
+          iconResource: $r('sys.symbol.sort'),
+          iconSize: 25,
+          pointColor: this.themeColor,
+          clickScale: 0.6,
+          clickHandler: () => {
+            this.getUIContext().animateTo({ duration: 555 }, () => {
+              this.isShowDrawer = !this.isShowDrawer
+              this.offsetX = 0
+            })
+          }
         })
-        .attributeModifier(new ShadowModifier())
-        .zIndex(0)
 
         Text($r('app.string.file_scan'))
           .margin({left:3,right:10})
@@ -218,18 +213,14 @@ export struct ScanFilePage{
           .layoutWeight(1)
           .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
 
-        Button({ type: ButtonType.Circle, stateEffect: true }) {
-          SymbolGlyph($r('sys.symbol.exclamationmark_circle'))
-            .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-        }
-        .attributeModifier(new ButtonFancyModifier(40, 40))
-        .animation({ duration: 300, curve: Curve.Ease })
-        .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
-        .attributeModifier(new ShadowModifier())
-        .zIndex(0)
-        .onClick(() => {
-          // 显示校正数据对话框
-          this.showCorrectDataDialog(true)
+        TitleBarPointLightButton({
+          iconResource: $r('sys.symbol.exclamationmark_circle'),
+          iconSize: 25,
+          pointColor: this.themeColor,
+          clickScale: 0.6,
+          clickHandler: () => {
+            this.showCorrectDataDialog(true)
+          }
         })
       }
     }

+ 12 - 17
entry/src/main/ets/pages/UserCenter.ets

@@ -23,7 +23,7 @@ import UserUtil, { VipFeature, VipPlanApi, VipPlanListApiResponse, WeChatPrepayI
 import { Utility } from '../common/util/Utility';
 import { pinyin4js } from '@ohos/pinyin4js';
 import { CustomContentDialog } from '@kit.ArkUI';
-import { ButtonFancyModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil';
+import { TitleBarPointLightButton } from '../view/PointLight/TitleBarPointLightButton';
 
 
 // 微信支付相关工具方法
@@ -459,23 +459,18 @@ export struct UserCenter {
       Row({ space: 15 }) {
 
         //左侧滑动按钮
-        Button({ type: ButtonType.Circle, stateEffect: true }) {
-          SymbolGlyph($r('sys.symbol.sort'))
-            .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-        }
-        .attributeModifier(new ButtonFancyModifier(40, 40))
-        .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
-        .animation({ duration: 300, curve: Curve.Ease })
-        .onClick(() => {
-          this.getUIContext().animateTo({ duration: 555 }, () => {
-            // 动画闭包内控制Image组件的出现和消失
-            this.isShowDrawer = !this.isShowDrawer
-            this.offsetX = 0
-          })
-
+        TitleBarPointLightButton({
+          iconResource: $r('sys.symbol.sort'),
+          iconSize: 25,
+          pointColor: this.themeColor,
+          clickScale: 0.6,
+          clickHandler: () => {
+            this.getUIContext().animateTo({ duration: 555 }, () => {
+              this.isShowDrawer = !this.isShowDrawer
+              this.offsetX = 0
+            })
+          }
         })
-        .attributeModifier(new ShadowModifier())
-        .zIndex(0)
 
         Text("用户中心")
           .margin({left:3,right:10})

+ 50 - 69
entry/src/main/ets/pages/WebDavMainPage.ets

@@ -13,9 +13,7 @@ import { hdsEffect } from '@kit.UIDesignKit';
 import { EventConstants } from '../common/constants/EventConstants';
 import { LazyDataSource } from '../common/util/LazyDataSource';
 import { ArrayUtil, FileUtil, MD5, PreferencesUtil, StrUtil, ToastUtil } from '@pura/harmony-utils';
-import { ButtonFancyModifier,
-  MenuModifier,
-  ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil';
+import { MenuModifier } from '../common/util/AttributeModifierUtil';
 import { getRemoteDriveAccountLabel, getRemoteDriveDisplayLabel, getRemoteDrivePlaylistPrefix } from '../common/util/RemoteDriveLabel';
 import { RemoteDriveType } from '../common/enums/RemoteDriveType';
 import { Utility } from '../common/util/Utility';
@@ -33,6 +31,7 @@ import { Playlist } from '../viewmodel/Playlist';
 import PlaylistTable from '../common/util/PlaylistTable';
 import MediaTable from '../common/util/MediaTable';
 import { DownloadCenterManager, DownloadCenterTask } from '../common/util/DownloadCenterManager';
+import { TitleBarPointLightButton } from '../view/PointLight/TitleBarPointLightButton';
 import { DownloadCenter } from '../view/DownloadCenter';
 import { WebDavUrlUtil } from '../common/util/WebDavUrlUtil';
 import { SearchHistoryUtil } from '../common/util/SearchHistoryUtil';
@@ -2891,48 +2890,39 @@ export struct WebDavMainPage {
       Row({ space: 15 }) {
         if (!this.isSearchMode &&!(this.webdavManager.canGoBack())) {
           //左侧滑动按钮
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.sort'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
-          .animation({ duration: 300, curve: Curve.Ease })
-          .onClick(() => {
-            this.getUIContext().animateTo({ duration: 555 }, () => {
-              // 动画闭包内控制Image组件的出现和消失
-              this.isShowDrawer = !this.isShowDrawer
-              this.offsetX = 0
-            })
-
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.sort'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6,
+            clickHandler: () => {
+              this.getUIContext().animateTo({ duration: 555 }, () => {
+                this.isShowDrawer = !this.isShowDrawer
+                this.offsetX = 0
+              })
+            }
           })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
 
         }else{
 
           //左侧搜索返回按钮
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.chevron_left'))
-              .attributeModifier(new SymbolGlyphFancyModifier(24, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
-          .animation({ duration: 300, curve: Curve.Ease })
-          .onClick(() => {
-            if(this.isSearchMode){
-              this.isSearchMode = false
-              this.searchController.stopEditing()
-              this.onSearchInput('')
-            }else{
-              if(this.webdavManager.canGoBack()){
-                this.goBack()
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.chevron_left'),
+            iconSize: 24,
+            pointColor: this.themeColor,
+            clickScale: 0.6,
+            clickHandler: () => {
+              if(this.isSearchMode){
+                this.isSearchMode = false
+                this.searchController.stopEditing()
+                this.onSearchInput('')
+              }else{
+                if(this.webdavManager.canGoBack()){
+                  this.goBack()
+                }
               }
             }
-
           })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
 
         }
 
@@ -2983,43 +2973,34 @@ export struct WebDavMainPage {
 
         //搜索按钮
         if (!this.isSearchMode) {
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.magnifyingglass'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .animation({ duration: 300, curve: Curve.Ease })
-          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
-          .onClick(()=>{
-            this.isSearchMode = true
-            this.loadSearchHistory()
-            this.restoreCurrentDirectorySearchView()
-            void this.webdavManager.ensureGlobalSearchIndex(this.selectedAccount)
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.magnifyingglass'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6,
+            clickHandler: () => {
+              this.isSearchMode = true
+              this.loadSearchHistory()
+              this.restoreCurrentDirectorySearchView()
+              void this.webdavManager.ensureGlobalSearchIndex(this.selectedAccount)
+            }
           })
           //排序按钮
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.list_number'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .animation({ duration: 300, curve: Curve.Ease })
-          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.list_number'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6
+          })
           .bindMenu(this.SortMenuBuilder)
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
           //添加/上传综合按钮
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.plus'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .animation({ duration: 300, curve: Curve.Ease })
-          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.plus'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6
+          })
           .bindMenu(this.MoreMenuBuilder)
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
           .bindContentCover($$this.isShowUploadFile, this.UploadFielBuilder(), {
             transition: TransitionEffect.translate({ x: 500 }).animation({ curve: curves.springMotion(0.6, 0.8) })
           })

+ 72 - 0
entry/src/main/ets/view/FindAlbumDetail.ets

@@ -44,10 +44,13 @@ export struct FindAlbumDetail {
   @State private sortType: number = 0
   @StorageLink('currentSong') currentSong: VideoItem | undefined = undefined
   @StorageLink('isPlaying') isPlaying: boolean = false
+  @Prop allowDelete: boolean = false
   onPlayAll: (songs?: VideoItem[]) => void = (_songs?: VideoItem[]) => {}
   onRandomPlay: (songs?: VideoItem[]) => void = (_songs?: VideoItem[]) => {}
   onBack: () => void = () => {}
   onSongTap: (index: number, songs?: VideoItem[]) => void = (_index: number, _songs?: VideoItem[]) => {}
+  onFavoriteSong: (song: VideoItem) => void = (_song: VideoItem) => {}
+  onDeleteSong: (song: VideoItem) => void = (_song: VideoItem) => {}
 
   aboutToAppear(): void {
     this.applySortType(this.sortType)
@@ -214,6 +217,43 @@ export struct FindAlbumDetail {
     return $r('app.color.album_detail_song_quality_background')
   }
 
+  private canFavoriteSong(item: VideoItem): boolean {
+    return StrUtil.isNotEmpty(item.filePath)
+  }
+
+  private canDeleteSong(item: VideoItem): boolean {
+    return this.allowDelete && item.type === CommonConstants.TYPE_LOCAL && StrUtil.isNotEmpty(item.filePath)
+  }
+
+  private isSongFavorite(item: VideoItem): boolean {
+    return item.isFav === 1
+  }
+
+  @Builder
+  private SongContextMenuBuilder(item: VideoItem) {
+    Menu() {
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier(this.isSongFavorite(item) ?
+          $r('sys.symbol.heart_fill') : $r('sys.symbol.heart')),
+        content: this.isSongFavorite(item) ? '取消收藏' : '收藏'
+      })
+        .visibility(this.canFavoriteSong(item) ? Visibility.Visible : Visibility.None)
+        .onClick(() => {
+          this.onFavoriteSong(item)
+        })
+
+      MenuItem({
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.trash')),
+        content: '删除'
+      })
+        .visibility(this.canDeleteSong(item) ? Visibility.Visible : Visibility.None)
+        .onClick(() => {
+          this.onDeleteSong(item)
+        })
+    }
+    .attributeModifier(new MenuModifier())
+  }
+
   private getHeroTagBackgroundColor(): ResourceColor {
     return $r('app.color.album_detail_hero_tag_background')
   }
@@ -545,6 +585,24 @@ export struct FindAlbumDetail {
     .justifyContent(FlexAlign.Center)
   }
 
+  @Builder
+  private buildSwipeDeleteAction(item: VideoItem) {
+    Row() {
+      Button('删除')
+        .width(72)
+        .height(52)
+        .fontSize(14)
+        .fontColor(Color.White)
+        .backgroundColor($r('app.color.btn_red'))
+        .borderRadius(14)
+        .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
+        .onClick(() => {
+          this.onDeleteSong(item)
+        })
+    }
+    .padding({ left: 8, right: 4 })
+  }
+
 
   build() {
     Stack() {
@@ -636,6 +694,20 @@ export struct FindAlbumDetail {
               this.onSongTap(index, this.getActiveSongs())
             })
           }
+          .swipeAction(this.canDeleteSong(item)
+            ? {
+                end: this.buildSwipeDeleteAction(item),
+                edgeEffect: SwipeEdgeEffect.None
+              }
+            : {})
+          .bindContextMenu(this.SongContextMenuBuilder(item), ResponseType.LongPress,
+            {
+              preview: MenuPreviewMode.IMAGE
+            })
+          .bindContextMenu(this.SongContextMenuBuilder(item), ResponseType.RightClick,
+            {
+              preview: MenuPreviewMode.IMAGE
+            })
           .padding({ left: 16, right: 16 })
         }, getFindAlbumSongKey)
       }

+ 293 - 100
entry/src/main/ets/view/FindView.ets

@@ -4,7 +4,7 @@ import { common } from '@kit.AbilityKit'
 import { CommonConstants } from '../common/constants/CommonConstants'
 import { EventConstants } from '../common/constants/EventConstants'
 import MediaTable from '../common/util/MediaTable'
-import { ButtonFancyModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil'
+import { SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil'
 import { BreakpointType, BreakpointTypeEnum } from '../common/util/BreakpointSystem'
 import Logger from '../common/util/Logger'
 import { RemoteDriveManager } from '../common/util/RemoteDriveManager'
@@ -16,6 +16,8 @@ import { Playlist } from '../viewmodel/Playlist'
 import { ConfigTitle } from './ConfigTitle'
 import { PointLightActionButton } from './PointLight/PointLightActionButton'
 import { PointLightContentButton } from './PointLight/PointLightContentButton'
+import { TitleBarPointLightButton } from './PointLight/TitleBarPointLightButton'
+import { DeleteComptent } from './DeleteComptent'
 import { SettingPage } from '../pages/SettingPage'
 import { FindAlbumDetail } from './FindAlbumDetail'
 import { PlayingIndicator } from './PlayingIndicator'
@@ -157,6 +159,7 @@ export struct FindView {
   @State private favoriteSectionPageIndex: number = 0
   @State private refreshPullRatio: number = 1
   @State private maxRefreshingHeight: number = 100
+  @State private pendingDeleteSongs: VideoItem[] = []
 
   searchController: SearchController = new SearchController()
   @StorageProp('currentBreakpoint') @Watch('onDiscoverBreakpointChange') currentBreakpoint: string = BreakpointTypeEnum.MD
@@ -189,6 +192,7 @@ export struct FindView {
   private isRemoteBootstrapLoading: boolean = false
   private playlistSongsCache: Map<string, VideoItem[]> = new Map<string, VideoItem[]>()
   private heartPlaylistCoverTicket: number = 0
+  private deleteComponentId: number = 0
 
   aboutToAppear(): void {
     this.initSetting()
@@ -254,7 +258,7 @@ export struct FindView {
 
     if (triggeredByRefresh) {
       this.isRefreshing = true
-      this.refreshText = '正在刷新推荐...'
+      this.refreshText = '正在刷新'
     } else if (this.isPageLoading) {
       this.refreshText = '加载中...'
     }
@@ -1192,6 +1196,179 @@ export struct FindView {
     this.currentAlbumSongs = []
   }
 
+  private canDeleteCurrentAlbumSongs(): boolean {
+    if (this.currentAlbumSongs.length <= 0) {
+      return false
+    }
+    return this.currentAlbumSongs.every((item: VideoItem) =>
+      item.type === CommonConstants.TYPE_LOCAL && StrUtil.isNotEmpty(item.filePath))
+  }
+
+  private canFavoriteCurrentAlbumSong(song: VideoItem): boolean {
+    return StrUtil.isNotEmpty(song.filePath)
+  }
+
+  private updateAlbumSongFavoriteState(filePath: string, isFav: number): void {
+    this.currentAlbumSongs.forEach((item: VideoItem) => {
+      if (item.filePath === filePath) {
+        item.isFav = isFav
+      }
+    })
+  }
+
+  private async handleAlbumFavoriteSong(song: VideoItem): Promise<void> {
+    if (!this.mediaTable) {
+      ToastUtil.showToast('收藏功能暂不可用')
+      return
+    }
+    if (!this.canFavoriteCurrentAlbumSong(song)) {
+      ToastUtil.showToast('当前歌曲暂不支持收藏')
+      return
+    }
+    const nextFav: number = song.isFav === 1 ? 0 : 1
+    this.mediaTable.updateIsFavByFilePath(song.filePath, nextFav, async (result: boolean, error?: string) => {
+      if (!result) {
+        Logger.warn(TAG, `发现页专辑详情收藏更新失败: ${song.filePath}, error=${error ?? ''}`)
+        ToastUtil.showToast(nextFav === 1 ? '收藏失败' : '取消收藏失败')
+        return
+      }
+      song.isFav = nextFav
+      this.updateAlbumSongFavoriteState(song.filePath, nextFav)
+      await this.loadDiscoveryContent(false)
+      this.syncCurrentAlbumDetailFromState()
+      ToastUtil.showToast(nextFav === 1 ? '收藏成功' : '取消收藏成功')
+    })
+  }
+
+  private closeDeleteDialog(afterClose?: () => void): void {
+    if (this.deleteComponentId > 0) {
+      try {
+        this.getUIContext().getPromptAction().closeCustomDialog(this.deleteComponentId)
+      } catch (_error) {
+      }
+      this.deleteComponentId = 0
+    }
+    if (afterClose) {
+      setTimeout(() => {
+        afterClose()
+      }, 16)
+    }
+  }
+
+  private filterPlaylistCachesByDeletedKeys(deletedKeys: Set<string>): void {
+    this.playlistSongsCache.forEach((songs: VideoItem[], key: string) => {
+      this.playlistSongsCache.set(key, songs.filter((item: VideoItem) => !deletedKeys.has(item.filePath)))
+    })
+  }
+
+  private syncCurrentAlbumDetailFromState(): void {
+    if (StrUtil.isEmpty(this.currentAlbumId)) {
+      return
+    }
+    if (this.currentAlbumId === 'find-recent-collection') {
+      this.currentAlbumSongs = this.recentSongsPool.slice()
+      this.currentAlbumCoverPath = this.pickAlbumCoverSong(this.currentAlbumSongs)?.pixelMapPath ?? ''
+    } else if (this.currentAlbumId === 'find-favorite-collection') {
+      this.currentAlbumSongs = this.favoriteSongsPool.slice()
+      this.currentAlbumCoverPath = this.pickAlbumCoverSong(this.currentAlbumSongs)?.pixelMapPath ?? ''
+    } else if (this.currentAlbumId.startsWith('find-playlist-')) {
+      const playlistId = this.currentAlbumId.replace('find-playlist-', '')
+      const playlist = this.heartPlaylists.find((item: Playlist) => item.id === playlistId)
+      const songs = this.playlistSongsCache.get(playlistId) ?? []
+      this.currentAlbumSongs = songs.slice()
+      if (playlist) {
+        this.currentAlbumTitle = playlist.name
+        this.currentAlbumArtist = '心动歌单'
+        const coverSong = this.pickAlbumCoverSong(songs)
+        this.currentAlbumCoverPath = StrUtil.isNotEmpty(playlist.coverPath) ? playlist.coverPath as string :
+          (coverSong?.pixelMapPath ?? '')
+      } else {
+        this.currentAlbumCoverPath = ''
+      }
+    } else {
+      const album = this.featuredAlbumsPool.find((item: FindAlbumGroup) => item.id === this.currentAlbumId) ??
+        this.cloudAlbumsPool.find((item: FindAlbumGroup) => item.id === this.currentAlbumId)
+      if (album) {
+        this.currentAlbumTitle = album.title
+        this.currentAlbumArtist = album.artist
+        this.currentAlbumCoverPath = album.coverPath
+        this.currentAlbumSourceLabel = album.isRemote ? '云端专辑' : '精选专辑'
+        this.currentAlbumSongs = album.songs.slice()
+      } else {
+        this.currentAlbumCoverPath = ''
+        this.currentAlbumSongs = []
+      }
+    }
+  }
+
+  private async handleAlbumDeleteSuccess(deletedItems: VideoItem[]): Promise<void> {
+    if (deletedItems.length <= 0) {
+      return
+    }
+    const deletedKeys: Set<string> = new Set<string>()
+    deletedItems.forEach((item: VideoItem) => {
+      if (StrUtil.isNotEmpty(item.filePath)) {
+        deletedKeys.add(item.filePath)
+      }
+    })
+    if (deletedKeys.size <= 0) {
+      return
+    }
+    this.currentAlbumSongs = this.currentAlbumSongs.filter((item: VideoItem) => !deletedKeys.has(item.filePath))
+    this.filterPlaylistCachesByDeletedKeys(deletedKeys)
+    await this.loadDiscoveryContent(false)
+    this.syncCurrentAlbumDetailFromState()
+    ToastUtil.showToast('删除成功')
+  }
+
+  private openDeleteSongDialog(song: VideoItem): void {
+    if (song.type !== CommonConstants.TYPE_LOCAL || StrUtil.isEmpty(song.filePath)) {
+      ToastUtil.showToast('云端歌曲暂不支持删除')
+      return
+    }
+    this.pendingDeleteSongs = [song]
+    this.getUIContext().getPromptAction().openCustomDialog({
+      builder: () => {
+        this.buildDeleteSongDialog()
+      },
+      isModal: true,
+      showInSubWindow: false,
+      maskColor: Color.Transparent,
+      dialogTransition: TransitionEffect.translate({ x: 0, y: 120, z: 0 })
+        .combine(TransitionEffect.opacity(0.01))
+        .animation({ duration: 260, curve: Curve.EaseOut }),
+      maskTransition: TransitionEffect.opacity(0)
+        .animation({ duration: 220, curve: Curve.EaseOut })
+    }).then((dialogId: number) => {
+      this.deleteComponentId = dialogId
+    }).catch((error: BusinessError) => {
+      Logger.error(TAG, `发现页打开删除弹窗失败: ${error.message}`)
+    })
+  }
+
+  @Builder
+  private buildDeleteSongDialog() {
+    DeleteComptent({
+      selectedFiles: this.pendingDeleteSongs,
+      onCancel: () => {
+        this.closeDeleteDialog(() => {
+          this.pendingDeleteSongs = []
+        })
+      },
+      onDeleteResult: (result: boolean) => {
+        const deletedItems = this.pendingDeleteSongs.slice()
+        this.closeDeleteDialog(() => {
+          this.pendingDeleteSongs = []
+          if (result) {
+            void this.handleAlbumDeleteSuccess(deletedItems)
+          } else {
+            ToastUtil.showToast('删除失败')
+          }
+        })
+      }
+    })
+  }
+
   private playCurrentAlbum(startIndex: number, songs: VideoItem[] = this.currentAlbumSongs): void {
     if (songs.length === 0) {
       ToastUtil.showToast('专辑里还没有歌曲')
@@ -1534,9 +1711,9 @@ export struct FindView {
     const mimeType = (item.mimeType ?? '').toLowerCase()
     const fileName = (item.fileName ?? item.name ?? '').toLowerCase()
     const isLosslessFormat = mimeType.includes('flac') || mimeType.includes('wav') || mimeType.includes('ape') ||
-      mimeType.includes('alac') || mimeType.includes('dsf') || mimeType.includes('dff') ||
-      fileName.endsWith('.flac') || fileName.endsWith('.wav') || fileName.endsWith('.ape') ||
-      fileName.endsWith('.alac') || fileName.endsWith('.dsf') || fileName.endsWith('.dff')
+    mimeType.includes('alac') || mimeType.includes('dsf') || mimeType.includes('dff') ||
+    fileName.endsWith('.flac') || fileName.endsWith('.wav') || fileName.endsWith('.ape') ||
+    fileName.endsWith('.alac') || fileName.endsWith('.dsf') || fileName.endsWith('.dff')
     return isLosslessFormat ? '无损' : ''
   }
 
@@ -1639,21 +1816,39 @@ export struct FindView {
   @Builder
   private buildTopErrorBanner() {
     if (StrUtil.isNotEmpty(this.refreshText)) {
-      Row({ space: 6 }) {
-        SymbolGlyph($r('sys.symbol.exclamationmark_circle'))
-          .fontSize(15)
-          .fontColor([$r('app.color.orange')])
-        Text(this.refreshText)
-          .layoutWeight(1)
-          .fontSize(12)
-          .fontColor($r('app.color.orange'))
-          .maxLines(1)
-          .textOverflow({ overflow: TextOverflow.Ellipsis })
+      if (this.isRefreshing) {
+        Row({ space: 8 }) {
+          LoadingProgress()
+            .width(14)
+            .height(14)
+            .color(this.themeColor)
+          Text(this.refreshText)
+            .fontSize(12)
+            .fontColor(this.getSecondaryTextColor())
+            .textAlign(TextAlign.Center)
+            .maxLines(1)
+        }
+        .width('100%')
+        .padding({ left: 12, right: 12, top: 10, bottom: 10 })
+        .justifyContent(FlexAlign.Center)
+        .alignItems(VerticalAlign.Center)
+      } else {
+        Row({ space: 6 }) {
+          SymbolGlyph($r('sys.symbol.exclamationmark_circle'))
+            .fontSize(15)
+            .fontColor([$r('app.color.orange')])
+          Text(this.refreshText)
+            .layoutWeight(1)
+            .fontSize(12)
+            .fontColor($r('app.color.orange'))
+            .maxLines(1)
+            .textOverflow({ overflow: TextOverflow.Ellipsis })
+        }
+        .width('100%')
+        .padding({ left: 12, right: 12, top: 10, bottom: 10 })
+        .backgroundColor($r('app.color.timeColor_bg'))
+        .borderRadius(16)
       }
-      .width('100%')
-      .padding({ left: 12, right: 12, top: 10, bottom: 10 })
-      .backgroundColor($r('app.color.timeColor_bg'))
-      .borderRadius(16)
     }
   }
 
@@ -1662,21 +1857,18 @@ export struct FindView {
     Column() {
       Row({ space: 12 }) {
         if (!this.isSearchMode && !this.isAlbumMode) {
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.sort'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
-          .animation({ duration: 300, curve: Curve.Ease })
-          .onClick(() => {
-            this.getUIContext().animateTo({ duration: 500 }, () => {
-              this.isShowDrawer = !this.isShowDrawer
-              this.offsetX = 0
-            })
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.sort'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.8,
+            clickHandler: () => {
+              this.getUIContext().animateTo({ duration: 500 }, () => {
+                this.isShowDrawer = !this.isShowDrawer
+                this.offsetX = 0
+              })
+            }
           })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
 
           Text('发现页')
             .margin({ left: 3, right: 10 })
@@ -1687,22 +1879,19 @@ export struct FindView {
             .textOverflow({ overflow: TextOverflow.MARQUEE })
             .layoutWeight(1)
         } else {
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.chevron_left'))
-              .attributeModifier(new SymbolGlyphFancyModifier(24, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
-          .animation({ duration: 300, curve: Curve.Ease })
-          .onClick(() => {
-            if (this.isSearchMode) {
-              this.exitSearchMode()
-              return
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.chevron_left'),
+            iconSize: 24,
+            pointColor: this.themeColor,
+            clickScale: 0.8,
+            clickHandler: () => {
+              if (this.isSearchMode) {
+                this.exitSearchMode()
+                return
+              }
+              this.exitAlbumMode()
             }
-            this.exitAlbumMode()
           })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
 
           if (this.isAlbumMode) {
             Column({ space: 2 }) {
@@ -1758,22 +1947,19 @@ export struct FindView {
         }
 
         if (!this.isSearchMode && !this.isAlbumMode) {
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.magnifyingglass'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .animation({ duration: 300, curve: Curve.Ease })
-          .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
-          .onClick(() => {
-            this.isAlbumMode = false
-            this.getUIContext()?.animateTo({ duration: 500 }, () => {
-              this.isSearchMode = true
-            })
-            this.loadSearchHistory()
-            void this.ensureSearchSourceReady()
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.magnifyingglass'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6,
+            clickHandler: () => {
+              this.isAlbumMode = false
+              this.getUIContext()?.animateTo({ duration: 500 }, () => {
+                this.isSearchMode = true
+              })
+              this.loadSearchHistory()
+              void this.ensureSearchSourceReady()
+            }
           })
         }
       }
@@ -2034,10 +2220,10 @@ export struct FindView {
               this.buildSwiperCardContent(item)
             }
           })
-          .width('100%')
-          .onClick(() => {
-            this.handleSwiperTap(index)
-          })
+            .width('100%')
+            .onClick(() => {
+              this.handleSwiperTap(index)
+            })
         }, getFindSongKey)
       }
       .width('100%')
@@ -2191,10 +2377,10 @@ export struct FindView {
                     this.buildCloudSongCardContent(item)
                   }
                 })
-                .width('100%')
-                .onClick(() => {
-                  this.handleRemoteSongTap(pageIndex * this.getSectionPageSize(this.getDiscoverSectionColumns()) + itemIndex)
-                })
+                  .width('100%')
+                  .onClick(() => {
+                    this.handleRemoteSongTap(pageIndex * this.getSectionPageSize(this.getDiscoverSectionColumns()) + itemIndex)
+                  })
               }
             }, getFindSongKey)
           }
@@ -2290,10 +2476,10 @@ export struct FindView {
                     this.buildAlbumCardContent(album)
                   }
                 })
-                .width('100%')
-                .onClick(() => {
-                  this.openAlbumDetail(album)
-                })
+                  .width('100%')
+                  .onClick(() => {
+                    this.openAlbumDetail(album)
+                  })
               }
             }, getFindAlbumKey)
           }
@@ -2336,10 +2522,10 @@ export struct FindView {
                     this.buildAlbumCardContent(album)
                   }
                 })
-                .width('100%')
-                .onClick(() => {
-                  this.openAlbumDetail(album)
-                })
+                  .width('100%')
+                  .onClick(() => {
+                    this.openAlbumDetail(album)
+                  })
               }
             }, getFindAlbumKey)
           }
@@ -2420,9 +2606,9 @@ export struct FindView {
                 this.buildHotSongCardContent(item)
               }
             })
-            .onClick(() => {
-              this.handleHotSongTap(index)
-            })
+              .onClick(() => {
+                this.handleHotSongTap(index)
+              })
           }
           .margin({
             left: index === 0 ? 2 : 0,
@@ -2502,9 +2688,9 @@ export struct FindView {
               this.buildHeartPlaylistCardContent(playlist)
             }
           })
-          .onClick(() => {
-            void this.openPlaylistDetail(playlist)
-          })
+            .onClick(() => {
+              void this.openPlaylistDetail(playlist)
+            })
         }
       }, getFindPlaylistKey)
     }
@@ -2534,9 +2720,9 @@ export struct FindView {
                 this.buildHotSongCardContent(item)
               }
             })
-            .onClick(() => {
-              this.handleCloudMoodSongTap(index)
-            })
+              .onClick(() => {
+                this.handleCloudMoodSongTap(index)
+              })
           }
           .margin({
             left: index === 0 ? 2 : 0,
@@ -2618,10 +2804,10 @@ export struct FindView {
                     this.buildRecentSongCardContent(item)
                   }
                 })
-                .width('100%')
-                .onClick(() => {
-                  this.handleRecentSongTap(pageIndex * this.getSectionPageSize(this.getDiscoverSectionColumns()) + itemIndex)
-                })
+                  .width('100%')
+                  .onClick(() => {
+                    this.handleRecentSongTap(pageIndex * this.getSectionPageSize(this.getDiscoverSectionColumns()) + itemIndex)
+                  })
               }
             }, getFindSongKey)
           }
@@ -2707,10 +2893,10 @@ export struct FindView {
                     this.buildPopularSongCardContent(item)
                   }
                 })
-                .width('100%')
-                .onClick(() => {
-                  this.handlePopularSongTap(pageIndex * this.getSectionPageSize(this.getDiscoverSectionColumns()) + itemIndex)
-                })
+                  .width('100%')
+                  .onClick(() => {
+                    this.handlePopularSongTap(pageIndex * this.getSectionPageSize(this.getDiscoverSectionColumns()) + itemIndex)
+                  })
               }
             }, getFindSongKey)
           }
@@ -2795,10 +2981,10 @@ export struct FindView {
                     this.buildFavoriteSongCardContent(item)
                   }
                 })
-                .width('100%')
-                .onClick(() => {
-                  this.handleFavoriteSongTap(pageIndex * this.getSectionPageSize(this.getDiscoverSectionColumns()) + itemIndex)
-                })
+                  .width('100%')
+                  .onClick(() => {
+                    this.handleFavoriteSongTap(pageIndex * this.getSectionPageSize(this.getDiscoverSectionColumns()) + itemIndex)
+                  })
               }
             }, getFindSongKey)
           }
@@ -2927,6 +3113,7 @@ export struct FindView {
           albumCoverPath: this.currentAlbumCoverPath,
           albumSourceLabel: this.currentAlbumSourceLabel,
           songs: this.currentAlbumSongs,
+          allowDelete: this.canDeleteCurrentAlbumSongs(),
           onBack: () => {
             this.exitAlbumMode()
           },
@@ -2941,6 +3128,12 @@ export struct FindView {
           },
           onSongTap: (index: number, songs?: VideoItem[]) => {
             this.handleAlbumSongTap(index, songs)
+          },
+          onFavoriteSong: (song: VideoItem) => {
+            void this.handleAlbumFavoriteSong(song)
+          },
+          onDeleteSong: (song: VideoItem) => {
+            this.openDeleteSongDialog(song)
           }
         })
       } else {

+ 48 - 64
entry/src/main/ets/view/LocalMusic.ets

@@ -27,10 +27,8 @@ import {
   ImplOnVideoSizeChangedListener
 } from '../common/IjkPlayerListenerImpls';
 import {
-  ButtonFancyModifier,
   ImageFancyModifier,
-  ShadowModifier,
-  SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil'
+} from '../common/util/AttributeModifierUtil'
 import {
   AppUtil,
   ArrayUtil,
@@ -68,6 +66,7 @@ import { RotatingCover } from './RotatingCover';
 import { SpectrumView } from './spectrum/SpectrumView';
 import { PlayingIndicator } from './PlayingIndicator';
 import { PointLightButton } from './PointLight/PointLightButton';
+import { TitleBarPointLightButton } from './PointLight/TitleBarPointLightButton'
 import { PlayConstants } from '../common/constants/PlayConstants';
 import { effectKit } from '@kit.ArkGraphics2D';
 import { ColorConversion } from '../common/util/ColorConversion';
@@ -4398,40 +4397,34 @@ export struct LocalMusic {
       Row({ space: 15 }) {
         if (!this.isSearchMode ) {
           //左侧滑动按钮
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph(this.rightTopImage)
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
-          .animation({ duration: 300, curve: Curve.Ease })
-          .onClick(() => {
-            this.triggerSwipeBackWithAnimation()
+          TitleBarPointLightButton({
+            iconResource: this.rightTopImage,
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6,
+            clickHandler: () => {
+              this.triggerSwipeBackWithAnimation()
+            }
           })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
         }else{
 
           //左侧搜索返回按钮
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.chevron_left'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .margin({bottom:5})
-          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
-          .animation({ duration: 300, curve: Curve.Ease })
-          .onClick(() => {
-            this.isSearchMode = false
-            if (this.modeType==0) {//如果是首页 直接getSortedFiles,如果不是就走this.onSearchInput('')
-              this.searchText = ''
-              this.getSortedFiles(this.currentPath)
-              return
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.chevron_left'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6,
+            clickHandler: () => {
+              this.isSearchMode = false
+              if (this.modeType==0) {//如果是首页 直接getSortedFiles,如果不是就走this.onSearchInput('')
+                this.searchText = ''
+                this.getSortedFiles(this.currentPath)
+                return
+              }
+              this.onSearchInput('')
             }
-            this.onSearchInput('')
           })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
+          .margin({bottom:5})
 
         }
         if (!this.isSearchMode) {
@@ -4485,40 +4478,36 @@ export struct LocalMusic {
 
         //搜索按钮
         if (!this.isSearchMode) {
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.magnifyingglass'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .animation({ duration: 300, curve: Curve.Ease })
-          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
-          .onClick(()=>{
-            this.isSearchMode = true
-            this.loadSearchHistory()
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.magnifyingglass'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6,
+            clickHandler: () => {
+              this.isSearchMode = true
+              this.loadSearchHistory()
+            }
           })
           //排序按钮
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.list_number'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .animation({ duration: 300, curve: Curve.Ease })
-          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.list_number'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6
+          })
           .bindMenu((this.modeType==2&&!this.isCanBack)||(this.modeType==3&&!this.isCanBack)
             ?this.SortMenuForArtistAblumBuilder:this.SortMenuBuilder)
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
           //添加按钮
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.plus'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .animation({ duration: 300, curve: Curve.Ease })
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.plus'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6,
+            clickHandler: () => {
+              this.showAddSheet = !this.showAddSheet;
+            }
+          })
           .visibility(this.modeType==0||this.modeType==1?Visibility.Visible:Visibility.None)
-          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
           .bindSheet($$this.showAddSheet, this.addSheet(), {
             height: SheetSize.FIT_CONTENT,
             dragBar: true,
@@ -4528,11 +4517,6 @@ export struct LocalMusic {
               title: '导入音乐'
             }
           })
-          .onClick(() => {
-            this.showAddSheet = !this.showAddSheet;
-          })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
 
 
         }

+ 64 - 0
entry/src/main/ets/view/PointLight/TitleBarPointLightButton.ets

@@ -0,0 +1,64 @@
+import { deviceInfo } from '@kit.BasicServicesKit'
+import { hdsEffect } from '@kit.UIDesignKit'
+import { ButtonFancyModifier } from '../../common/util/AttributeModifierUtil'
+
+@Component
+export struct TitleBarPointLightButton {
+  @Require @Prop iconResource: Resource
+  @Prop iconSize: number = 25
+  @Prop buttonSize: number = 40
+  @Prop pointColor: ResourceColor = Color.White
+  @Prop iconColor: ResourceColor = $r('app.color.text_color')
+  @Prop pointLightHeight: number = 100
+  @Prop clickScale: number = 0.8
+  @Prop useShadow: boolean = true
+
+  clickHandler: () => void = () => {}
+
+  @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() {
+    Button({ type: ButtonType.Circle, stateEffect: true }) {
+      SymbolGlyph(this.iconResource)
+        .fontSize(this.iconSize)
+        .fontColor([this.iconColor])
+    }
+    .attributeModifier(new ButtonFancyModifier(this.buttonSize, this.buttonSize))
+    .backdropBlur(150)
+    .opacity(0.9)
+    .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: this.clickScale })
+    .animation({ duration: 300, curve: Curve.Ease })
+    .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 ? { radius: 26, color: $r('app.color.shadow_color') } : undefined)
+    .visualEffect(this.enablePointLight && this.sdkApiVersion >= 20
+      ? new hdsEffect.HdsEffectBuilder()
+        .pointLight({
+          options: this.pointLightOptions,
+          illuminatedType: hdsEffect.PointLightIlluminatedType.BORDER_CONTENT
+        })
+        .buildEffect()
+      : undefined)
+    .zIndex(0)
+    .onClick(() => {
+      this.clickHandler()
+    })
+  }
+}

+ 48 - 67
entry/src/main/ets/view/RemoteMusicPage.ets

@@ -5,10 +5,7 @@ import {
   PreferencesUtil, ToastUtil, StrUtil
 } from '@pura/harmony-utils';
 import {
-  ButtonFancyModifier,
   MenuModifier,
-  SymbolGlyphFancyModifier,
-  ShadowModifier
 } from '../common/util/AttributeModifierUtil';
 import { CommonConstants } from '../common/constants/CommonConstants';
 import { WebDavAccount } from '../viewmodel/WebDavAccount';
@@ -26,6 +23,7 @@ import { navidromeApi, NavidromeSong } from '../common/network/NavidromeApi';
 import { ServerLogUtil } from '../common/util/ServerLogUtil';
 import { RemoteDriveManager } from '../common/util/RemoteDriveManager';
 import { SettingPage } from '../pages/SettingPage';
+import { TitleBarPointLightButton } from './PointLight/TitleBarPointLightButton';
 import { jellyfinApi, JellyfinAlbum, JellyfinArtist, JellyfinSong } from '../common/network/JellyfinApi';
 import { embyApi, EmbyAlbum, EmbyArtist, EmbySong } from '../common/network/EmbyApi';
 import { audioStationApi, AudioStationAlbum, AudioStationArtist, AudioStationPlaylist, AudioStationSong } from '../common/network/AudioStationApi';
@@ -2598,20 +2596,16 @@ export struct RemoteMusicPage {
         if (!this.isSearchMode) {
           // 详情视图模式下显示返回按钮
           if (this.isDetailView) {
-            Button({ type: ButtonType.Circle, stateEffect: true }) {
-              SymbolGlyph($r('sys.symbol.chevron_left'))
-                .attributeModifier(new SymbolGlyphFancyModifier(24, '', ''))
-            }
-            .attributeModifier(new ButtonFancyModifier(40, 40))
-            .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
-            .animation({ duration: 300, curve: Curve.Ease })
-            .onClick(() => {
-              // 返回到之前的标签页
-              this.isDetailView = false;
-              this.clearFilter();
+            TitleBarPointLightButton({
+              iconResource: $r('sys.symbol.chevron_left'),
+              iconSize: 24,
+              pointColor: this.themeColor,
+              clickScale: 0.6,
+              clickHandler: () => {
+                this.isDetailView = false;
+                this.clearFilter();
+              }
             })
-            .attributeModifier(new ShadowModifier())
-            .zIndex(0)
 
             Text(this.filterLabel)
               .margin({ left: 3, right: 10 })
@@ -2624,22 +2618,18 @@ export struct RemoteMusicPage {
               .animation({ duration: 300, curve: Curve.Ease })
           } else {
             // 正常模式:侧边栏按钮和账号名
-            Button({ type: ButtonType.Circle, stateEffect: true }) {
-              SymbolGlyph($r('sys.symbol.sort'))
-                .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-            }
-            .attributeModifier(new ButtonFancyModifier(40, 40))
-            .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
-            .animation({ duration: 300, curve: Curve.Ease })
-            .onClick(() => {
-              this.getUIContext().animateTo({ duration: 555 }, () => {
-                // 动画闭包内控制Image组件的出现和消失
-                this.isShowDrawer = !this.isShowDrawer
-                this.offsetX = 0
-              })
+            TitleBarPointLightButton({
+              iconResource: $r('sys.symbol.sort'),
+              iconSize: 25,
+              pointColor: this.themeColor,
+              clickScale: 0.6,
+              clickHandler: () => {
+                this.getUIContext().animateTo({ duration: 555 }, () => {
+                  this.isShowDrawer = !this.isShowDrawer
+                  this.offsetX = 0
+                })
+              }
             })
-            .attributeModifier(new ShadowModifier())
-            .zIndex(0)
             Text(this.selectedAccount.name)
               .margin({ left: 3, right: 10 })
               .fontColor($r('app.color.text_color'))
@@ -2654,19 +2644,16 @@ export struct RemoteMusicPage {
               .animation({ duration: 300, curve: Curve.Ease })
           }
         } else {
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.chevron_left'))
-              .attributeModifier(new SymbolGlyphFancyModifier(24, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
-          .animation({ duration: 300, curve: Curve.Ease })
-          .onClick(() => {
-            this.isSearchMode = false;
-            void this.onSearchInput('');
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.chevron_left'),
+            iconSize: 24,
+            pointColor: this.themeColor,
+            clickScale: 0.6,
+            clickHandler: () => {
+              this.isSearchMode = false;
+              void this.onSearchInput('');
+            }
           })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
 
         }
 
@@ -2702,35 +2689,29 @@ export struct RemoteMusicPage {
         // 搜索/排序按钮
         if (!this.isSearchMode) {
           // 搜索按钮
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.magnifyingglass'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .animation({ duration: 300, curve: Curve.Ease })
-          .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
-          .visibility(this.selectedTab==0?Visibility.Visible:Visibility.None)
-          .onClick(() => {
-            this.isSearchMode = true;
-            this.loadSearchHistory();
-            if (!this.isSearchLoading && this.searchText.length === 0) {
-              this.filteredList = [];
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.magnifyingglass'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6,
+            clickHandler: () => {
+              this.isSearchMode = true;
+              this.loadSearchHistory();
+              if (!this.isSearchLoading && this.searchText.length === 0) {
+                this.filteredList = [];
+              }
             }
           })
+          .visibility(this.selectedTab==0?Visibility.Visible:Visibility.None)
 
           // 排序按钮
-          Button({ type: ButtonType.Circle, stateEffect: true }) {
-            SymbolGlyph($r('sys.symbol.list_number'))
-              .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
-          }
-          .attributeModifier(new ButtonFancyModifier(40, 40))
-          .animation({ duration: 300, curve: Curve.Ease })
-          .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
+          TitleBarPointLightButton({
+            iconResource: $r('sys.symbol.list_number'),
+            iconSize: 25,
+            pointColor: this.themeColor,
+            clickScale: 0.6
+          })
           .bindMenu(this.SortMenuBuilder)
-          .attributeModifier(new ShadowModifier())
-          .zIndex(0)
         }
       }
       .width('100%')