FormLayoutManager.ets 14 KB

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