import { MusicCardActionConstants } from '../../common/player/MusicCardActionConstants' import Logger from '../../common/util/Logger' import { clampMusicCardWidgetProgress, resolveMusicCardWidgetCoverSource } from '../common/MusicPlayerWidgetHelper' const cardStorage: LocalStorage = new LocalStorage() const ENTRY_ABILITY_NAME = 'EntryAbility' const TAG = 'MusicPlayerWidgetCard' @Entry(cardStorage) @Component struct MusicPlayerWidgetCard { @LocalStorageProp('formId') formId: string = '' @LocalStorageProp('title') title: string = '未在播放' @LocalStorageProp('artist') artist: string = '点击打开播放器' @LocalStorageProp('coverPath') coverPath: string = '' @LocalStorageProp('coverImageName') coverImageName: string = '' @LocalStorageProp('hasCoverImage') hasCoverImage: boolean = false @LocalStorageProp('hasSong') hasSong: boolean = false @LocalStorageProp('isPlaying') isPlaying: boolean = false @LocalStorageProp('currentPositionMs') @Watch('syncSliderFromPlayback') currentPositionMs: number = 0 @LocalStorageProp('durationMs') @Watch('syncSliderFromPlayback') durationMs: number = 0 @LocalStorageProp('currentTimeText') currentTimeText: string = '00:00' @LocalStorageProp('durationTimeText') durationTimeText: string = '00:00' @State isChangeBg: boolean = true @State sliderProgressMs: number = 0 aboutToAppear(): void { Logger.info(TAG, `musicCard small aboutToAppear formId=${this.formId}, title=${this.title}, artist=${this.artist}, ` + `coverImageName=${this.coverImageName}, coverPath=${this.coverPath}, hasCoverImage=${this.hasCoverImage}, ` + `hasSong=${this.hasSong}, isPlaying=${this.isPlaying}, currentPositionMs=${this.currentPositionMs}, ` + `durationMs=${this.durationMs}`) this.syncSliderFromPlayback() } private syncSliderFromPlayback(): void { this.sliderProgressMs = clampMusicCardWidgetProgress(this.currentPositionMs, this.durationMs) } private postControlAction(action: string): void { Logger.info(TAG, `musicCard small postControlAction formId=${this.formId}, action=${action}`) postCardAction(this, { action: 'call', abilityName: ENTRY_ABILITY_NAME, params: { method: MusicCardActionConstants.CALL_METHOD_HANDLE_ACTION, ttmusic_music_card_form_id: this.formId, ttmusic_music_card_action: action, ttmusic_music_card_source: MusicCardActionConstants.ACTION_SOURCE_WIDGET } }) } private postRouterAction(action: string): void { Logger.info(TAG, `musicCard small postRouterAction formId=${this.formId}, action=${action}`) postCardAction(this, { action: 'router', abilityName: ENTRY_ABILITY_NAME, params: { ttmusic_music_card_form_id: this.formId, ttmusic_music_card_action: action, ttmusic_music_card_source: MusicCardActionConstants.ACTION_SOURCE_WIDGET } }) } private resolveCoverSource(): string | Resource { return resolveMusicCardWidgetCoverSource(this.coverImageName, this.coverPath, this.hasCoverImage) } private resolveProgressValue(): number { if (this.durationMs <= 0) { return 0 } return Math.min(100, Math.max(0, Math.floor(this.sliderProgressMs * 100 / this.durationMs))) } private resolveTitleArtistText(): string { const safeTitle = this.title?.trim() ?? '' const safeArtist = this.artist?.trim() ?? '' if (safeTitle !== '' && safeArtist !== '') { return `${safeTitle} - ${safeArtist}` } if (safeTitle !== '') { return safeTitle } if (safeArtist !== '') { return safeArtist } return '未在播放' } private resolvePlayPauseIcon(): Resource { return this.isPlaying ? $r('sys.symbol.pause_fill') : $r('sys.symbol.play_fill') } @Builder private ControlButton(imageResource: Resource, action: string, buttonSize: number, symbolSize: number) { Button() { SymbolGlyph(imageResource) .fontSize(symbolSize) .fontColor([Color.White]) .effectStrategy(SymbolEffectStrategy.HIERARCHICAL) } .width(buttonSize * 1.35) .height(buttonSize * 1.35) .backgroundColor(Color.Transparent) .type(ButtonType.Circle) .stateEffect(true) .onClick(() => this.postControlAction(action)) } @Builder private PlayProgressButton() { Stack({ alignContent: Alignment.Center }) { Progress({ value: this.resolveProgressValue(), total: 100, type: ProgressType.Ring }) .width(34) .height(34) .color(Color.White) .style({ strokeWidth: 2 }) Button() { SymbolGlyph(this.resolvePlayPauseIcon()) .fontSize(19) .fontColor([Color.White]) .effectStrategy(SymbolEffectStrategy.HIERARCHICAL) } .width(34) .height(34) .type(ButtonType.Circle) .backgroundColor('#26FFFFFF') .stateEffect(false) .onClick(() => this.postControlAction(MusicCardActionConstants.ACTION_PLAY_PAUSE)) } .width(34) .height(34) } private hasCoverBackground(): boolean { if (this.hasCoverImage && this.coverImageName.length > 0) { return true; } return this.coverPath.length > 0; } build() { Stack() { if (this.hasCoverBackground()&&this.isChangeBg) { Column() .width('100%') .height('100%') .backgroundImage(this.resolveCoverSource()) .backgroundImageSize({ width: '100%' }) .backgroundBlurStyle(BlurStyle.BACKGROUND_ULTRA_THICK) } else { Column() .width('100%') .height('100%') .linearGradient( { direction: GradientDirection.Right, colors:[ ['#00BC70',0.0],['#D81B60',1.0]]}) } Column({ space: 12 }) { Image(this.resolveCoverSource()) .width(60) .height(60) .borderRadius(6) .objectFit(ImageFit.Cover) .onClick(()=>{ this.isChangeBg = !this.isChangeBg }) Text(this.resolveTitleArtistText()) .fontSize(13) .fontWeight(FontWeight.Bold) .fontColor(Color.White) .maxLines(1) .textAlign(TextAlign.Center) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row({ space: 16 }) { this.ControlButton($r('sys.symbol.backward_end_fill'), MusicCardActionConstants.ACTION_PREVIOUS, 18, 18) this.PlayProgressButton() this.ControlButton($r('sys.symbol.forward_end_fill'), MusicCardActionConstants.ACTION_NEXT, 18, 18) } .justifyContent(FlexAlign.Center) .alignItems(VerticalAlign.Center) } .width('100%') .height('100%') .alignItems(HorizontalAlign.Center) .justifyContent(FlexAlign.Center) .padding({ left: 16, right: 16, top: 16, bottom: 14 }) } .width('100%') .height('100%') .clip(true) .onClick(() => this.postRouterAction(MusicCardActionConstants.ACTION_OPEN_PLAYER)) } }