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