FormLayoutManager.ets 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. import { WidgetSize, WidgetData, FormattedWidgetData, WidgetConfig, ContainerPadding, ButtonSize, FontSizeConfig, SpacingConfig, ShowElementsConfig, ResponsiveLayoutParams } from './WidgetTypes';
  2. import hilog from '@ohos.hilog';
  3. const TAG = 'FormLayoutManager';
  4. /**
  5. * 卡片布局管理器
  6. * 负责处理多尺寸卡片的布局适配和UI重构
  7. * 需求: 5.4, 5.5
  8. */
  9. export class FormLayoutManager {
  10. private static instance: FormLayoutManager;
  11. /**
  12. * 获取单例实例
  13. */
  14. public static getInstance(): FormLayoutManager {
  15. if (!FormLayoutManager.instance) {
  16. FormLayoutManager.instance = new FormLayoutManager();
  17. }
  18. return FormLayoutManager.instance;
  19. }
  20. private constructor() {
  21. hilog.info(0x0000, TAG, 'FormLayoutManager initialized');
  22. }
  23. /**
  24. * 根据卡片尺寸适配数据
  25. * @param widgetData 原始卡片数据
  26. * @param size 卡片尺寸
  27. * @returns 适配后的格式化数据
  28. */
  29. public adaptDataForSize(widgetData: WidgetData, size: WidgetSize): FormattedWidgetData {
  30. hilog.info(0x0000, TAG, `Adapting data for size: ${size}, coverImagePath: ${widgetData.currentSong.coverImagePath}`);
  31. const baseData = this.formatBaseData(widgetData);
  32. switch (size) {
  33. case WidgetSize.SMALL:
  34. return this.adaptForSmallWidget(baseData, widgetData);
  35. case WidgetSize.MEDIUM:
  36. return this.adaptForMediumWidget(baseData, widgetData);
  37. case WidgetSize.SQUARE:
  38. return this.adaptForSquareWidget(baseData, widgetData);
  39. case WidgetSize.LARGE:
  40. return this.adaptForLargeWidget(baseData, widgetData);
  41. default:
  42. hilog.warn(0x0000, TAG, `Unknown widget size: ${size}, using medium as default`);
  43. return this.adaptForMediumWidget(baseData, widgetData);
  44. }
  45. }
  46. /**
  47. * 格式化基础数据
  48. */
  49. private formatBaseData(data: WidgetData): FormattedWidgetData {
  50. hilog.info(0x0000, TAG, `Formatting base data: coverImagePath=${data.currentSong.coverImagePath}`);
  51. return {
  52. // 播放状态
  53. isPlaying: data.playState.isPlaying,
  54. isPaused: data.playState.isPaused,
  55. isLoading: data.playState.isLoading,
  56. // 歌曲信息
  57. songTitle: this.truncateText(data.currentSong.title, 30),
  58. songArtist: this.truncateText(data.currentSong.artist, 20),
  59. songAlbum: this.truncateText(data.currentSong.album, 20),
  60. coverImage: data.currentSong.coverImagePath || '',
  61. // 播放进度
  62. currentTime: data.progress.currentTimeText,
  63. totalTime: data.progress.totalTimeText,
  64. progressPercentage: data.progress.percentage,
  65. // 控制按钮状态
  66. hasNext: data.playlist.hasNext,
  67. hasPrevious: data.playlist.hasPrevious,
  68. // 卡片配置
  69. showProgress: data.config.showProgress,
  70. showCover: data.config.showCover,
  71. widgetSize: data.config.size as string,
  72. // 时间戳用于强制更新
  73. timestamp: Date.now()
  74. };
  75. }
  76. /**
  77. * 适配小尺寸卡片
  78. */
  79. private adaptForSmallWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData {
  80. return {
  81. isPlaying: baseData.isPlaying,
  82. isPaused: baseData.isPaused,
  83. isLoading: baseData.isLoading,
  84. songTitle: this.truncateText(widgetData.currentSong.title, 15),
  85. songArtist: this.truncateText(widgetData.currentSong.artist, 12),
  86. songAlbum: baseData.songAlbum,
  87. coverImage: baseData.coverImage,
  88. currentTime: baseData.currentTime,
  89. totalTime: baseData.totalTime,
  90. progressPercentage: baseData.progressPercentage,
  91. hasNext: baseData.hasNext,
  92. hasPrevious: baseData.hasPrevious,
  93. showProgress: false,
  94. showCover: false,
  95. widgetSize: baseData.widgetSize,
  96. timestamp: baseData.timestamp
  97. };
  98. }
  99. /**
  100. * 适配中等尺寸卡片
  101. */
  102. private adaptForMediumWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData {
  103. hilog.info(0x0000, TAG, `Adapting for medium widget: coverImage=${baseData.coverImage}`);
  104. return {
  105. isPlaying: baseData.isPlaying,
  106. isPaused: baseData.isPaused,
  107. isLoading: baseData.isLoading,
  108. songTitle: this.truncateText(widgetData.currentSong.title, 25),
  109. songArtist: this.truncateText(widgetData.currentSong.artist, 18),
  110. songAlbum: baseData.songAlbum,
  111. coverImage: widgetData.currentSong.coverImagePath || '', // 确保coverImage被正确传递
  112. currentTime: baseData.currentTime,
  113. totalTime: baseData.totalTime,
  114. progressPercentage: baseData.progressPercentage,
  115. hasNext: baseData.hasNext,
  116. hasPrevious: baseData.hasPrevious,
  117. showProgress: false,
  118. showCover: true,
  119. widgetSize: baseData.widgetSize,
  120. timestamp: baseData.timestamp
  121. };
  122. }
  123. /**
  124. * 适配方形尺寸卡片 (2x2)
  125. */
  126. private adaptForSquareWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData {
  127. hilog.info(0x0000, TAG, `Adapting for square widget: coverImage=${baseData.coverImage}`);
  128. return {
  129. isPlaying: baseData.isPlaying,
  130. isPaused: baseData.isPaused,
  131. isLoading: baseData.isLoading,
  132. songTitle: this.truncateText(widgetData.currentSong.title, 20),
  133. songArtist: this.truncateText(widgetData.currentSong.artist, 15),
  134. songAlbum: baseData.songAlbum,
  135. coverImage: widgetData.currentSong.coverImagePath || '', // 确保coverImage被正确传递
  136. currentTime: baseData.currentTime,
  137. totalTime: baseData.totalTime,
  138. progressPercentage: baseData.progressPercentage,
  139. hasNext: baseData.hasNext,
  140. hasPrevious: baseData.hasPrevious,
  141. showProgress: false,
  142. showCover: true,
  143. widgetSize: baseData.widgetSize,
  144. timestamp: baseData.timestamp
  145. };
  146. }
  147. /**
  148. * 适配大尺寸卡片
  149. */
  150. private adaptForLargeWidget(baseData: FormattedWidgetData, widgetData: WidgetData): FormattedWidgetData {
  151. return {
  152. isPlaying: baseData.isPlaying,
  153. isPaused: baseData.isPaused,
  154. isLoading: baseData.isLoading,
  155. songTitle: this.truncateText(widgetData.currentSong.title, 35),
  156. songArtist: this.truncateText(widgetData.currentSong.artist, 25),
  157. songAlbum: baseData.songAlbum,
  158. coverImage: baseData.coverImage,
  159. currentTime: baseData.currentTime,
  160. totalTime: baseData.totalTime,
  161. progressPercentage: baseData.progressPercentage,
  162. hasNext: baseData.hasNext,
  163. hasPrevious: baseData.hasPrevious,
  164. showProgress: true,
  165. showCover: true,
  166. widgetSize: baseData.widgetSize,
  167. timestamp: baseData.timestamp
  168. };
  169. }
  170. /**
  171. * 截断文本
  172. */
  173. private truncateText(text: string, maxLength: number): string {
  174. if (!text || text.length <= maxLength) {
  175. return text || '';
  176. }
  177. return text.substring(0, maxLength - 1) + '…';
  178. }
  179. /**
  180. * 获取卡片尺寸对应的页面路径
  181. * @param size 卡片尺寸
  182. * @returns 页面路径
  183. */
  184. public getWidgetPagePath(size: WidgetSize): string {
  185. switch (size) {
  186. case WidgetSize.SMALL:
  187. return 'widget/pages/PlayerWidgetSmall';
  188. case WidgetSize.MEDIUM:
  189. return 'widget/pages/PlayerWidgetMedium';
  190. case WidgetSize.SQUARE:
  191. return 'widget/pages/PlayerWidgetSquare';
  192. case WidgetSize.LARGE:
  193. return 'widget/pages/PlayerWidgetLarge';
  194. default:
  195. hilog.warn(0x0000, TAG, `Unknown widget size: ${size}, using medium as default`);
  196. return 'widget/pages/PlayerWidgetMedium';
  197. }
  198. }
  199. /**
  200. * 检查尺寸变化并返回是否需要重构UI
  201. * @param oldSize 旧尺寸
  202. * @param newSize 新尺寸
  203. * @returns 是否需要重构UI
  204. */
  205. public shouldRebuildUI(oldSize: WidgetSize, newSize: WidgetSize): boolean {
  206. const needsRebuild = oldSize !== newSize;
  207. hilog.info(0x0000, TAG, `Size change from ${oldSize} to ${newSize}, needs rebuild: ${needsRebuild}`);
  208. return needsRebuild;
  209. }
  210. /**
  211. * 获取卡片尺寸的显示配置
  212. * @param size 卡片尺寸
  213. * @returns 显示配置
  214. */
  215. public getSizeDisplayConfig(size: WidgetSize): WidgetConfig {
  216. const baseConfig: WidgetConfig = {
  217. size: size,
  218. theme: 'auto',
  219. showProgress: true,
  220. showCover: true
  221. };
  222. switch (size) {
  223. case WidgetSize.SMALL:
  224. return {
  225. size: baseConfig.size,
  226. theme: baseConfig.theme,
  227. showProgress: false,
  228. showCover: false
  229. };
  230. case WidgetSize.MEDIUM:
  231. return {
  232. size: baseConfig.size,
  233. theme: baseConfig.theme,
  234. showProgress: false,
  235. showCover: true
  236. };
  237. case WidgetSize.SQUARE:
  238. return {
  239. size: baseConfig.size,
  240. theme: baseConfig.theme,
  241. showProgress: false,
  242. showCover: true
  243. };
  244. case WidgetSize.LARGE:
  245. return {
  246. size: baseConfig.size,
  247. theme: baseConfig.theme,
  248. showProgress: true,
  249. showCover: true
  250. };
  251. default:
  252. return baseConfig;
  253. }
  254. }
  255. /**
  256. * 验证卡片尺寸是否有效
  257. * @param size 卡片尺寸
  258. * @returns 是否有效
  259. */
  260. public isValidSize(size: string): boolean {
  261. return Object.values(WidgetSize).includes(size as WidgetSize);
  262. }
  263. /**
  264. * 从字符串解析卡片尺寸
  265. * @param sizeStr 尺寸字符串
  266. * @returns 卡片尺寸枚举
  267. */
  268. public parseSizeFromString(sizeStr: string): WidgetSize {
  269. if (this.isValidSize(sizeStr)) {
  270. return sizeStr as WidgetSize;
  271. }
  272. // 尝试从维度字符串解析 (如 "1*2", "2*4", "2*2")
  273. switch (sizeStr) {
  274. case '1*2':
  275. case '1x2':
  276. return WidgetSize.SMALL;
  277. case '2*4':
  278. case '2x4':
  279. return WidgetSize.MEDIUM;
  280. case '2*2':
  281. case '2x2':
  282. return WidgetSize.SQUARE;
  283. case '4*3':
  284. case '4x3':
  285. return WidgetSize.LARGE;
  286. default:
  287. hilog.warn(0x0000, TAG, `Unknown size string: ${sizeStr}, using medium as default`);
  288. return WidgetSize.MEDIUM;
  289. }
  290. }
  291. /**
  292. * 获取响应式布局参数
  293. * @param size 卡片尺寸
  294. * @returns 布局参数
  295. */
  296. public getResponsiveLayoutParams(size: WidgetSize): ResponsiveLayoutParams {
  297. switch (size) {
  298. case WidgetSize.SMALL:
  299. const smallPadding: ContainerPadding = { left: 16, right: 16, top: 8, bottom: 8 };
  300. const smallButtonSize: ButtonSize = { width: 32, height: 32 };
  301. const smallPlayButtonSize: ButtonSize = { width: 36, height: 36 };
  302. const smallFontSize: FontSizeConfig = { title: 14, artist: 12, time: 10 };
  303. const smallSpacing: SpacingConfig = { horizontal: 8, vertical: 4 };
  304. const smallShowElements: ShowElementsConfig = {
  305. progress: false,
  306. cover: false,
  307. album: false,
  308. time: false
  309. };
  310. return {
  311. containerPadding: smallPadding,
  312. buttonSize: smallButtonSize,
  313. playButtonSize: smallPlayButtonSize,
  314. fontSize: smallFontSize,
  315. spacing: smallSpacing,
  316. showElements: smallShowElements
  317. };
  318. case WidgetSize.MEDIUM:
  319. const mediumPadding: ContainerPadding = { left: 16, right: 16, top: 16, bottom: 16 };
  320. const mediumButtonSize: ButtonSize = { width: 36, height: 36 };
  321. const mediumPlayButtonSize: ButtonSize = { width: 44, height: 44 };
  322. const mediumFontSize: FontSizeConfig = { title: 16, artist: 12, time: 10 };
  323. const mediumSpacing: SpacingConfig = { horizontal: 12, vertical: 8 };
  324. const mediumShowElements: ShowElementsConfig = {
  325. progress: true,
  326. cover: false,
  327. album: true,
  328. time: true
  329. };
  330. return {
  331. containerPadding: mediumPadding,
  332. buttonSize: mediumButtonSize,
  333. playButtonSize: mediumPlayButtonSize,
  334. fontSize: mediumFontSize,
  335. spacing: mediumSpacing,
  336. showElements: mediumShowElements
  337. };
  338. case WidgetSize.SQUARE:
  339. const squarePadding: ContainerPadding = { left: 16, right: 16, top: 16, bottom: 16 };
  340. const squareButtonSize: ButtonSize = { width: 40, height: 40 };
  341. const squarePlayButtonSize: ButtonSize = { width: 56, height: 56 };
  342. const squareFontSize: FontSizeConfig = { title: 18, artist: 14, time: 12 };
  343. const squareSpacing: SpacingConfig = { horizontal: 16, vertical: 12 };
  344. const squareShowElements: ShowElementsConfig = {
  345. progress: false,
  346. cover: true,
  347. album: false,
  348. time: false
  349. };
  350. return {
  351. containerPadding: squarePadding,
  352. buttonSize: squareButtonSize,
  353. playButtonSize: squarePlayButtonSize,
  354. fontSize: squareFontSize,
  355. spacing: squareSpacing,
  356. showElements: squareShowElements
  357. };
  358. case WidgetSize.LARGE:
  359. const largePadding: ContainerPadding = { left: 16, right: 16, top: 16, bottom: 16 };
  360. const largeButtonSize: ButtonSize = { width: 40, height: 40 };
  361. const largePlayButtonSize: ButtonSize = { width: 52, height: 52 };
  362. const largeFontSize: FontSizeConfig = { title: 16, artist: 13, time: 11 };
  363. const largeSpacing: SpacingConfig = { horizontal: 16, vertical: 16 };
  364. const largeShowElements: ShowElementsConfig = {
  365. progress: true,
  366. cover: true,
  367. album: true,
  368. time: true
  369. };
  370. return {
  371. containerPadding: largePadding,
  372. buttonSize: largeButtonSize,
  373. playButtonSize: largePlayButtonSize,
  374. fontSize: largeFontSize,
  375. spacing: largeSpacing,
  376. showElements: largeShowElements
  377. };
  378. default:
  379. return this.getResponsiveLayoutParams(WidgetSize.MEDIUM);
  380. }
  381. }
  382. }