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