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