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 resourceManager from '@ohos.resourceManager'; 17import Matrix4 from '@ohos.matrix4'; 18import router from '@ohos.router'; 19import { MediaItem } from '../model/browser/photo/MediaItem'; 20import { AnimationParam } from '../model/browser/photo/EventPipeline'; 21import { EventPipeline } from '../model/browser/photo/EventPipeline'; 22import { BroadCast } from '../utils/BroadCast'; 23import { Log } from '../utils/Log'; 24import { Constants as PhotoConstants } from '../model/browser/photo/Constants'; 25import { UserFileManagerAccess } from '../access/UserFileManagerAccess'; 26import { ColumnSize, ScreenManager } from '../model/common/ScreenManager'; 27import { Constants } from '../model/common/Constants'; 28import { LoadingPanel } from './LoadingPanel'; 29import { ImageUtil } from '../utils/ImageUtil'; 30import { BigDataConstants, ReportToBigDataUtil } from '../utils/ReportToBigDataUtil'; 31import { MultimodalInputManager } from '../model/common/MultimodalInputManager'; 32import { BroadCastConstants } from '../model/common/BroadCastConstants'; 33import { PhotoDataSource } from '../model/browser/photo/PhotoDataSource'; 34import { Matrix4x4 } from '../utils/Matrix4x4'; 35 36const TAG: string = 'common_PhotoItem'; 37 38@Component 39export struct PhotoItem { 40 @State @Watch('onViewDataChanged') item: MediaItem = new MediaItem(); 41 @State matrix: Matrix4.Matrix4Transit = Matrix4.identity().copy(); 42 @State mDirection: PanDirection = PanDirection.Vertical; 43 // @Consume broadCast: BroadCast; 44 @Link broadCast: BroadCast; 45 @State visible: Visibility = Visibility.Hidden; 46 @State objectFit: ImageFit = ImageFit.Cover; 47 @State thumbnail: string = ''; 48 @State ratio: number = 1.0; 49 @State showError: boolean = false; 50 @State lcdPixelMapUpdate: boolean = false; 51 @Consume pageFrom: number; 52 @Consume('transitionIndex') @Watch('onTransitionChange') updateTransition: number; 53 mPosition: number = 0; 54 transitionTag: string = ''; 55 @State isLoading: boolean = true; 56 @State usePixmap: boolean = false; 57 @Provide listCardWidth: number = 0; 58 verifyPhotoScaled: (matrix: Matrix4.Matrix4Transit) => void = (matrix: Matrix4.Matrix4Transit): void => { 59 }; 60 @Consume currentIndex: number; 61 @StorageLink('geometryScale') geometryScale: number = 1; 62 @StorageLink('geometryOffsetX') geometryOffsetX: number = 0; 63 @StorageLink('geometryOffsetY') geometryOffsetY: number = 0; 64 @State isPullDownAndDragPhoto: boolean = false; 65 @Link isOnSwiperAnimation: boolean; 66 @State imageTop: number = 0; 67 @StorageLink('isHorizontal') isHorizontal: boolean = 68 ScreenManager.getInstance().isHorizontal(); 69 @State isPhotoScaled: boolean = false; 70 @State justifyWidth: boolean = true; 71 @State imageWidth?: string = Constants.PERCENT_100; 72 @State imageHeight?: string = Constants.PERCENT_100; 73 @State isEdgeTop: boolean = true; 74 @Link isRunningAnimation: boolean; 75 @State geometryTransitionId: string = 'default_id'; 76 @StorageLink('geometryTransitionBrowserId') @Watch('onGeometryIdChange') geometryTransitionBrowserId: string = ''; 77 private lastUpdateTransition: number = -1; 78 private eventPipeline: EventPipeline | null = null; 79 private animationOption?: AnimationParam; 80 private animationEndMatrix?: Matrix4.Matrix4Transit; 81 private isHandlingTap: boolean = false; 82 private timerCounter: number = 0; 83 private imgScale: number = 1; 84 private firstLoad: boolean = true; 85 private preItem: PreItem = { height: 0, width: 0 }; 86 private albumUri: string = ''; 87 private imagePropertyComponentHeight: number = 578; 88 private propertyHeightHorizontal: number = 300; 89 private lastTouchDownY: number = 0; 90 private lastTouchDownX: number = 0; 91 private isInSelectedMode: boolean = false; 92 private geometryTransitionEnable: boolean = false; 93 private windowLayoutWidth: number = ScreenManager.getInstance().getWinLayoutWidth(); 94 private windowLayoutHeight: number = ScreenManager.getInstance().getWinLayoutHeight(); 95 private windowColumns: number = ScreenManager.getInstance().getScreenColumns(); 96 private isFromFACard: boolean = false; 97 private dataSource: PhotoDataSource | null = null; 98 private isDefaultFA: boolean = false; 99 private onWindowSizeChangeCallBack = () => this.onWindowSizeChanged(); 100 private onDataReloadFunc: Function = (addCount: KeyEvent): void => this.onDataReload(addCount); 101 private resetDefaultScaleFunc: Function = (): void => this.resetDefaultScale(); 102 private saveScaleFunc: Function = (): void => this.saveScale(); 103 104 onGeometryIdChange() { 105 this.geometryTransitionId = (this.mPosition === this.currentIndex) ? this.geometryTransitionBrowserId : 'default_id'; 106 Log.debug(TAG, 'geometryTransitionId = ' + this.geometryTransitionId + ', this.mPosition = ' + this.mPosition + 107 ', this.currentIndex = ' + this.currentIndex); 108 } 109 110 private onDataReload(addCount: KeyEvent): void { 111 Log.info(TAG, `onDataReload ${this.item.uri}`); 112 let item: MediaItem | null = this.dataSource?.getItemByUri(this.item.uri) as MediaItem ?? null; 113 if (item) { 114 this.item = item; 115 Log.debug(TAG, `Item[${this.item.uri}] is changed`); 116 } 117 } 118 119 private resetDefaultScale(): void { 120 this.resetScaleAnimation(Matrix4.identity().copy()); 121 } 122 123 private saveScale(): void { 124 if (this.currentIndex == this.mPosition) { 125 AppStorage.setOrCreate(PhotoConstants.MATRIX, this.matrix); 126 } 127 } 128 129 aboutToAppear(): void { 130 Log.info(TAG, `aboutToAppear ${this.item.uri}`); 131 this.eventPipeline = new EventPipeline(this.broadCast, this.item, this); 132 this.matrix = Matrix4.identity().copy(); 133 this.isPhotoScaled = false; 134 this.firstLoad = true; 135 if (this.dataSource) { 136 this.broadCast.on(BroadCastConstants.ON_DATA_RELOADED, this.onDataReloadFunc); 137 } 138 ScreenManager.getInstance().on(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack); 139 this.broadCast.on(PhotoConstants.RESET_DEFAULT_SCALE + this.item.uri, this.resetDefaultScaleFunc); 140 this.broadCast.on(PhotoConstants.SAVE_SCALE, this.saveScaleFunc); 141 this.onTransitionChange(); 142 this.onViewDataChanged(); 143 this.updateListCardWidth(); 144 this.calculateImagePos(); 145 this.onGeometryIdChange(); 146 147 let matrix: Matrix4.Matrix4Transit = 148 AppStorage.get<Matrix4.Matrix4Transit>(PhotoConstants.MATRIX) as Matrix4.Matrix4Transit; 149 if (this.currentIndex == this.mPosition && matrix) { 150 this.matrix = matrix; 151 this.updatePhotoScaledStatus(); 152 } 153 Log.info(TAG, `aboutToAppear ${this.item.uri} end`); 154 } 155 156 onWindowSizeChanged(): void { 157 let winLayoutW = ScreenManager.getInstance().getWinLayoutWidth(); 158 let winLayoutH = ScreenManager.getInstance().getWinLayoutHeight(); 159 if (this.windowLayoutWidth !== winLayoutW || this.windowLayoutHeight !== winLayoutH) { 160 Log.debug(TAG, `size change. oldWH: [${this.windowLayoutWidth}, ${this.windowLayoutHeight}]` + 161 `, newWH: [${winLayoutW}, ${winLayoutH}]`); 162 this.windowLayoutWidth = winLayoutW; 163 this.windowLayoutHeight = winLayoutH; 164 this.windowColumns = ScreenManager.getInstance().getScreenColumns(); 165 // 跟随屏幕旋转动效同时对Image的宽高重新赋值 166 animateTo({ 167 duration: Constants.SCREEN_ROTATE_DURATION, 168 curve: PhotoConstants.PHOTO_TRANSITION_CURVE, 169 onFinish: () => { 170 this.eventPipeline?.onAnimationEnd(this.animationEndMatrix); 171 this.animationOption = undefined; 172 this.animationEndMatrix = undefined; 173 } 174 }, () => { 175 this.eventPipelineSizeChange(); 176 this.matrix = this.animationEndMatrix as Matrix4.Matrix4Transit; 177 this.calculateImagePos(); 178 this.updateListCardWidth(); 179 }); 180 } 181 } 182 183 eventPipelineSizeChange(): void { 184 this.eventPipeline?.onComponentSizeChanged(vp2px(this.windowLayoutWidth), vp2px(this.windowLayoutHeight)); 185 } 186 187 aboutToDisappear(): void { 188 Log.info(TAG, `aboutToDisappear ${this.item.uri}`); 189 if (this.currentIndex == this.mPosition) { 190 AppStorage.delete(PhotoConstants.MATRIX); 191 } 192 if (this.dataSource) { 193 this.broadCast.off(BroadCastConstants.ON_DATA_RELOADED, this.onDataReloadFunc); 194 } 195 ScreenManager.getInstance().off(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack); 196 this.broadCast.off(PhotoConstants.RESET_DEFAULT_SCALE + this.item.uri, this.resetDefaultScaleFunc); 197 this.broadCast.off(PhotoConstants.SAVE_SCALE, this.saveScaleFunc); 198 this.lcdPixelMapUpdate = false; 199 Log.info(TAG, `aboutToDisappear ${this.item.uri} end`); 200 } 201 202 onViewDataChanged(): void { 203 if (this.eventPipeline == null || this.item == null) { 204 return; 205 } 206 if (!this.usePixmap && !this.isDefaultFA) { 207 this.ratio = ImageUtil.calcRatio(this.item); 208 } 209 210 if ((this.preItem.height == this.item.imgHeight && 211 this.preItem.width == this.item.imgWidth) && !this.firstLoad) { 212 this.preItem.width = this.item.imgWidth; 213 this.preItem.height = this.item.imgHeight; 214 this.eventPipeline && this.eventPipeline.onDataChanged(this.item) 215 return; 216 } 217 218 this.matrix = Matrix4.identity().copy(); 219 this.updatePhotoScaledStatus(); 220 this.eventPipeline && this.eventPipeline.setDefaultScale(1); 221 222 this.eventPipeline && this.eventPipeline.onDataChanged(this.item); 223 this.firstLoad = false; 224 this.preItem.width = this.item.imgWidth; 225 this.preItem.height = this.item.imgHeight; 226 Log.info(TAG, 'onViewDataChanged, index: ' + this.mPosition + ', usePixmap: ' + this.usePixmap + 227 ', ratio: ' + this.ratio + ', thumbnail: ' + JSON.stringify(this.thumbnail)); 228 } 229 230 onTouchEventRespond(matrix: Matrix4.Matrix4Transit): void { 231 Log.info(TAG, `on touch event respond ${this.item.uri}`); 232 this.matrix = matrix; 233 this.updatePhotoScaledStatus(); 234 } 235 236 onDirectionChangeRespond(direction: PanDirection): void { 237 Log.info(TAG, `item: ${this.item.uri}, direction: ${direction}`); 238 if (this.mDirection === direction) { 239 return; 240 } 241 this.mDirection = direction; 242 } 243 244 resetScaleAnimation(matrix: Matrix4.Matrix4Transit): void { 245 if (this.eventPipeline?.isDefaultScale()) { 246 return; 247 } 248 this.eventPipeline?.startAnimation(matrix); 249 animateTo({ 250 duration: (this.animationOption as AnimationParam).duration, 251 curve: (this.animationOption as AnimationParam).curve as Curve, 252 onFinish: (): void => { 253 this.eventPipeline?.onAnimationEnd(this.animationEndMatrix as Matrix4.Matrix4Transit); 254 this.animationOption = undefined; 255 this.animationEndMatrix = undefined; 256 } 257 }, () => { 258 this.matrix = this.animationEndMatrix as Matrix4.Matrix4Transit; 259 this.updatePhotoScaledStatus(); 260 }) 261 } 262 263 onAnimationEventRespond(animationOption: AnimationParam, animationEndMatrix: Matrix4.Matrix4Transit): void { 264 Log.info(TAG, `item: ${this.item.uri}, animationOption: ${JSON.stringify(animationOption)}`); 265 this.animationOption = animationOption; 266 this.animationEndMatrix = animationEndMatrix; 267 } 268 269 updatePhotoScaledStatus() { 270 let matrix: Matrix4.Matrix4Transit = this.matrix; 271 if (!!matrix) { 272 let mat: number[] | undefined = (matrix.copy() as Matrix4x4).matrix4x4; 273 if (mat) { 274 let xScale: number = mat[Constants.NUMBER_0]; 275 let yScale: number = mat[Constants.NUMBER_5]; 276 Log.debug(TAG, `photo in PhotoItem has Scaled x scale: ${xScale}, y scale: ${yScale}, mat: ${mat}`); 277 this.isPhotoScaled = (xScale != 1 || yScale != 1); 278 } 279 } else { 280 this.isPhotoScaled = false; 281 } 282 } 283 284 isFilterListEmpty(fingerList: FingerInfo[]): FingerInfo[] { 285 fingerList = fingerList.filter((item: FingerInfo) => item != undefined); 286 if (fingerList.length === 0) { 287 Log.error(TAG, 'all elements are undefined'); 288 } 289 return fingerList; 290 } 291 292 @Builder 293 buildImage() { 294 Image(this.thumbnail) 295 .key('browserFocus_' + this.thumbnail) 296 .aspectRatio(this.ratio) 297 .focusable(true) 298 .transform(this.matrix) 299 .objectFit(ImageFit.Cover) 300 .autoResize(false) 301 .onComplete(() => { 302 Log.info(TAG, `onComplete finish index: ${this.mPosition}, uri: ${this.item.uri}`); 303 this.showError = false; 304 this.isLoading = false; 305 if (this.updateTransition == this.mPosition) { 306 this.broadCast.emit(PhotoConstants.PHOTO_SHOW_STATE, [true]); 307 } 308 }) 309 .onError(() => { 310 Log.info(TAG, `image show error`); 311 this.showError = true; 312 this.isLoading = false; 313 if (this.updateTransition == this.mPosition) { 314 this.broadCast.emit(PhotoConstants.PHOTO_SHOW_STATE, [false]); 315 } 316 }) 317 .onKeyEvent((event?: KeyEvent) => { 318 this.handleKeyEvent(event as KeyEvent); 319 }) 320 .width(this.imageWidth as string) 321 .height(this.imageHeight as string) 322 .scale({ 323 x: this.geometryScale, 324 y: this.geometryScale 325 }) 326 .offset({ 327 x: this.geometryOffsetX, 328 y: this.geometryOffsetY 329 }) 330 .geometryTransition(this.geometryTransitionId) 331 .transition(TransitionEffect.asymmetric(TransitionEffect.opacity(0.99), 332 TransitionEffect.scale({ 333 x: 1 / this.geometryScale, 334 y: 1 / this.geometryScale, 335 centerX: '50%', 336 centerY: '50%' }) 337 .combine(TransitionEffect.opacity(0.99)) 338 )) 339 .onAppear(() => { 340 if (this.currentIndex == this.mPosition) { 341 let ret: Boolean = focusControl.requestFocus('browserFocus_' + this.thumbnail); 342 if (ret !== true) { 343 Log.error(TAG, `requestFocus faild, ret:${ret}`); 344 } 345 } 346 }) 347 } 348 349 build() { 350 Stack() { 351 // 加载图标文字组件 352 if (this.isLoading && !this.isRunningAnimation) { 353 Column() { 354 LoadingPanel() 355 } 356 .width(Constants.PERCENT_100) 357 .height(Constants.PERCENT_100) 358 } 359 360 Column() { 361 Stack({ alignContent: Alignment.TopStart }) { 362 Column() { 363 // 图片主体组件 364 if (this.item.width != 0 && this.item.height != 0) { 365 this.buildImage() 366 } else { 367 Image(this.thumbnail) 368 .key('browserFocus_' + this.thumbnail) 369 .focusable(true) 370 .transform(this.matrix) 371 .objectFit(ImageFit.Cover) 372 .autoResize(false) 373 .onComplete(() => { 374 Log.info(TAG, `onComplete finish index: ${this.mPosition}, uri: ${this.item.uri}`); 375 this.showError = false; 376 this.isLoading = false; 377 if (this.updateTransition == this.mPosition) { 378 this.broadCast.emit(PhotoConstants.PHOTO_SHOW_STATE, [true]); 379 } 380 }) 381 .onError(() => { 382 Log.info(TAG, `image show error`); 383 this.showError = true; 384 this.isLoading = false; 385 if (this.updateTransition == this.mPosition) { 386 this.broadCast.emit(PhotoConstants.PHOTO_SHOW_STATE, [false]); 387 } 388 }) 389 .onKeyEvent((event?: KeyEvent) => { 390 this.handleKeyEvent(event as KeyEvent); 391 }) 392 .onAppear(() => { 393 if (this.currentIndex == this.mPosition) { 394 let ret: Boolean = focusControl.requestFocus('browserFocus_' + this.thumbnail); 395 if (ret !== true) { 396 Log.error(TAG, `requestFocus faild, ret:${ret}`); 397 } 398 } 399 }) 400 } 401 } 402 .width(Constants.PERCENT_100) 403 .height(Constants.PERCENT_100) 404 .alignItems(HorizontalAlign.Center) 405 .justifyContent(FlexAlign.Center) 406 } 407 } 408 .hitTestBehavior(HitTestMode.Default) 409 .width(Constants.PERCENT_100) 410 .height(Constants.PERCENT_100) 411 // 绑定手势 412 .parallelGesture( 413 GestureGroup(GestureMode.Parallel, 414 // 捏合手势 415 PinchGesture({ 416 fingers: 2, 417 distance: 1 418 }) 419 .onActionStart((event?: GestureEvent) => { 420 Log.info(TAG, 'PinchGesture onActionStart'); 421 Log.info(TAG, `PinchGesture onActionStart scale:\ 422 ${(event as GestureEvent).scale}, cx: ${(event as GestureEvent).pinchCenterX}, 423 cy: ${(event as GestureEvent).pinchCenterY}`); 424 if (this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_IMAGE || 425 this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) { 426 this.eventPipeline?.onScaleStart((event as GestureEvent).scale, 427 (event as GestureEvent).pinchCenterX, (event as GestureEvent).pinchCenterY); 428 } 429 }) 430 .onActionUpdate((event?: GestureEvent) => { 431 Log.debug(TAG, `PinchGesture onActionUpdate scale: ${(event as GestureEvent).scale}`); 432 if (this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_IMAGE || 433 this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) { 434 this.eventPipeline?.onScale((event as GestureEvent).scale); 435 } 436 }) 437 .onActionEnd(() => { 438 Log.info(TAG, 'PinchGesture onActionEnd'); 439 if (this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_IMAGE || 440 this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) { 441 this.eventPipeline?.onScaleEnd(); 442 if (this.animationOption != null) { 443 animateTo({ 444 duration: this.animationOption.duration, 445 curve: this.animationOption.curve, 446 onFinish: () => { 447 this.eventPipeline?.onAnimationEnd(this.animationEndMatrix); 448 this.animationOption = undefined; 449 this.animationEndMatrix = undefined; 450 } 451 }, () => { 452 this.matrix = this.animationEndMatrix as Matrix4.Matrix4Transit; 453 }) 454 if (!!this.verifyPhotoScaled) { 455 this.verifyPhotoScaled(this.matrix) 456 } 457 this.updatePhotoScaledStatus(); 458 } 459 } 460 }), 461 // 平移手势 462 PanGesture({ 463 direction: this.mDirection 464 }) 465 .onActionStart((event?: GestureEvent) => { 466 Log.info(TAG, `PanGesture start offsetX:\ 467 ${vp2px((event as GestureEvent).offsetX)}, 468 offsetY: ${vp2px((event as GestureEvent).offsetY)}`); 469 this.eventPipeline?.onMoveStart(vp2px((event as GestureEvent).offsetX), 470 vp2px((event as GestureEvent).offsetY)); 471 }) 472 .onActionUpdate((event?: GestureEvent) => { 473 Log.info(TAG, `PanGesture update offsetX:\ 474 ${vp2px((event as GestureEvent).offsetX)}, 475 offsetY: ${vp2px((event as GestureEvent).offsetY)}`); 476 this.eventPipeline?.onMove(vp2px((event as GestureEvent).offsetX), 477 vp2px((event as GestureEvent).offsetY)); 478 this.isPullDownAndDragPhoto = this.eventPipeline?.canPullDownAndDragPhoto() ?? false; 479 if (this.isPullDownAndDragPhoto && this.geometryTransitionEnable && 480 !this.isOnSwiperAnimation && !this.isFromFACard) { 481 this.doDragPhoto((event as GestureEvent).offsetX, (event as GestureEvent).offsetY); 482 } 483 }) 484 .onActionEnd((event?: GestureEvent) => { 485 Log.info(TAG, `PanGesture end offsetX:\ 486 ${vp2px((event as GestureEvent).offsetX)}, offsetY: ${vp2px((event as GestureEvent).offsetY)} \ 487 this.isOnSwiperAnimation ${this.isOnSwiperAnimation}`); 488 if (this.isOnSwiperAnimation) { 489 return; 490 } 491 this.eventPipeline?.onMoveEnd(vp2px((event as GestureEvent).offsetX), 492 vp2px((event as GestureEvent).offsetY)); 493 this.isPullDownAndDragPhoto = this.eventPipeline?.canPullDownAndDragPhoto() ?? false; 494 if (this.animationOption != null) { 495 animateTo({ 496 duration: this.animationOption.duration, 497 curve: this.animationOption.curve, 498 onFinish: () => { 499 this.eventPipeline?.onAnimationEnd(this.animationEndMatrix); 500 this.animationOption = undefined; 501 this.animationEndMatrix = undefined; 502 } 503 }, () => { 504 this.matrix = this.animationEndMatrix as Matrix4.Matrix4Transit; 505 }) 506 if (!!this.verifyPhotoScaled) { 507 this.verifyPhotoScaled(this.matrix) 508 } 509 this.updatePhotoScaledStatus(); 510 } 511 }), 512 // 点击手势 513 TapGesture({ 514 count: 1 515 }) 516 .onAction((event: GestureEvent) => { 517 if (this.isHandlingTap) { 518 if (this.timerCounter != null) { 519 clearTimeout(this.timerCounter) 520 this.timerCounter = 0; 521 this.isHandlingTap = false; 522 } 523 } else { 524 this.isHandlingTap = true; 525 this.timerCounter = setTimeout(() => { 526 this.broadCast.emit(PhotoConstants.TOGGLE_BAR, [null]); 527 this.isHandlingTap = false; 528 }, Constants.DOUBLE_CLICK_GAP) 529 return; 530 } 531 Log.info(TAG, `onDoubleTap event: ${JSON.stringify(event)}`); 532 event.fingerList = this.isFilterListEmpty(event.fingerList); 533 if (event.fingerList.length > 0) { 534 this.eventPipeline?.onDoubleTap((event as GestureEvent).fingerList[0].localX, 535 (event as GestureEvent).fingerList[0].localY); 536 } 537 if (this.animationOption != null) { 538 Log.info(TAG, 'TapGesture animateTo start'); 539 animateTo({ 540 duration: this.animationOption.duration, 541 curve: this.animationOption.curve, 542 onFinish: () => { 543 this.eventPipeline?.onAnimationEnd(this.animationEndMatrix); 544 this.animationOption = undefined; 545 this.animationEndMatrix = undefined; 546 } 547 }, () => { 548 this.matrix = this.animationEndMatrix as Matrix4.Matrix4Transit; 549 }) 550 if (!!this.verifyPhotoScaled) { 551 this.verifyPhotoScaled(this.matrix) 552 } 553 this.updatePhotoScaledStatus(); 554 } 555 }), 556 ) 557 ) 558 .clip(true) 559 .onTouch((event?: TouchEvent) => { 560 Log.info(TAG, 'onTouch start'); 561 this.dealTouchEvent(event as TouchEvent); 562 this.eventPipeline?.onTouch(event as TouchEvent); 563 }) 564 // TODO Remind users when pictures of other devices cannot be show 565 if ((this.showError || this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) && 566 this.pageFrom == Constants.ENTRY_FROM.DISTRIBUTED) { 567 Row() { 568 Text((this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) ? 569 $r('app.string.no_distributed_photo_show_video') : 570 $r('app.string.no_distributed_photo_show_image')) 571 .fontSize($r('sys.float.ohos_id_text_size_body2')) 572 .fontFamily($r('app.string.id_text_font_family_regular')) 573 .fontColor($r('sys.color.ohos_id_color_text_tertiary')) 574 } 575 .margin({ 576 top: (this.item.mediaType == 577 UserFileManagerAccess.MEDIA_TYPE_VIDEO) ? $r('app.float.input_text_notify_margin') : 0 578 }) 579 } 580 // 播放视频按键 581 if (this.isVideoPlayBtnShow() && !this.isPullDownAndDragPhoto && !this.isRunningAnimation) { 582 Row() { 583 Image($r('app.media.ic_video_play_btn_png')) 584 .key('VideoPlayButton') 585 .objectFit(ImageFit.Contain) 586 .width($r('app.float.icon_video_size')) 587 .height($r('app.float.icon_video_size')) 588 .onClick(() => { 589 Log.info(TAG, 'video item: ' + JSON.stringify(this.item)) 590 Log.info(TAG, 'video thumbail: ' + JSON.stringify(this.thumbnail)) 591 if (this.item != undefined) { 592 router.pushUrl({ 593 url: 'pages/VideoBrowser', 594 params: { 595 uri: this.item.uri, 596 dateTaken: this.item.getDataTaken(), 597 previewUri: this.thumbnail 598 } 599 }) 600 interface Msg { 601 photoButton: string; 602 from: string; 603 } 604 let msg: Msg = { 605 photoButton: BigDataConstants.PHOTO_BUTTON_VIDEO, 606 from: BigDataConstants.LOCAL_MEDIA, 607 } 608 ReportToBigDataUtil.report(BigDataConstants.CLICK_PHOTO_BUTTON_ID, msg); 609 } 610 }) 611 } 612 } 613 } 614 .width(Constants.PERCENT_100) 615 .height(Constants.PERCENT_100) 616 } 617 618 isVideoPlayBtnShow(): boolean { 619 return (this.item != undefined) && (this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO); 620 } 621 622 doDragPhoto(offsetX: number, offsetY: number) { 623 animateTo({ 624 curve: PhotoConstants.RESPONSIVE_SPRING_MOTION_CURVE 625 }, () => { 626 let distanceY = Math.abs(offsetY); 627 this.geometryOffsetX = offsetX; 628 this.geometryOffsetY = offsetY; 629 630 // Calculate image size by distance Y, min size is 50% 631 let calcGeometryScale = Constants.NUMBER_1 - distanceY * PhotoConstants.DRAG_SCALE; 632 this.geometryScale = calcGeometryScale > PhotoConstants.MIN_DRAG_SCALE ? 633 calcGeometryScale : PhotoConstants.MIN_DRAG_SCALE; 634 635 // Calculate image opacity by distance Y, min opacity is 0 636 let calcTouchOpacity = Constants.NUMBER_1 - distanceY * PhotoConstants.DRAG_OPACITY; 637 let touchOpacity = calcTouchOpacity > Constants.NUMBER_0 ? calcTouchOpacity : Constants.NUMBER_0; 638 AppStorage.setOrCreate<number>('geometryOpacity', touchOpacity); 639 }) 640 } 641 642 private calculateImagePos(): void { 643 let screenWidth = vp2px(this.windowLayoutWidth); 644 let screenHeight = vp2px(this.windowLayoutHeight); 645 this.justifyWidth = this.needJustifyWidth(); 646 this.imageWidth = this.justifyWidth ? Constants.PERCENT_100 : undefined; 647 this.imageHeight = !this.justifyWidth ? Constants.PERCENT_100 : undefined; 648 if (!this.justifyWidth) { 649 this.imageTop = 0; 650 } else { 651 let imgHeight = screenWidth / this.ratio; 652 this.imageTop = screenHeight / 2 - imgHeight / 2; 653 Log.debug(TAG, `calculate image size: height ${imgHeight}, screen height ${screenHeight}, ratio ${this.ratio}`); 654 } 655 Log.debug(TAG, `calculate image size: top ${this.imageTop}`); 656 } 657 658 private updateListCardWidth(): void { 659 if (this.windowColumns == ColumnSize.COLUMN_FOUR) { 660 this.listCardWidth = ScreenManager.getInstance().getColumnsWidth(ColumnSize.COLUMN_FOUR); 661 } else if (this.windowColumns == ColumnSize.COLUMN_EIGHT) { 662 this.listCardWidth = ScreenManager.getInstance().getColumnsWidth(ColumnSize.COLUMN_SIX); 663 } else if (this.windowColumns == ColumnSize.COLUMN_TWELVE) { 664 this.listCardWidth = ScreenManager.getInstance().getColumnsWidth(ColumnSize.COLUMN_EIGHT); 665 } else { 666 Log.error(TAG, `screenColumns is: ${this.windowColumns}`); 667 } 668 } 669 670 private onTransitionChange() { 671 Log.info(TAG, `onTransitionChange , ${this.updateTransition} ${this.position}`); 672 if (this.lastUpdateTransition != this.updateTransition) { 673 this.lastUpdateTransition = this.updateTransition; 674 if (this.updateTransition == this.mPosition) { 675 this.broadCast.emit(PhotoConstants.PHOTO_SHOW_STATE, [!this.showError]); 676 } else { 677 678 } 679 // reset matrix 680 if (this.imgScale != 1) { 681 this.matrix = Matrix4.identity().scale({ 682 x: this.imgScale, 683 y: this.imgScale 684 }).copy(); 685 this.eventPipeline && this.eventPipeline.setDefaultScale(this.imgScale); 686 } else { 687 this.matrix = Matrix4.identity().copy(); 688 // 重置大小 689 this.eventPipeline && this.eventPipeline.reset(); 690 } 691 this.updatePhotoScaledStatus(); 692 Log.info(TAG, `onTransitionChange end`); 693 } 694 } 695 696 private needJustifyWidth(): boolean { 697 let maxWidth = vp2px(this.windowLayoutWidth); 698 let maxHeight = vp2px(this.windowLayoutHeight); 699 let justifyWidth: boolean = this.ratio >= (maxWidth / maxHeight); 700 Log.info(TAG, `maxWidth:${maxWidth}, maxHeight:${maxHeight}, ratio:${this.ratio}, justifyWidth:${justifyWidth}`); 701 return justifyWidth; 702 } 703 704 private dealTouchEvent(event: TouchEvent): void { 705 if (this.eventPipeline === null) { 706 return; 707 }; 708 if (!this.eventPipeline.canTouch() || this.isOnSwiperAnimation || event.touches.length > 1 || 709 this.isPhotoScaled || this.isInSelectedMode || this.isDefaultFA) { 710 return; 711 } 712 if (event.type == TouchType.Move) { 713 let yOffset = event.touches[0].screenY - this.lastTouchDownY; 714 let xOffset = event.touches[0].screenX - this.lastTouchDownX; 715 this.lastTouchDownY = event.touches[0].screenY; 716 this.lastTouchDownX = event.touches[0].screenX; 717 if (yOffset == undefined || xOffset == undefined) { 718 Log.info(TAG, 'dealTouchEvent screenY or screenX undefined'); 719 return; 720 } 721 } else if (event.type == TouchType.Down) { 722 this.lastTouchDownY = event.touches[0].screenY; 723 this.lastTouchDownX = event.touches[0].screenX; 724 } else if (event.type == TouchType.Up) { 725 } 726 } 727 728 private handleKeyEvent(event: KeyEvent): void { 729 Log.info(TAG, `type=${event.type}, keyCode=${event.keyCode}`); 730 interface Msg { 731 from: string; 732 } 733 if (KeyType.Up == event.type) { 734 switch (event.keyCode) { 735 case MultimodalInputManager.KEY_CODE_KEYBOARD_ESC: 736 let msg: Msg = { 737 from: BigDataConstants.BY_KEYBOARD, 738 } 739 ReportToBigDataUtil.report(BigDataConstants.ESC_PHOTO_BROWSER_WAY, msg); 740 this.broadCast.emit(PhotoConstants.PULL_DOWN_END, []); 741 break; 742 case MultimodalInputManager.KEY_CODE_KEYBOARD_ENTER: 743 if (this.item != undefined && this.item.mediaType === UserFileManagerAccess.MEDIA_TYPE_VIDEO) { 744 router.pushUrl({ 745 url: 'pages/VideoBrowser', 746 params: { 747 uri: this.item.uri, 748 dateTaken: this.item.getDataTaken(), 749 previewUri: this.thumbnail 750 } 751 }) 752 } 753 break; 754 default: 755 Log.info(TAG, `on key event Up, default`); 756 break; 757 } 758 } 759 } 760 761 private isNeedShieldPullUpEvent(event: GestureEvent): boolean { 762 return event.offsetY < 0 && 763 !this.isPhotoScaled; 764 } 765} 766 767interface PreItem { 768 height: number; 769 width: number; 770} 771