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