• 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 =
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