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 { Log } from '../../utils/Log' 19import { getStore } from '../../redux/store' 20import { Action } from '../../redux/actions/Action' 21import { EventBus } from '../../worker/eventbus/EventBus' 22import { EventBusManager } from '../../worker/eventbus/EventBusManager' 23import { SettingManager } from '../../setting/SettingManager' 24import Timer from '../../setting/settingitem/Timer' 25 26let localState = (state) => { 27 return { 28 uiEnable: state.ContextReducer.uiEnable, 29 shutterIcon: state.CameraReducer.shutterIcon, 30 captureBtnScale: state.CaptureReducer.captureBtnScale, 31 videoState: state.RecordReducer.videoState, 32 mode: state.ModeReducer.mode, 33 videoUri: state.CameraInitReducer.videoUri, 34 resourceUri: state.CameraInitReducer.resourceUri, 35 isThirdPartyCall: state.ContextReducer.isThirdPartyCall, 36 xComponentWidth: state.PreviewReducer.xComponentWidth, 37 xComponentHeight: state.PreviewReducer.xComponentHeight, 38 } 39} 40 41let localDispatcher = (dispatch) => { 42 return { 43 updateSmallVideoTimerVisible: (visible: boolean) => { 44 dispatch(Action.updateSmallVideoTimerVisible(visible)) 45 }, 46 updateShutterIcon: (icon: Resource) => { 47 dispatch(Action.updateShutterIcon(icon)) 48 }, 49 capture: () => { 50 dispatch(Action.updateShowFlashBlackFlag(true)) 51 dispatch(Action.capture()) 52 }, 53 startRecording: () => { 54 dispatch(Action.startRecording()) 55 dispatch(Action.updateVideoState('startTakeVideo')) 56 dispatch(Action.updateBigVideoTimerVisible(true)) 57 dispatch(Action.updateScreenStatus(true)) 58 }, 59 pauseRecording: () => { 60 dispatch(Action.pauseRecording()) 61 dispatch(Action.updateVideoState('pauseTakeVideo')) 62 }, 63 resumeRecording: () => { 64 dispatch(Action.resumeRecording()) 65 dispatch(Action.updateVideoState('startTakeVideo')) 66 }, 67 stopRecording: () => { 68 dispatch(Action.stopRecording()) 69 dispatch(Action.updateVideoState('beforeTakeVideo')) 70 dispatch(Action.updateBigVideoTimerVisible(false)) 71 dispatch(Action.updateSmallVideoTimerVisible(false)) 72 dispatch(Action.updateScreenStatus(false)) 73 }, 74 changeTimeLapse: (isShowtimeLapse: boolean) => { 75 dispatch(Action.changeTimeLapse(isShowtimeLapse)) 76 } 77 } 78} 79 80class StateStruct { 81 uiEnable 82 shutterIcon 83 captureBtnScale 84 videoState 85 mode 86 resourceUri 87 videoUri 88 thumbnail 89 isThirdPartyCall 90 xComponentWidth 91 xComponentHeight 92 updateSmallVideoTimerVisible : Function 93 updateShutterIcon : Function 94 capture : Function 95 startRecording : Function 96 pauseRecording : Function 97 resumeRecording : Function 98 stopRecording : Function 99 changeTimeLapse : Function 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 108 type: ButtonType 109 stateEffect: boolean 110 111 @State state: StateStruct = new StateStruct() 112 @State captureBtnScale: number = 1 113 114 aboutToAppear() { 115 Log.debug(`${this.TAG} aboutToAppear E`) 116 getStore().connect(localState, localDispatcher)(this.state) 117 this.appEventBus.on(Action.ACTION_CHANGE_MODE, this.changeShutterIcon.bind(this)) 118 this.appEventBus.on(Action.ACTION_UPDATE_THUMBNAIL, this.onThumbnailUpdate.bind(this)) 119 this.appEventBus.on(Action.ACTION_INIT_MODE, this.changeShutterIcon.bind(this)) 120 this.refreshIcon(this.state.mode) 121 Log.debug(`${this.TAG} aboutToAppear X`) 122 } 123 124 aboutToDisappear(): void { 125 Log.debug(`${this.TAG} aboutToDisappear E`) 126 this.appEventBus.off(Action.ACTION_CHANGE_MODE, this.changeShutterIcon.bind(this)) 127 this.appEventBus.off(Action.ACTION_UPDATE_THUMBNAIL, this.onThumbnailUpdate.bind(this)) 128 Log.debug(`${this.TAG} aboutToDisappear X`) 129 } 130 131 private async onThumbnailUpdate(data) { 132 Log.info(`${this.TAG} onThumbnailUpdate data: ${JSON.stringify(data)} E`) 133 Log.info(`${this.TAG} onThumbnailUpdate resourceUri= ${JSON.stringify(this.state.resourceUri)} E`) 134 Log.info(`${this.TAG} onThumbnailUpdate isThirdPartyCall= ${this.state.isThirdPartyCall} E`) 135 Log.info(`${this.TAG} onThumbnailUpdate videoUri= ${this.state.videoUri} E`) 136 if (this.state.isThirdPartyCall) { 137 Log.info(`${this.TAG} onThumbnailUpdate start router to ThirdPreviewView`) 138 router.push({ 139 uri: "pages/ThirdPreviewView", 140 params: { 141 width: this.state.xComponentWidth, 142 height: this.state.xComponentHeight, 143 mode: this.state.mode, 144 uri: this.state.resourceUri, 145 videoUri: this.state.videoUri, 146 callBundleName:globalThis.cameraAbilityWant?.parameters?.callBundleName 147 } 148 }) 149 } 150 Log.info(`${this.TAG} onThumbnailUpdate this.state.thumbnail: ${JSON.stringify(this.state.thumbnail)} X`) 151 } 152 153 private async changeShutterIcon(data) { 154 Log.debug(`${this.TAG} resetShutterIcon E`) 155 this.refreshIcon(data.mode) 156 Log.debug(`${this.TAG} resetShutterIcon X`) 157 } 158 159 private async refreshIcon(mode: string) { 160 Log.debug(`${this.TAG} refreshIcon E`) 161 if (mode === 'PHOTO') { 162 this.state.updateShutterIcon($r('app.media.ic_circled_filled')) 163 } else if (mode === 'VIDEO') { 164 this.state.updateShutterIcon($r('app.media.take_video_normal')) 165 } else { 166 this.state.updateShutterIcon($r('app.media.ic_circled_filled')) 167 } 168 Log.debug(`${this.TAG} refreshIcon X`) 169 } 170 171 build() { 172 if (this.state.videoState === 'beforeTakeVideo') { 173 Stack({ alignContent: Alignment.Center }) { 174 if (this.state.mode === 'VIDEO') { 175 Image(this.state.shutterIcon) 176 .width(76).aspectRatio(1).enabled(this.state.uiEnable) 177 .onTouch((event: TouchEvent) => { 178 if (event.type === TouchType.Up) { 179 let timerLapse = this.settingManager.getTimeLapse() 180 Log.log(`${this.TAG} ShutterButton startRecording getValue= ${JSON.stringify(timerLapse)}`) 181 if (timerLapse && timerLapse.id !== Timer.RESOURCE_OFF.id) { 182 Log.log('ShutterButton startRecording changeTimeLapse called') 183 this.state.changeTimeLapse(true) 184 } else { 185 Log.log('ShutterButton startRecording changeTimeLapse not called') 186 this.state.startRecording() 187 } 188 } 189 }) 190 } else { 191 Image($r('app.media.ic_circled')).fillColor(Color.White) 192 Image(this.state.shutterIcon).width(54).aspectRatio(1).fillColor(Color.White) 193 .scale({ x: this.captureBtnScale, y: this.captureBtnScale, z: this.captureBtnScale }) 194 .enabled(this.state.uiEnable) 195 .onTouch((event: TouchEvent) => { 196 if (event.type === TouchType.Down) { 197 animateTo( 198 { duration: 125, curve: Curve.Sharp, delay: 0 }, 199 () => { this.captureBtnScale = 0.85 }) 200 } else if (event.type === TouchType.Up) { 201 animateTo( 202 { duration: 125, curve: Curve.Sharp, delay: 0, 203 onFinish: () => { this.captureBtnScale = 1 }}, 204 () => { this.captureBtnScale = 1 }) 205 let timerLapse = this.settingManager.getTimeLapse() 206 Log.log(`${this.TAG} ShutterButton startRecording getValue= ${JSON.stringify(timerLapse)}`) 207 if (timerLapse && timerLapse.id !== Timer.RESOURCE_OFF.id) { 208 Log.log('ShutterButton startRecording changeTimeLapse called') 209 this.state.changeTimeLapse(true) 210 } else { 211 Log.log('ShutterButton startRecording changeTimeLapse not called') 212 this.state.capture() 213 } 214 } 215 }) 216 } 217 }.width(76).aspectRatio(1).margin({ left: 48, right: 48 }) 218 } else { 219 Column() { 220 Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { 221 Column() { 222 Image($r('app.media.ic_video_end')) 223 .width(20) 224 .aspectRatio(1) 225 .fillColor(Color.White) 226 .enabled(this.state.uiEnable) 227 } 228 .width(40) 229 .padding({ left: 10, right: 10 }) 230 .margin({ right: 6 }) 231 .enabled(this.state.uiEnable) 232 .onClick(() => { 233 this.state.stopRecording() 234 }) 235 236 Column() { 237 if (this.state.videoState === 'startTakeVideo') { 238 Image($r('app.media.ic_video_recording')) 239 .width(20).aspectRatio(1).fillColor(Color.White) 240 .enabled(this.state.uiEnable) 241 } else if (this.state.videoState === 'pauseTakeVideo') { 242 Image($r('app.media.ic_video_pause')).width(20).aspectRatio(1).fillColor(Color.Red) 243 .enabled(this.state.uiEnable) 244 } 245 } 246 .width(40) 247 .padding({ left: 10, right: 10 }) 248 .margin({ left: 6 }) 249 .enabled(this.state.uiEnable) 250 .onClick(() => { 251 this.state.videoState === 'startTakeVideo' ? this.state.pauseRecording() : this.state.resumeRecording() 252 }) 253 }.width('100%').height('100%') 254 } 255 .width(120).height(56).borderRadius(28) 256 .border({ width: 1, color: Color.White, style: BorderStyle.Solid }) 257 .margin({ left: 24, right: 24 }) 258 } 259 } 260}