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 @Builder 285 buildImage() { 286 Image(this.thumbnail) 287 .key('browserFocus_' + this.thumbnail) 288 .aspectRatio(this.ratio) 289 .focusable(true) 290 .transform(this.matrix) 291 .objectFit(ImageFit.Cover) 292 .autoResize(false) 293 .onComplete(() => { 294 Log.info(TAG, `onComplete finish index: ${this.mPosition}, uri: ${this.item.uri}`); 295 this.showError = false; 296 this.isLoading = false; 297 if (this.updateTransition == this.mPosition) { 298 this.broadCast.emit(PhotoConstants.PHOTO_SHOW_STATE, [true]); 299 } 300 }) 301 .onError(() => { 302 Log.info(TAG, `image show error`); 303 this.showError = true; 304 this.isLoading = false; 305 if (this.updateTransition == this.mPosition) { 306 this.broadCast.emit(PhotoConstants.PHOTO_SHOW_STATE, [false]); 307 } 308 }) 309 .onKeyEvent((event?: KeyEvent) => { 310 this.handleKeyEvent(event as KeyEvent); 311 }) 312 .width(this.imageWidth as string) 313 .height(this.imageHeight as string) 314 .scale({ 315 x: this.geometryScale, 316 y: this.geometryScale 317 }) 318 .offset({ 319 x: this.geometryOffsetX, 320 y: this.geometryOffsetY 321 }) 322 .geometryTransition(this.geometryTransitionId) 323 .transition(TransitionEffect.asymmetric(TransitionEffect.opacity(0.99), 324 TransitionEffect.scale({ 325 x: 1 / this.geometryScale, 326 y: 1 / this.geometryScale, 327 centerX: '50%', 328 centerY: '50%' }) 329 .combine(TransitionEffect.opacity(0.99)) 330 )) 331 .onAppear(() => { 332 if (this.currentIndex == this.mPosition) { 333 let ret: Boolean = focusControl.requestFocus('browserFocus_' + this.thumbnail); 334 if (ret !== true) { 335 Log.error(TAG, `requestFocus faild, ret:${ret}`); 336 } 337 } 338 }) 339 } 340 341 build() { 342 Stack() { 343 // 加载图标文字组件 344 if (this.isLoading && !this.isRunningAnimation) { 345 Column() { 346 LoadingPanel() 347 } 348 .width(Constants.PERCENT_100) 349 .height(Constants.PERCENT_100) 350 } 351 352 Column() { 353 Stack({ alignContent: Alignment.TopStart }) { 354 Column() { 355 // 图片主体组件 356 if (this.item.width != 0 && this.item.height != 0) { 357 this.buildImage() 358 } else { 359 Image(this.thumbnail) 360 .key('browserFocus_' + this.thumbnail) 361 .focusable(true) 362 .transform(this.matrix) 363 .objectFit(ImageFit.Cover) 364 .autoResize(false) 365 .onComplete(() => { 366 Log.info(TAG, `onComplete finish index: ${this.mPosition}, uri: ${this.item.uri}`); 367 this.showError = false; 368 this.isLoading = false; 369 if (this.updateTransition == this.mPosition) { 370 this.broadCast.emit(PhotoConstants.PHOTO_SHOW_STATE, [true]); 371 } 372 }) 373 .onError(() => { 374 Log.info(TAG, `image show error`); 375 this.showError = true; 376 this.isLoading = false; 377 if (this.updateTransition == this.mPosition) { 378 this.broadCast.emit(PhotoConstants.PHOTO_SHOW_STATE, [false]); 379 } 380 }) 381 .onKeyEvent((event?: KeyEvent) => { 382 this.handleKeyEvent(event as KeyEvent); 383 }) 384 .onAppear(() => { 385 if (this.currentIndex == this.mPosition) { 386 let ret: Boolean = focusControl.requestFocus('browserFocus_' + this.thumbnail); 387 if (ret !== true) { 388 Log.error(TAG, `requestFocus faild, ret:${ret}`); 389 } 390 } 391 }) 392 } 393 } 394 .width(Constants.PERCENT_100) 395 .height(Constants.PERCENT_100) 396 .alignItems(HorizontalAlign.Center) 397 .justifyContent(FlexAlign.Center) 398 } 399 } 400 .hitTestBehavior(HitTestMode.Default) 401 .width(Constants.PERCENT_100) 402 .height(Constants.PERCENT_100) 403 // 绑定手势 404 .parallelGesture( 405 GestureGroup(GestureMode.Parallel, 406 // 捏合手势 407 PinchGesture({ 408 fingers: 2, 409 distance: 1 410 }) 411 .onActionStart((event?: GestureEvent) => { 412 Log.info(TAG, 'PinchGesture onActionStart'); 413 Log.info(TAG, `PinchGesture onActionStart scale:\ 414 ${(event as GestureEvent).scale}, cx: ${(event as GestureEvent).pinchCenterX}, 415 cy: ${(event as GestureEvent).pinchCenterY}`); 416 if (this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_IMAGE || 417 this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) { 418 this.eventPipeline?.onScaleStart((event as GestureEvent).scale, 419 (event as GestureEvent).pinchCenterX, (event as GestureEvent).pinchCenterY); 420 } 421 }) 422 .onActionUpdate((event?: GestureEvent) => { 423 Log.debug(TAG, `PinchGesture onActionUpdate scale: ${(event as GestureEvent).scale}`); 424 if (this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_IMAGE || 425 this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) { 426 this.eventPipeline?.onScale((event as GestureEvent).scale); 427 } 428 }) 429 .onActionEnd(() => { 430 Log.info(TAG, 'PinchGesture onActionEnd'); 431 if (this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_IMAGE || 432 this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) { 433 this.eventPipeline?.onScaleEnd(); 434 if (this.animationOption != null) { 435 animateTo({ 436 duration: this.animationOption.duration, 437 curve: this.animationOption.curve, 438 onFinish: () => { 439 this.eventPipeline?.onAnimationEnd(this.animationEndMatrix); 440 this.animationOption = undefined; 441 this.animationEndMatrix = undefined; 442 } 443 }, () => { 444 this.matrix = this.animationEndMatrix as Matrix4.Matrix4Transit; 445 }) 446 if (!!this.verifyPhotoScaled) { 447 this.verifyPhotoScaled(this.matrix) 448 } 449 this.updatePhotoScaledStatus(); 450 } 451 } 452 }), 453 // 平移手势 454 PanGesture({ 455 direction: this.mDirection 456 }) 457 .onActionStart((event?: GestureEvent) => { 458 Log.info(TAG, `PanGesture start offsetX:\ 459 ${vp2px((event as GestureEvent).offsetX)}, 460 offsetY: ${vp2px((event as GestureEvent).offsetY)}`); 461 this.eventPipeline?.onMoveStart(vp2px((event as GestureEvent).offsetX), 462 vp2px((event as GestureEvent).offsetY)); 463 }) 464 .onActionUpdate((event?: GestureEvent) => { 465 Log.info(TAG, `PanGesture update offsetX:\ 466 ${vp2px((event as GestureEvent).offsetX)}, 467 offsetY: ${vp2px((event as GestureEvent).offsetY)}`); 468 this.eventPipeline?.onMove(vp2px((event as GestureEvent).offsetX), 469 vp2px((event as GestureEvent).offsetY)); 470 this.isPullDownAndDragPhoto = this.eventPipeline?.canPullDownAndDragPhoto() ?? false; 471 if (this.isPullDownAndDragPhoto && this.geometryTransitionEnable && 472 !this.isOnSwiperAnimation && !this.isFromFACard) { 473 this.doDragPhoto((event as GestureEvent).offsetX, (event as GestureEvent).offsetY); 474 } 475 }) 476 .onActionEnd((event?: GestureEvent) => { 477 Log.info(TAG, `PanGesture end offsetX:\ 478 ${vp2px((event as GestureEvent).offsetX)}, offsetY: ${vp2px((event as GestureEvent).offsetY)} \ 479 this.isOnSwiperAnimation ${this.isOnSwiperAnimation}`); 480 if (this.isOnSwiperAnimation) { 481 return; 482 } 483 this.eventPipeline?.onMoveEnd(vp2px((event as GestureEvent).offsetX), 484 vp2px((event as GestureEvent).offsetY)); 485 this.isPullDownAndDragPhoto = this.eventPipeline?.canPullDownAndDragPhoto() ?? false; 486 if (this.animationOption != null) { 487 animateTo({ 488 duration: this.animationOption.duration, 489 curve: this.animationOption.curve, 490 onFinish: () => { 491 this.eventPipeline?.onAnimationEnd(this.animationEndMatrix); 492 this.animationOption = undefined; 493 this.animationEndMatrix = undefined; 494 } 495 }, () => { 496 this.matrix = this.animationEndMatrix as Matrix4.Matrix4Transit; 497 }) 498 if (!!this.verifyPhotoScaled) { 499 this.verifyPhotoScaled(this.matrix) 500 } 501 this.updatePhotoScaledStatus(); 502 } 503 }), 504 // 点击手势 505 TapGesture({ 506 count: 1 507 }) 508 .onAction((event?: GestureEvent) => { 509 if (this.isHandlingTap) { 510 if (this.timerCounter != null) { 511 clearTimeout(this.timerCounter) 512 this.timerCounter = 0; 513 this.isHandlingTap = false; 514 } 515 } else { 516 this.isHandlingTap = true; 517 this.timerCounter = setTimeout(() => { 518 this.broadCast.emit(PhotoConstants.TOGGLE_BAR, [null]); 519 this.isHandlingTap = false; 520 }, Constants.DOUBLE_CLICK_GAP) 521 return; 522 } 523 Log.info(TAG, `onDoubleTap event: ${JSON.stringify(event)}`); 524 this.eventPipeline?.onDoubleTap((event as GestureEvent).fingerList[0].localX, 525 (event as GestureEvent).fingerList[0].localY); 526 if (this.animationOption != null) { 527 Log.info(TAG, 'TapGesture animateTo start'); 528 animateTo({ 529 duration: this.animationOption.duration, 530 curve: this.animationOption.curve, 531 onFinish: () => { 532 this.eventPipeline?.onAnimationEnd(this.animationEndMatrix); 533 this.animationOption = undefined; 534 this.animationEndMatrix = undefined; 535 } 536 }, () => { 537 this.matrix = this.animationEndMatrix as Matrix4.Matrix4Transit; 538 }) 539 if (!!this.verifyPhotoScaled) { 540 this.verifyPhotoScaled(this.matrix) 541 } 542 this.updatePhotoScaledStatus(); 543 } 544 }), 545 ) 546 ) 547 .clip(true) 548 .onTouch((event?: TouchEvent) => { 549 Log.info(TAG, 'onTouch start'); 550 this.dealTouchEvent(event as TouchEvent); 551 this.eventPipeline?.onTouch(event as TouchEvent); 552 }) 553 // TODO Remind users when pictures of other devices cannot be show 554 if ((this.showError || this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) && 555 this.pageFrom == Constants.ENTRY_FROM.DISTRIBUTED) { 556 Row() { 557 Text((this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO) ? 558 $r('app.string.no_distributed_photo_show_video') : 559 $r('app.string.no_distributed_photo_show_image')) 560 .fontSize($r('sys.float.ohos_id_text_size_body2')) 561 .fontFamily($r('app.string.id_text_font_family_regular')) 562 .fontColor($r('sys.color.ohos_id_color_text_tertiary')) 563 } 564 .margin({ 565 top: (this.item.mediaType == 566 UserFileManagerAccess.MEDIA_TYPE_VIDEO) ? $r('app.float.input_text_notify_margin') : 0 567 }) 568 } 569 // 播放视频按键 570 if (this.isVideoPlayBtnShow() && !this.isPullDownAndDragPhoto && !this.isRunningAnimation) { 571 Row() { 572 Image($r('app.media.ic_video_play_btn_png')) 573 .key('VideoPlayButton') 574 .objectFit(ImageFit.Contain) 575 .width($r('app.float.icon_video_size')) 576 .height($r('app.float.icon_video_size')) 577 .onClick(() => { 578 Log.info(TAG, 'video item: ' + JSON.stringify(this.item)) 579 Log.info(TAG, 'video thumbail: ' + JSON.stringify(this.thumbnail)) 580 if (this.item != undefined) { 581 router.pushUrl({ 582 url: 'pages/VideoBrowser', 583 params: { 584 uri: this.item.uri, 585 dateTaken: this.item.getDataTaken(), 586 previewUri: this.thumbnail 587 } 588 }) 589 interface Msg { 590 photoButton: string; 591 from: string; 592 } 593 let msg: Msg = { 594 photoButton: BigDataConstants.PHOTO_BUTTON_VIDEO, 595 from: BigDataConstants.LOCAL_MEDIA, 596 } 597 ReportToBigDataUtil.report(BigDataConstants.CLICK_PHOTO_BUTTON_ID, msg); 598 } 599 }) 600 } 601 } 602 } 603 .width(Constants.PERCENT_100) 604 .height(Constants.PERCENT_100) 605 } 606 607 isVideoPlayBtnShow(): boolean { 608 return (this.item != undefined) && (this.item.mediaType == UserFileManagerAccess.MEDIA_TYPE_VIDEO); 609 } 610 611 doDragPhoto(offsetX: number, offsetY: number) { 612 animateTo({ 613 curve: PhotoConstants.RESPONSIVE_SPRING_MOTION_CURVE 614 }, () => { 615 let distanceY = Math.abs(offsetY); 616 this.geometryOffsetX = offsetX; 617 this.geometryOffsetY = offsetY; 618 619 // Calculate image size by distance Y, min size is 50% 620 let calcGeometryScale = Constants.NUMBER_1 - distanceY * PhotoConstants.DRAG_SCALE; 621 this.geometryScale = calcGeometryScale > PhotoConstants.MIN_DRAG_SCALE ? 622 calcGeometryScale : PhotoConstants.MIN_DRAG_SCALE; 623 624 // Calculate image opacity by distance Y, min opacity is 0 625 let calcTouchOpacity = Constants.NUMBER_1 - distanceY * PhotoConstants.DRAG_OPACITY; 626 let touchOpacity = calcTouchOpacity > Constants.NUMBER_0 ? calcTouchOpacity : Constants.NUMBER_0; 627 AppStorage.setOrCreate<number>('geometryOpacity', touchOpacity); 628 }) 629 } 630 631 private calculateImagePos(): void { 632 let screenWidth = vp2px(this.windowLayoutWidth); 633 let screenHeight = vp2px(this.windowLayoutHeight); 634 this.justifyWidth = this.needJustifyWidth(); 635 this.imageWidth = this.justifyWidth ? Constants.PERCENT_100 : undefined; 636 this.imageHeight = !this.justifyWidth ? Constants.PERCENT_100 : undefined; 637 if (!this.justifyWidth) { 638 this.imageTop = 0; 639 } else { 640 let imgHeight = screenWidth / this.ratio; 641 this.imageTop = screenHeight / 2 - imgHeight / 2; 642 Log.debug(TAG, `calculate image size: height ${imgHeight}, screen height ${screenHeight}, ratio ${this.ratio}`); 643 } 644 Log.debug(TAG, `calculate image size: top ${this.imageTop}`); 645 } 646 647 private updateListCardWidth(): void { 648 if (this.windowColumns == ColumnSize.COLUMN_FOUR) { 649 this.listCardWidth = ScreenManager.getInstance().getColumnsWidth(ColumnSize.COLUMN_FOUR); 650 } else if (this.windowColumns == ColumnSize.COLUMN_EIGHT) { 651 this.listCardWidth = ScreenManager.getInstance().getColumnsWidth(ColumnSize.COLUMN_SIX); 652 } else if (this.windowColumns == ColumnSize.COLUMN_TWELVE) { 653 this.listCardWidth = ScreenManager.getInstance().getColumnsWidth(ColumnSize.COLUMN_EIGHT); 654 } else { 655 Log.error(TAG, `screenColumns is: ${this.windowColumns}`); 656 } 657 } 658 659 private onTransitionChange() { 660 Log.info(TAG, `onTransitionChange , ${this.updateTransition} ${this.position}`); 661 if (this.lastUpdateTransition != this.updateTransition) { 662 this.lastUpdateTransition = this.updateTransition; 663 if (this.updateTransition == this.mPosition) { 664 this.broadCast.emit(PhotoConstants.PHOTO_SHOW_STATE, [!this.showError]); 665 } else { 666 667 } 668 // reset matrix 669 if (this.imgScale != 1) { 670 this.matrix = Matrix4.identity().scale({ 671 x: this.imgScale, 672 y: this.imgScale 673 }).copy(); 674 this.eventPipeline && this.eventPipeline.setDefaultScale(this.imgScale); 675 } else { 676 this.matrix = Matrix4.identity().copy(); 677 // 重置大小 678 this.eventPipeline && this.eventPipeline.reset(); 679 } 680 this.updatePhotoScaledStatus(); 681 Log.info(TAG, `onTransitionChange end`); 682 } 683 } 684 685 private needJustifyWidth(): boolean { 686 let maxWidth = vp2px(this.windowLayoutWidth); 687 let maxHeight = vp2px(this.windowLayoutHeight); 688 let justifyWidth: boolean = this.ratio >= (maxWidth / maxHeight); 689 Log.info(TAG, `maxWidth:${maxWidth}, maxHeight:${maxHeight}, ratio:${this.ratio}, justifyWidth:${justifyWidth}`); 690 return justifyWidth; 691 } 692 693 private dealTouchEvent(event: TouchEvent): void { 694 if (this.eventPipeline === null) { 695 return; 696 }; 697 if (!this.eventPipeline.canTouch() || this.isOnSwiperAnimation || event.touches.length > 1 || 698 this.isPhotoScaled || this.isInSelectedMode || this.isDefaultFA) { 699 return; 700 } 701 if (event.type == TouchType.Move) { 702 let yOffset = event.touches[0].screenY - this.lastTouchDownY; 703 let xOffset = event.touches[0].screenX - this.lastTouchDownX; 704 this.lastTouchDownY = event.touches[0].screenY; 705 this.lastTouchDownX = event.touches[0].screenX; 706 if (yOffset == undefined || xOffset == undefined) { 707 Log.info(TAG, 'dealTouchEvent screenY or screenX undefined'); 708 return; 709 } 710 } else if (event.type == TouchType.Down) { 711 this.lastTouchDownY = event.touches[0].screenY; 712 this.lastTouchDownX = event.touches[0].screenX; 713 } else if (event.type == TouchType.Up) { 714 } 715 } 716 717 private handleKeyEvent(event: KeyEvent): void { 718 Log.info(TAG, `type=${event.type}, keyCode=${event.keyCode}`); 719 interface Msg { 720 from: string; 721 } 722 if (KeyType.Up == event.type) { 723 switch (event.keyCode) { 724 case MultimodalInputManager.KEY_CODE_KEYBOARD_ESC: 725 let msg: Msg = { 726 from: BigDataConstants.BY_KEYBOARD, 727 } 728 ReportToBigDataUtil.report(BigDataConstants.ESC_PHOTO_BROWSER_WAY, msg); 729 this.broadCast.emit(PhotoConstants.PULL_DOWN_END, []); 730 break; 731 case MultimodalInputManager.KEY_CODE_KEYBOARD_ENTER: 732 if (this.item != undefined && this.item.mediaType === UserFileManagerAccess.MEDIA_TYPE_VIDEO) { 733 router.pushUrl({ 734 url: 'pages/VideoBrowser', 735 params: { 736 uri: this.item.uri, 737 dateTaken: this.item.getDataTaken(), 738 previewUri: this.thumbnail 739 } 740 }) 741 } 742 break; 743 default: 744 Log.info(TAG, `on key event Up, default`); 745 break; 746 } 747 } 748 } 749 750 private isNeedShieldPullUpEvent(event: GestureEvent): boolean { 751 return event.offsetY < 0 && 752 !this.isPhotoScaled; 753 } 754} 755 756interface PreItem { 757 height: number; 758 width: number; 759} 760