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}`); 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.LARGE: return this.adaptForLargeWidget(baseData, widgetData); default: hilog.warn(0x0000, TAG, `Unknown widget size: ${size}, using medium as default`); return this.adaptForMediumWidget(baseData, widgetData); } } /** * 获取卡片尺寸对应的页面路径 * @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.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: const smallConfig: WidgetConfig = { size: baseConfig.size, theme: baseConfig.theme, showProgress: false, showCover: false }; return smallConfig; case WidgetSize.MEDIUM: const mediumConfig: WidgetConfig = { size: baseConfig.size, theme: baseConfig.theme, showProgress: true, showCover: false }; return mediumConfig; case WidgetSize.LARGE: const largeConfig: WidgetConfig = { size: baseConfig.size, theme: baseConfig.theme, showProgress: true, showCover: true }; return largeConfig; 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; } // 尝试从维度字符串解析 (如 "2*1", "4*2", "4*3") switch (sizeStr) { case '2*1': case '2x1': return WidgetSize.SMALL; case '4*2': case '4x2': return WidgetSize.MEDIUM; 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.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); } } /** * 格式化基础数据 */ private formatBaseData(widgetData: WidgetData): FormattedWidgetData { return { isPlaying: widgetData.playState.isPlaying, isPaused: widgetData.playState.isPaused, isLoading: widgetData.playState.isLoading, songTitle: widgetData.currentSong.title || '暂无播放', songArtist: widgetData.currentSong.artist || '未知艺术家', songAlbum: widgetData.currentSong.album || '未知专辑', coverImage: widgetData.currentSong.coverImagePath || '', currentTime: widgetData.progress.currentTimeText || '00:00', totalTime: widgetData.progress.totalTimeText || '00:00', progressPercentage: widgetData.progress.percentage || 0, hasNext: widgetData.playlist.hasNext, hasPrevious: widgetData.playlist.hasPrevious, showProgress: widgetData.config.showProgress, showCover: widgetData.config.showCover, widgetSize: widgetData.config.size.toString(), timestamp: Date.now() }; } /** * 适配小尺寸卡片数据 */ private adaptForSmallWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData { const result: FormattedWidgetData = { isPlaying: baseData.isPlaying, isPaused: baseData.isPaused, isLoading: baseData.isLoading, songTitle: this.truncateText(baseData.songTitle, 20), // 小卡片只显示歌曲标题,如果标题过长则截断 songArtist: baseData.songArtist, 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: WidgetSize.SMALL, timestamp: baseData.timestamp }; return result; } /** * 适配中等尺寸卡片数据 */ private adaptForMediumWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData { const result: FormattedWidgetData = { isPlaying: baseData.isPlaying, isPaused: baseData.isPaused, isLoading: baseData.isLoading, songTitle: this.truncateText(baseData.songTitle, 30), // 中等卡片显示更多信息,但仍需要适当截断 songArtist: this.truncateText(baseData.songArtist, 25), songAlbum: this.truncateText(baseData.songAlbum, 25), coverImage: baseData.coverImage, currentTime: baseData.currentTime, totalTime: baseData.totalTime, progressPercentage: baseData.progressPercentage, hasNext: baseData.hasNext, hasPrevious: baseData.hasPrevious, showProgress: true, showCover: false, widgetSize: WidgetSize.MEDIUM, timestamp: baseData.timestamp }; return result; } /** * 适配大尺寸卡片数据 */ private adaptForLargeWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData { const result: FormattedWidgetData = { isPlaying: baseData.isPlaying, isPaused: baseData.isPaused, isLoading: baseData.isLoading, songTitle: this.truncateText(baseData.songTitle, 40), // 大卡片可以显示完整信息 songArtist: this.truncateText(baseData.songArtist, 35), songAlbum: this.truncateText(baseData.songAlbum, 35), coverImage: baseData.coverImage, currentTime: baseData.currentTime, totalTime: baseData.totalTime, progressPercentage: baseData.progressPercentage, hasNext: baseData.hasNext, hasPrevious: baseData.hasPrevious, showProgress: true, showCover: true, widgetSize: WidgetSize.LARGE, timestamp: baseData.timestamp }; return result; } /** * 截断文本 */ private truncateText(text: string, maxLength: number): string { if (!text || text.length <= maxLength) { return text; } return text.substring(0, maxLength - 3) + '...'; } }