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'; 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 private mDispatch: Dispatch = (data) => data; 45 46 public setDispatch(dispatch: Dispatch) { 47 this.mDispatch = dispatch; 48 } 49 50 public updateSmallVideoTimerVisible(visible: boolean): void { 51 this.mDispatch(Action.updateSmallVideoTimerVisible(visible)); 52 } 53 54 public updateShutterIcon(icon: Resource): void { 55 this.mDispatch(Action.updateShutterIcon(icon)); 56 } 57 58 public capture(): void { 59 this.mDispatch(Action.updateShowFlashBlackFlag(true)); 60 this.mDispatch(Action.capture()); 61 } 62 63 public startRecording(): void { 64 this.mDispatch(Action.startRecording()); 65 this.mDispatch(Action.updateVideoState('startTakeVideo')); 66 this.mDispatch(Action.updateBigVideoTimerVisible(true)); 67 this.mDispatch(Action.updateScreenStatus(true)); 68 } 69 70 public pauseRecording(): void { 71 this.mDispatch(Action.pauseRecording()); 72 this.mDispatch(Action.updateVideoState('pauseTakeVideo')); 73 } 74 75 public resumeRecording(): void { 76 this.mDispatch(Action.resumeRecording()); 77 this.mDispatch(Action.updateVideoState('startTakeVideo')); 78 } 79 80 public stopRecording(): void { 81 this.mDispatch(Action.stopRecording()); 82 this.mDispatch(Action.updateVideoState('beforeTakeVideo')); 83 this.mDispatch(Action.updateBigVideoTimerVisible(false)); 84 this.mDispatch(Action.updateSmallVideoTimerVisible(false)); 85 this.mDispatch(Action.updateScreenStatus(false)); 86 } 87 88 public changeTimeLapse(isShowtimeLapse: boolean): void { 89 this.mDispatch(Action.changeTimeLapse(isShowtimeLapse)); 90 } 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 private TAG: string = '[ShutterButton]:'; 105 private appEventBus: EventBus = EventBusManager.getInstance().getEventBus(); 106 private settingManager = SettingManager.getInstance(); 107 type: ButtonType = ButtonType.Capsule; 108 stateEffect: boolean = false; 109 @State state: StateStruct = new StateStruct(); 110 @State captureBtnScale: number = 1; 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.debug(`${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.debug(`${this.TAG} aboutToDisappear X`) 145 } 146 147 private async onThumbnailUpdate(data: UpdateThumbnailStruct): Promise<void> { 148 Log.info(`${this.TAG} onThumbnailUpdate data: ${JSON.stringify(data)} E`) 149 Log.info(`${this.TAG} onThumbnailUpdate resourceUri= ${JSON.stringify(this.state.resourceUri)} E`) 150 Log.info(`${this.TAG} onThumbnailUpdate isThirdPartyCall= ${this.state.isThirdPartyCall} E`) 151 Log.info(`${this.TAG} onThumbnailUpdate videoUri= ${this.state.videoUri} E`) 152 if (this.state.isThirdPartyCall) { 153 Log.info(`${this.TAG} onThumbnailUpdate start router to ThirdPreviewView`) 154 router.push({ 155 uri: "pages/ThirdPreviewView", 156 params: { 157 width: this.state.xComponentWidth, 158 height: this.state.xComponentHeight, 159 mode: this.state.mode, 160 uri: this.state.resourceUri, 161 videoUri: this.state.videoUri, 162 callBundleName: this.getCameraAbilityWant(), 163 } 164 }) 165 } 166 Log.info(`${this.TAG} onThumbnailUpdate this.state.thumbnail: ${JSON.stringify(this.state.thumbnail)} X`) 167 } 168 169 private getCameraAbilityWant(): string { 170 let parameters = GlobalContext.get().getCameraAbilityWant().parameters; 171 if (!parameters) { 172 return ''; 173 } 174 return parameters?.callBundleName as string; 175 } 176 177 private async changeShutterIcon(data: ModeStruct): Promise<void> { 178 Log.debug(`${this.TAG} resetShutterIcon E`) 179 this.refreshIcon(data.mode) 180 Log.debug(`${this.TAG} resetShutterIcon X`) 181 } 182 183 private async refreshIcon(mode: string): Promise<void> { 184 Log.debug(`${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.debug(`${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) { 203 return; 204 } 205 if (event.type === TouchType.Up) { 206 let timerLapse = this.settingManager.getTimeLapse() 207 Log.log(`${this.TAG} ShutterButton startRecording getValue= ${JSON.stringify(timerLapse)}`) 208 if (timerLapse && timerLapse.id !== Timer.RESOURCE_OFF.id) { 209 Log.log('ShutterButton startRecording changeTimeLapse called') 210 this.mAction.changeTimeLapse(true) 211 } else { 212 Log.log('ShutterButton startRecording changeTimeLapse not called') 213 this.mAction.startRecording() 214 } 215 } 216 }) 217 } else { 218 Image($r('app.media.ic_circled')).fillColor(Color.White) 219 Image(this.state.shutterIcon).width(54).aspectRatio(1).fillColor(Color.White) 220 .scale({ x: this.captureBtnScale, y: this.captureBtnScale, z: this.captureBtnScale }) 221 .enabled(this.state.uiEnable) 222 .onTouch((event?: TouchEvent) => { 223 if (!event) { 224 return; 225 } 226 if (event.type === TouchType.Down) { 227 animateTo( 228 { duration: 125, curve: Curve.Sharp, delay: 0 }, 229 () => { this.captureBtnScale = 0.85 }) 230 } else if (event.type === TouchType.Up) { 231 animateTo( 232 { duration: 125, curve: Curve.Sharp, delay: 0, 233 onFinish: () => { this.captureBtnScale = 1 }}, 234 () => { this.captureBtnScale = 1 }) 235 let timerLapse = this.settingManager.getTimeLapse() 236 Log.log(`${this.TAG} ShutterButton start capture getValue= ${JSON.stringify(timerLapse)}`) 237 if (timerLapse && timerLapse.id !== Timer.RESOURCE_OFF.id) { 238 Log.log('ShutterButton startRecording changeTimeLapse called') 239 this.mAction.changeTimeLapse(true) 240 } else { 241 Log.log('ShutterButton capture changeTimeLapse not called') 242 this.mAction.capture() 243 } 244 } 245 }) 246 } 247 }.width(76).aspectRatio(1).margin({ left: 48, right: 48 }) 248 } else { 249 Column() { 250 Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { 251 Column() { 252 Image($r('app.media.ic_video_end')) 253 .width(20) 254 .aspectRatio(1) 255 .fillColor(Color.White) 256 .enabled(this.state.uiEnable) 257 } 258 .width(40) 259 .padding({ left: 10, right: 10 }) 260 .margin({ right: 6 }) 261 .enabled(this.state.uiEnable) 262 .onClick(() => { 263 this.mAction.stopRecording() 264 }) 265 266 Column() { 267 if (this.state.videoState === 'startTakeVideo') { 268 Image($r('app.media.ic_video_recording')) 269 .width(20).aspectRatio(1).fillColor(Color.White) 270 .enabled(this.state.uiEnable) 271 } else if (this.state.videoState === 'pauseTakeVideo') { 272 Image($r('app.media.ic_video_pause')).width(20).aspectRatio(1).fillColor(Color.Red) 273 .enabled(this.state.uiEnable) 274 } 275 } 276 .width(40) 277 .padding({ left: 10, right: 10 }) 278 .margin({ left: 6 }) 279 .enabled(this.state.uiEnable) 280 .onClick(() => { 281 this.state.videoState === 'startTakeVideo' ? this.mAction.pauseRecording() : this.mAction.resumeRecording() 282 }) 283 }.width('100%').height('100%') 284 } 285 .width(120).height(56).borderRadius(28) 286 .border({ width: 1, color: Color.White, style: BorderStyle.Solid }) 287 .margin({ left: 24, right: 24 }) 288 } 289 } 290}