1/* 2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import prompt from '@ohos.prompt'; 17import CommonEvent from '@ohos.commonEvent'; 18import { 19 AppItemInfo, 20 CheckEmptyUtils, 21 EventConstants, 22 CommonConstants, 23 GridLayoutItemInfo, 24 FormManager, 25 FormModel, 26 Logger, 27 LauncherAbilityManager, 28 MenuInfo, 29 RdbManager, 30 ResourceManager, 31 FormCardItem 32} from '@ohos/base'; 33import formHost from '@ohos.app.form.formHost'; 34import { BusinessError } from '@ohos.base'; 35 36const TAG: string = 'LayoutInfoModel'; 37 38export const SHOPPING_BUNDLE: string = 'com.samples.asorangeshopping'; 39 40const SYSTEM_APPLICATIONS: string = 'com.ohos.adminprovisioning,com.ohos.launcher,ohos.samples.launcher,com.ohos.systemui,com.ohos.devicemanagerui,com.ohos.callui,com.example.kikakeyboard,com.ohos.contactdataability,com.ohos.telephonydataability,com.ohos.medialibrary.MediaLibraryDataA,com.ohos.medialibrary.MediaScannerAbilityA' 41const KEY_NAME = 'name'; 42 43export class DesktopLayoutModel { 44 public static layoutInfoModel: DesktopLayoutModel | undefined = undefined; 45 private layoutInfo: Array<Array<GridLayoutItemInfo>> = []; 46 private readonly mSystemApplicationName = SYSTEM_APPLICATIONS.split(','); 47 private mLauncherAbilityManager: LauncherAbilityManager | undefined = undefined; 48 private context: Context; 49 50 constructor(context: Context) { 51 this.context = context 52 this.mLauncherAbilityManager = LauncherAbilityManager.getInstance(context); 53 this.mLauncherAbilityManager.registerLauncherAbilityChangeListener(this.appChangeListener); 54 } 55 56 appChangeListener = (event: string, bundleName: string, userId: string) => { 57 Logger.info(TAG, `appChangeListener event = ${event},bundle = ${bundleName}`); 58 FormModel.updateAppItemFormInfo(bundleName); 59 if (event === EventConstants.EVENT_PACKAGE_REMOVED) { 60 this.removeItemByBundle(bundleName); 61 } else if (event === EventConstants.EVENT_PACKAGE_ADDED) { 62 this.mLauncherAbilityManager?.getAppInfoByBundleName(bundleName).then(appInfo => { 63 Logger.debug(TAG, `appChangeListener EVENT_PACKAGE_ADDED,info = ${JSON.stringify(appInfo)}`); 64 this.addAppToDesktop(appInfo, true); 65 }) 66 } 67 } 68 69 70 /** 71 * Get the application data model object. 72 * 73 * @return {object} application data model singleton 74 */ 75 public static getInstance(context: Context): DesktopLayoutModel { 76 if (DesktopLayoutModel.layoutInfoModel == null || DesktopLayoutModel.layoutInfoModel === undefined) { 77 DesktopLayoutModel.layoutInfoModel = new DesktopLayoutModel(context); 78 } 79 return DesktopLayoutModel.layoutInfoModel; 80 } 81 82 private async removeItemByBundle(bundleName: string): Promise<void> { 83 let page = this.layoutInfo.length; 84 for (let i = 0;i < page; i++) { 85 for (let j = 0;j < this.layoutInfo[i].length; j++) { 86 if (this.layoutInfo[i][j].bundleName === bundleName) { 87 await this.removeItemFromDeskTop(this.layoutInfo[i][j]); 88 } 89 } 90 } 91 } 92 93 /** 94 * getAppItemFormInfo 95 * 96 * @param bundleName 97 */ 98 getAppItemFormInfo(bundleName: string) { 99 return FormModel.getAppItemFormInfo(bundleName); 100 } 101 102 /** 103 * buildMenuInfoList 104 * 105 * @param appInfo: GridLayoutItemInfo 106 */ 107 buildMenuInfoList(appInfo: GridLayoutItemInfo, dialog: CustomDialogController) { 108 if (CheckEmptyUtils.isEmpty(appInfo)) { 109 return undefined; 110 } 111 let menuInfoList = new Array<MenuInfo>(); 112 let open = new MenuInfo(); 113 open.menuImgSrc = $r('app.media.ic_public_add_norm'); 114 open.menuText = $r('app.string.app_menu_open'); 115 open.onMenuClick = () => { 116 this.jumpTo(appInfo.abilityName, appInfo.bundleName); 117 } 118 menuInfoList.push(open); 119 120 Logger.info(TAG, `buildMenuInfoList getAppItemFormInfo,bundleName = ${appInfo.bundleName}`); 121 const formInfoList = FormModel.getAppItemFormInfo(appInfo.bundleName); 122 Logger.info(TAG, `buildMenuInfoList formInfoList = ${JSON.stringify(formInfoList)}`); 123 if (!CheckEmptyUtils.isEmptyArr(formInfoList)) { 124 let addFormToDeskTopMenu = new MenuInfo(); 125 addFormToDeskTopMenu.menuImgSrc = $r('app.media.ic_public_app'); 126 addFormToDeskTopMenu.menuText = $r('app.string.add_form_to_desktop'); 127 addFormToDeskTopMenu.onMenuClick = () => { 128 Logger.info(TAG, 'Launcher click menu item into add form to desktop view'); 129 if (!CheckEmptyUtils.isEmpty(appInfo)) { 130 AppStorage.SetOrCreate('formAppInfo', appInfo); 131 Logger.info(TAG, 'Launcher AppStorage.SetOrCreate formAppInfo'); 132 this.jumpToFormManagerView(); 133 } 134 } 135 menuInfoList.push(addFormToDeskTopMenu); 136 } 137 138 const uninstallMenu = new MenuInfo(); 139 uninstallMenu.menuImgSrc = $r('app.media.ic_public_delete'); 140 uninstallMenu.menuText = $r('app.string.uninstall'); 141 uninstallMenu.onMenuClick = () => { 142 Logger.info(TAG, 'Launcher click menu item uninstall'); 143 if (!CheckEmptyUtils.isEmpty(dialog)) { 144 dialog.open(); 145 } 146 } 147 menuInfoList.push(uninstallMenu); 148 return menuInfoList; 149 } 150 151 /** 152 * buildCardInfoList 153 * 154 * @param dialog 155 */ 156 buildCardInfoList(dialog: CustomDialogController) { 157 let menuInfoList = new Array<MenuInfo>(); 158 const uninstallMenu = new MenuInfo(); 159 uninstallMenu.menuImgSrc = $r('app.media.ic_public_delete'); 160 uninstallMenu.menuText = $r('app.string.remove'); 161 uninstallMenu.onMenuClick = () => { 162 Logger.info(TAG, 'Launcher click menu item uninstall'); 163 if (!CheckEmptyUtils.isEmpty(dialog)) { 164 dialog.open(); 165 } 166 } 167 menuInfoList.push(uninstallMenu); 168 return menuInfoList; 169 } 170 171 /** 172 * getAppName 173 * 174 * @param cacheKey 175 */ 176 getAppName(cacheKey: string): string { 177 return ResourceManager.getInstance(this.context).getAppResourceCache(cacheKey, KEY_NAME); 178 } 179 180 /** 181 * jump to form manager 182 * @param formInfo 183 184 * */ 185 jumpToFormManagerView(): void { 186 CommonEvent.publish(EventConstants.EVENT_ENTER_FORM_MANAGER, () => { 187 Logger.info(TAG, 'publish EVENT_ENTER_FORM_MANAGER'); 188 }); 189 } 190 191 /** 192 * Start target ability 193 * 194 * @param bundleName target bundle name 195 * @param abilityName target ability name 196 */ 197 jumpTo(abilityName: string | undefined, bundleName: string | undefined): void { 198 this.mLauncherAbilityManager?.startLauncherAbilityFromRecent(abilityName, bundleName); 199 } 200 201 /** 202 * getLayoutInfoCache 203 */ 204 getLayoutInfoCache() { 205 return this.layoutInfo; 206 } 207 208 /** 209 * Get the list of apps displayed on the desktop (private function). 210 * 211 * @return {array} bundleInfoList, excluding system applications 212 */ 213 async getAppListAsync(): Promise<AppItemInfo[]> { 214 let allAbilityList: AppItemInfo[] | undefined = await this.mLauncherAbilityManager?.getLauncherAbilityList(); 215 Logger.info(TAG, `getAppListAsync allAbilityList length: ${allAbilityList?.length}`); 216 let launcherAbilityList: AppItemInfo[] = []; 217 for (let i = 0; i < allAbilityList!.length; i++) { 218 if (this.mSystemApplicationName.indexOf(allAbilityList![i].bundleName) === CommonConstants.INVALID_VALUE) { 219 launcherAbilityList.push(allAbilityList![i]); 220 FormModel.updateAppItemFormInfo(allAbilityList![i].bundleName); 221 } 222 } 223 Logger.debug(TAG, `getAppListAsync launcherAbiltyList length: ${launcherAbilityList.length}`); 224 return launcherAbilityList; 225 } 226 227 /** 228 * getLayoutInfo 229 */ 230 async getLayoutInfo() { 231 await RdbManager.initRdbConfig(this.context); 232 let infos = await this.getAppListAsync(); 233 let gridLayoutItemInfos = await RdbManager.queryLayoutInfo(); 234 Logger.info(TAG, `queryLayoutInfo,gridLayoutItemInfos = ${gridLayoutItemInfos.length}`); 235 let result: Array<Array<GridLayoutItemInfo>> = []; 236 let plusApps: Array<AppItemInfo> = []; 237 // 如果查询到的数据长度是0,说明之前没有过数据,此时初始化数据并插入 238 if (gridLayoutItemInfos.length === 0) { 239 let result = this.initPositionInfos(infos); 240 await RdbManager.insertData(result); 241 this.layoutInfo = result; 242 Logger.info(TAG, `getLayoutInfo result0,${JSON.stringify(this.layoutInfo)}`); 243 this.addEmptyCard(); 244 return result; 245 } 246 // 数据库中查询到了数据,则优先加载数据库中的应用和卡片,剩余的应用图标在最后一个图标的位置后面添加 247 else { 248 for (let i = 0;i < infos.length; i++) { 249 let find = false; 250 for (let j = 0;j < gridLayoutItemInfos.length; j++) { 251 if (infos[i].bundleName === gridLayoutItemInfos[j].bundleName) { 252 if (gridLayoutItemInfos[j].page >= result.length) { 253 result.push([]); 254 } 255 result[gridLayoutItemInfos[j].page].push(gridLayoutItemInfos[j]); 256 find = true; 257 } 258 } 259 if (!find) { 260 plusApps.push(infos[i]); 261 } 262 } 263 this.layoutInfo = result 264 Logger.info(TAG, `getLayoutInfo result1,${JSON.stringify(this.layoutInfo[0].length)}`); 265 // 加载完数据库中的后,剩余的app 266 if (plusApps.length > 0) { 267 Logger.info(TAG, `加载完数据库中的后,剩余的app`) 268 for (let k = 0;k < plusApps.length; k++) { 269 let item = plusApps[k]; 270 if (item) { 271 this.addAppToDesktop(item, false); 272 } 273 } 274 } 275 Logger.info(TAG, `getLayoutInfo result2,${JSON.stringify(result[0].length)}`); 276 this.addEmptyCard(); 277 return this.layoutInfo; 278 } 279 } 280 281 // mock一下桌面静态图的信息 282 private mockItem(): GridLayoutItemInfo { 283 let mockTemp = new GridLayoutItemInfo(); 284 mockTemp.typeId = CommonConstants.TYPE_IMAGE; 285 mockTemp.page = this.layoutInfo.length; 286 mockTemp.bundleName = SHOPPING_BUNDLE + '1'; 287 mockTemp.row = 0; 288 mockTemp.column = 0; 289 mockTemp.area = CommonConstants.DEFAULT_IMAGE_AREA; 290 return mockTemp; 291 } 292 293 // 检测免安装的应用是否已经替换过占位卡片,没有时添加一个空的占位卡片 294 private addEmptyCard(): void { 295 Logger.info(TAG, 'addEmptyCard'); 296 for (let i = 0; i < this.layoutInfo.length; i++) { 297 for (let j = 0; j < this.layoutInfo[i].length; j++) { 298 if (this.layoutInfo[i][j].bundleName === SHOPPING_BUNDLE) { 299 return; 300 } 301 } 302 } 303 this.layoutInfo.push([this.mockItem()]); 304 } 305 306 private async addAppToDesktop(appInfo: AppItemInfo | undefined, isRefresh: boolean) { 307 if (CheckEmptyUtils.isEmpty(appInfo)) { 308 return; 309 } 310 let pageInfos = this.layoutInfo; 311 for (let i = 0;i < pageInfos.length; i++) { 312 Logger.info(TAG, `removeCardFromDeskTop pageInfos${i}`); 313 for (let j = 0;j < pageInfos[i].length; j++) { 314 if (pageInfos[i][j].bundleName === appInfo?.bundleName && pageInfos[i][j].abilityName === appInfo?.abilityName) { 315 return; 316 } 317 } 318 } 319 let gridItem: GridLayoutItemInfo | undefined = this.covertAppItemToGridItem(appInfo, 0, 0, 0); 320 let page = this.layoutInfo.length; 321 gridItem = this.updateItemLayoutInfo(gridItem); 322 if (gridItem.page >= page) { 323 this.layoutInfo.push([]); 324 } 325 this.layoutInfo[gridItem.page].push(gridItem); 326 Logger.info(TAG, `addAppToDesktop item ${JSON.stringify(gridItem)}`); 327 await RdbManager.initRdbConfig(this.context); 328 await RdbManager.insertItem(gridItem); 329 if (isRefresh) { 330 AppStorage.SetOrCreate('isRefresh', true); 331 } 332 } 333 334 /** 335 * uninstallAppItem 336 * 337 * @param itemInfo: GridLayoutItemInfo 338 */ 339 async uninstallAppItem(itemInfo: GridLayoutItemInfo) { 340 if (CheckEmptyUtils.isEmpty(itemInfo)) { 341 return; 342 } 343 let appInfo = await this.mLauncherAbilityManager?.getAppInfoByBundleName(itemInfo.bundleName); 344 if (CheckEmptyUtils.isEmpty(appInfo)) { 345 return; 346 } 347 if (appInfo?.isUninstallAble) { 348 this.mLauncherAbilityManager?.uninstallLauncherAbility(itemInfo.bundleName, (err: BusinessError) => { 349 if (err.code == CommonConstants.UNINSTALL_SUCCESS) { 350 } 351 this.informUninstallResult(err.code); 352 }) 353 } else { 354 this.informUninstallResult(CommonConstants.UNINSTALL_FORBID); 355 } 356 } 357 358 private informUninstallResult(resultCode: number) { 359 let uninstallMessage: string = ''; 360 if (resultCode === CommonConstants.UNINSTALL_FORBID) { 361 uninstallMessage = this.context?.resourceManager.getStringSync($r('app.string.disable_uninstall').id); 362 } else if (resultCode === CommonConstants.UNINSTALL_SUCCESS) { 363 uninstallMessage = this.context.resourceManager.getStringSync($r('app.string.uninstall_success').id); 364 } else { 365 uninstallMessage = this.context.resourceManager.getStringSync($r('app.string.uninstall_failed').id); 366 } 367 prompt.showToast({ 368 message: uninstallMessage 369 }) 370 } 371 372 /** 373 * initPositionInfos 374 * 375 * @param appInfos 376 */ 377 initPositionInfos(appInfos: Array<AppItemInfo>) { 378 if (CheckEmptyUtils.isEmptyArr(appInfos)) { 379 return []; 380 } 381 Logger.info(TAG, `initPositionInfos, appInfos size = ${appInfos.length}`); 382 let countsOnePage = CommonConstants.DEFAULT_COLUMN_COUNT * CommonConstants.DEFAULT_ROW_COUNT; 383 let result: Array<Array<GridLayoutItemInfo>> = []; 384 let page = Math.floor(appInfos.length / countsOnePage) + 1; 385 for (let i = 0;i < page; i++) { 386 let item: Array<GridLayoutItemInfo> | undefined = []; 387 result.push(item); 388 } 389 Logger.info(TAG, `initPositionInfos result0 = ${JSON.stringify(result)}`); 390 for (let j = 0;j < appInfos.length; j++) { 391 let item = appInfos[j]; 392 Logger.info(TAG, `initPositionInfos infos[${j}], item = ${JSON.stringify(item)}`); 393 // 获取appLabelId,之后需要修改包名 394 if (appInfos[j].bundleName === SHOPPING_BUNDLE) { 395 AppStorage.SetOrCreate('cardLabelId', appInfos[j].appLabelId); 396 } 397 let page = Math.floor(j / countsOnePage); 398 let column = Math.floor(j % CommonConstants.DEFAULT_COLUMN_COUNT); 399 let row = Math.floor(j / CommonConstants.DEFAULT_COLUMN_COUNT) % countsOnePage; 400 let gridItem: GridLayoutItemInfo | undefined = this.covertAppItemToGridItem(item, page, column, row); 401 if (!CheckEmptyUtils.isEmpty(gridItem)) { 402 result[page].push(gridItem!); 403 } 404 Logger.info(TAG, `initPositionInfos infos[${j}], page = ${page},row = ${row},column = ${column}`); 405 } 406 Logger.info(TAG, `initPositionInfos result1 = ${JSON.stringify(result)}`); 407 return result; 408 } 409 410 private covertAppItemToGridItem(item: AppItemInfo | undefined, page: number, column: number, row: number) { 411 if (CheckEmptyUtils.isEmpty(item)) { 412 return undefined; 413 } 414 let gridItem: GridLayoutItemInfo = new GridLayoutItemInfo(); 415 gridItem.appName = item?.appName; 416 gridItem.appIconId = item?.appIconId; 417 gridItem.bundleName = item?.bundleName; 418 gridItem.moduleName = item?.moduleName; 419 gridItem.abilityName = item?.abilityName; 420 gridItem.container = -100; 421 gridItem.page = page; 422 gridItem.column = column; 423 gridItem.row = row; 424 gridItem.area = [1, 1]; 425 gridItem.typeId = 0; 426 return gridItem; 427 } 428 429 /** 430 * createCardToDeskTop 431 * 432 * @param formCardItem 433 */ 434 async createCardToDeskTop(formCardItem: FormCardItem) { 435 if (CheckEmptyUtils.isEmpty(formCardItem)) { 436 return; 437 } 438 Logger.info(TAG, `createCardToDeskTop formCardItem ${JSON.stringify(formCardItem)}`); 439 let gridItem = this.createNewCardItemInfo(formCardItem); 440 if (formCardItem.bundleName === SHOPPING_BUNDLE) { 441 gridItem!.page = this.layoutInfo.length; 442 gridItem!.row = 0; 443 gridItem!.column = 0; 444 } else { 445 gridItem = this.updateItemLayoutInfo(gridItem); 446 } 447 if (gridItem!.page >= this.layoutInfo.length) { 448 this.layoutInfo.push([]); 449 } 450 this.layoutInfo[gridItem!.page].push(gridItem!); 451 await RdbManager.initRdbConfig(this.context); 452 await RdbManager.insertItem(gridItem); 453 Logger.info(TAG, `createCardToDeskTop gridItem = ${JSON.stringify(gridItem)}`); 454 AppStorage.SetOrCreate('isRefresh', true); 455 } 456 457 /** 458 * remove item from desktop 459 * 460 * @param item 461 */ 462 async removeItemFromDeskTop(item: GridLayoutItemInfo) { 463 if (CheckEmptyUtils.isEmpty(item)) { 464 return; 465 } 466 Logger.info(TAG, 'removeCardFromDeskTop start'); 467 let pageInfos = this.layoutInfo; 468 searchCircle:for (let i = 0;i < pageInfos.length; i++) { 469 Logger.info(TAG, `removeCardFromDeskTop pageInfos${i}`); 470 for (let j = 0;j < pageInfos[i].length; j++) { 471 if (pageInfos[i][j].bundleName === item.bundleName && pageInfos[i][j].page === item.page 472 && pageInfos[i][j].row === item.row && pageInfos[i][j].column === item.column) { 473 Logger.debug(TAG, `removeCardFromDeskTop pageInfos${i}${j} is find,remove`); 474 pageInfos[i].splice(j, 1); 475 // 移除后是空白屏幕,移除屏幕 476 if (pageInfos[i].length === 0) { 477 pageInfos.splice(i, 1); 478 } 479 break searchCircle; 480 } 481 } 482 } 483 this.layoutInfo = pageInfos; 484 await RdbManager.deleteItemByPosition(item.page, item.row, item.column); 485 formHost.deleteForm(item.cardId.toString(), (err) => { 486 if (err) { 487 Logger.info(TAG, `deleteForm err: ${JSON.stringify(err)}`); 488 } else { 489 Logger.info(TAG, 'deleteForm success'); 490 } 491 }) 492 Logger.info(TAG, `removeCardFromDeskTop item= ${JSON.stringify(item)}`); 493 AppStorage.SetOrCreate('isRefresh', true); 494 } 495 496 private updateItemLayoutInfo(item: GridLayoutItemInfo | undefined): GridLayoutItemInfo { 497 Logger.info(TAG, 'updateItemLayoutInfo' + this.layoutInfo.length); 498 let page = this.layoutInfo.length; 499 const row = CommonConstants.DEFAULT_ROW_COUNT; 500 const column = CommonConstants.DEFAULT_COLUMN_COUNT; 501 let isNeedNewPage = true; 502 for (let i = 0; i < page; i++) { 503 for (let y = 0; y < row; y++) { 504 for (let x = 0; x < column; x++) { 505 Logger.info(TAG, `updateItemLayoutInfo page=${page}, startColumn=${x}, startRow=${y}`); 506 if (this.isPositionValid(item, i, x, y)) { 507 isNeedNewPage = false 508 item!.page = i 509 item!.column = x 510 item!.row = y 511 return item!; 512 } 513 } 514 } 515 } 516 517 if (isNeedNewPage) { 518 item!.page = page 519 item!.column = 0 520 item!.row = 0 521 } 522 return item!; 523 } 524 525 private isPositionValid(item: GridLayoutItemInfo | undefined, page: number, startColumn: number, startRow: number) { 526 const row = CommonConstants.DEFAULT_ROW_COUNT; 527 const column = CommonConstants.DEFAULT_COLUMN_COUNT; 528 if ((startRow + item!.area[0]) > row || (startColumn + item!.area[1]) > column) { 529 Logger.info(TAG, 'isPositionValid return false 1'); 530 return false; 531 } 532 let isValid = true; 533 for (let x = startColumn; x < startColumn + item!.area[1]; x++) { 534 for (let y = startRow; y < startRow + item!.area[0]; y++) { 535 if (this.isPositionOccupied(page, x, y)) { 536 Logger.info(TAG, `isPositionValid isPositionOccupied page=${page},x=${x},y=${y}`); 537 isValid = false; 538 break; 539 } 540 } 541 } 542 return isValid; 543 } 544 545 private isPositionOccupied(page: number, column: number, row: number) { 546 const layoutInfo = this.layoutInfo[page]; 547 // current page has space 548 for (let item of layoutInfo) { 549 const xMatch = (column >= item.column) && (column < item.column + item.area[1]); 550 const yMatch = (row >= item.row) && (row < item.row + item.area[0]); 551 if (xMatch && yMatch) { 552 return true; 553 } 554 } 555 return false; 556 } 557 558 private createNewCardItemInfo(formCardItem: FormCardItem): GridLayoutItemInfo | undefined { 559 if (CheckEmptyUtils.isEmpty(formCardItem)) { 560 return undefined; 561 } 562 let gridItem: GridLayoutItemInfo = new GridLayoutItemInfo(); 563 gridItem.appName = formCardItem.appName; 564 gridItem.typeId = CommonConstants.TYPE_CARD; 565 gridItem.cardId = formCardItem.cardId; 566 gridItem.cardName = formCardItem.cardName; 567 gridItem.bundleName = formCardItem.bundleName; 568 gridItem.moduleName = formCardItem.moduleName; 569 gridItem.abilityName = formCardItem.abilityName; 570 gridItem.container = -100; 571 gridItem.page = 0; 572 gridItem.column = 0; 573 gridItem.row = 0; 574 gridItem.area = FormManager.getCardSize(formCardItem.dimension); 575 return gridItem; 576 } 577}