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