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