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