|
@@ -176,6 +176,7 @@ import { getFirstLetter } from '../Constants';
|
|
|
import { audio } from '@kit.AudioKit';
|
|
import { audio } from '@kit.AudioKit';
|
|
|
import { PointLightDefaultButton } from './PointLight/PointLightDeFaultButton';
|
|
import { PointLightDefaultButton } from './PointLight/PointLightDeFaultButton';
|
|
|
import { CoverThumbCache } from '../common/util/CoverThumbCache';
|
|
import { CoverThumbCache } from '../common/util/CoverThumbCache';
|
|
|
|
|
+import { LyricCopySheet } from './LyricCopySheet';
|
|
|
|
|
|
|
|
const TAG = 'LocalMusic';
|
|
const TAG = 'LocalMusic';
|
|
|
const HICAR_LOG_TAG = 'HiCarWindow';
|
|
const HICAR_LOG_TAG = 'HiCarWindow';
|
|
@@ -724,6 +725,9 @@ export struct LocalMusic {
|
|
|
@State translateY: number = 0;
|
|
@State translateY: number = 0;
|
|
|
@State isShowSheet: boolean = false;
|
|
@State isShowSheet: boolean = false;
|
|
|
@State isShowSheetView: boolean = false;
|
|
@State isShowSheetView: boolean = false;
|
|
|
|
|
+ @State isShowLyricCopySheet: boolean = false;
|
|
|
|
|
+ @State lyricCopyLines: string[] = [];
|
|
|
|
|
+ @State lyricSelectedIndexes: number[] = [];
|
|
|
@State isShowTimeCloseView: boolean = false;
|
|
@State isShowTimeCloseView: boolean = false;
|
|
|
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Vertical });
|
|
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Vertical });
|
|
|
@StorageLink('currIndex') curIndex: number = 0;
|
|
@StorageLink('currIndex') curIndex: number = 0;
|
|
@@ -5142,6 +5146,28 @@ export struct LocalMusic {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private syncFavStateLocal(item: VideoItem, isFav: number): void {
|
|
|
|
|
+ const nextFavList = [...this.favList];
|
|
|
|
|
+ const favIndex = nextFavList.findIndex((favItem: VideoItem) => favItem.filePath === item.filePath);
|
|
|
|
|
+ if (isFav === 1) {
|
|
|
|
|
+ if (favIndex >= 0) {
|
|
|
|
|
+ nextFavList[favIndex].isFav = 1;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const favItem = cloneVideoItem(item);
|
|
|
|
|
+ favItem.isFav = 1;
|
|
|
|
|
+ nextFavList.push(favItem);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (favIndex >= 0) {
|
|
|
|
|
+ nextFavList.splice(favIndex, 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.favList = nextFavList;
|
|
|
|
|
+ item.isFav = isFav;
|
|
|
|
|
+ if (this.currentSong && this.currentSong.filePath === item.filePath) {
|
|
|
|
|
+ this.currentSong.isFav = isFav;
|
|
|
|
|
+ this.isFac = isFav === 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
doFav(item: VideoItem) {
|
|
doFav(item: VideoItem) {
|
|
|
LogUtil.info('onecold doFav isFav=' + item.isFav)
|
|
LogUtil.info('onecold doFav isFav=' + item.isFav)
|
|
|
|
|
|
|
@@ -5154,6 +5180,7 @@ export struct LocalMusic {
|
|
|
|
|
|
|
|
this.table.updateIsFavByFilePath(item.filePath, isFav, async (result: boolean) => {
|
|
this.table.updateIsFavByFilePath(item.filePath, isFav, async (result: boolean) => {
|
|
|
if (result) {
|
|
if (result) {
|
|
|
|
|
+ this.syncFavStateLocal(item, isFav);
|
|
|
if (isFav === 1) {
|
|
if (isFav === 1) {
|
|
|
ToastUtil.showToast('收藏成功');
|
|
ToastUtil.showToast('收藏成功');
|
|
|
} else {
|
|
} else {
|
|
@@ -11348,6 +11375,83 @@ export struct LocalMusic {
|
|
|
this.lyricControllerSingle.setLyric(null);
|
|
this.lyricControllerSingle.setLyric(null);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private extractLyricCopyLines(lyricText: string): string[] {
|
|
|
|
|
+ if (StrUtil.isEmpty(lyricText)) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ const normalizedLyric = LyricUtil.convertLyricToSimpleLrc(lyricText);
|
|
|
|
|
+ const source = StrUtil.isNotEmpty(normalizedLyric) ? normalizedLyric : lyricText;
|
|
|
|
|
+ const lines = source.split('\n');
|
|
|
|
|
+ const result: string[] = [];
|
|
|
|
|
+ const metaTagPattern = /^\[(ti|ar|al|by|offset|hash|sign|qq|total|tool|re|ve|length|au):?.*]$/i;
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < lines.length; i++) {
|
|
|
|
|
+ const line = lines[i].trim();
|
|
|
|
|
+ if (!line || metaTagPattern.test(line)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ const pureLine = line
|
|
|
|
|
+ .replace(/\[\d{1,2}:\d{2}(?:\.\d{1,3})?]/g, '')
|
|
|
|
|
+ .replace(/<\d{1,2}:\d{2}(?:\.\d{1,3})?>/g, '')
|
|
|
|
|
+ .trim();
|
|
|
|
|
+ if (pureLine.length > 0) {
|
|
|
|
|
+ result.push(pureLine);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private openLyricCopySheet(): void {
|
|
|
|
|
+ const lines = this.extractLyricCopyLines(this.lyricContent);
|
|
|
|
|
+ if (ArrayUtil.isEmpty(lines)) {
|
|
|
|
|
+ ToastUtil.showToast('暂无可复制歌词');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.lyricCopyLines = lines;
|
|
|
|
|
+ this.lyricSelectedIndexes = [];
|
|
|
|
|
+ this.isShowLyricCopySheet = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private toggleLyricLineSelection(index: number): void {
|
|
|
|
|
+ const selectedIndex = this.lyricSelectedIndexes.indexOf(index);
|
|
|
|
|
+ if (selectedIndex >= 0) {
|
|
|
|
|
+ const nextSelectedIndexes = [...this.lyricSelectedIndexes];
|
|
|
|
|
+ nextSelectedIndexes.splice(selectedIndex, 1);
|
|
|
|
|
+ this.lyricSelectedIndexes = nextSelectedIndexes;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.lyricSelectedIndexes = [...this.lyricSelectedIndexes, index];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private toggleLyricSelectAll(): void {
|
|
|
|
|
+ if (this.lyricSelectedIndexes.length === this.lyricCopyLines.length) {
|
|
|
|
|
+ this.lyricSelectedIndexes = [];
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.lyricSelectedIndexes = this.lyricCopyLines.map((_: string, index: number) => index);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private copySelectedLyricLines(): void {
|
|
|
|
|
+ if (ArrayUtil.isEmpty(this.lyricSelectedIndexes)) {
|
|
|
|
|
+ ToastUtil.showToast('请先选择歌词');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const selectedIndexes = [...this.lyricSelectedIndexes].sort((a: number, b: number) => a - b);
|
|
|
|
|
+ const selectedLines: string[] = [];
|
|
|
|
|
+ for (let i = 0; i < selectedIndexes.length; i++) {
|
|
|
|
|
+ const index = selectedIndexes[i];
|
|
|
|
|
+ if (index >= 0 && index < this.lyricCopyLines.length) {
|
|
|
|
|
+ selectedLines.push(this.lyricCopyLines[index]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (ArrayUtil.isEmpty(selectedLines)) {
|
|
|
|
|
+ ToastUtil.showToast('暂无可复制歌词');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Utility.copyText(selectedLines.join('\n'));
|
|
|
|
|
+ ToastUtil.showToast(`已复制${selectedLines.length}行歌词`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 从在线API获取歌词
|
|
* 从在线API获取歌词
|
|
|
*/
|
|
*/
|
|
@@ -11704,6 +11808,18 @@ export struct LocalMusic {
|
|
|
.width("100%")
|
|
.width("100%")
|
|
|
.layoutWeight(1)
|
|
.layoutWeight(1)
|
|
|
.margin({ left: this.currentLyricAlignMode === 0 ? 0 : 50, top: 2, bottom: this.isPhoneLan()||this.isHiCarKuanBianPing() ? 40 : 10 })
|
|
.margin({ left: this.currentLyricAlignMode === 0 ? 0 : 50, top: 2, bottom: this.isPhoneLan()||this.isHiCarKuanBianPing() ? 40 : 10 })
|
|
|
|
|
+ .gesture(LongPressGesture()
|
|
|
|
|
+ .onAction(() => {
|
|
|
|
|
+ this.openLyricCopySheet();
|
|
|
|
|
+ }))
|
|
|
|
|
+ .bindSheet($$this.isShowLyricCopySheet, this.LyricCopySheetBuilder(), {
|
|
|
|
|
+ height: '99%',
|
|
|
|
|
+ dragBar: true,
|
|
|
|
|
+ showClose: true,
|
|
|
|
|
+ preferType: SheetType.CENTER,
|
|
|
|
|
+ blurStyle: BlurStyle.Thin,
|
|
|
|
|
+ title: { title: '歌词' }
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.isPhoneLan()) {
|
|
if (this.isPhoneLan()) {
|
|
@@ -11725,6 +11841,26 @@ export struct LocalMusic {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ @Builder
|
|
|
|
|
+ LyricCopySheetBuilder() {
|
|
|
|
|
+ LyricCopySheet({
|
|
|
|
|
+ lyricCopyLines: this.lyricCopyLines,
|
|
|
|
|
+ lyricSelectedIndexes: this.lyricSelectedIndexes,
|
|
|
|
|
+ isDarkMode: this.isDarkMode,
|
|
|
|
|
+ themeColor: this.themeColor,
|
|
|
|
|
+ bottomSafeHeight: this.bottomSafeHeight,
|
|
|
|
|
+ onToggleLyricLine: (index: number) => {
|
|
|
|
|
+ this.toggleLyricLineSelection(index);
|
|
|
|
|
+ },
|
|
|
|
|
+ onToggleSelectAll: () => {
|
|
|
|
|
+ this.toggleLyricSelectAll();
|
|
|
|
|
+ },
|
|
|
|
|
+ onCopySelectedLines: () => {
|
|
|
|
|
+ this.copySelectedLyricLines();
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@Builder
|
|
@Builder
|
|
|
SingleLyricView() {
|
|
SingleLyricView() {
|
|
|
Column() {
|
|
Column() {
|
|
@@ -11736,6 +11872,10 @@ export struct LocalMusic {
|
|
|
seekLineColor: "#80ffffff", // 滑动定位线颜色
|
|
seekLineColor: "#80ffffff", // 滑动定位线颜色
|
|
|
seekUIStyle: "listItem", // 滑动定位样式(seekLine传统样式,listItem类似抖音汽水音乐样式)
|
|
seekUIStyle: "listItem", // 滑动定位样式(seekLine传统样式,listItem类似抖音汽水音乐样式)
|
|
|
})
|
|
})
|
|
|
|
|
+ .gesture(LongPressGesture()
|
|
|
|
|
+ .onAction(() => {
|
|
|
|
|
+ this.openLyricCopySheet();
|
|
|
|
|
+ }))
|
|
|
}
|
|
}
|
|
|
.height(82)
|
|
.height(82)
|
|
|
.margin({ bottom: 20 })
|
|
.margin({ bottom: 20 })
|
|
@@ -12094,20 +12234,24 @@ export struct LocalMusic {
|
|
|
|
|
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-
|
|
|
|
|
PointLightDefaultButton({
|
|
PointLightDefaultButton({
|
|
|
- isSysBol:false,
|
|
|
|
|
|
|
+ isSysBol:true,
|
|
|
pointColor:Color.White,
|
|
pointColor:Color.White,
|
|
|
- imageResource:$r('app.media.lyric'),
|
|
|
|
|
|
|
+ imageResource:Utility.getIsFav(this.favList,this.currentSong)?
|
|
|
|
|
+ $r('sys.symbol.heart_fill'):$r('sys.symbol.heart'),
|
|
|
isPx: false,
|
|
isPx: false,
|
|
|
builderHeight: 26,
|
|
builderHeight: 26,
|
|
|
builderWidth: 26,
|
|
builderWidth: 26,
|
|
|
})
|
|
})
|
|
|
.onClick(async () => {
|
|
.onClick(async () => {
|
|
|
- this.isLyricSetting = !this.isLyricSetting;
|
|
|
|
|
|
|
+ if(this.currentSong){
|
|
|
|
|
+ this.doFav(this.currentSong)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
this.playListBuilder()
|
|
this.playListBuilder()
|
|
|
|
|
|
|
|
}
|
|
}
|
|
@@ -12565,29 +12709,21 @@ export struct LocalMusic {
|
|
|
.margin({ left: 15 })
|
|
.margin({ left: 15 })
|
|
|
Blank()
|
|
Blank()
|
|
|
Row(){
|
|
Row(){
|
|
|
-
|
|
|
|
|
- Button({type:ButtonType.Circle,stateEffect:true}){
|
|
|
|
|
- //添加或取消收藏
|
|
|
|
|
- SymbolGlyph(Utility.getIsFav(this.favList,this.currentSong)?
|
|
|
|
|
- $r('sys.symbol.heart_fill'):$r('sys.symbol.heart'))
|
|
|
|
|
- .fontSize(22)
|
|
|
|
|
- .fontColor([Color.White])
|
|
|
|
|
- .alignSelf(ItemAlign.Center)
|
|
|
|
|
- .clickEffect({level:ClickEffectLevel.MIDDLE})
|
|
|
|
|
- .aspectRatio(CommonConstants.ASPECT_RATIO)
|
|
|
|
|
- .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
|
|
|
|
|
- .onClick(async () => {
|
|
|
|
|
- if(this.currentSong){
|
|
|
|
|
- this.doFav(this.currentSong)
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- .margin({ left: 8,right:8 })
|
|
|
|
|
- .backgroundColor(Color.Transparent)
|
|
|
|
|
- .width(45)
|
|
|
|
|
- .height(45)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ PointLightDefaultButton({
|
|
|
|
|
+ isSysBol:true,
|
|
|
|
|
+ pointColor:Color.White,
|
|
|
|
|
+ imageResource:this.showSimple?
|
|
|
|
|
+ $r('sys.symbol.sun_max'):$r('sys.symbol.sun_max_fill'),
|
|
|
|
|
+ isPx: false,
|
|
|
|
|
+ builderHeight: 26,
|
|
|
|
|
+ builderWidth: 26,
|
|
|
|
|
+ })
|
|
|
|
|
+ .onClick(async () => {
|
|
|
|
|
+ this.showSimple = !this.showSimple
|
|
|
|
|
+ PreferencesUtil.put('showSimple', this.showSimple)
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
+ .margin({ left: 8,right:8 })
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|