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