|
@@ -0,0 +1,308 @@
|
|
|
|
|
+import { router } from '@kit.ArkUI';
|
|
|
|
|
+import { VideoItem } from '../viewmodel/VideoItem';
|
|
|
|
|
+import { LengthMetrics, SegmentButton, SegmentButtonItemTuple, SegmentButtonOptions } from '@kit.ArkUI';
|
|
|
|
|
+import {
|
|
|
|
|
+ PreferencesUtil, ToastUtil
|
|
|
|
|
+} from '@pura/harmony-utils';
|
|
|
|
|
+import {
|
|
|
|
|
+ ButtonFancyModifier,
|
|
|
|
|
+ SymbolGlyphFancyModifier,
|
|
|
|
|
+ ShadowModifier
|
|
|
|
|
+} from '../common/util/AttributeModifierUtil';
|
|
|
|
|
+import { CommonConstants } from '../common/constants/CommonConstants';
|
|
|
|
|
+import { WebDavAccount } from '../viewmodel/WebDavAccount';
|
|
|
|
|
+
|
|
|
|
|
+@Component
|
|
|
|
|
+export struct NavidromePage {
|
|
|
|
|
+ @Link mType: number;
|
|
|
|
|
+ @Link offsetX: number;
|
|
|
|
|
+ @Link isShowDrawer: boolean;
|
|
|
|
|
+ @Prop @Watch('onSwitchAccount') selectedAccount: WebDavAccount;
|
|
|
|
|
+ @StorageProp('currentTheme') currentTheme: number = 0;
|
|
|
|
|
+ @State selectedTab: number = 0; // 0: 全部, 1: 艺术家, 2: 专辑
|
|
|
|
|
+ @State allVideos: VideoItem[] = [];
|
|
|
|
|
+ @State artistVideos: VideoItem[] = [];
|
|
|
|
|
+ @State albumVideos: VideoItem[] = [];
|
|
|
|
|
+ @State loading: boolean = false;
|
|
|
|
|
+ @StorageProp('topRectHeight') topRectHeight: number = 0;
|
|
|
|
|
+ @State @Watch('onTabSelectedIndexesChanged') tabSelectedIndexes: number[] = [0];
|
|
|
|
|
+ @StorageProp('themeColor') themeColor: string =
|
|
|
|
|
+ PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
|
|
|
|
|
+ private tabs: string[] = ['全部', '艺术家', '专辑'];
|
|
|
|
|
+
|
|
|
|
|
+ // SegmentButton选项
|
|
|
|
|
+ @State tabOptions: SegmentButtonOptions = SegmentButtonOptions.capsule({
|
|
|
|
|
+ buttons: [{ text: '全部' }, { text: '艺术家' }, { text: '专辑' }] as SegmentButtonItemTuple,
|
|
|
|
|
+ direction: Direction.Ltr,
|
|
|
|
|
+ buttonPadding: { top: 12, bottom: 12 },
|
|
|
|
|
+ backgroundColor: $r('app.color.start_window_background'),
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ selectedFontSize: 15,
|
|
|
|
|
+ selectedFontColor: Color.White,
|
|
|
|
|
+ selectedBackgroundColor: $r('app.color.title_bar_bg'),
|
|
|
|
|
+ localizedTextPadding: {
|
|
|
|
|
+ end: LengthMetrics.vp(20),
|
|
|
|
|
+ start: LengthMetrics.vp(20)
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ //当胶囊按钮的选择发生变化时调用此函数
|
|
|
|
|
+ onTabSelectedIndexesChanged() {
|
|
|
|
|
+ this.selectedTab = this.tabSelectedIndexes[0];
|
|
|
|
|
+ console.info('heanup', `Selected tab: ${this.tabs[this.selectedTab]}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //切换不同的NavidromePage
|
|
|
|
|
+ async onSwitchAccount(){
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ aboutToAppear() {
|
|
|
|
|
+ this.initMockData();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private initMockData() {
|
|
|
|
|
+ // 模拟全部数据
|
|
|
|
|
+ this.allVideos = [
|
|
|
|
|
+ new VideoItem('夜曲', '1', 'http://example.com/music1.mp3', CommonConstants.TYPE_NAVIDROME, 1024000, '2024-01-01', '1.0MB', '', '周杰伦', '十一月的萧邦', '夜曲.mp3', new Date('2024-01-15')),
|
|
|
|
|
+ new VideoItem('青花瓷', '2', 'http://example.com/music2.mp3', CommonConstants.TYPE_NAVIDROME, 2048000, '2024-01-02', '2.0MB', '', '周杰伦', '我很忙', '青花瓷.mp3', new Date('2024-01-14')),
|
|
|
|
|
+ new VideoItem('稻香', '3', 'http://example.com/music3.mp3', CommonConstants.TYPE_NAVIDROME, 1536000, '2024-01-03', '1.5MB', '', '周杰伦', '魔杰座', '稻香.mp3', new Date('2024-01-13')),
|
|
|
|
|
+ new VideoItem('告白气球', '4', 'http://example.com/music4.mp3', CommonConstants.TYPE_NAVIDROME, 2560000, '2024-01-04', '2.5MB', '', '周杰伦', '周杰伦的床边故事', '告白气球.mp3', new Date('2024-01-12')),
|
|
|
|
|
+ new VideoItem('演员', '5', 'http://example.com/music5.mp3', CommonConstants.TYPE_NAVIDROME, 1280000, '2024-01-05', '1.3MB', '', '薛之谦', '绅士', '演员.mp3', new Date('2024-01-11')),
|
|
|
|
|
+ new VideoItem('丑八怪', '6', 'http://example.com/music6.mp3', CommonConstants.TYPE_NAVIDROME, 1792000, '2024-01-06', '1.8MB', '', '薛之谦', '意外', '丑八怪.mp3', new Date('2024-01-10'))
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟艺术家数据 - 筛选周杰伦的歌曲
|
|
|
|
|
+ this.artistVideos = this.allVideos.filter(item => item.artist === '周杰伦');
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟专辑数据 - 筛选十一月的萧邦专辑的歌曲
|
|
|
|
|
+ this.albumVideos = this.allVideos.filter(item => item.album === '十一月的萧邦');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Builder
|
|
|
|
|
+ topTitleBar() {
|
|
|
|
|
+ Column() {
|
|
|
|
|
+ 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
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ .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 })
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .height(60)
|
|
|
|
|
+ .padding({left: 15, right: 15})
|
|
|
|
|
+ .justifyContent(FlexAlign.SpaceBetween)
|
|
|
|
|
+ .alignItems(VerticalAlign.Center)
|
|
|
|
|
+
|
|
|
|
|
+ // 分段按钮
|
|
|
|
|
+ SegmentButton({
|
|
|
|
|
+ options: this.tabOptions,
|
|
|
|
|
+ selectedIndexes: $tabSelectedIndexes
|
|
|
|
|
+ })
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .padding({ left: 25, right: 25, top: 5, bottom: 5 })
|
|
|
|
|
+ .margin({top:5})
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .padding({ top: this.topRectHeight+10})
|
|
|
|
|
+ .backgroundColor($r('app.color.start_window_background'))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private getCurrentVideos(): VideoItem[] {
|
|
|
|
|
+ switch (this.selectedTab) {
|
|
|
|
|
+ case 1: // 艺术家
|
|
|
|
|
+ return this.artistVideos;
|
|
|
|
|
+ case 2: // 专辑
|
|
|
|
|
+ return this.albumVideos;
|
|
|
|
|
+ default: // 全部
|
|
|
|
|
+ return this.allVideos;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Builder
|
|
|
|
|
+ buildSongItem(song: VideoItem, index: number) {
|
|
|
|
|
+ Button({ type: ButtonType.Normal, stateEffect: false }) {
|
|
|
|
|
+ Row({ space: 12 }) {
|
|
|
|
|
+ // 歌曲封面
|
|
|
|
|
+ Image(song.pixelMapPath ? song.pixelMapPath : $r('app.media.music_red'))
|
|
|
|
|
+ .width(48)
|
|
|
|
|
+ .height(48)
|
|
|
|
|
+ .borderRadius(10)
|
|
|
|
|
+ .sourceSize({width:38, height:38})
|
|
|
|
|
+ .alt($r('app.media.music_red'))
|
|
|
|
|
+ .fillColor(this.themeColor)
|
|
|
|
|
+ .objectFit(ImageFit.Cover)
|
|
|
|
|
+ .margin({ left: 8 })
|
|
|
|
|
+
|
|
|
|
|
+ // 歌曲信息
|
|
|
|
|
+ Column({ space: 4 }) {
|
|
|
|
|
+ Text(song.name)
|
|
|
|
|
+ .fontSize(15)
|
|
|
|
|
+ .fontColor(this.themeColor)
|
|
|
|
|
+ .maxLines(1)
|
|
|
|
|
+ .textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
|
|
|
+
|
|
|
|
|
+ Row(){
|
|
|
|
|
+ Text((song.artist ?? '') + " ")
|
|
|
|
|
+ .fontSize(13)
|
|
|
|
|
+ .fontColor($r('app.color.index_tab_font_color'))
|
|
|
|
|
+ .opacity(0.6)
|
|
|
|
|
+ .maxLines(1)
|
|
|
|
|
+ .visibility(song.artist?Visibility.Visible:Visibility.None)
|
|
|
|
|
+ .textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
|
|
|
+ Text(this.buildSongMetaLine(song))
|
|
|
|
|
+ .fontSize(13)
|
|
|
|
|
+ .fontColor($r('app.color.index_tab_font_color'))
|
|
|
|
|
+ .opacity(0.6)
|
|
|
|
|
+ .maxLines(1)
|
|
|
|
|
+ .textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('90%')
|
|
|
|
|
+ }
|
|
|
|
|
+ .alignItems(HorizontalAlign.Start)
|
|
|
|
|
+ .layoutWeight(1)
|
|
|
|
|
+ .padding({ right: 20 })
|
|
|
|
|
+
|
|
|
|
|
+ Column() {
|
|
|
|
|
+ ImageAnimator()
|
|
|
|
|
+ .images(CommonConstants.IMAGE_FRAME_INFO)// 动画数组
|
|
|
|
|
+ .duration(1000)// 持续
|
|
|
|
|
+ .state(AnimationStatus.Running)// 动画状态
|
|
|
|
|
+ .fillMode(FillMode.Forwards)
|
|
|
|
|
+ .width(18)
|
|
|
|
|
+ .margin({ right: 12, top: 8, bottom: 8 })
|
|
|
|
|
+ .height(18)
|
|
|
|
|
+ .iterations(-1) // 播放次数
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .padding(12)
|
|
|
|
|
+ .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
|
|
|
|
|
+ .backgroundColor(Color.Transparent)
|
|
|
|
|
+ .onClick(() => {
|
|
|
|
|
+ this.playSong(song, index);
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private buildSongMetaLine(song: VideoItem): string {
|
|
|
|
|
+ const parts: string[] = [];
|
|
|
|
|
+ if (song.duration) {
|
|
|
|
|
+ parts.push(song.duration as string);
|
|
|
|
|
+ } else if (song.size) {
|
|
|
|
|
+ parts.push(song.size as string);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (parts.length === 0 && song.cTime) {
|
|
|
|
|
+ parts.push(song.cTime as string);
|
|
|
|
|
+ }
|
|
|
|
|
+ return parts.join(' · ');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private playSong(song: VideoItem, index: number): void {
|
|
|
|
|
+ try {
|
|
|
|
|
+ console.info('heanup', `Navidrome 播放歌曲: ${song.name}, 索引: ${index}`);
|
|
|
|
|
+
|
|
|
|
|
+ // TODO: 实现Navidrome播放功能
|
|
|
|
|
+ // 这里可以参考WebDavMainPage中的playSong方法
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ const err = error as Error;
|
|
|
|
|
+ console.error('heanup', '播放歌曲失败: ' + err.message);
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ build() {
|
|
|
|
|
+ Column() {
|
|
|
|
|
+ this.topTitleBar()
|
|
|
|
|
+
|
|
|
|
|
+ // 主内容区域
|
|
|
|
|
+ if (this.loading) {
|
|
|
|
|
+ Column() {
|
|
|
|
|
+ LoadingProgress()
|
|
|
|
|
+ .width(50)
|
|
|
|
|
+ .height(50)
|
|
|
|
|
+ .color($r('app.color.title_bar_bg'))
|
|
|
|
|
+
|
|
|
|
|
+ Text('加载中...')
|
|
|
|
|
+ .margin({ top: 10 })
|
|
|
|
|
+ .fontSize(14)
|
|
|
|
|
+ .fontColor($r('app.color.index_tab_unselected_font_color'))
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .layoutWeight(1)
|
|
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ List({ space: 8 }) {
|
|
|
|
|
+ ForEach(this.getCurrentVideos(), (item: VideoItem, index: number) => {
|
|
|
|
|
+ ListItem() {
|
|
|
|
|
+ this.buildSongItem(item, index)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .layoutWeight(1)
|
|
|
|
|
+ .padding({ top: 10, bottom: 10 })
|
|
|
|
|
+ .listDirection(Axis.Vertical)
|
|
|
|
|
+ .scrollBar(BarState.Auto)
|
|
|
|
|
+ .edgeEffect(EdgeEffect.Spring)
|
|
|
|
|
+
|
|
|
|
|
+ // 空状态
|
|
|
|
|
+ if (this.getCurrentVideos().length === 0) {
|
|
|
|
|
+ Column() {
|
|
|
|
|
+ Image($r('app.media.music_red'))
|
|
|
|
|
+ .width(80)
|
|
|
|
|
+ .height(80)
|
|
|
|
|
+ .opacity(0.6)
|
|
|
|
|
+
|
|
|
|
|
+ Text('暂无音乐')
|
|
|
|
|
+ .margin({ top: 16 })
|
|
|
|
|
+ .fontSize(16)
|
|
|
|
|
+ .fontColor($r('app.color.index_tab_unselected_font_color'))
|
|
|
|
|
+
|
|
|
|
|
+ Text('当前分类下没有找到音乐文件')
|
|
|
|
|
+ .margin({ top: 8 })
|
|
|
|
|
+ .fontSize(14)
|
|
|
|
|
+ .fontColor($r('app.color.index_tab_unselected_font_color'))
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .layoutWeight(1)
|
|
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .width('100%')
|
|
|
|
|
+ .height('100%')
|
|
|
|
|
+ .backgroundColor($r('app.color.start_window_background'))
|
|
|
|
|
+ }
|
|
|
|
|
+}
|