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