|
@@ -6,12 +6,13 @@ import { emitter } from '@kit.BasicServicesKit';
|
|
|
import { ToastUtil, AppUtil, LogUtil, PreferencesUtil, StrUtil } from '@pura/harmony-utils';
|
|
import { ToastUtil, AppUtil, LogUtil, PreferencesUtil, StrUtil } from '@pura/harmony-utils';
|
|
|
import { showEditPlaylistDialog } from '../dialog/PlaylistDialog';
|
|
import { showEditPlaylistDialog } from '../dialog/PlaylistDialog';
|
|
|
import { showAddSongsToPlaylistDialog } from '../dialog/AddSongsToPlaylistDialog';
|
|
import { showAddSongsToPlaylistDialog } from '../dialog/AddSongsToPlaylistDialog';
|
|
|
-import { router } from '@kit.ArkUI';
|
|
|
|
|
|
|
+import { curves, router, SymbolGlyphModifier } from '@kit.ArkUI';
|
|
|
import { GlobalContext } from '../common/util/GlobalContext';
|
|
import { GlobalContext } from '../common/util/GlobalContext';
|
|
|
import { CommonConstants } from '../common/constants/CommonConstants';
|
|
import { CommonConstants } from '../common/constants/CommonConstants';
|
|
|
import { EventConstants } from '../common/constants/EventConstants';
|
|
import { EventConstants } from '../common/constants/EventConstants';
|
|
|
import { common } from '@kit.AbilityKit';
|
|
import { common } from '@kit.AbilityKit';
|
|
|
import { ImagePickerUtil } from '../common/util/ImagePickerUtil';
|
|
import { ImagePickerUtil } from '../common/util/ImagePickerUtil';
|
|
|
|
|
+import { LazyDataSource } from '../common/util/LazyDataSource';
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 歌单播放事件数据
|
|
* 歌单播放事件数据
|
|
@@ -49,6 +50,36 @@ export struct PlaylistDetailPage {
|
|
|
private playlistId: string = ''
|
|
private playlistId: string = ''
|
|
|
@StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
|
|
@StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
|
|
|
|
|
|
|
|
|
|
+ //长按拖动
|
|
|
|
|
+ @State dragItem: number = -1
|
|
|
|
|
+ @State scaleItem: number = -1
|
|
|
|
|
+ @State neighborItem: number = -1
|
|
|
|
|
+ @State neighborScale: number = -1
|
|
|
|
|
+ @State offsetY: number = 0
|
|
|
|
|
+ private dragRefOffset: number = 0
|
|
|
|
|
+ private ITEM_INTV: number = 78
|
|
|
|
|
+ //播放列表的拖动List 重新排序
|
|
|
|
|
+ @State sonDataSource: LazyDataSource<VideoItem> = new LazyDataSource(this.songList)
|
|
|
|
|
+ scaleSelect(item: number): number {
|
|
|
|
|
+ if (this.scaleItem === item) {
|
|
|
|
|
+ return 1.05
|
|
|
|
|
+ } else if (this.neighborItem === item) {
|
|
|
|
|
+ return this.neighborScale
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return 1
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ itemMove(index: number, newIndex: number): void {
|
|
|
|
|
+ if (newIndex < 0 || newIndex >= this.songList.length) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ let tmp = this.songList.splice(index, 1);
|
|
|
|
|
+ this.songList.splice(newIndex, 0, tmp[0]);
|
|
|
|
|
+ this.sonDataSource.pushArrayData(this.songList)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
aboutToAppear() {
|
|
aboutToAppear() {
|
|
|
|
|
|
|
|
// 获取传入的歌单对象
|
|
// 获取传入的歌单对象
|
|
@@ -72,6 +103,11 @@ export struct PlaylistDetailPage {
|
|
|
*/
|
|
*/
|
|
|
async loadPlaylistDetail() {
|
|
async loadPlaylistDetail() {
|
|
|
try {
|
|
try {
|
|
|
|
|
+ if (!this.playlist) {
|
|
|
|
|
+ ToastUtil.showToast('歌单不存在')
|
|
|
|
|
+ router.back()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
//先获取缓存
|
|
//先获取缓存
|
|
|
const cacheSongs:string =PreferencesUtil.getStringSync(this.playlistId+'currentSongList', '')
|
|
const cacheSongs:string =PreferencesUtil.getStringSync(this.playlistId+'currentSongList', '')
|
|
|
if(StrUtil.isNotEmpty(cacheSongs)){
|
|
if(StrUtil.isNotEmpty(cacheSongs)){
|
|
@@ -79,12 +115,12 @@ export struct PlaylistDetailPage {
|
|
|
this.songList = JSON.parse(cacheSongs) as VideoItem[]
|
|
this.songList = JSON.parse(cacheSongs) as VideoItem[]
|
|
|
}else{
|
|
}else{
|
|
|
console.log('onecold loadPlaylistDetail 歌曲数据为空')
|
|
console.log('onecold loadPlaylistDetail 歌曲数据为空')
|
|
|
|
|
+ this.isLoading = true
|
|
|
}
|
|
}
|
|
|
- if (!this.playlist) {
|
|
|
|
|
- ToastUtil.showToast('歌单不存在')
|
|
|
|
|
- router.back()
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.sonDataSource.pushArrayData(this.songList)
|
|
|
|
|
+ // 启动页面进入动画
|
|
|
|
|
+ this.animatePageEntry()
|
|
|
|
|
+
|
|
|
|
|
|
|
|
// 加载歌单歌曲
|
|
// 加载歌单歌曲
|
|
|
const playlistSongs = await this.playlistTable.queryPlaylistSongs(this.playlistId)
|
|
const playlistSongs = await this.playlistTable.queryPlaylistSongs(this.playlistId)
|
|
@@ -92,6 +128,7 @@ export struct PlaylistDetailPage {
|
|
|
// 将 PlaylistSong 转换为 VideoItem
|
|
// 将 PlaylistSong 转换为 VideoItem
|
|
|
this.songList = await convertPlaylistSongsToVideoItems(this.context,playlistSongs)
|
|
this.songList = await convertPlaylistSongsToVideoItems(this.context,playlistSongs)
|
|
|
|
|
|
|
|
|
|
+ this.sonDataSource.pushArrayData(this.songList)
|
|
|
// 歌单加载完成后,加载当前播放状态
|
|
// 歌单加载完成后,加载当前播放状态
|
|
|
this.loadCurrentPlaybackStatus()
|
|
this.loadCurrentPlaybackStatus()
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
@@ -151,7 +188,7 @@ export struct PlaylistDetailPage {
|
|
|
* 页面入场动画
|
|
* 页面入场动画
|
|
|
*/
|
|
*/
|
|
|
animatePageEntry() {
|
|
animatePageEntry() {
|
|
|
- animateTo({
|
|
|
|
|
|
|
+ this.getUIContext().animateTo({
|
|
|
duration: 600,
|
|
duration: 600,
|
|
|
curve: Curve.EaseOut,
|
|
curve: Curve.EaseOut,
|
|
|
delay: 100,
|
|
delay: 100,
|
|
@@ -749,7 +786,7 @@ export struct PlaylistDetailPage {
|
|
|
// 歌曲列表 - 参考LocalMusic的MusicItem样式
|
|
// 歌曲列表 - 参考LocalMusic的MusicItem样式
|
|
|
if (this.songList.length > 0) {
|
|
if (this.songList.length > 0) {
|
|
|
List({ space: 0 }) {
|
|
List({ space: 0 }) {
|
|
|
- ForEach(this.songList, (song: VideoItem, index: number) => {
|
|
|
|
|
|
|
+ LazyForEach(this.sonDataSource, (song: VideoItem, index: number) => {
|
|
|
ListItem() {
|
|
ListItem() {
|
|
|
Button({ type: ButtonType.Normal, stateEffect: true }) {
|
|
Button({ type: ButtonType.Normal, stateEffect: true }) {
|
|
|
Row() {
|
|
Row() {
|
|
@@ -857,9 +894,8 @@ export struct PlaylistDetailPage {
|
|
|
.fontColor($r('app.color.text_color'))
|
|
.fontColor($r('app.color.text_color'))
|
|
|
.opacity(0.6)
|
|
.opacity(0.6)
|
|
|
.margin({ right: 15 })
|
|
.margin({ right: 15 })
|
|
|
- .onClick(() => {
|
|
|
|
|
- this.showSongMenu(song)
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ .bindMenu(this.MoreMenuBuilder(song))
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
.width('100%')
|
|
.width('100%')
|
|
@@ -869,6 +905,7 @@ export struct PlaylistDetailPage {
|
|
|
}
|
|
}
|
|
|
.backgroundColor(Color.Transparent)
|
|
.backgroundColor(Color.Transparent)
|
|
|
.height(70)
|
|
.height(70)
|
|
|
|
|
+ .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.9 })
|
|
|
.width('100%')
|
|
.width('100%')
|
|
|
.onClick(() => {
|
|
.onClick(() => {
|
|
|
// 排序模式下禁用点击播放
|
|
// 排序模式下禁用点击播放
|
|
@@ -876,15 +913,7 @@ export struct PlaylistDetailPage {
|
|
|
this.playSong(song, index)
|
|
this.playSong(song, index)
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
- .gesture(
|
|
|
|
|
- LongPressGesture()
|
|
|
|
|
- .onAction(() => {
|
|
|
|
|
- // 排序模式下不显示菜单
|
|
|
|
|
- if (!this.isSortMode) {
|
|
|
|
|
- this.showSongMenu(song)
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- )
|
|
|
|
|
|
|
+
|
|
|
.stateStyles({
|
|
.stateStyles({
|
|
|
normal: {
|
|
normal: {
|
|
|
.backgroundColor(Color.Transparent)
|
|
.backgroundColor(Color.Transparent)
|
|
@@ -912,10 +941,100 @@ export struct PlaylistDetailPage {
|
|
|
delay: 300 + index * 30
|
|
delay: 300 + index * 30
|
|
|
}))
|
|
}))
|
|
|
}
|
|
}
|
|
|
- })
|
|
|
|
|
|
|
+ //拖动List关键代码开始
|
|
|
|
|
+ .scale({ x: this.scaleSelect(index), y: this.scaleSelect(index) })
|
|
|
|
|
+ .zIndex(this.dragItem === index ? 1 : 0)
|
|
|
|
|
+ .translate(this.dragItem === index ? { y: this.offsetY } : { y: 0 })
|
|
|
|
|
+ .gesture(
|
|
|
|
|
+ GestureGroup(GestureMode.Sequence,
|
|
|
|
|
+ LongPressGesture({ repeat: true })
|
|
|
|
|
+ .onAction((event?: GestureEvent) => {
|
|
|
|
|
+ this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () => {
|
|
|
|
|
+ this.scaleItem = index
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ .onActionEnd(() => {
|
|
|
|
|
+ this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () => {
|
|
|
|
|
+ this.scaleItem = -1
|
|
|
|
|
+
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ }),
|
|
|
|
|
+ PanGesture({ fingers: 1, direction: null, distance: 0 })
|
|
|
|
|
+ .onActionStart(() => {
|
|
|
|
|
+ this.dragItem = index // 记录当前拖动项索引
|
|
|
|
|
+ this.dragRefOffset = 0 // 重置偏移基准
|
|
|
|
|
+ })
|
|
|
|
|
+ .onActionUpdate((event: GestureEvent) => {
|
|
|
|
|
+ this.offsetY = event.offsetY - this.dragRefOffset;
|
|
|
|
|
+ this.neighborItem = -1;
|
|
|
|
|
+ let indexA = this.songList.indexOf(song);
|
|
|
|
|
+ // LogUtil.info('onecold indexA = '+indexA +' index=='+index)
|
|
|
|
|
+ let curveValue: curves.ICurve = curves.initCurve(Curve.Sharp);
|
|
|
|
|
+ let value: number = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (this.offsetY < 0) {
|
|
|
|
|
+ value = curveValue.interpolate(-this.offsetY / this.ITEM_INTV);
|
|
|
|
|
+ this.neighborItem = indexA - 1;
|
|
|
|
|
+ this.neighborScale = 1 - value / 20;
|
|
|
|
|
+ } else if (this.offsetY > 0) {
|
|
|
|
|
+ value = curveValue.interpolate(this.offsetY / this.ITEM_INTV);
|
|
|
|
|
+ this.neighborItem = indexA + 1;
|
|
|
|
|
+ this.neighborScale = 1 - value / 20;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ if (this.offsetY > this.ITEM_INTV / 2 && indexA + 1 < this.songList.length) {
|
|
|
|
|
+ this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
|
|
|
|
|
+ this.offsetY -= this.ITEM_INTV;
|
|
|
|
|
+ this.dragRefOffset += this.ITEM_INTV;
|
|
|
|
|
+ this.itemMove(indexA, indexA + 1);
|
|
|
|
|
+ });
|
|
|
|
|
+ } else if (this.offsetY < -this.ITEM_INTV / 2 && indexA - 1 >= 0) {
|
|
|
|
|
+ this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
|
|
|
|
|
+ this.offsetY += this.ITEM_INTV;
|
|
|
|
|
+ this.dragRefOffset -= this.ITEM_INTV;
|
|
|
|
|
+ this.itemMove(indexA, indexA - 1);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ })
|
|
|
|
|
+ .onActionEnd((event: GestureEvent) => {
|
|
|
|
|
+
|
|
|
|
|
+ this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
|
|
|
|
|
+ this.dragItem = -1
|
|
|
|
|
+ this.neighborItem = -1
|
|
|
|
|
+ })
|
|
|
|
|
+ this.getUIContext().animateTo({
|
|
|
|
|
+ curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
|
|
|
|
|
+ }, () => {
|
|
|
|
|
+ this.scaleItem = -1
|
|
|
|
|
+ })
|
|
|
|
|
+ this.isSortMode = true
|
|
|
|
|
+
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ )
|
|
|
|
|
+ .onCancel(() => {
|
|
|
|
|
+ animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
|
|
|
|
|
+ this.dragItem = -1
|
|
|
|
|
+ this.neighborItem = -1
|
|
|
|
|
+ })
|
|
|
|
|
+ animateTo({
|
|
|
|
|
+ curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
|
|
|
|
|
+ }, () => {
|
|
|
|
|
+ this.scaleItem = -1
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ })
|
|
|
|
|
+ ) //拖动List关键代码结束
|
|
|
|
|
+
|
|
|
|
|
+ }, (item: VideoItem) => item.filePath)
|
|
|
}
|
|
}
|
|
|
.width('100%')
|
|
.width('100%')
|
|
|
.layoutWeight(1)
|
|
.layoutWeight(1)
|
|
|
|
|
+ .reuseId('list_item')
|
|
|
.backgroundColor($r('app.color.bg_card'))
|
|
.backgroundColor($r('app.color.bg_card'))
|
|
|
.margin({ left: 16, right: 16 })
|
|
.margin({ left: 16, right: 16 })
|
|
|
.borderRadius(12)
|
|
.borderRadius(12)
|
|
@@ -1029,6 +1148,53 @@ export struct PlaylistDetailPage {
|
|
|
.backgroundColor($r('app.color.index_background'))
|
|
.backgroundColor($r('app.color.index_background'))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ @Builder
|
|
|
|
|
+ MoreMenuBuilder(song: VideoItem) {
|
|
|
|
|
+ Scroll(){
|
|
|
|
|
+ Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
|
|
|
|
|
+ Menu() {
|
|
|
|
|
+ //设为歌单封面
|
|
|
|
|
+ MenuItem({
|
|
|
|
|
+ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.picture')),
|
|
|
|
|
+ content: $r('app.string.set_cover')
|
|
|
|
|
+ })
|
|
|
|
|
+ .onClick(async () => {
|
|
|
|
|
+ if (StrUtil.isEmpty(song.pixelMapPath)) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if(this.playlist){
|
|
|
|
|
+ const success = await this.playlistTable.updatePlaylist(this.playlist.id,
|
|
|
|
|
+ this.playlist.name, this.playlist.description, song.pixelMapPath)
|
|
|
|
|
+ if (success) {
|
|
|
|
|
+ this.playlist.coverPath = song.pixelMapPath
|
|
|
|
|
+ ToastUtil.showToast('设为歌单封面成功')
|
|
|
|
|
+ // 发送刷新事件
|
|
|
|
|
+ const eventRefresh: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH }
|
|
|
|
|
+ emitter.emit(eventRefresh, {})
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ToastUtil.showToast('设为歌单封面失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ MenuItem({
|
|
|
|
|
+ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.trash')),
|
|
|
|
|
+ content: $r('app.string.delete')
|
|
|
|
|
+ })
|
|
|
|
|
+ .onClick(() => {
|
|
|
|
|
+ this.showSongMenu(song)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ .font({ size: 15, weight: FontWeight.Normal })
|
|
|
|
|
+ }
|
|
|
|
|
+ .width(180)
|
|
|
|
|
+ }
|
|
|
|
|
+ .height('auto')
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 显示更多菜单
|
|
* 显示更多菜单
|
|
|
*/
|
|
*/
|