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 Curves from '@ohos.curves'; 17import { Log } from '../utils/Log'; 18import { Constants } from '../model/common/Constants'; 19import { Constants as PhotoConstants } from '../model/browser/photo/Constants'; 20import { MediaItem } from '../model/browser/photo/MediaItem'; 21import { DateUtil } from '../utils/DateUtil'; 22import { BroadCast } from '../utils/BroadCast'; 23import { BroadCastConstants } from '../model/common/BroadCastConstants'; 24import { Action } from './browserOperation/Action'; 25import { ImageUtil } from '../utils/ImageUtil'; 26import { ColumnSize, ScreenManager } from '../model/common/ScreenManager'; 27import { TraceControllerUtils } from '../utils/TraceControllerUtils'; 28import { UserFileManagerAccess } from '../access/UserFileManagerAccess'; 29import { MultimodalInputManager } from '../model/common/MultimodalInputManager'; 30import { BigDataConstants, ReportToBigDataUtil } from '../utils/ReportToBigDataUtil'; 31import { AlbumDefine } from '../model/browser/AlbumDefine'; 32import { MediaDataSource } from '../model/browser/photo/MediaDataSource'; 33import { BroadCastManager } from '../model/common/BroadCastManager'; 34 35const TAG: string = 'common_ImageGridItemComponent'; 36 37@Extend(Image) function focusSetting(uri: string, handleEvent: Function) { 38 .key('ImageGridFocus_' + uri) 39 .focusable(true) 40 .onKeyEvent((event?: KeyEvent) => { 41 handleEvent((event as KeyEvent)); 42 }) 43} 44 45interface Msg { 46 from: string; 47} 48 49// General grid picture control 50@Component 51export struct ImageGridItemComponent { 52 item: MediaItem | null = null; 53 @StorageLink('isHorizontal') isHorizontal: boolean = ScreenManager.getInstance().isHorizontal(); 54 @Consume @Watch('onModeChange') isSelectedMode: boolean; 55 @State isSelected: boolean = false; 56 isRecycle: boolean = false; 57 @Consume broadCast: BroadCast; 58 @Consume @Watch('onShow') isShow: boolean; 59 @Link selectedCount: number; 60 @State autoResize: boolean = true; 61 loaded = false; 62 mPosition: number = 0; 63 pageName = ''; 64 @State isLoadImageError: boolean = false; 65 @State pressAnimScale: number = 1.0; 66 @State recycleDays: number = 0; 67 @Consume rightClickMenuList: Array<Action>; 68 onMenuClicked: Function = (): void => {}; 69 onMenuClickedForSingleItem: Function = (): void => {}; 70 @State geometryTransitionString: string = 'default_id'; 71 @State isTap: boolean = false; 72 @StorageLink('placeholderIndex') @Watch('verifyTapStatus') placeholderIndex: number = -1; 73 @StorageLink('geometryTransitionBrowserId') @Watch('verifyTapStatus') geometryTransitionBrowserId: string = ''; 74 private imageThumbnail: string = ''; 75 private transitionId: string = ''; 76 private isEnteringPhoto = false; 77 private isThird = false; 78 private isThirdMultiPick: boolean = false; 79 private albumUri: string = ''; 80 private dataSource: MediaDataSource | null = null; 81 private geometryTapIndex: number = 0; 82 private isTapStatusChange: boolean = false; 83 private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast(); 84 private updateSelectFunc: Function = (updateUri: string, select: boolean): void => this.updateSelect(updateUri, select); 85 86 verifyTapStatus() { 87 if (this.placeholderIndex === Constants.INVALID) { 88 this.isTap = false; 89 return; 90 } 91 this.updateGeometryTapInfo(); 92 let pageFromGlobal = this.geometryTransitionBrowserId.split(':')[0]; 93 let pageFrom = this.geometryTransitionString.split(':')[0]; 94 let oldTapStatus = this.isTap; 95 let newTapStatus = (pageFromGlobal === pageFrom) && (this.placeholderIndex === this.geometryTapIndex); 96 this.isTapStatusChange = oldTapStatus !== newTapStatus; 97 this.isTap = newTapStatus; 98 if (this.isTap) { 99 this.geometryTransitionString = this.geometryTransitionBrowserId; 100 Log.debug(TAG, 'update placeholderIndex = ' + this.placeholderIndex + 101 'geometryTapIndex = ' + this.geometryTapIndex + ', isTap = ' + this.isTap + 102 ', geometryTransitionString = ' + this.geometryTransitionString); 103 } 104 } 105 106 aboutToAppear(): void { 107 this.imageThumbnail = this.item?.thumbnail ?? ''; 108 this.albumUri = AppStorage.get<string>(Constants.KEY_OF_ALBUM_URI) as string; 109 if (this.item != null) { 110 if (this.isSelected) { 111 this.transitionId = `${this.item.hashCode}_${this.albumUri}_${this.isSelected}`; 112 } else { 113 this.transitionId = `${this.item.hashCode}_${this.albumUri}`; 114 } 115 } 116 if (this.isRecycle) { 117 this.calculateRecycleDays(); 118 } 119 Log.info(TAG, `transitionId: ${this.transitionId}`); 120 this.isTap = this.geometryTransitionString === this.geometryTransitionBrowserId; 121 this.appBroadCast.on(BroadCastConstants.UPDATE_SELECT, this.updateSelectFunc); 122 } 123 124 updateSelect(updateUri: string, select: boolean): void { 125 if (updateUri === this.item?.uri) { 126 this.isSelected = select; 127 } 128 } 129 130 aboutToDisappear(): void { 131 this.appBroadCast.off(BroadCastConstants.UPDATE_SELECT, this.updateSelectFunc); 132 this.resetPressAnim(); 133 } 134 135 onModeChange(newMode: boolean): void { 136 Log.debug(TAG, `newMode ${newMode}`); 137 if (!this.isSelectedMode) { 138 this.isSelected = false; 139 } 140 } 141 142 onAllSelect(newMode: boolean): boolean { 143 Log.debug(TAG, `onAllSelect ${newMode}`); 144 return newMode; 145 } 146 147 async routePage(isError: boolean) { 148 Log.info(TAG, `routePage ${isError}`); 149 try { 150 TraceControllerUtils.startTrace('enterPhotoBrowser'); 151 if (this.isThird) { 152 this.broadCast.emit(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, [this.pageName, this.item]); 153 } else { 154 this.broadCast.emit(BroadCastConstants.JUMP_PHOTO_BROWSER, [this.pageName, this.item]); 155 } 156 } catch (err) { 157 Log.error(TAG, `fail callback, code: ${err.code}, msg: ${err.msg}`); 158 } 159 } 160 161 async routeToPreviewPage() { 162 try { 163 Log.info(TAG, 'routeToPreviewPage'); 164 this.updateGeometryTapInfo(); 165 this.broadCast.emit(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, 166 [this.pageName, this.item, this.geometryTapIndex, this.geometryTransitionString]); 167 } catch (err) { 168 Log.error(TAG, `fail callback, code: ${err.code}, msg: ${err.msg}`); 169 } 170 } 171 172 selectStateChange() { 173 Log.info(TAG, 'change selected.'); 174 let newState = !this.isSelected; 175 AppStorage.SetOrCreate('focusUpdate', true); 176 if (this.item != null && this.item.uri) { 177 this.mPosition = this.getPosition(); 178 this.broadCast.emit(BroadCastConstants.SELECT, [this.mPosition, this.item.uri, newState, (isSelected: boolean): void => { 179 let itemUri: string = this.item == null ? '' : this.item.uri; 180 Log.info(TAG, `enter callback, select status ${this.mPosition} ${itemUri} ${newState} ${this.isSelected}`); 181 this.isSelected = isSelected == undefined ? newState : isSelected; 182 }]); 183 } 184 } 185 186 @Builder RightClickMenuBuilder() { 187 Column() { 188 ForEach(this.rightClickMenuList, (menu: Action) => { 189 Text(this.changeTextResToPlural(menu)) 190 .key('RightClick_' + this.mPosition + menu.componentKey) 191 .width('100%') 192 .height($r('app.float.menu_height')) 193 .fontColor(menu.fillColor) 194 .fontSize($r('sys.float.ohos_id_text_size_body1')) 195 .fontWeight(FontWeight.Regular) 196 .maxLines(2) 197 .textOverflow({ overflow: TextOverflow.Ellipsis }) 198 .onClick(() => { 199 Log.info(TAG, 'on right click menu, action: ' + menu.actionID); 200 if (menu == Action.MULTISELECT) { 201 this.selectStateChange(); 202 } else { 203 // 1.当鼠标对着被选中的项按右键时,菜单中的功能,针对所有被选中的项做处理 204 // 2.当鼠标对着未被选中的项按右键时,菜单中的功能,仅针对当前项处理,其他被选中的项不做任何处理 205 if (this.isSelectedMode && this.isSelected) { 206 this.onMenuClicked && this.onMenuClicked(menu); 207 } else { 208 this.onMenuClickedForSingleItem && this.onMenuClickedForSingleItem(menu, this.item); 209 } 210 } 211 }) 212 }, (item: Action) => JSON.stringify(item)) 213 } 214 .width(ScreenManager.getInstance().getColumnsWidth(ColumnSize.COLUMN_TWO)) 215 .borderRadius($r('sys.float.ohos_id_corner_radius_card')) 216 .padding({ 217 top: $r('app.float.menu_padding_vertical'), 218 bottom: $r('app.float.menu_padding_vertical'), 219 left: $r('app.float.menu_padding_horizontal'), 220 right: $r('app.float.menu_padding_horizontal') 221 }) 222 .backgroundColor(Color.White) 223 .margin({ 224 right: $r('sys.float.ohos_id_max_padding_end'), 225 bottom: $r('app.float.menu_margin_bottom') 226 }) 227 } 228 229 230 build() { 231 Column() { 232 if (this.isTap) { 233 Column() { 234 } 235 .aspectRatio(1) 236 .rotate({ x: 0, y: 0, z: 1, angle: 0 }) 237 .backgroundColor($r('app.color.default_background_color')) 238 .width("100%") 239 .height("100%") 240 .zIndex(-1) 241 } else { 242 this.buildNormal() 243 } 244 } 245 } 246 247 @Builder buildImage() { 248 Image(this.imageThumbnail) 249 .width('100%') 250 .height('100%') 251 .rotate({ x: 0, y: 0, z: 1, angle: 0 }) 252 .objectFit(ImageFit.Cover) 253 .autoResize(false) 254 .focusSetting(this.item == null ? '' : this.item.uri, (event: KeyEvent): void => this.handleKeyEvent(event)) 255 .onError(() => { 256 this.isLoadImageError = true; 257 AppStorage.SetOrCreate('focusUpdate', true); 258 Log.error(TAG, 'item Image error'); 259 }) 260 .onComplete(() => { 261 Log.debug(TAG, `Draw the image! ${this.imageThumbnail}`); 262 }) 263 .onAppear(() => { 264 this.requestFocus('ImageGridFocus_'); 265 }) 266 .geometryTransition(this.geometryTransitionString) 267 .transition(TransitionEffect.asymmetric( 268 TransitionEffect.scale({ x: AppStorage.get('geometryScale'), y: AppStorage.get('geometryScale') }), 269 TransitionEffect.opacity(0.99))) 270 271 if (this.geometryTransitionBrowserId === '' || !this.isTapStatusChange) { 272 this.buildIcon(); 273 } 274 } 275 276 @Builder 277 buildIcon() { 278 if (this.item != null && this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO || this.isRecycle) { 279 Row() { 280 // 缩略图左下角视频时长 281 if (this.item != null && this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) { 282 Text(DateUtil.getFormattedDuration(this.item.duration)) 283 .fontSize($r('sys.float.ohos_id_text_size_caption')) 284 .fontFamily($r('app.string.id_text_font_family_regular')) 285 .fontColor($r('app.color.text_color_above_picture')) 286 .lineHeight(12) 287 .margin({ 288 left: $r('app.float.grid_item_text_margin_lr'), 289 bottom: $r('app.float.grid_item_text_margin_bottom') 290 }) 291 .key('VideoDurationOfIndex' + this.mPosition) 292 } 293 // 缩略图右下角距离删除天数 294 if (this.isRecycle && !this.isSelectedMode) { 295 Blank() 296 297 Text($r('app.plural.recycle_days', this.recycleDays, this.recycleDays)) 298 .fontSize($r('sys.float.ohos_id_text_size_caption')) 299 .fontFamily($r('app.string.id_text_font_family_regular')) 300 .fontColor(this.recycleDays <= Constants.RECYCLE_DAYS_WARN ? $r('sys.color.ohos_id_color_warning') : $r('app.color.text_color_above_picture')) 301 .lineHeight(12) 302 .margin({ 303 right: $r('app.float.grid_item_text_margin_lr'), 304 bottom: $r('app.float.grid_item_text_margin_bottom') 305 }) 306 } 307 } 308 .position({ x: '0%', y: '50%' }) 309 .height('50%') 310 .width('100%') 311 .alignItems(VerticalAlign.Bottom) 312 .linearGradient({ angle: 0, colors: 313 [[$r('app.color.album_cover_gradient_start_color'), 0], [$r('app.color.transparent'), 1.0]] }) 314 } 315 316 if (this.item != null && this.item.isFavor) { 317 Image($r('app.media.ic_favorite_overlay')) 318 .height($r('app.float.overlay_icon_size')) 319 .width($r('app.float.overlay_icon_size')) 320 .fillColor($r('sys.color.ohos_id_color_primary_dark')) 321 .objectFit(ImageFit.Contain) 322 .position({ x: '100%', y: '0%' }) 323 .markAnchor({ 324 x: $r('app.float.grid_item_favor_markAnchor_x'), 325 y: $r('app.float.grid_item_favor_markAnchor_y') 326 }) 327 .key('Favor_' + this.mPosition) 328 } 329 330 // 当三方拉起 picker 时, 只有多选模式下才显示蒙层 331 if (this.isSelected && this.isSelectedMode && (!this.isThird || this.isThirdMultiPick)) { 332 Column() 333 .key('MaskLayer_' + this.mPosition) 334 .height('100%') 335 .width('100%') 336 .backgroundColor($r('app.color.item_selection_bg_color')) 337 } 338 339 // 缩略图上方功能图标 340 if (this.isSelectedMode) { 341 Image($r('app.media.ic_photo_preview')) 342 .key('Previewer_' + this.mPosition) 343 .height($r('app.float.icon_size')) 344 .width($r('app.float.icon_size')) 345 .position({ x: '0%', y: '0%' }) 346 .markAnchor({ 347 x: $r('app.float.grid_item_preview_padding'), 348 y: $r('app.float.grid_item_preview_padding') 349 }) 350 .onClick(() => { 351 Log.info(TAG, 'onClick loadThumbnailUri' + this.imageThumbnail); 352 this.routeToPreviewPage(); 353 Log.info(TAG, 'expand.'); 354 }) 355 } 356 if (this.isSelectedMode && (!this.isThird || this.isThirdMultiPick)) { 357 Checkbox() 358 .key('Selector_' + this.mPosition) 359 .select(this.isSelected) 360 .margin(0) 361 .position({ x: '100%', y: '100%' }) 362 .markAnchor({ 363 x: $r('app.float.grid_item_checkbox_markAnchor'), 364 y: $r('app.float.grid_item_checkbox_markAnchor') 365 }) 366 .focusable(false) 367 .hitTestBehavior(HitTestMode.None) 368 } 369 } 370 371 @Builder 372 buildNormal() { 373 Stack({ alignContent: Alignment.Start }) { 374 // 缩略图 375 if (this.isLoadImageError) { 376 Image((this.item != null && this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) 377 ? $r('app.media.alt_video_placeholder') : $r('app.media.alt_placeholder')) 378 .aspectRatio(1) 379 .rotate({ x: 0, y: 0, z: 1, angle: 0 }) 380 .objectFit(ImageFit.Cover) 381 .autoResize(false) 382 .focusSetting(this.item == null ? '' : this.item.uri, (event: KeyEvent): void => this.handleKeyEvent(event)) 383 .onAppear(() => { 384 Log.debug(TAG, `appear the default image!`); 385 }) 386 387 if (this.geometryTransitionBrowserId === '' || !this.isTapStatusChange) { 388 this.buildIcon(); 389 } 390 } else { 391 if (this.albumUri === UserFileManagerAccess.getInstance() 392 .getSystemAlbumUri(UserFileManagerAccess.TRASH_ALBUM_SUB_TYPE) 393 || this.pageName === Constants.PHOTO_TRANSITION_TIMELINE) { 394 this.buildImage(); 395 } else { 396 Stack() { 397 this.buildImage(); 398 } 399 .borderRadius(0) 400 .clip(true) 401 .geometryTransition(this.transitionId) 402 } 403 } 404 } 405 .key('Gesture_' + this.mPosition) 406 .height('100%') 407 .width('100%') 408 .bindContextMenu(this.RightClickMenuBuilder, ResponseType.RightClick) // 右键点击菜单,后续整改至新组件 409 .scale({ 410 x: this.pressAnimScale, 411 y: this.pressAnimScale 412 }) 413 .onTouch(event => { 414 Log.debug(TAG, `onTouch trigger: isSelectedMode: ${this.isSelectedMode}, 415 isEnteringPhoto: ${this.isEnteringPhoto}, ${JSON.stringify(event)}`); 416 if (this.isSelectedMode) { 417 return; 418 } 419 420 // Press animation 421 if (event?.type === TouchType.Down) { 422 animateTo({ 423 duration: Constants.PRESS_ANIM_DURATION, 424 curve: Curve.Ease 425 }, () => { 426 this.pressAnimScale = Constants.PRESS_ANIM_SCALE; 427 }) 428 } 429 430 if ((event?.type == TouchType.Up || event?.type == TouchType.Cancel) && this.pressAnimScale != 1) { 431 animateTo({ 432 duration: Constants.PRESS_ANIM_DURATION, 433 curve: Curve.Ease 434 }, () => { 435 this.pressAnimScale = 1; 436 }) 437 } 438 }) 439 .gesture(GestureGroup(GestureMode.Exclusive, 440 TapGesture().onAction((event?: GestureEvent) => { 441 let ret: boolean = focusControl.requestFocus('ImageGridFocus_' + (this.item == null ? '' : this.item.uri)); 442 if (ret !== true) { 443 let itemUri: string = this.item == null ? '' : this.item.uri; 444 Log.error(TAG, `requestFocus${'ImageGridFocus_' + itemUri}, ret:${ret}`); 445 } 446 let msg: Msg = { 447 from: BigDataConstants.BY_CLICK, 448 } 449 ReportToBigDataUtil.report(BigDataConstants.ENTER_PHOTO_BROWSER_WAY, msg); 450 this.openPhotoBrowser(); 451 }), 452 LongPressGesture().onAction((event?: GestureEvent) => { 453 Log.info(TAG, `LongPressGesture ${event as GestureEvent}`); 454 this.selectStateChange(); 455 this.pressAnimScale = 1; 456 }) 457 )) 458 } 459 460 private resetPressAnim(): void { 461 this.pressAnimScale = 1; 462 this.isEnteringPhoto = false; 463 } 464 465 private onShow(): void { 466 this.resetPressAnim(); 467 } 468 469 private generateSampleSize(imageWidth: number, imageHeight: number): number { 470 let width = ScreenManager.getInstance().getWinWidth(); 471 let height = ScreenManager.getInstance().getWinHeight(); 472 width = width == 0 ? ScreenManager.DEFAULT_WIDTH : width; 473 height = height == 0 ? ScreenManager.DEFAULT_HEIGHT : height; 474 let maxNumOfPixels = width * height; 475 let minSide = Math.min(width, height); 476 return ImageUtil.computeSampleSize(imageWidth, imageHeight, minSide, maxNumOfPixels); 477 } 478 479 private changeTextResToPlural(action: Action): Resource { 480 let textStr: Resource = action.textRes; 481 if (Action.RECOVER.equals(action)) { 482 textStr = this.isSelected 483 ? $r('app.plural.action_recover_count', this.selectedCount, this.selectedCount) 484 : $r('app.string.action_recover'); 485 } else if (Action.DELETE.equals(action)) { 486 textStr = this.isSelected 487 ? $r('app.plural.action_delete_count', this.selectedCount, this.selectedCount) 488 : $r('app.string.action_delete'); 489 } else if (Action.MOVE.equals(action)) { 490 textStr = this.isSelected 491 ? $r('app.plural.move_to_album_count', this.selectedCount, this.selectedCount) 492 : $r('app.string.move_to_album'); 493 } else if (Action.ADD.equals(action)) { 494 textStr = this.isSelected 495 ? $r('app.plural.add_to_album_count', this.selectedCount, this.selectedCount) 496 : $r('app.string.add_to_album'); 497 } 498 return textStr; 499 } 500 501 // 获取最近删除中,待回收照片倒计天数 502 private calculateRecycleDays(): void { 503 let currentTimeSeconds: number = new Date().getTime() / 1000; 504 let itemDateTrashed: number = this.item == null ? 0 : this.item.dateTrashed; 505 let trashedDay = DateUtil.convertSecondsToDays(currentTimeSeconds - itemDateTrashed); 506 Log.debug(TAG, `currentSec=${currentTimeSeconds}, trashedSec=${itemDateTrashed}, trashedDay=${trashedDay}`); 507 if (trashedDay > Constants.RECYCLE_DAYS_MAX) { 508 this.recycleDays = 0; 509 } else if (trashedDay <= 0) { 510 this.recycleDays = Constants.RECYCLE_DAYS_MAX - 1; 511 } else { 512 this.recycleDays = Number.parseInt((Constants.RECYCLE_DAYS_MAX - trashedDay) + ''); 513 } 514 } 515 516 private requestFocus(keyName: string): void { 517 if (AppStorage.get<string>('deviceType') == Constants.DEFAULT_DEVICE_TYPE) { 518 return; 519 } 520 let positionUri = AppStorage.get<string>('focusPosition'); 521 let isUpdate = AppStorage.get<boolean>('focusUpdate'); 522 if (this.item !== null && isUpdate && positionUri === this.item.uri) { 523 let ret: Boolean = focusControl.requestFocus(keyName + this.item.uri); 524 if (ret !== true) { 525 Log.error(TAG, `requestFocus${keyName + this.item.uri}, ret:${ret}`); 526 } 527 AppStorage.SetOrCreate('focusUpdate', false); 528 } 529 } 530 531 private openPhotoBrowser(): void { 532 if (this.isSelectedMode) { 533 this.selectStateChange(); 534 } else { 535 Log.info(TAG, 'item onClick loadBmp'); 536 Log.info(TAG, 'onClick loadThumbnailUri' + this.imageThumbnail); 537 this.updateGeometryTapInfo(); 538 if (this.isThird) { 539 this.broadCast.emit(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, 540 [this.pageName, this.item, this.geometryTapIndex, this.geometryTransitionString]); 541 } else { 542 this.broadCast.emit(BroadCastConstants.JUMP_PHOTO_BROWSER, 543 [this.pageName, this.item, this.geometryTapIndex, this.geometryTransitionString]); 544 } 545 this.isEnteringPhoto = true; 546 } 547 } 548 549 private handleKeyEvent(event: KeyEvent): void { 550 if (KeyType.Up == event.type) { 551 switch (event.keyCode) { 552 case MultimodalInputManager.KEY_CODE_KEYBOARD_ENTER: 553 let msg: Msg = { 554 from: BigDataConstants.BY_KEYBOARD, 555 } 556 ReportToBigDataUtil.report(BigDataConstants.ENTER_PHOTO_BROWSER_WAY, msg); 557 this.openPhotoBrowser(); 558 break; 559 case MultimodalInputManager.KEY_CODE_KEYBOARD_ESC: 560 this.onMenuClicked && this.onMenuClicked(Action.BACK); 561 break; 562 default: 563 Log.info(TAG, `on key event Up, default`); 564 break; 565 } 566 } 567 } 568 569 private updateGeometryTapInfo(): void { 570 this.geometryTapIndex = this.getPosition(); 571 } 572 573 private getPosition(): number { 574 if (this.dataSource == null || this.item == null) return 0; 575 return this.dataSource.getDataIndex(this.item) + this.dataSource.getGroupCountBeforeItem(this.item); 576 } 577}