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 router from '@system.router';
17
18import { Action } from '../../redux/actions/Action';
19import { Log } from '../../utils/Log';
20import { EventBus } from '../../worker/eventbus/EventBus';
21import { EventBusManager } from '../../worker/eventbus/EventBusManager';
22import { Dispatch, OhCombinedState } from '../../redux/store';
23import { getStore } from '../../redux/store';
24import { SettingManager } from '../../setting/SettingManager';
25import Timer from '../../setting/settingitem/Timer';
26import { ComponentPosition } from '../../utils/ComponentPosition';
27import { GlobalContext } from '../../utils/GlobalContext';
28import image from '@ohos.multimedia.image';
29
30class StateStruct {
31  uiEnable: boolean = true;
32  shutterIcon: Resource = $r('app.media.ic_circled_filled');
33  captureBtnScale: number = 0;
34  videoState: string = 'beforeTakeVideo';
35  mode: string = '';
36  resourceUri: string = '';
37  videoUri: string = ''
38  thumbnail: Resource = $r('app.media.ic_camera_thumbnail_default_white');
39  isThirdPartyCall: boolean = false;
40  xComponentWidth: number = 0;
41  xComponentHeight: number = 0;
42}
43
44class ShutterButtonDispatcher {
45  private mDispatch: Dispatch = (data) => data;
46
47  public setDispatch(dispatch: Dispatch) {
48    this.mDispatch = dispatch;
49  }
50
51  public updateSmallVideoTimerVisible(visible: boolean): void {
52    this.mDispatch(Action.updateSmallVideoTimerVisible(visible));
53  }
54
55  public updateShutterIcon(icon: Resource): void {
56    this.mDispatch(Action.updateShutterIcon(icon));
57  }
58
59  public capture(): void {
60    this.mDispatch(Action.updateShowFlashBlackFlag(true));
61    this.mDispatch(Action.capture());
62  }
63
64  public startRecording(): void {
65    this.mDispatch(Action.startRecording());
66    this.mDispatch(Action.updateVideoState('startTakeVideo'));
67    this.mDispatch(Action.updateBigVideoTimerVisible(true));
68    this.mDispatch(Action.updateScreenStatus(true));
69  }
70
71  public pauseRecording(): void {
72    this.mDispatch(Action.pauseRecording());
73    this.mDispatch(Action.updateVideoState('pauseTakeVideo'));
74  }
75
76  public resumeRecording(): void {
77    this.mDispatch(Action.resumeRecording());
78    this.mDispatch(Action.updateVideoState('startTakeVideo'));
79  }
80
81  public stopRecording(): void {
82    this.mDispatch(Action.stopRecording());
83    this.mDispatch(Action.updateVideoState('beforeTakeVideo'));
84    this.mDispatch(Action.updateBigVideoTimerVisible(false));
85    this.mDispatch(Action.updateSmallVideoTimerVisible(false));
86    this.mDispatch(Action.updateScreenStatus(false));
87  }
88
89  public changeTimeLapse(isShowtimeLapse: boolean): void {
90    this.mDispatch(Action.changeTimeLapse(isShowtimeLapse));
91  }
92}
93
94class ScreenSizeType {
95  width: number = 0;
96  height: number = 0;
97}
98
99class ModeStruct {
100  mode: string = '';
101}
102
103class UpdateThumbnailStruct {
104  thumbnail: image.PixelMap | undefined = undefined;
105  resourceUri: string = '';
106}
107
108@Component
109export struct ShutterButtonLand {
110  private TAG: string = '[ShutterButtonLand]:';
111  private appEventBus: EventBus = EventBusManager.getInstance().getEventBus();
112  private settingManager = SettingManager.getInstance();
113  private lastTime = 0;
114  @Link screenSize: ScreenSizeType;
115  type: ButtonType = ButtonType.Capsule;
116  stateEffect: boolean = false;
117  @State state: StateStruct = new StateStruct();
118  @State captureBtnScale: number = 1;
119  private mAction: ShutterButtonDispatcher = new ShutterButtonDispatcher();
120
121  aboutToAppear(): void {
122    Log.info(`${this.TAG} aboutToAppear E`);
123    getStore().subscribe((state: OhCombinedState) => {
124      this.state = {
125        uiEnable: state.contextReducer.uiEnable,
126        shutterIcon: state.cameraReducer.shutterIcon,
127        captureBtnScale: state.captureReducer.captureBtnScale,
128        videoState: state.recordReducer.videoState,
129        mode: state.modeReducer.mode,
130        resourceUri: state.cameraInitReducer.resourceUri,
131        videoUri: state.cameraInitReducer.videoUri,
132        thumbnail: state.cameraInitReducer.thumbnail,
133        isThirdPartyCall: state.contextReducer.isThirdPartyCall,
134        xComponentWidth: state.previewReducer.xComponentWidth,
135        xComponentHeight: state.previewReducer.xComponentHeight
136      };
137    }, (dispatch: Dispatch) => {
138      this.mAction.setDispatch(dispatch);
139    });
140    this.appEventBus.on(Action.ACTION_CHANGE_MODE, (data: ModeStruct) => this.changeShutterIcon(data));
141    this.appEventBus.on(Action.ACTION_UPDATE_THUMBNAIL, (data: UpdateThumbnailStruct) => this.onThumbnailUpdate(data));
142    this.appEventBus.on(Action.ACTION_INIT_MODE, (data: ModeStruct) => this.changeShutterIcon(data));
143    this.refreshIcon(this.state.mode)
144    Log.info(`${this.TAG} aboutToAppear X`)
145  }
146
147  aboutToDisappear(): void {
148    Log.info(`${this.TAG} aboutToDisappear E`);
149    this.appEventBus.off(Action.ACTION_CHANGE_MODE, (data: ModeStruct) => this.changeShutterIcon(data))
150    this.appEventBus.off(Action.ACTION_UPDATE_THUMBNAIL, (data: UpdateThumbnailStruct) => this.onThumbnailUpdate(data))
151    this.appEventBus.off(Action.ACTION_INIT_MODE, (data: ModeStruct) => this.changeShutterIcon(data));
152    Log.info(`${this.TAG} aboutToDisappear X`);
153  }
154
155  private async onThumbnailUpdate(data: UpdateThumbnailStruct): Promise<void> {
156    Log.info(`${this.TAG} onThumbnailUpdate data: ${JSON.stringify(data)} E`)
157    Log.info(`${this.TAG} onThumbnailUpdate resourceUri= ${JSON.stringify(this.state.resourceUri)} E`);
158    Log.info(`${this.TAG} onThumbnailUpdate isThirdPartyCall= ${this.state.isThirdPartyCall} E`)
159    Log.info(`${this.TAG} onThumbnailUpdate videoUri= ${this.state.videoUri} E`);
160    if (this.state.isThirdPartyCall) {
161      Log.info(`${this.TAG} onThumbnailUpdate start router to ThirdPreviewView`)
162      router.push({
163        uri: "pages/ThirdPreviewView",
164        params: {
165          width: this.state.xComponentWidth,
166          height: this.state.xComponentHeight,
167          mode: this.state.mode,
168          uri: this.state.resourceUri,
169          videoUri: this.state.videoUri,
170          callBundleName: GlobalContext.get().getCameraAbilityWant()?.parameters?.callBundleName
171        }
172      })
173    }
174    Log.info(`${this.TAG} onThumbnailUpdate this.state.thumbnail: ${JSON.stringify(this.state.thumbnail)} X`)
175  }
176
177  private async changeShutterIcon(data: ModeStruct): Promise<void> {
178    Log.info(`${this.TAG} resetShutterIcon E`);
179    this.refreshIcon(data.mode)
180    Log.info(`${this.TAG} resetShutterIcon X`);
181  }
182
183  private async refreshIcon(mode: string): Promise<void> {
184    Log.info(`${this.TAG} refreshIcon E`);
185    if (mode === 'PHOTO') {
186      this.mAction.updateShutterIcon($r('app.media.ic_circled_filled'))
187    } else if (mode === 'VIDEO') {
188      this.mAction.updateShutterIcon($r('app.media.take_video_normal'))
189    } else {
190      this.mAction.updateShutterIcon($r('app.media.ic_circled_filled'))
191    }
192    Log.info(`${this.TAG} refreshIcon X`);
193  }
194
195  build() {
196    if (this.state.videoState === 'beforeTakeVideo') {
197      Stack({ alignContent: Alignment.Center }) {
198        if (this.state.mode === 'VIDEO') {
199          Image(this.state.shutterIcon)
200            .width(76).aspectRatio(1).enabled(this.state.uiEnable)
201            .onTouch((event: TouchEvent) => {
202              if (event.type === TouchType.Up) {
203                let timerLapse = this.settingManager.getTimeLapse()
204                Log.log(`${this.TAG} startRecording getValue= ${JSON.stringify(timerLapse)}`)
205                if (timerLapse && timerLapse.id !== Timer.RESOURCE_OFF.id) {
206                  this.mAction.changeTimeLapse(true)
207                } else {
208                  this.mAction.startRecording()
209                }
210              }
211            })
212        } else {
213          Image($r('app.media.ic_circled')).fillColor(Color.White).width(76).aspectRatio(1)
214          Image(this.state.shutterIcon)
215            .width(54)
216            .aspectRatio(1)
217            .fillColor(Color.White)
218            .scale({ x: this.captureBtnScale, y: this.captureBtnScale, z: this.captureBtnScale })
219            .enabled(this.state.uiEnable)
220            .onTouch((event: TouchEvent) => {
221              if (event.type === TouchType.Down) {
222                animateTo(
223                  { duration: 125, curve: Curve.Sharp, delay: 0 },
224                  () => {
225                    this.captureBtnScale = 0.85
226                  })
227              } else if (event.type === TouchType.Up) {
228                animateTo(
229                  { duration: 125, curve: Curve.Sharp, delay: 0,
230                    onFinish: () => {
231                      this.captureBtnScale = 1
232                    } },
233                  () => {
234                    this.captureBtnScale = 1
235                  })
236                let timerLapse = this.settingManager.getTimeLapse()
237                Log.log(`${this.TAG} startCapture getValue= ${JSON.stringify(timerLapse)}`)
238                if (timerLapse && timerLapse.id !== Timer.RESOURCE_OFF.id) {
239                  this.mAction.changeTimeLapse(true)
240                } else {
241                  let waitTime = 450
242                  let curTime = Date.now();
243                  if (Math.abs(curTime - this.lastTime) >= waitTime) {
244                    Log.log(`${this.TAG} throttle invoke time = ${JSON.stringify(curTime - this.lastTime)}`)
245                    this.mAction.capture()
246                    this.lastTime = curTime;
247                  }
248                }
249              }
250            })
251        }
252      }.width(76).aspectRatio(1).margin({
253        top: ComponentPosition.getShutterButtonMargin(this.screenSize.width, this.screenSize.height, this.state.xComponentHeight),
254        bottom: ComponentPosition.getShutterButtonMargin(this.screenSize.width, this.screenSize.height, this.state.xComponentHeight)
255      })
256    } else {
257      Column() {
258        Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
259          if (this.state.videoState === 'startTakeVideo') {
260            Image($r('app.media.ic_video_recording'))
261              .width(20)
262              .aspectRatio(1)
263              .fillColor(Color.White)
264              .margin({ bottom: 16 })
265              .enabled(this.state.uiEnable)
266              .onClick(() => {
267                this.mAction.pauseRecording()
268              })
269          } else if (this.state.videoState === 'pauseTakeVideo') {
270            Image($r('app.media.ic_video_pause'))
271              .width(20)
272              .aspectRatio(1)
273              .fillColor(Color.Red)
274              .margin({ bottom: 16 })
275              .enabled(this.state.uiEnable)
276              .onClick(() => {
277                this.mAction.resumeRecording()
278              })
279          }
280          Image($r('app.media.ic_video_end'))
281            .width(20)
282            .aspectRatio(1)
283            .fillColor(Color.White)
284            .margin({ top: 16 })
285            .enabled(this.state.uiEnable)
286            .onClick(() => {
287              this.mAction.stopRecording()
288            })
289        }
290      }
291      .width(56)
292      .height(120)
293      .borderRadius(28)
294      .border({ width: 1, color: 0xffffff, style: BorderStyle.Solid })
295      .margin({ top: 26, bottom: 26 })
296    }
297  }
298}