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