1/*
2 * Copyright (c) 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 { Dispatch, OhCombinedState, Unsubscribe } from '../../redux/store';
17import { getStore } from '../../redux/store';
18import { Action } from '../../redux/actions/Action';
19import { EventBus } from '../../worker/eventbus/EventBus';
20import { EventBusManager } from '../../worker/eventbus/EventBusManager';
21import { CameraId } from '../../setting/settingitem/CameraId';
22
23let SHOW_FOLD_CANVAS: number = 0
24let SHOW_NOT_TAKE_VIDEO_CANVAS: number = 1
25let SHOW_TAKING_VIDEO_CANVAS: number = 2
26
27class StateStruct {
28  mode: string = 'PHOTO';
29  videoState: string = 'beforeTakeVideo';
30  cameraPosition: CameraId = CameraId.BACK;
31  zoomRatio: number = 1;
32  isShowZoomText: boolean = false;
33  showZoomLabelValue: boolean = true;
34  minZoomRatio: number = 1;
35  maxZoomRatio: number = 6;
36}
37
38class ZoomViewDispatcher {
39  private mDispatch: Dispatch = (data) => data;
40
41  public setDispatch(dispatch: Dispatch) {
42    this.mDispatch = dispatch;
43  }
44
45  public updateZoomRatio(zoomRatio: number): void {
46    this.mDispatch(Action.changeZoomRatio(zoomRatio));
47  }
48
49  public updateShowZoomFlag(flag: boolean): void {
50    this.mDispatch(Action.updateShowZoomTextFlag(flag));
51  }
52
53  public updateShowZoomLabelValue(flag: boolean): void {
54    this.mDispatch(Action.updateShowZoomLabelValue(flag));
55  }
56}
57
58class ZoomRatioStruct {
59  zoomRatio: number = 0;
60}
61
62class VideoStateStruct {
63  videoState: string = '';
64}
65
66@Component
67export struct ZoomView {
68  private appEventBus: EventBus = EventBusManager.getInstance().getEventBus()
69  @State state: StateStruct = new StateStruct();
70  @State offsetX: number = 0
71  @State triggerRebuildNum: number = 0
72  private mAction: ZoomViewDispatcher = new ZoomViewDispatcher();
73  private notTakeVideoExtCanvasWidth: number = 360
74  private takingVideoExtCanvasWidth: number = 196
75  private foldCanvasWidth: number = 94
76  private canvasHeight: number = 82
77  private touchedOffsetX: number = this.takingVideoExtCanvasWidth / 2
78  private startOffsetX: number = 0
79  private canvasSettings: RenderingContextSettings = new RenderingContextSettings(true)
80  private notTakeVideoExtCanvasCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.canvasSettings)
81  private takingVideoExtCanvasCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.canvasSettings)
82  private foldCanvasCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.canvasSettings)
83  private notTakeVideoExtOffCanvasCtx: OffscreenCanvasRenderingContext2D = new OffscreenCanvasRenderingContext2D(
84  this.notTakeVideoExtCanvasWidth, this.canvasHeight, this.canvasSettings)
85  private takingVideoExtOffCanvasCxt: OffscreenCanvasRenderingContext2D = new OffscreenCanvasRenderingContext2D(
86  this.takingVideoExtCanvasWidth, this.canvasHeight, this.canvasSettings)
87  private foldOffCanvasCtx: OffscreenCanvasRenderingContext2D = new OffscreenCanvasRenderingContext2D(
88  this.foldCanvasWidth, this.canvasHeight, this.canvasSettings)
89  private lpgTimer: number = 0
90  private pgTimer: number = 0
91  private lpgExp: boolean = false
92  private pgExp: boolean = false
93  private baseZoomRatio: number = 1
94  private mainDotRadius: number = 1.5
95  private secDotRadius: number = 0.75
96  private centerDotRadius: number = 2.5
97  private dotSpacing: number = 4
98
99  aboutToAppear(): void {
100    getStore().subscribe((state: OhCombinedState) => {
101      this.state = {
102        mode: state.modeReducer.mode,
103        videoState: state.recordReducer.videoState,
104        cameraPosition: state.cameraReducer.cameraPosition,
105        zoomRatio: state.zoomReducer.zoomRatio,
106        isShowZoomText: state.zoomReducer.isShowZoomText,
107        showZoomLabelValue: state.zoomReducer.showZoomLabelValue,
108        minZoomRatio: state.zoomReducer.minZoomRatio,
109        maxZoomRatio: state.zoomReducer.maxZoomRatio
110      };
111    }, (dispatch: Dispatch) => {
112      this.mAction.setDispatch(dispatch);
113    });
114    this.appEventBus.on(Action.ACTION_CHANGE_ZOOM_RATIO, (data: ZoomRatioStruct) => this.updateZoomOffset(data))
115    this.appEventBus.on(Action.ACTION_UPDATE_VIDEO_STATE, (data: VideoStateStruct) => this.updateZoomState(data))
116  }
117
118  aboutToDisappear(): void {
119    this.appEventBus.off(Action.ACTION_CHANGE_ZOOM_RATIO, (data: ZoomRatioStruct) => this.updateZoomOffset(data))
120    this.appEventBus.off(Action.ACTION_UPDATE_VIDEO_STATE, (data: VideoStateStruct) => this.updateZoomState(data))
121  }
122
123  private getCurrentCanvasType(): number {
124    if (this.state.isShowZoomText && (this.state.videoState === 'beforeTakeVideo'
125    && (this.state.mode === 'PHOTO' || this.state.mode === 'VIDEO'))) {
126      return SHOW_NOT_TAKE_VIDEO_CANVAS
127    } else if (this.state.mode === 'VIDEO'
128    && (this.state.isShowZoomText || this.state.videoState !== 'beforeTakeVideo')) {
129      return SHOW_TAKING_VIDEO_CANVAS
130    } else {
131      return SHOW_FOLD_CANVAS
132    }
133  }
134
135  private lpgOnAction(): void {
136    this.clearTimer()
137    this.mAction.updateShowZoomFlag(true)
138    this.baseZoomRatio = this.state.zoomRatio
139    this.offsetX = (this.state.zoomRatio - 1) * this.getZoomOffsetUnit()
140    this.lpgExp = true
141    this.pgExp = false
142    this.triggerRebuildNum = this.triggerRebuildNum + 0.0001
143  }
144
145  private lpgOnActionEnd(): void {
146    if (this.lpgTimer) {
147      clearTimeout(this.lpgTimer)
148    }
149    this.lpgTimer = setTimeout(() => {
150      if (this.lpgExp && !this.pgExp) {
151        this.mAction.updateShowZoomFlag(false)
152        this.triggerRebuildNum = this.triggerRebuildNum + 0.0001
153      }
154      this.lpgExp = false
155    }, 3000)
156  }
157
158  private changeZoomRatioOnTakingVideoExt(): void {
159    if (this.touchedOffsetX > this.takingVideoExtCanvasWidth / 2) {
160      this.addZoomRatio()
161    } else if (this.touchedOffsetX < this.takingVideoExtCanvasWidth / 2) {
162      this.subtractZoomRatio()
163    } else {
164      this.triggerRebuildNum = this.triggerRebuildNum + 0.0001
165      this.mAction.updateShowZoomFlag(false)
166    }
167  }
168
169  private addZoomRatio(): void {
170    let curZoomRatio = this.state.zoomRatio + 0.1
171    if (curZoomRatio > this.state.maxZoomRatio) {
172      curZoomRatio = this.state.maxZoomRatio
173    }
174    this.mAction.updateZoomRatio(curZoomRatio)
175    this.mAction.updateShowZoomFlag(true)
176    this.triggerRebuildNum = this.triggerRebuildNum + 0.0001
177  }
178
179  private subtractZoomRatio(): void {
180    let curZoomRatio = this.state.zoomRatio - 0.1
181    if (curZoomRatio < this.state.minZoomRatio) {
182      curZoomRatio = this.state.minZoomRatio
183    }
184    this.mAction.updateZoomRatio(curZoomRatio)
185    this.mAction.updateShowZoomFlag(true)
186    this.triggerRebuildNum = this.triggerRebuildNum - 0.0001
187  }
188
189  private takingVideoExtTouched(event?: TouchEvent): void {
190    if (!event) {
191      return;
192    }
193    if (event.type === TouchType.Down) {
194      this.touchedOffsetX = event.touches[0].x
195      this.startOffsetX = event.touches[0].x
196      this.changeZoomRatioOnTakingVideoExt()
197    }
198    if (event.type === TouchType.Up) {
199      this.touchedOffsetX = this.takingVideoExtCanvasWidth / 2
200      this.changeZoomRatioOnTakingVideoExt()
201    }
202  }
203
204  private takingVideoExtLongPgAction(event?: GestureEvent): void {
205    if (!event) {
206      return;
207    }
208    this.touchedOffsetX = event.fingerList[0].localX
209    this.changeZoomRatioOnTakingVideoExt()
210  }
211
212  private takingVideoExtLongPgActionEnd(): void {
213    this.touchedOffsetX = this.takingVideoExtCanvasWidth / 2
214    this.changeZoomRatioOnTakingVideoExt()
215  }
216
217  private takingVideoExtPgActionStart(event?: GestureEvent): void {
218    if (!event) {
219      return;
220    }
221    this.touchedOffsetX = this.startOffsetX + event.offsetX
222    this.changeZoomRatioOnTakingVideoExt()
223  }
224
225  private takingVideoExtPgActionUpdate(event?: GestureEvent): void {
226    if (!event) {
227      return;
228    }
229    this.touchedOffsetX = this.startOffsetX + event.offsetX
230    let takingVideoExtMaxOffsetX = this.takingVideoExtCanvasWidth - this.getZoomBtnRadius() - this.secDotRadius
231    let takingVideoExtMinOffsetX = this.getZoomBtnRadius() + this.secDotRadius
232    if (this.touchedOffsetX > takingVideoExtMaxOffsetX) {
233      this.touchedOffsetX = takingVideoExtMaxOffsetX
234    } else if (this.touchedOffsetX < takingVideoExtMinOffsetX) {
235      this.touchedOffsetX = takingVideoExtMinOffsetX
236    }
237    this.changeZoomRatioOnTakingVideoExt()
238  }
239
240  private takingVideoExtPgActionEnd(event?: GestureEvent): void {
241    if (!event) {
242      return;
243    }
244    this.touchedOffsetX = this.takingVideoExtCanvasWidth / 2
245    this.startOffsetX = 0
246    this.changeZoomRatioOnTakingVideoExt()
247  }
248
249  private subtractTouched(event?: TouchEvent): void {
250    if (!event) {
251      return;
252    }
253    if (event.type === TouchType.Down) {
254      this.subtractZoomRatio()
255    }
256    if (event.type === TouchType.Up) {
257      this.mAction.updateShowZoomFlag(false)
258    }
259  }
260
261  private subtractLongOnAction(event?: GestureEvent): void {
262    if (!event) {
263      return;
264    }
265    this.subtractZoomRatio()
266  }
267
268  private subtractLongOnActionEnd(): void {
269    this.mAction.updateShowZoomFlag(false)
270  }
271
272  private addTouched(event?: TouchEvent): void {
273    if (!event) {
274      return;
275    }
276    if (event.type === TouchType.Down) {
277      this.addZoomRatio()
278    }
279    if (event.type === TouchType.Up) {
280      this.mAction.updateShowZoomFlag(false)
281    }
282  }
283
284  private addLongOnAction(): void {
285    this.addZoomRatio()
286  }
287
288  private addLongOnActionEnd(): void {
289    this.mAction.updateShowZoomFlag(false)
290  }
291
292  private pgOnActionStart(): void {
293    this.clearTimer()
294    this.mAction.updateShowZoomFlag(true)
295    this.mAction.updateShowZoomLabelValue(false)
296    this.baseZoomRatio = this.state.zoomRatio
297    this.pgExp = true
298    this.lpgExp = false
299  }
300
301  private pgOnActionUpdate(event?: GestureEvent): void {
302    if (!event) {
303      return;
304    }
305    this.offsetX = (this.baseZoomRatio - this.state.minZoomRatio) * this.getZoomOffsetUnit() + event.offsetX
306    this.updateZoomRatio()
307  }
308
309  private pgOnActionEnd(): void {
310    this.mAction.updateShowZoomLabelValue(true)
311    if (this.pgTimer) {
312      clearTimeout(this.pgTimer)
313    }
314    this.pgTimer = setTimeout(() => {
315      if (this.pgExp && !this.lpgExp) {
316        this.mAction.updateShowZoomFlag(false)
317      }
318      this.pgExp = false
319    }, 3000)
320  }
321
322  private mOnTouch(event: TouchEvent): void {
323    if (event.type === TouchType.Down) {
324      this.clearTimer()
325      this.mAction.updateShowZoomFlag(true)
326      this.pgExp = true
327      this.lpgExp = false
328
329      let x = event.touches[0].x
330      let zoomRatio = this.state.zoomRatio
331      if (this.state.videoState === 'beforeTakeVideo' && this.getCurrentCanvasType() === SHOW_NOT_TAKE_VIDEO_CANVAS) {
332        if (x < vp2px(36)) {
333          zoomRatio = this.state.minZoomRatio
334        }
335        if (x > this.notTakeVideoExtCanvasWidth - vp2px(36)) {
336          zoomRatio = this.state.maxZoomRatio
337        }
338        if (x > vp2px(36) && x < this.notTakeVideoExtCanvasWidth - vp2px(36)) {
339          this.offsetX = x - this.getPadding()
340          this.updateZoomRatio()
341          return;
342        }
343      }
344      this.offsetX = (zoomRatio - 1) * this.getZoomOffsetUnit()
345      this.updateZoomRatio()
346    } else if (event.type === TouchType.Up) {
347      if (this.pgTimer) {
348        clearTimeout(this.pgTimer)
349      }
350      this.pgTimer = setTimeout(() => {
351        if (this.pgExp && !this.lpgExp) {
352          this.mAction.updateShowZoomFlag(false)
353        }
354        this.pgExp = false
355      }, 3000)
356    }
357
358  }
359
360  private getZoomBtnCenterX(): number {
361    if (this.getCurrentCanvasType() === SHOW_TAKING_VIDEO_CANVAS) {
362      return this.touchedOffsetX
363    }
364    if (this.offsetX === 0 && this.state.zoomRatio !== 1) {
365      this.offsetX = (this.state.zoomRatio - this.state.minZoomRatio) * this.getZoomOffsetUnit()
366    }
367    if (this.state.zoomRatio === 1 && this.offsetX !== 0) {
368      this.offsetX = 0
369    }
370    let padding = this.getPadding()
371    let result = this.offsetX + padding + this.mainDotRadius
372    if (result > this.notTakeVideoExtCanvasWidth - padding - this.mainDotRadius) {
373      result = this.notTakeVideoExtCanvasWidth - padding - this.mainDotRadius
374    }
375    if (result < padding + this.mainDotRadius) {
376      result = padding + this.mainDotRadius
377    }
378    return result
379  }
380
381  private getZoomOffsetUnit(): number {
382    let padding = this.getPadding();
383    let fullWidth = this.notTakeVideoExtCanvasWidth - padding * 2 - this.mainDotRadius * 2;
384    return fullWidth / (this.state.maxZoomRatio - this.state.minZoomRatio);
385  }
386
387  private updateZoomOffset(data: ZoomRatioStruct): void {
388    let offset = (data.zoomRatio - this.state.minZoomRatio) * this.getZoomOffsetUnit();
389    this.offsetX = offset;
390  }
391
392  private updateZoomState(data: VideoStateStruct): void {
393    if (data.videoState === 'beforeTakeVideo') {
394      this.clearTimer();
395      this.mAction.updateShowZoomFlag(false);
396      this.pgExp = false;
397    }
398  }
399
400  private clearTimer(): void {
401    if (this.pgTimer) {
402      clearTimeout(this.pgTimer);
403    }
404    if (this.lpgTimer) {
405      clearTimeout(this.lpgTimer);
406    }
407  }
408
409  private updateZoomRatio(): void {
410    let padding = this.getPadding()
411    let fullWidth = this.notTakeVideoExtCanvasWidth - padding * 2 - this.mainDotRadius * 2
412    let curZoomRatio = (this.offsetX / fullWidth) * (this.state.maxZoomRatio - this.state.minZoomRatio) + this.state.minZoomRatio
413    if (curZoomRatio > this.state.maxZoomRatio) {
414      curZoomRatio = this.state.maxZoomRatio
415    }
416    if (curZoomRatio < this.state.minZoomRatio) {
417      curZoomRatio = this.state.minZoomRatio
418    }
419    this.mAction.updateZoomRatio(curZoomRatio)
420  }
421
422  private getPadding(): number {
423    if (this.getCurrentCanvasType() === SHOW_NOT_TAKE_VIDEO_CANVAS) {
424      return 32
425    } else if (this.getCurrentCanvasType() === SHOW_TAKING_VIDEO_CANVAS) {
426      return 15.5
427    } else {
428      return 32
429    }
430  }
431
432  private getZoomText() {
433    return `${Number(this.state.zoomRatio.toFixed(1))}x`
434  }
435
436  private getZoomBtnRadius(): number {
437    if (!this.state.showZoomLabelValue) {
438      return 17.25
439    } else {
440      return 15.25
441    }
442  }
443
444  build() {
445    Stack({ alignContent: Alignment.Top }) {
446      Stack({ alignContent: Alignment.Top })
447        .width(this.offsetX + this.touchedOffsetX + this.state.zoomRatio)
448        .height(this.triggerRebuildNum)
449        .visibility(Visibility.None)
450      if (this.getCurrentCanvasType() === SHOW_NOT_TAKE_VIDEO_CANVAS) {
451        Canvas(this.notTakeVideoExtCanvasCtx)
452          .width(this.notTakeVideoExtCanvasWidth)
453          .height(this.canvasHeight)
454          .onReady(() => {
455            this.notTakeVideoExtCanvasCtx.clearRect(0, 0, this.notTakeVideoExtCanvasWidth, this.canvasHeight)
456            this.notTakeVideoExtOffCanvasCtx.clearRect(0, 0, this.notTakeVideoExtCanvasWidth, this.canvasHeight)
457            this.notTakeVideoExtOffCanvasCtx.strokeStyle = '#ffffff'
458            this.notTakeVideoExtOffCanvasCtx.fillStyle = '#ffffff'
459            this.notTakeVideoExtOffCanvasCtx.lineWidth = 1.5
460            this.notTakeVideoExtOffCanvasCtx.beginPath()
461            this.notTakeVideoExtOffCanvasCtx.arc(this.getZoomBtnCenterX(), this.canvasHeight / 2, this.getZoomBtnRadius(), 0, 6.28)
462            this.notTakeVideoExtOffCanvasCtx.stroke()
463            if (this.state.showZoomLabelValue) {
464              this.notTakeVideoExtOffCanvasCtx.font = `bold ${vp2px(11)}px`
465              this.notTakeVideoExtOffCanvasCtx.textAlign = 'center'
466              this.notTakeVideoExtOffCanvasCtx.fillText(this.getZoomText(), this.getZoomBtnCenterX(), this.canvasHeight / 2 + 5)
467            } else {
468              this.notTakeVideoExtOffCanvasCtx.beginPath()
469              this.notTakeVideoExtOffCanvasCtx.arc(this.getZoomBtnCenterX(), this.canvasHeight / 2, this.centerDotRadius, 0, 6.28)
470              this.notTakeVideoExtOffCanvasCtx.fill()
471            }
472
473            let spotCount = (this.notTakeVideoExtCanvasWidth - this.getPadding() * 2 - this.mainDotRadius * 4 - this.dotSpacing) / (this.dotSpacing + this.secDotRadius * 2) + 2
474            for (let i = 0; i < spotCount; i++) {
475              let spotCenter = 0
476              let spotRadius = 0
477              if (i === 0) {
478                spotRadius = this.mainDotRadius
479                spotCenter = this.getPadding() + spotRadius
480                this.notTakeVideoExtOffCanvasCtx.font = `bold ${vp2px(11)}px`
481                this.notTakeVideoExtOffCanvasCtx.textAlign = 'center'
482                this.notTakeVideoExtOffCanvasCtx.fillText(`${this.state.minZoomRatio}x`, spotCenter, this.canvasHeight / 2 - (!this.state.showZoomLabelValue ? 26 : 24))
483              } else if (i === spotCount - 1) {
484                spotRadius = this.mainDotRadius
485                spotCenter = this.notTakeVideoExtCanvasWidth - this.getPadding() - spotRadius
486                this.notTakeVideoExtOffCanvasCtx.font = `bold ${vp2px(11)}px`
487                this.notTakeVideoExtOffCanvasCtx.textAlign = 'center'
488                this.notTakeVideoExtOffCanvasCtx.fillText(`${this.state.maxZoomRatio}x`, spotCenter, this.canvasHeight / 2 - (!this.state.showZoomLabelValue ? 26 : 24))
489              } else {
490                spotRadius = this.secDotRadius
491                spotCenter = this.getPadding() + this.mainDotRadius + (2 * i - 1) * this.secDotRadius + i * this.dotSpacing
492                this.notTakeVideoExtOffCanvasCtx.globalAlpha = 0.2
493              }
494              if (spotCenter < this.getZoomBtnCenterX() - this.getZoomBtnRadius() || spotCenter > this.getZoomBtnCenterX() + this.getZoomBtnRadius()) {
495                this.notTakeVideoExtOffCanvasCtx.beginPath()
496                this.notTakeVideoExtOffCanvasCtx.arc(spotCenter, this.canvasHeight / 2, spotRadius, 0, 6.28)
497                this.notTakeVideoExtOffCanvasCtx.fill()
498              }
499              this.notTakeVideoExtOffCanvasCtx.globalAlpha = 1
500            }
501            this.notTakeVideoExtCanvasCtx.transferFromImageBitmap(this.notTakeVideoExtOffCanvasCtx.transferToImageBitmap())
502          })
503          .gesture(GestureGroup(GestureMode.Parallel,
504          PanGesture({ fingers: 1, distance: 1, direction: PanDirection.Horizontal })
505            .onActionStart(() => this.pgOnActionStart())
506            .onActionUpdate((event?: GestureEvent) => {
507              if (event) {
508                return this.pgOnActionUpdate(event);
509              }
510            })
511            .onActionEnd(() => this.pgOnActionEnd())))
512          .onTouch((event?: TouchEvent) => {
513            if (event) {
514              return this.mOnTouch(event);
515            }
516          })
517      } else if (this.getCurrentCanvasType() === SHOW_TAKING_VIDEO_CANVAS) {
518        Row() {
519          Image($r('app.media.ic_camera_public_focus_ev_bright_subtract'))
520            .width(24)
521            .height(24)
522            .fillColor(Color.White)
523            .onTouch((event?: TouchEvent) => this.subtractTouched(event))
524            .gesture(
525            GestureGroup(
526            GestureMode.Parallel,
527            LongPressGesture({ repeat: true })
528              .onAction((event?: GestureEvent) => this.subtractLongOnAction(event))
529              .onActionEnd(() => this.subtractLongOnActionEnd()),
530            )
531            )
532          Canvas(this.takingVideoExtCanvasCtx)
533            .width(this.takingVideoExtCanvasWidth)
534            .height(this.canvasHeight)
535            .onReady(() => {
536              this.takingVideoExtCanvasCtx.clearRect(0, 0, this.takingVideoExtCanvasWidth, this.canvasHeight)
537              this.takingVideoExtOffCanvasCxt.clearRect(0, 0, this.takingVideoExtCanvasWidth, this.canvasHeight)
538              this.takingVideoExtOffCanvasCxt.strokeStyle = '#ffffff'
539              this.takingVideoExtOffCanvasCxt.fillStyle = '#ffffff'
540              this.takingVideoExtOffCanvasCxt.lineWidth = 1.5
541              this.takingVideoExtOffCanvasCxt.beginPath()
542              this.takingVideoExtOffCanvasCxt.arc(this.getZoomBtnCenterX(), this.canvasHeight / 2, this.getZoomBtnRadius(), 0, 6.28)
543              this.takingVideoExtOffCanvasCxt.stroke()
544              if (this.state.isShowZoomText) {
545                this.takingVideoExtOffCanvasCxt.beginPath()
546                this.takingVideoExtOffCanvasCxt.arc(this.getZoomBtnCenterX(), this.canvasHeight / 2, this.centerDotRadius, 0, 6.28)
547                this.takingVideoExtOffCanvasCxt.fill()
548              } else {
549                this.takingVideoExtOffCanvasCxt.font = `bold ${vp2px(11)}px`
550                this.takingVideoExtOffCanvasCxt.textAlign = 'center'
551                this.takingVideoExtOffCanvasCxt.fillText(this.getZoomText(), this.getZoomBtnCenterX(), this.canvasHeight / 2 + 5)
552              }
553
554              let spotCount = 30
555              for (let i = 0; i < spotCount; i++) {
556                let spotCenter = 0
557                let spotRadius = 0
558                spotRadius = this.secDotRadius
559                spotCenter = this.getPadding() + (2 * i + 1) * this.secDotRadius + i * this.dotSpacing
560                this.takingVideoExtOffCanvasCxt.globalAlpha = 0.2
561                if (spotCenter < this.getZoomBtnCenterX() - this.getZoomBtnRadius() || spotCenter > this.getZoomBtnCenterX() + this.getZoomBtnRadius()) {
562                  this.takingVideoExtOffCanvasCxt.beginPath()
563                  this.takingVideoExtOffCanvasCxt.arc(spotCenter, this.canvasHeight / 2, spotRadius, 0, 6.28)
564                  this.takingVideoExtOffCanvasCxt.fill()
565                }
566                this.takingVideoExtOffCanvasCxt.globalAlpha = 1
567              }
568
569              this.takingVideoExtCanvasCtx.transferFromImageBitmap(this.takingVideoExtOffCanvasCxt.transferToImageBitmap())
570            })
571            .gesture(
572            GestureGroup(
573            GestureMode.Parallel,
574            LongPressGesture({ repeat: true })
575              .onAction((event?: GestureEvent) => this.takingVideoExtLongPgAction(event))
576              .onActionEnd(() => this.takingVideoExtLongPgActionEnd()),
577            PanGesture({ fingers: 1, distance: 1, direction: PanDirection.Horizontal })
578              .onActionStart((event?: GestureEvent) => this.takingVideoExtPgActionStart(event))
579              .onActionUpdate((event?: GestureEvent) => this.takingVideoExtPgActionUpdate(event))
580              .onActionEnd((event?: GestureEvent) => this.takingVideoExtPgActionEnd(event))
581            ))
582            .onTouch((event?: TouchEvent) => this.takingVideoExtTouched(event))
583          Image($r('app.media.ic_camera_public_focus_ev_bright_add'))
584            .width(24)
585            .height(24)
586            .fillColor(Color.White)
587            .onTouch((event?: TouchEvent) => this.addTouched(event))
588            .gesture(
589            GestureGroup(
590            GestureMode.Parallel,
591            LongPressGesture({ repeat: true })
592              .onAction(() => this.addLongOnAction())
593              .onActionEnd(() => this.addLongOnActionEnd()),
594            )
595            )
596        }.width(this.notTakeVideoExtCanvasWidth).height('100%').padding({ left: 58, right: 58 })
597      } else {
598        Canvas(this.foldCanvasCtx)
599          .width(this.foldCanvasWidth)
600          .height(this.canvasHeight)
601          .onReady(() => {
602            this.foldCanvasCtx.clearRect(0, 0, this.foldCanvasWidth, this.canvasHeight)
603            this.foldOffCanvasCtx.clearRect(0, 0, this.foldCanvasWidth, this.canvasHeight)
604            this.foldOffCanvasCtx.strokeStyle = '#ffffff'
605            this.foldOffCanvasCtx.fillStyle = '#ffffff'
606            this.foldOffCanvasCtx.lineWidth = 1.5
607            this.foldOffCanvasCtx.beginPath()
608            this.foldOffCanvasCtx.arc(this.foldCanvasWidth / 2, this.canvasHeight / 2, this.getZoomBtnRadius(), 0, 6.28)
609            this.foldOffCanvasCtx.stroke()
610            this.foldOffCanvasCtx.font = `bold ${vp2px(10)}px`
611            this.foldOffCanvasCtx.textAlign = 'center'
612            this.foldOffCanvasCtx.fillText(this.getZoomText(), this.foldCanvasWidth / 2, this.canvasHeight / 2 + 3)
613
614            let fullWidth = this.foldCanvasWidth / 2 - this.mainDotRadius
615            let spotCount = (fullWidth - this.mainDotRadius * 2 - this.dotSpacing) / (this.dotSpacing + this.secDotRadius * 2) + 2
616            let spotOffset = this.state.zoomRatio === this.state.maxZoomRatio ? this.foldCanvasWidth / 2 - fullWidth
617                                                                              : this.foldCanvasWidth / 2
618            for (let i = 0; i < spotCount; i++) {
619              let spotCenter = 0
620              let spotRadius = 0
621              if (i === 0) {
622                spotRadius = this.mainDotRadius
623                spotCenter = spotOffset + spotRadius
624              } else if (i === spotCount - 1) {
625                spotRadius = this.mainDotRadius
626                spotCenter = spotOffset + this.mainDotRadius * 2 + (i - 1) * this.dotSpacing + (2 * i - 1) * this.secDotRadius - this.secDotRadius + spotRadius
627              } else {
628                spotRadius = this.secDotRadius
629                spotCenter = spotOffset + this.mainDotRadius * 2 + (i - 1) * this.dotSpacing + (2 * i - 1) * this.secDotRadius + spotRadius
630                this.foldOffCanvasCtx.globalAlpha = 0.2
631              }
632              if (spotCenter < this.foldCanvasWidth / 2 - this.getZoomBtnRadius() || spotCenter > this.foldCanvasWidth / 2 + this.getZoomBtnRadius()) {
633                this.foldOffCanvasCtx.beginPath()
634                this.foldOffCanvasCtx.arc(spotCenter, this.canvasHeight / 2, spotRadius, 0, 6.28)
635                this.foldOffCanvasCtx.fill()
636              }
637              this.foldOffCanvasCtx.globalAlpha = 1.0
638            }
639
640            this.foldCanvasCtx.transferFromImageBitmap(this.foldOffCanvasCtx.transferToImageBitmap())
641          })
642          .gesture(
643          GestureGroup(
644          GestureMode.Parallel,
645          LongPressGesture({ repeat: true })
646            .onAction(() => this.lpgOnAction())
647            .onActionEnd(() => this.lpgOnActionEnd()),
648          PanGesture({ fingers: 1, distance: 1, direction: PanDirection.Horizontal })
649            .onActionStart(() => this.pgOnActionStart())
650            .onActionUpdate((event?: GestureEvent) => this.pgOnActionUpdate(event))
651            .onActionEnd(() => this.pgOnActionEnd())
652          )
653          )
654      }
655    }.width('100%').height(this.canvasHeight).margin({ bottom: !this.state.showZoomLabelValue ? 58 : 0 })
656  }
657}