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}