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 curves from '@ohos.curves'; 17import PiPWindow from '@ohos.PiPWindow'; 18import pip from '@ohos.pip'; 19 20const TAG: string = 'PiPCall'; 21const TIMEOUT: number = 3000; 22 23@Styles 24function fillSize() { 25 .size({ width: '100%', height: '100%' }) 26} 27 28@Entry 29@Component 30export struct PiPCall { 31 xComponentId: string = 'pip'; 32 windowType: PiPWindow.PiPTemplateType = PiPWindow.PiPTemplateType.VIDEO_CALL; 33 private hideEventId: number = -1; 34 @State private showControl: boolean = false; 35 private xComponentController: XComponentController = new XComponentController(); 36 private surfaceId: string = ''; 37 private controlTransEffect: TransitionEffect = TransitionEffect.OPACITY; 38 @Provide @Watch('onHideControlNow') hideControlNow: boolean = false; 39 @Provide @Watch('onHideControlDelay') hideControlDelay: boolean = false; 40 41 onHideControlNow() { 42 if (this.hideControlNow) { 43 this.switchToHideWithoutAnime(); 44 } 45 this.hideControlNow = false; 46 } 47 48 onHideControlDelay() { 49 if (this.hideControlDelay) { 50 this.delayHide(); 51 } 52 this.hideControlDelay = false; 53 } 54 55 switchToShow() { 56 animateTo({ curve: curves.responsiveSpringMotion(0.25, 1) }, () => { 57 this.showControl = true; 58 }); 59 this.delayHide(); 60 } 61 62 switchToHide() { 63 if (this.hideEventId !== -1) { 64 clearTimeout(this.hideEventId); 65 } 66 animateTo({ curve: curves.responsiveSpringMotion(0.25, 1) }, () => { 67 this.showControl = false; 68 }); 69 } 70 71 switchToHideWithoutAnime() { 72 if (this.hideEventId !== -1) { 73 clearTimeout(this.hideEventId); 74 } 75 this.showControl = false; 76 } 77 78 delayHide() { 79 if (this.hideEventId !== -1) { 80 clearTimeout(this.hideEventId); 81 } 82 this.hideEventId = this.showControl ? 83 setTimeout(() => { 84 animateTo({ curve: curves.responsiveSpringMotion(0.25, 1) }, () => { 85 this.showControl = false; 86 }); 87 }, TIMEOUT) : 88 -1; 89 } 90 91 build() { 92 Stack() { 93 XComponent({ id: this.xComponentId, type: 'surface', controller: this.xComponentController }) 94 .onLoad(() => { 95 pip.initXComponentController(this.xComponentController); 96 console.debug(TAG, 'XComponent onLoad done'); 97 }) 98 .fillSize(); 99 100 RelativeContainer() { 101 Stack() 102 .fillSize() 103 .id('fill_stack') 104 if (this.showControl) { 105 RelativeContainer() { 106 DefaultControl(); 107 CallControl(); 108 } 109 .fillSize() 110 .transition(this.controlTransEffect) 111 .alignRules({ 112 top: { anchor: '__container__', align: VerticalAlign.Top }, 113 right: { anchor: '__container__', align: HorizontalAlign.End } 114 }) 115 .id('control_inner') 116 } 117 } 118 .fillSize() 119 .id('control') 120 .gesture( 121 GestureGroup(GestureMode.Exclusive, 122 TapGesture({ count: 2 }) 123 .onAction((event: GestureEvent) => { 124 this.switchToHideWithoutAnime(); 125 pip.processScale(); 126 }), 127 TapGesture({ count: 1 }) 128 .onAction((event: GestureEvent) => { 129 if (this.showControl) { 130 this.switchToHide(); 131 } else { 132 this.switchToShow(); 133 } 134 }), 135 PanGesture() 136 .onActionStart((event: GestureEvent) => { 137 this.switchToHide(); 138 pip.startMove(); 139 }) 140 ) 141 ) 142 } 143 .fillSize() 144 } 145} 146 147@Component 148struct DefaultControl { 149 @Consume hideControlNow: boolean; 150 151 build() { 152 RelativeContainer() { 153 Button({ type: ButtonType.Circle }) { 154 Image($r('sys.media.ohos_ic_public_close')) 155 .size({ width: 24, height: 24 }) 156 .fillColor($r('sys.color.ohos_id_color_primary_contrary')) 157 .objectFit(ImageFit.Contain) 158 } 159 .backgroundColor('#00FFFFFF') 160 .size({ width: 24, height: 24 }) 161 .margin(12) 162 .alignRules({ 163 center: { anchor: '__container__', align: VerticalAlign.Center }, 164 left: { anchor: '__container__', align: HorizontalAlign.Start } 165 }) 166 .id('control_exit') 167 .responseRegion({ x: '-50%', y: '-50%', width: '200%', height: '200%' }) 168 .onClick(() => { 169 this.hideControlNow = true; 170 pip.close(); 171 console.debug(TAG, 'action: exit'); 172 }) 173 174 Button({ type: ButtonType.Circle }) { 175 Image($r('sys.media.ohos_ic_public_restore')) 176 .fillColor($r('sys.color.ohos_id_color_primary_contrary')) 177 .objectFit(ImageFit.Contain) 178 } 179 .backgroundColor('#00FFFFFF') 180 .size({ width: 24, height: 24 }) 181 .margin(12) 182 .alignRules({ 183 center: { anchor: '__container__', align: VerticalAlign.Center }, 184 right: { anchor: '__container__', align: HorizontalAlign.End } 185 }) 186 .id('control_restore') 187 .responseRegion({ x: '-50%', y: '-50%', width: '200%', height: '200%' }) 188 .onClick(() => { 189 this.hideControlNow = true; 190 pip.restore(); 191 console.debug(TAG, 'action: restore'); 192 }) 193 } 194 .width('100%') 195 .height(48) 196 .linearGradient({ 197 angle: 180, 198 colors: [['#30000000', 0.0], ['#00000000', 1.0]] 199 }) 200 .alignRules({ 201 top: { anchor: '__container__', align: VerticalAlign.Top }, 202 left: { anchor: '__container__', align: HorizontalAlign.Start } 203 }) 204 .id('default_control') 205 } 206} 207 208const sizeArray = [1.0, 1.5]; 209 210@Component 211struct CallControl { 212 @State isMute: boolean = true; 213 @State isRecord: boolean = true; 214 @State defaultMargin: number = 8; 215 @State defaultSize: number = 12; 216 @State defaultBigSize: number = 24; 217 @State sizeIndex: number = 0; 218 @Consume hideControlDelay: boolean; 219 220 build() { 221 RelativeContainer() { 222 Button({ type: ButtonType.Circle }) { 223 Image($r('sys.media.ohos_ic_public_hang_up')) 224 .size({ width: 12 * sizeArray[this.sizeIndex], height: 12 * sizeArray[this.sizeIndex] }) 225 .fillColor($r('sys.color.ohos_id_color_primary_contrary')) 226 .objectFit(ImageFit.Contain) 227 } 228 .backgroundColor($r('sys.color.ohos_id_color_handup')) 229 .size({ width: 24 * sizeArray[this.sizeIndex], height: 24 * sizeArray[this.sizeIndex] }) 230 .margin({ top: 12, bottom: 12 }) 231 .alignRules({ 232 bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, 233 middle: { anchor: '__container__', align: HorizontalAlign.Center } 234 }) 235 .id('control_hangup') 236 .onClick(() => { 237 this.hideControlDelay = true; 238 pip.triggerAction('hangUp'); 239 console.debug(TAG, 'action: hangup'); 240 }) 241 242 Button({ type: ButtonType.Circle }) { 243 Image(this.isMute ? $r('sys.media.ohos_ic_public_voice') : $r('sys.media.ohos_ic_public_voice_off')) 244 .size({ width: 8 * sizeArray[this.sizeIndex], height: 8 * sizeArray[this.sizeIndex] }) 245 .fillColor($r('sys.color.ohos_id_color_primary')) 246 .objectFit(ImageFit.Contain) 247 } 248 .backgroundColor($r('sys.color.ohos_id_color_floating_button_icon')) 249 .size({ width: 16 * sizeArray[this.sizeIndex], height: 16 * sizeArray[this.sizeIndex] }) 250 .margin({ left: 8 * sizeArray[this.sizeIndex], right: 8 * sizeArray[this.sizeIndex] }) 251 .alignRules({ 252 center: { anchor: 'control_hangup', align: VerticalAlign.Center }, 253 right: { anchor: 'control_hangup', align: HorizontalAlign.Start } 254 }) 255 .id('control_mute') 256 .onClick(() => { 257 this.hideControlDelay = true; 258 this.isMute = !this.isMute; 259 pip.triggerAction('micStateChanged'); 260 console.debug(TAG, 'action: mic enable or disable'); 261 }) 262 263 Button({ type: ButtonType.Circle }) { 264 Image(this.isRecord ? $r('sys.media.ohos_ic_public_video') : $r('sys.media.ohos_ic_public_video_off')) 265 .size({ width: 8 * sizeArray[this.sizeIndex], height: 8 * sizeArray[this.sizeIndex] }) 266 .fillColor($r('sys.color.ohos_id_color_primary')) 267 .objectFit(ImageFit.Contain) 268 } 269 .backgroundColor($r('sys.color.ohos_id_color_floating_button_icon')) 270 .size({ width: 16 * sizeArray[this.sizeIndex], height: 16 * sizeArray[this.sizeIndex] }) 271 .margin({ left: 8 * sizeArray[this.sizeIndex], right: 8 * sizeArray[this.sizeIndex] }) 272 .alignRules({ 273 center: { anchor: 'control_hangup', align: VerticalAlign.Center }, 274 left: { anchor: 'control_hangup', align: HorizontalAlign.End } 275 }) 276 .id('control_record') 277 .onClick(() => { 278 this.hideControlDelay = true; 279 this.isRecord = !this.isRecord; 280 pip.triggerAction('videoStateChanged'); 281 console.debug(TAG, 'action: video enable or disable'); 282 }) 283 } 284 .width('100%') 285 .height(48) 286 .onAreaChange((oldValue: Area, newValue: Area) => { 287 if (oldValue.width == newValue.width) { 288 return; 289 } 290 this.sizeIndex = newValue.width >= 150 ? 1 : 0; 291 }) 292 .alignRules({ 293 bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, 294 left: { anchor: '__container__', align: HorizontalAlign.Start } 295 }) 296 .id('call_control') 297 } 298} 299