• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022 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 Matrix4 from '@ohos.matrix4';
17import router from '@system.router';
18import MediaLib from '@ohos.multimedia.mediaLibrary';
19import { MediaDataItem } from '@ohos/base/src/main/ets/data/MediaDataItem';
20import { EventPipeline } from '../utils/EventPipeline';
21import { Broadcast } from '@ohos/base/src/main/ets/utils/Broadcast';
22import { Log } from '@ohos/base/src/main/ets/utils/Log';
23import { Constants } from '../constants/Constants';
24import screenManager from '@ohos/base/src/main/ets/manager/ScreenManager';
25import { RouterConstants } from '@ohos/base/src/main/ets/constants/RouterConstants';
26import { startTrace, finishTrace } from '@ohos/base/src/main/ets/utils/TraceControllerUtils';
27import { LoadingPanel } from './LoadingPanel';
28import { VideoIcon } from './VideoIcon';
29import { MediaConstants } from '@ohos/base/src/main/ets/constants/MediaConstants';
30import { getThumbnail } from '../model/ThumbnailModel';
31
32const TAG = "PhotoItem"
33
34@Component
35export struct PhotoItem {
36    item: MediaDataItem;
37    pageName: string
38    @State matrix: Matrix4.Matrix4Transit = Matrix4.identity().copy();
39    @State photoItemDirection: PanDirection = PanDirection.Vertical;
40    @Consume broadCast: Broadcast;
41    @State objectFit: ImageFit = ImageFit.Contain;
42    @State thumbnail: string = '';
43    @State ratio: number = 1.0;
44    @State showError: boolean = false;
45    @Consume isPullingDown: boolean;
46    @Consume pageFrom: number;
47    @Consume('transitionIndex') @Watch('onTransitionChange') updateTransition: number;
48    private eventPipeline: EventPipeline;
49    private animationOption: any = null;
50    private animationEndMatrix: any = null;
51    @Provide isLoading: boolean = true;
52    private imgScale: number = 1;
53    private firstLoad: boolean = true;
54    private preItem = { rotate: 0, height: 0, width: 0 };
55    private timeStamp: string = '';
56
57    onTransitionChange() {
58        Log.info(TAG, `onTransitionChange, ${this.updateTransition} ${this.item.index}`);
59        this.updateThumbnail()
60        if (this.updateTransition == this.item.index) {
61            this.broadCast.emit(Constants.PHOTO_SHOW_STATE, [!this.showError]);
62        } else {
63            this.isPullingDown = false;
64        }
65        // reset matrix
66        if (this.imgScale != 1) {
67            this.matrix = Matrix4.identity().scale({
68                x: this.imgScale,
69                y: this.imgScale
70            }).copy();
71            this.eventPipeline && this.eventPipeline.setDefaultScale(this.imgScale);
72        } else {
73            this.matrix = Matrix4.identity().copy();
74            this.eventPipeline && this.eventPipeline.reset();
75        }
76        Log.info(TAG, `onTransitionChange end, ${this.updateTransition} ${this.item.index}`);
77    }
78
79    aboutToAppear(): void {
80        Log.info(TAG, `aboutToAppear`);
81        this.timeStamp = (new Date()).valueOf().toString();
82        this.eventPipeline = new EventPipeline(this.broadCast, this.item, this.timeStamp, this.updateMatrix.bind(this));
83        this.matrix = Matrix4.identity().copy();
84        this.firstLoad = true;
85        this.isLoading = true;
86        this.thumbnail = this.item.getThumbnail(this.item.imgWidth, this.item.imgHeight);
87        this.item.load(true).then(() => {
88            this.isLoading = false
89            this.updateThumbnail()
90            Log.info(TAG, `aboutToAppear loadFinish thumbnail ${this.thumbnail}`);
91        })
92        // register event handling
93        this.broadCast.on(Constants.TOUCH_EVENT + this.item.uri + this.timeStamp, (matrix: any) => {
94            this.matrix = matrix;
95        });
96
97        this.broadCast.on(Constants.DIRECTION_CHANGE + this.item.uri + this.timeStamp, (direction: PanDirection) => {
98            Log.info(TAG, `direction: ${direction}`);
99            this.photoItemDirection = direction;
100        });
101
102        this.broadCast.on(Constants.ANIMATION_EVENT + this.item.uri + this.timeStamp, (animationOption: any, animationEndMatrix: any) => {
103            Log.info(TAG, `animationOption: ${JSON.stringify(animationOption)}`);
104            this.animationOption = animationOption;
105            this.animationEndMatrix = animationEndMatrix;
106        });
107        this.onTransitionChange();
108        this.onViewDataChanged();
109        Log.info(TAG, `photoItem aboutToAppear ${this.item.uri} end ${this.item.orientation}}`);
110    }
111
112    private updateMatrix(matrix) {
113        Log.info(TAG, `updateMatrix start ${matrix}`);
114        this.matrix = matrix;
115    }
116
117    private updateThumbnail() {
118        getThumbnail(this.item, this.updateTransition == this.item.index).then((thumbnail: string) => {
119            this.thumbnail = thumbnail
120        })
121    }
122
123    aboutToDisappear(): void {
124        Log.info(TAG, `aboutToDisappear ${this.item.uri}/${this.thumbnail}`);
125        // clean up event registration
126        this.broadCast.off(Constants.TOUCH_EVENT + this.item.uri + this.timeStamp, null);
127        this.broadCast.off(Constants.DIRECTION_CHANGE + this.item.uri + this.timeStamp, null);
128        this.broadCast.off(Constants.ANIMATION_EVENT + this.item.uri + this.timeStamp, null);
129        this.isPullingDown = false;
130        Log.info(TAG, `aboutToDisappear ${this.item.uri}/${this.thumbnail} end`);
131    }
132
133    onViewDataChanged(): void {
134        Log.info(TAG, `onViewDataChanged start`);
135        this.ratio = this.calcRatio(this.item);
136        if ((this.preItem.rotate == this.item.orientation && this.preItem.height == this.item.imgHeight &&
137        this.preItem.width == this.item.imgWidth) && !this.firstLoad) {
138            this.preItem.rotate = this.item.orientation;
139            this.preItem.width = this.item.imgWidth;
140            this.preItem.height = this.item.imgHeight;
141            this.eventPipeline && this.eventPipeline.onDataChanged(this.item)
142            return;
143        }
144        this.matrix = Matrix4.identity().copy();
145        this.eventPipeline && this.eventPipeline.setDefaultScale(1);
146        this.eventPipeline && this.eventPipeline.onDataChanged(this.item)
147        this.firstLoad = false;
148        this.preItem.width = this.item.imgWidth;
149        this.preItem.height = this.item.imgHeight;
150        Log.debug(TAG, `onViewDataChanged thumbnail:${JSON.stringify(this.thumbnail)}`);
151    }
152
153    calcRatio(info: any): number {
154        if (info == null || info == undefined) {
155            return 1;
156        }
157        if (info.width == 0 || info.height == 0) {
158            return 1;
159        }
160        return  info.width / info.height;
161    }
162
163    build() {
164        Stack() {
165            Column() {
166                LoadingPanel()
167            }
168            .width('100%')
169            .height('100%')
170
171            Row() {
172                Image(this.thumbnail)
173                    .rotate({ x: 0, y: 0, z: 1, angle: 0 })
174                    .syncLoad(true)
175                    .transform(this.matrix)
176                    .autoResize(false)
177                    .fitOriginalSize(false)
178                    .aspectRatio(this.ratio)
179                    .objectFit(ImageFit.Cover)
180                    .width("100%")
181                    .height("100%")
182                    .constraintSize({maxWidth:"100%", maxHeight:"100%"})
183                    .sharedTransition(this.item.index == this.updateTransition ? this.pageName + this.item.getHashCode() : Constants.DEFAULT_TRANSITION_ID, {
184                        duration: Constants.SHARE_TRANSITION_DURATION,
185                        zIndex: 0,
186                    })
187                    .onComplete(() => {
188                        startTrace(`PhotoItemComplete_${this.item}`);
189                        Log.info(TAG, `onComplete finish, index: ${this.item.index}, item: ${JSON.stringify(this.item)}, uri: ${this.thumbnail}.`);
190                        this.showError = false;
191                        this.isLoading = false;
192                        if (this.updateTransition == this.item.index) {
193                            this.broadCast.emit(Constants.PHOTO_SHOW_STATE, [true]);
194                        }
195                        finishTrace(`PhotoItemComplete_${this.item.index}`);
196                    })
197                    .onError(() => {
198                        Log.error(TAG, `image show error ${this.thumbnail} ${this.item.width} ${this.item.height}`);
199                        if (this.thumbnail.length == 0 || this.item.width == 0 || this.item.height == 0) {
200                            this.updateThumbnail()
201                            return
202                        }
203                        this.showError = true;
204                        this.isLoading = true;
205                        if (this.updateTransition == this.item.index) {
206                            this.broadCast.emit(Constants.PHOTO_SHOW_STATE, [false]);
207                        }
208                    })
209            }
210            .width('100%')
211            .height('100%')
212            .justifyContent(FlexAlign.Center)
213            .alignItems(VerticalAlign.Center)
214            .onClick(() => {
215                this.broadCast.emit(Constants.TOGGLE_BAR, [null]);
216            })
217            .parallelGesture(
218            GestureGroup(GestureMode.Parallel,
219            PinchGesture({
220                fingers: 2,
221                distance: 1
222            })
223                .onActionStart((event: GestureEvent) => {
224                    Log.debug(TAG, 'PinchGesture onActionStart');
225                    startTrace("PinchGesture onActionStart")
226                    if (this.isPullingDown) {
227                        Log.debug(TAG, 'Not allow to pinch when pullingDown');
228                        finishTrace("PinchGesture onActionStart")
229                        return;
230                    }
231                    Log.debug(TAG, `PinchGesture onActionStart scale:\
232                            ${event.scale}, cx: ${event.pinchCenterX}, cy: ${event.pinchCenterY}`);
233                    if (this.item.mediaType == MediaLib.MediaType.IMAGE) {
234                        this.eventPipeline.onScaleStart(event.scale, event.pinchCenterX, event.pinchCenterY);
235                    }
236                    finishTrace("PinchGesture onActionStart")
237                })
238                .onActionUpdate((event) => {
239                    Log.debug(TAG, `PinchGesture onActionUpdate scale: ${event.scale}`);
240                    startTrace("PinchGesture onActionUpdate")
241                    if (this.isPullingDown) {
242                        Log.debug(TAG, 'Not allow to pinch when pullingDown');
243                        finishTrace("PinchGesture onActionUpdate")
244                        return;
245                    }
246                    if (this.item.mediaType == MediaLib.MediaType.IMAGE) {
247                        this.eventPipeline.onScale(event.scale);
248                    }
249                    finishTrace("PinchGesture onActionUpdate")
250                })
251                .onActionEnd(() => {
252                    Log.debug(TAG, 'PinchGesture onActionEnd');
253                    startTrace("PinchGesture onActionEnd")
254                    if (this.isPullingDown) {
255                        Log.debug(TAG, 'Not allow to pinch when pullingDown');
256                        finishTrace("PinchGesture onActionEnd")
257                        return;
258                    }
259                    if (this.item.mediaType == MediaLib.MediaType.IMAGE) {
260                        this.eventPipeline.onScaleEnd();
261                        if (this.animationOption != null) {
262                            animateTo({
263                                duration: this.animationOption.duration,
264                                curve: this.animationOption.curve,
265                                onFinish: () => {
266                                    this.eventPipeline.onAnimationEnd(this.animationEndMatrix);
267                                    this.animationOption = null;
268                                    this.animationEndMatrix = null;
269                                }
270                            }, () => {
271                                this.matrix = this.animationEndMatrix;
272                            })
273                        }
274                    }
275                    finishTrace("PinchGesture onActionEnd")
276                }),
277            PanGesture({
278                direction: this.photoItemDirection
279            })
280                .onActionStart((event: GestureEvent) => {
281                    startTrace("PanGesture onActionStart")
282                    Log.debug(TAG, `PanGesture start offsetX:\
283                                    ${vp2px(event.offsetX)}, offsetY: ${vp2px(event.offsetY)}`);
284                    this.eventPipeline.onMoveStart(vp2px(event.offsetX), vp2px(event.offsetY));
285                    finishTrace("PanGesture onActionStart")
286                })
287                .onActionUpdate((event: GestureEvent) => {
288                    startTrace("PanGesture onActionUpdate")
289                    Log.debug(TAG, `PanGesture update offsetX:\
290                                    ${vp2px(event.offsetX)}, offsetY: ${vp2px(event.offsetY)}`);
291                    this.eventPipeline.onMove(vp2px(event.offsetX), vp2px(event.offsetY));
292                    finishTrace("PanGesture onActionUpdate")
293                })
294                .onActionEnd((event: GestureEvent) => {
295                    startTrace("PanGesture onActionEnd")
296                    Log.debug(TAG, `PanGesture end offsetX:\
297                                    ${vp2px(event.offsetX)}, offsetY: ${vp2px(event.offsetY)}`);
298                    this.eventPipeline.onMoveEnd(vp2px(event.offsetX), vp2px(event.offsetY));
299                    if (this.animationOption != null) {
300                        animateTo({
301                            duration: this.animationOption.duration,
302                            curve: this.animationOption.curve,
303                            onFinish: () => {
304                                this.eventPipeline.onAnimationEnd(this.animationEndMatrix);
305                                this.animationOption = null;
306                                this.animationEndMatrix = null;
307                            }
308                        }, () => {
309                            this.matrix = this.animationEndMatrix;
310                        })
311                    }
312                    finishTrace("PanGesture onActionEnd")
313                }),
314            TapGesture({
315                count: 2
316            })
317                .onAction((event: GestureEvent) => {
318                    if (this.isPullingDown) {
319                        Log.debug(TAG, 'Not allow to double tap when pullingDown');
320                        return;
321                    }
322                    Log.debug(TAG, `onDoubleTap event: ${JSON.stringify(event)}`);
323                    if (this.item.mediaType == MediaLib.MediaType.VIDEO) {
324                        return;
325                    }
326                    this.eventPipeline.onDoubleTap(event.fingerList[0].localX, event.fingerList[0].localY);
327                    if (this.animationOption != null) {
328                        Log.debug(TAG, 'TapGesture animateTo start');
329                        animateTo({
330                            duration: this.animationOption.duration,
331                            curve: this.animationOption.curve,
332                            onFinish: () => {
333                                this.eventPipeline.onAnimationEnd(this.animationEndMatrix);
334                                this.animationOption = null;
335                                this.animationEndMatrix = null;
336                            }
337                        }, () => {
338                            this.matrix = this.animationEndMatrix;
339                        })
340                    }
341                })
342            )
343            )
344            .clip(true)
345            .onTouch((event) => {
346                this.eventPipeline.onTouch(event);
347            })
348            // TODO Remind users when pictures of other devices cannot be show
349            if ((this.showError || this.item.mediaType == MediaLib.MediaType.VIDEO) &&
350            this.pageFrom == RouterConstants.ENTRY_FROM.DISTRIBUTED) {
351                Row() {
352                    Text((this.item.mediaType == MediaLib.MediaType.VIDEO) ?
353                    $r('app.string.no_distributed_photo_show_video') :
354                    $r('app.string.no_distributed_photo_show_image'))
355                        .fontSize($r('sys.float.ohos_id_text_size_body2'))
356                        .fontFamily($r('app.string.id_text_font_family_regular'))
357                        .fontColor($r('sys.color.ohos_id_color_text_tertiary'))
358                }
359                .margin({
360                    top: (this.item.mediaType == MediaLib.MediaType.VIDEO) ? $r('app.float.input_text_notify_margin') : 0
361                })
362            }
363            if (this.isVideoPlayBtnShow()) {
364                Row() {
365                    VideoIcon()
366                }
367                .width($r('app.float.icon_video_size'))
368                .height($r('app.float.icon_video_size'))
369                .onClick(() => {
370                    if (this.item != undefined) {
371                        router.push({
372                            uri: 'feature/browser/view/VideoBrowser',
373                            params: {
374                                uri: this.item.uri,
375                                dateAdded: this.item.dateAdded,
376                                previewUri: this.thumbnail
377                            }
378                        })
379                    }
380                })
381            }
382
383        }.width('100%')
384        .height('100%')
385    }
386
387    private isVideoPlayBtnShow(): boolean {
388        Log.debug(TAG, `isVideoPlayBtnShow: ${this.item.mediaType}`);
389        return ((this.item) && (this.item.mediaType == MediaLib.MediaType.VIDEO));
390    }
391}
392