|
|
@@ -1,7 +1,8 @@
|
|
|
import { VideoItem } from '../viewmodel/VideoItem';
|
|
|
-import { LengthMetrics, SegmentButton, SegmentButtonItemTuple, SegmentButtonOptions } from '@kit.ArkUI';
|
|
|
+import { LengthMetrics, SegmentButton, SegmentButtonItemTuple, SegmentButtonOptions,
|
|
|
+ SymbolGlyphModifier } from '@kit.ArkUI';
|
|
|
import {
|
|
|
- PreferencesUtil, ToastUtil
|
|
|
+ PreferencesUtil, ToastUtil, StrUtil, ArrayUtil
|
|
|
} from '@pura/harmony-utils';
|
|
|
import {
|
|
|
ButtonFancyModifier,
|
|
|
@@ -59,6 +60,12 @@ export struct NavidromePage {
|
|
|
@State filterType: NavFilterType = NavFilterType.None;
|
|
|
@State filterLabel: string = '';
|
|
|
@State filterId: string = '';
|
|
|
+
|
|
|
+ // 搜索和排序相关状态
|
|
|
+ @State isSearchMode: boolean = false;
|
|
|
+ @State searchText: string = ''; // 用户输入内容
|
|
|
+ @State filteredList: Array<VideoItem> = []; // 过滤后的歌曲结果
|
|
|
+ @State sortType: number = 0; // 排序类型 0:名称升序 1:名称降序 2:时间升序 3:时间降序 4:大小升序 5:大小降序
|
|
|
|
|
|
// SegmentButton选项
|
|
|
@State tabOptions: SegmentButtonOptions = SegmentButtonOptions.capsule({
|
|
|
@@ -89,6 +96,7 @@ export struct NavidromePage {
|
|
|
|
|
|
aboutToAppear() {
|
|
|
this.isNoJumpToHome = PreferencesUtil.getBooleanSync('isNoJumpToHome', true)
|
|
|
+ this.sortType = PreferencesUtil.getNumberSync('navidromeSortType', 0);
|
|
|
this.refreshNavidromeData();
|
|
|
}
|
|
|
|
|
|
@@ -102,6 +110,7 @@ export struct NavidromePage {
|
|
|
return;
|
|
|
}
|
|
|
await this.loadNavidromeLibrary(account);
|
|
|
+ this.doSortType(this.sortType)
|
|
|
}
|
|
|
|
|
|
private resolveActiveAccount(): WebDavAccount | undefined {
|
|
|
@@ -200,41 +209,118 @@ export struct NavidromePage {
|
|
|
topTitleBar() {
|
|
|
Column() {
|
|
|
Row({ space: 6 }) {
|
|
|
- // 左侧返回按钮
|
|
|
- Button({ type: ButtonType.Circle, stateEffect: true }) {
|
|
|
- SymbolGlyph($r('sys.symbol.sort'))
|
|
|
- .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
|
|
|
+ // 标题或搜索框
|
|
|
+ if (!this.isSearchMode) {
|
|
|
+ // 左侧返回按钮
|
|
|
+ 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
|
|
|
+ })
|
|
|
+ })
|
|
|
+ .attributeModifier(new ShadowModifier())
|
|
|
+ .zIndex(0)
|
|
|
+ Text('Navidrome音乐')
|
|
|
+ .margin({ left: 3, right: 10 })
|
|
|
+ .fontColor($r('app.color.text_color'))
|
|
|
+ .fontSize(18)
|
|
|
+ .maxLines(1)
|
|
|
+ .textOverflow({ overflow: TextOverflow.MARQUEE })
|
|
|
+ .layoutWeight(1)
|
|
|
+ .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
|
|
|
+ .onClick(() => {
|
|
|
+ // 可以添加标题点击事件
|
|
|
+ })
|
|
|
+ .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;
|
|
|
+ this.onSearchInput('');
|
|
|
+ })
|
|
|
+ .attributeModifier(new ShadowModifier())
|
|
|
+ .zIndex(0)
|
|
|
+
|
|
|
}
|
|
|
- .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
|
|
|
+
|
|
|
+ // 搜索框
|
|
|
+ Search({ value: this.searchText, placeholder: '搜索标题、艺术家...' })
|
|
|
+ .searchButton('搜索', { fontColor: this.themeColor })
|
|
|
+ .searchIcon({
|
|
|
+ src: $r('sys.media.ohos_ic_public_search_filled')
|
|
|
+ })
|
|
|
+ .cancelButton({
|
|
|
+ style: CancelButtonStyle.CONSTANT,
|
|
|
+ icon: {
|
|
|
+ src: $r('sys.media.ohos_ic_public_cancel_filled')
|
|
|
+ }
|
|
|
})
|
|
|
- })
|
|
|
- .attributeModifier(new ShadowModifier())
|
|
|
- .zIndex(0)
|
|
|
-
|
|
|
- // 标题
|
|
|
- Text('Navidrome音乐')
|
|
|
- .margin({ left: 3, right: 10 })
|
|
|
- .fontColor($r('app.color.text_color'))
|
|
|
- .fontSize(18)
|
|
|
- .maxLines(1)
|
|
|
- .textOverflow({ overflow: TextOverflow.MARQUEE })
|
|
|
.layoutWeight(1)
|
|
|
+ .height(35)
|
|
|
+ .maxLength(20)
|
|
|
+ .backgroundColor(Color.White)
|
|
|
+ .placeholderColor(Color.Grey)
|
|
|
+ .placeholderFont({ size: 14, weight: 400 })
|
|
|
+ .textFont({ size: 14, weight: 400 })
|
|
|
+ .onSubmit((value: string) => {
|
|
|
+ this.onSearchInput(value);
|
|
|
+ })
|
|
|
+ .onChange((value: string) => {
|
|
|
+ this.onSearchInput(value);
|
|
|
+ })
|
|
|
+ .visibility(this.isSearchMode?Visibility.Visible:Visibility.None)
|
|
|
+ .animation({ duration: 300, curve: Curve.Ease })
|
|
|
+
|
|
|
+ // 搜索/排序按钮
|
|
|
+ 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;
|
|
|
+ if(ArrayUtil.isEmpty(this.filteredList)){
|
|
|
+ this.filteredList = [...this.allVideos];
|
|
|
+ }
|
|
|
})
|
|
|
+
|
|
|
+ // 排序按钮
|
|
|
+ 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 })
|
|
|
+ .bindMenu(this.SortMenuBuilder)
|
|
|
+ .attributeModifier(new ShadowModifier())
|
|
|
+ .zIndex(0)
|
|
|
+ }
|
|
|
}
|
|
|
.width('100%')
|
|
|
.height(55)
|
|
|
- .padding({left: 15, right: 15})
|
|
|
+ .padding({ left: 15, right: 15 })
|
|
|
.justifyContent(FlexAlign.SpaceBetween)
|
|
|
.alignItems(VerticalAlign.Center)
|
|
|
|
|
|
@@ -247,10 +333,137 @@ export struct NavidromePage {
|
|
|
.padding({ left: 25, right: 25, top: 5, bottom: 5 })
|
|
|
}
|
|
|
.width('100%')
|
|
|
- .padding({ top: this.topRectHeight+5})
|
|
|
+ .padding({ top: this.topRectHeight + 5 })
|
|
|
.backgroundColor($r('app.color.start_window_background'))
|
|
|
}
|
|
|
|
|
|
+ @Builder
|
|
|
+ SortMenuBuilder() {
|
|
|
+ Menu() {
|
|
|
+ MenuItem({
|
|
|
+ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_up')),
|
|
|
+ content: $r('app.string.sort_by_name')
|
|
|
+ })
|
|
|
+ .onClick(async () => {
|
|
|
+ this.doSortType(0);
|
|
|
+ PreferencesUtil.put("navidromeSortType", 0);
|
|
|
+ })
|
|
|
+ MenuItem({
|
|
|
+ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_down')),
|
|
|
+ content: '按名称降序'
|
|
|
+ })
|
|
|
+ .onClick(async () => {
|
|
|
+ this.doSortType(1);
|
|
|
+ PreferencesUtil.put("navidromeSortType", 1);
|
|
|
+ })
|
|
|
+ MenuItem({
|
|
|
+ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.clock')),
|
|
|
+ content: '按艺术家升序'
|
|
|
+ })
|
|
|
+ .onClick(async () => {
|
|
|
+ this.doSortType(2);
|
|
|
+ PreferencesUtil.put("navidromeSortType", 2);
|
|
|
+ })
|
|
|
+
|
|
|
+ MenuItem({
|
|
|
+ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.timer')),
|
|
|
+ content: '按艺术家降序'
|
|
|
+ })
|
|
|
+ .onClick(async () => {
|
|
|
+ this.doSortType(3);
|
|
|
+ PreferencesUtil.put("navidromeSortType", 3);
|
|
|
+ })
|
|
|
+ MenuItem({
|
|
|
+ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_up')),
|
|
|
+ content: '按专辑升序'
|
|
|
+ })
|
|
|
+ .onClick(async () => {
|
|
|
+ this.doSortType(4);
|
|
|
+ PreferencesUtil.put("navidromeSortType", 4);
|
|
|
+ })
|
|
|
+
|
|
|
+ MenuItem({
|
|
|
+ symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.text_and_arrow_down')),
|
|
|
+ content: '按专辑降序'
|
|
|
+ })
|
|
|
+ .onClick(async () => {
|
|
|
+ this.doSortType(5);
|
|
|
+ PreferencesUtil.put("navidromeSortType", 5);
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ doSortType(index: number) {
|
|
|
+ this.sortType = index;
|
|
|
+ const songs = this.isSearchMode ? this.filteredList :this.allVideos;
|
|
|
+
|
|
|
+ // 对歌曲列表进行排序
|
|
|
+ switch (index) {
|
|
|
+ case 0: // 名称升序
|
|
|
+ Utility.doSortListAscending(songs,false)
|
|
|
+ break;
|
|
|
+ case 1: // 名称降序
|
|
|
+ Utility.doSortListDescending(songs,true)
|
|
|
+ break;
|
|
|
+ case 2: // 艺术家升序
|
|
|
+ songs.sort((a, b) => {
|
|
|
+ // 处理艺术家可能为undefined的字符串比较
|
|
|
+ const artistA = a.artist?.trim() || ''; // 可选添加 trim() 处理空格
|
|
|
+ const artistB = b.artist?.trim() || '';
|
|
|
+ return artistA.localeCompare(artistB);
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case 3: // 艺术家降序
|
|
|
+ songs.sort((a, b) => {
|
|
|
+ // 处理艺术家可能为undefined的字符串比较
|
|
|
+ const artistA = a.artist?.trim() || ''; // 可选添加 trim() 处理空格
|
|
|
+ const artistB = b.artist?.trim() || '';
|
|
|
+ return artistB.localeCompare(artistA);
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case 4: // 专辑升序
|
|
|
+ songs.sort((a, b) => {
|
|
|
+ const albumA = a.album?.trim() || ''; // 可选添加 trim() 处理空格
|
|
|
+ const albumB = b.album?.trim() || '';
|
|
|
+ return albumA.localeCompare(albumB);
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case 5: // 专辑降序
|
|
|
+ songs.sort((a, b) => {
|
|
|
+ const albumA = a.album?.trim() || ''; // 可选添加 trim() 处理空格
|
|
|
+ const albumB = b.album?.trim() || '';
|
|
|
+ return albumB.localeCompare(albumA);
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新显示列表
|
|
|
+ if (this.isSearchMode) {
|
|
|
+ this.filteredList = [...songs];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 实时搜索逻辑
|
|
|
+ private onSearchInput(value: string) {
|
|
|
+ this.searchText = value.trim();
|
|
|
+ let mSearchList: Array<VideoItem> = [...this.allVideos];
|
|
|
+
|
|
|
+ // 新增条件判断:空输入时显示所有数据
|
|
|
+ if (this.searchText === '') {
|
|
|
+ this.filteredList = [...mSearchList]; // 创建新数组以触发状态更新
|
|
|
+ } else {
|
|
|
+ this.filteredList = mSearchList.filter((item: VideoItem) => {
|
|
|
+ // 支持模糊匹配和艺术家 专辑匹配
|
|
|
+ const regex = new RegExp(this.searchText.toLowerCase().replace(/\s+/g, '.*'), 'i');
|
|
|
+ return regex.test(item.name.toLowerCase()) ||
|
|
|
+ regex.test(item.fileName?.toLowerCase() ?? "") ||
|
|
|
+ regex.test(item.artist?.toLowerCase() ?? "") ||
|
|
|
+ regex.test(item.album?.toLowerCase() ?? "")
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
private getCurrentCount(): number {
|
|
|
switch (this.selectedTab) {
|
|
|
case 1:
|
|
|
@@ -293,7 +506,7 @@ export struct NavidromePage {
|
|
|
.width(48)
|
|
|
.height(48)
|
|
|
.borderRadius(10)
|
|
|
- .sourceSize({width:38, height:38})
|
|
|
+ .sourceSize({ width: 38, height: 38 })
|
|
|
.alt($r('app.media.music_red'))
|
|
|
.fillColor(this.themeColor)
|
|
|
.objectFit(ImageFit.Cover)
|
|
|
@@ -306,21 +519,21 @@ export struct NavidromePage {
|
|
|
Column({ space: 4 }) {
|
|
|
Text(song.name)
|
|
|
.fontSize(15)
|
|
|
- .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:$r('app.color.index_tab_font_color'))
|
|
|
+ .fontColor(this.currentSong?.filePath == song.filePath ? this.themeColor : $r('app.color.index_tab_font_color'))
|
|
|
.maxLines(1)
|
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
|
|
|
|
- Row(){
|
|
|
+ Row() {
|
|
|
Text((song.artist ?? '') + " ")
|
|
|
.fontSize(13)
|
|
|
- .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:$r('app.color.index_tab_font_color'))
|
|
|
+ .fontColor(this.currentSong?.filePath == song.filePath ? this.themeColor : $r('app.color.index_tab_font_color'))
|
|
|
.opacity(0.6)
|
|
|
.maxLines(1)
|
|
|
- .visibility(song.artist?Visibility.Visible:Visibility.None)
|
|
|
+ .visibility(song.artist ? Visibility.Visible : Visibility.None)
|
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
|
Text(this.buildSongMetaLine(song))
|
|
|
.fontSize(13)
|
|
|
- .fontColor(this.currentSong?.filePath==song.filePath?this.themeColor:$r('app.color.index_tab_font_color'))
|
|
|
+ .fontColor(this.currentSong?.filePath == song.filePath ? this.themeColor : $r('app.color.index_tab_font_color'))
|
|
|
.opacity(0.6)
|
|
|
.maxLines(1)
|
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
|
@@ -337,7 +550,7 @@ export struct NavidromePage {
|
|
|
.duration(1000)// 持续
|
|
|
.state(AnimationStatus.Running)// 动画状态
|
|
|
.fillMode(FillMode.Forwards)
|
|
|
- .visibility(this.currentSong?.filePath==song.filePath ? Visibility.Visible :
|
|
|
+ .visibility(this.currentSong?.filePath == song.filePath ? Visibility.Visible :
|
|
|
Visibility.None)
|
|
|
.width(18)
|
|
|
.margin({ right: 12, top: 8, bottom: 8 })
|
|
|
@@ -412,7 +625,7 @@ export struct NavidromePage {
|
|
|
.maxLines(1)
|
|
|
.textAlign(TextAlign.Start)
|
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
|
- Row(){
|
|
|
+ Row() {
|
|
|
Text(album.artist ?? Constants.UNKNOWN_ARTIST)
|
|
|
.fontSize(13)
|
|
|
.fontColor($r('app.color.index_tab_font_color'))
|
|
|
@@ -482,12 +695,16 @@ export struct NavidromePage {
|
|
|
}
|
|
|
|
|
|
private getVisibleSongs(): VideoItem[] {
|
|
|
+ if(this.isSearchMode)
|
|
|
+ return this.filteredList;
|
|
|
+
|
|
|
if (this.filterType === NavFilterType.Artist && this.filterId.length > 0) {
|
|
|
return this.allVideos.filter(item => item.navArtistId === this.filterId || item.artist === this.filterLabel);
|
|
|
}
|
|
|
if (this.filterType === NavFilterType.Album && this.filterId.length > 0) {
|
|
|
return this.allVideos.filter(item => item.navAlbumId === this.filterId || item.album === this.filterLabel);
|
|
|
}
|
|
|
+
|
|
|
return this.allVideos;
|
|
|
}
|
|
|
|
|
|
@@ -522,7 +739,7 @@ export struct NavidromePage {
|
|
|
this.filterLabel = '';
|
|
|
}
|
|
|
|
|
|
- private playSong(song: VideoItem, index: number,isJump:boolean = false): void {
|
|
|
+ private playSong(song: VideoItem, index: number, isJump: boolean = false): void {
|
|
|
try {
|
|
|
if (!this.allVideos || this.allVideos.length === 0) {
|
|
|
ToastUtil.showToast('暂无可播放的歌曲');
|
|
|
@@ -654,4 +871,4 @@ export struct NavidromePage {
|
|
|
.height('100%')
|
|
|
.backgroundColor($r('app.color.start_window_background'))
|
|
|
}
|
|
|
-}
|
|
|
+}
|