• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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