import { WidgetSize, WidgetData, FormattedWidgetData, WidgetConfig, ContainerPadding, ButtonSize, FontSizeConfig, SpacingConfig, ShowElementsConfig, ResponsiveLayoutParams } from './WidgetTypes'; import hilog from '@ohos.hilog'; const TAG = 'FormLayoutManager'; /** * 卡片布局管理器 * 负责处理多尺寸卡片的布局适配和UI重构 * 需求: 5.4, 5.5 */ export class FormLayoutManager { private static instance: FormLayoutManager; /** * 获取单例实例 */ public static getInstance(): FormLayoutManager { if (!FormLayoutManager.instance) { FormLayoutManager.instance = new FormLayoutManager(); } return FormLayoutManager.instance; } private constructor() { hilog.info(0x0000, TAG, 'FormLayoutManager initialized'); } /** * 根据卡片尺寸适配数据 * @param widgetData 原始卡片数据 * @param size 卡片尺寸 * @returns 适配后的格式化数据 */ public adaptDataForSize(widgetData: WidgetData, size: WidgetSize): FormattedWidgetData { hilog.info(0x0000, TAG, `Adapting data for size: ${size}, coverImagePath: ${widgetData.currentSong.coverImagePath}`); const baseData = this.formatBaseData(widgetData); switch (size) { case WidgetSize.SMALL: return this.adaptForSmallWidget(baseData, widgetData); case WidgetSize.MEDIUM: return this.adaptForMediumWidget(baseData, widgetData); case WidgetSize.SQUARE: return this.adaptForSquareWidget(baseData, widgetData); case WidgetSize.LARGE: return this.adaptForLargeWidget(baseData, widgetData); default: hilog.warn(0x0000, TAG, `Unknown widget size: ${size}, using medium as default`); return this.adaptForMediumWidget(baseData, widgetData); } } /** * 格式化基础数据 */ private formatBaseData(data: WidgetData): FormattedWidgetData { hilog.info(0x0000, TAG, `Formatting base data: coverImagePath=${data.currentSong.coverImagePath}`); return { // 播放状态 isPlaying: data.playState.isPlaying, isPaused: data.playState.isPaused, isLoading: data.playState.isLoading, // 歌曲信息 songTitle: this.truncateText(data.currentSong.title, 30), songArtist: this.truncateText(data.currentSong.artist, 20), songAlbum: this.truncateText(data.currentSong.album, 20), coverImage: data.currentSong.coverImagePath || '', // 播放进度 currentTime: data.progress.currentTimeText, totalTime: data.progress.totalTimeText, progressPercentage: data.progress.percentage, // 控制按钮状态 hasNext: data.playlist.hasNext, hasPrevious: data.playlist.hasPrevious, // 卡片配置 showProgress: data.config.showProgress, showCover: data.config.showCover, widgetSize: data.config.size as string, // 时间戳用于强制更新 timestamp: Date.now() }; } /** * 适配小尺寸卡片 */ private adaptForSmallWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData { return { isPlaying: baseData.isPlaying, isPaused: baseData.isPaused, isLoading: baseData.isLoading, songTitle: this.truncateText(widgetData.currentSong.title, 15), songArtist: this.truncateText(widgetData.currentSong.artist, 12), songAlbum: baseData.songAlbum, coverImage: baseData.coverImage, currentTime: baseData.currentTime, totalTime: baseData.totalTime, progressPercentage: baseData.progressPercentage, hasNext: baseData.hasNext, hasPrevious: baseData.hasPrevious, showProgress: false, showCover: false, widgetSize: baseData.widgetSize, timestamp: baseData.timestamp }; } /** * 适配中等尺寸卡片 */ private adaptForMediumWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData { hilog.info(0x0000, TAG, `Adapting for medium widget: coverImage=${baseData.coverImage}`); return { isPlaying: baseData.isPlaying, isPaused: baseData.isPaused, isLoading: baseData.isLoading, songTitle: this.truncateText(widgetData.currentSong.title, 25), songArtist: this.truncateText(widgetData.currentSong.artist, 18), songAlbum: baseData.songAlbum, coverImage: widgetData.currentSong.coverImagePath || '', // 确保coverImage被正确传递 currentTime: baseData.currentTime, totalTime: baseData.totalTime, progressPercentage: baseData.progressPercentage, hasNext: baseData.hasNext, hasPrevious: baseData.hasPrevious, showProgress: false, showCover: true, widgetSize: baseData.widgetSize, timestamp: baseData.timestamp }; } /** * 适配方形尺寸卡片 (2x2) */ private adaptForSquareWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData { hilog.info(0x0000, TAG, `Adapting for square widget: coverImage=${baseData.coverImage}`); return { isPlaying: baseData.isPlaying, isPaused: baseData.isPaused, isLoading: baseData.isLoading, songTitle: this.truncateText(widgetData.currentSong.title, 20), songArtist: this.truncateText(widgetData.currentSong.artist, 15), songAlbum: baseData.songAlbum, coverImage: widgetData.currentSong.coverImagePath || '', // 确保coverImage被正确传递 currentTime: baseData.currentTime, totalTime: baseData.totalTime, progressPercentage: baseData.progressPercentage, hasNext: baseData.hasNext, hasPrevious: baseData.hasPrevious, showProgress: false, showCover: true, widgetSize: baseData.widgetSize, timestamp: baseData.timestamp }; } /** * 适配大尺寸卡片 */ private adaptForLargeWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData { return { isPlaying: baseData.isPlaying, isPaused: baseData.isPaused, isLoading: baseData.isLoading, songTitle: this.truncateText(widgetData.currentSong.title, 35), songArtist: this.truncateText(widgetData.currentSong.artist, 25), songAlbum: baseData.songAlbum, coverImage: baseData.coverImage, currentTime: baseData.currentTime, totalTime: baseData.totalTime, progressPercentage: baseData.progressPercentage, hasNext: baseData.hasNext, hasPrevious: baseData.hasPrevious, showProgress: true, showCover: true, widgetSize: baseData.widgetSize, timestamp: baseData.timestamp }; } /** * 截断文本 */ private truncateText(text: string, maxLength: number): string { if (!text || text.length <= maxLength) { return text || ''; } return text.substring(0, maxLength - 1) + '…'; } /** * 获取卡片尺寸对应的页面路径 * @param size 卡片尺寸 * @returns 页面路径 */ public getWidgetPagePath(size: WidgetSize): string { switch (size) { case WidgetSize.SMALL: return 'widget/pages/PlayerWidgetSmall'; case WidgetSize.MEDIUM: return 'widget/pages/PlayerWidgetMedium'; case WidgetSize.SQUARE: return 'widget/pages/PlayerWidgetSquare'; case WidgetSize.LARGE: return 'widget/pages/PlayerWidgetLarge'; default: hilog.warn(0x0000, TAG, `Unknown widget size: ${size}, using medium as default`); return 'widget/pages/PlayerWidgetMedium'; } } /** * 检查尺寸变化并返回是否需要重构UI * @param oldSize 旧尺寸 * @param newSize 新尺寸 * @returns 是否需要重构UI */ public shouldRebuildUI(oldSize: WidgetSize, newSize: WidgetSize): boolean { const needsRebuild = oldSize !== newSize; hilog.info(0x0000, TAG, `Size change from ${oldSize} to ${newSize}, needs rebuild: ${needsRebuild}`); return needsRebuild; } /** * 获取卡片尺寸的显示配置 * @param size 卡片尺寸 * @returns 显示配置 */ public getSizeDisplayConfig(size: WidgetSize): WidgetConfig { const baseConfig: WidgetConfig = { size: size, theme: 'auto', showProgress: true, showCover: true }; switch (size) { case WidgetSize.SMALL: return { size: baseConfig.size, theme: baseConfig.theme, showProgress: false, showCover: false }; case WidgetSize.MEDIUM: return { size: baseConfig.size, theme: baseConfig.theme, showProgress: false, showCover: true }; case WidgetSize.SQUARE: return { size: baseConfig.size, theme: baseConfig.theme, showProgress: false, showCover: true }; case WidgetSize.LARGE: return { size: baseConfig.size, theme: baseConfig.theme, showProgress: true, showCover: true }; default: return baseConfig; } } /** * 验证卡片尺寸是否有效 * @param size 卡片尺寸 * @returns 是否有效 */ public isValidSize(size: string): boolean { return Object.values(WidgetSize).includes(size as WidgetSize); } /** * 从字符串解析卡片尺寸 * @param sizeStr 尺寸字符串 * @returns 卡片尺寸枚举 */ public parseSizeFromString(sizeStr: string): WidgetSize { if (this.isValidSize(sizeStr)) { return sizeStr as WidgetSize; } // 尝试从维度字符串解析 (如 "1*2", "2*4", "2*2") switch (sizeStr) { case '1*2': case '1x2': return WidgetSize.SMALL; case '2*4': case '2x4': return WidgetSize.MEDIUM; case '2*2': case '2x2': return WidgetSize.SQUARE; case '4*3': case '4x3': return WidgetSize.LARGE; default: hilog.warn(0x0000, TAG, `Unknown size string: ${sizeStr}, using medium as default`); return WidgetSize.MEDIUM; } } /** * 获取响应式布局参数 * @param size 卡片尺寸 * @returns 布局参数 */ public getResponsiveLayoutParams(size: WidgetSize): ResponsiveLayoutParams { switch (size) { case WidgetSize.SMALL: const smallPadding: ContainerPadding = { left: 16, right: 16, top: 8, bottom: 8 }; const smallButtonSize: ButtonSize = { width: 32, height: 32 }; const smallPlayButtonSize: ButtonSize = { width: 36, height: 36 }; const smallFontSize: FontSizeConfig = { title: 14, artist: 12, time: 10 }; const smallSpacing: SpacingConfig = { horizontal: 8, vertical: 4 }; const smallShowElements: ShowElementsConfig = { progress: false, cover: false, album: false, time: false }; return { containerPadding: smallPadding, buttonSize: smallButtonSize, playButtonSize: smallPlayButtonSize, fontSize: smallFontSize, spacing: smallSpacing, showElements: smallShowElements }; case WidgetSize.MEDIUM: const mediumPadding: ContainerPadding = { left: 16, right: 16, top: 16, bottom: 16 }; const mediumButtonSize: ButtonSize = { width: 36, height: 36 }; const mediumPlayButtonSize: ButtonSize = { width: 44, height: 44 }; const mediumFontSize: FontSizeConfig = { title: 16, artist: 12, time: 10 }; const mediumSpacing: SpacingConfig = { horizontal: 12, vertical: 8 }; const mediumShowElements: ShowElementsConfig = { progress: true, cover: false, album: true, time: true }; return { containerPadding: mediumPadding, buttonSize: mediumButtonSize, playButtonSize: mediumPlayButtonSize, fontSize: mediumFontSize, spacing: mediumSpacing, showElements: mediumShowElements }; case WidgetSize.SQUARE: const squarePadding: ContainerPadding = { left: 16, right: 16, top: 16, bottom: 16 }; const squareButtonSize: ButtonSize = { width: 40, height: 40 }; const squarePlayButtonSize: ButtonSize = { width: 56, height: 56 }; const squareFontSize: FontSizeConfig = { title: 18, artist: 14, time: 12 }; const squareSpacing: SpacingConfig = { horizontal: 16, vertical: 12 }; const squareShowElements: ShowElementsConfig = { progress: false, cover: true, album: false, time: false }; return { containerPadding: squarePadding, buttonSize: squareButtonSize, playButtonSize: squarePlayButtonSize, fontSize: squareFontSize, spacing: squareSpacing, showElements: squareShowElements }; case WidgetSize.LARGE: const largePadding: ContainerPadding = { left: 16, right: 16, top: 16, bottom: 16 }; const largeButtonSize: ButtonSize = { width: 40, height: 40 }; const largePlayButtonSize: ButtonSize = { width: 52, height: 52 }; const largeFontSize: FontSizeConfig = { title: 16, artist: 13, time: 11 }; const largeSpacing: SpacingConfig = { horizontal: 16, vertical: 16 }; const largeShowElements: ShowElementsConfig = { progress: true, cover: true, album: true, time: true }; return { containerPadding: largePadding, buttonSize: largeButtonSize, playButtonSize: largePlayButtonSize, fontSize: largeFontSize, spacing: largeSpacing, showElements: largeShowElements }; default: return this.getResponsiveLayoutParams(WidgetSize.MEDIUM); } } }