mirror of
				https://github.com/vector-im/element-web.git
				synced 2025-10-31 00:01:23 +01:00 
			
		
		
		
	Merge pull request #6470 from SimonBrandner/feature/incoming-call-toast
This commit is contained in:
		
						commit
						94e77e70c6
					
				| @ -266,6 +266,7 @@ | |||||||
| @import "./views/spaces/_SpacePublicShare.scss"; | @import "./views/spaces/_SpacePublicShare.scss"; | ||||||
| @import "./views/terms/_InlineTermsAgreement.scss"; | @import "./views/terms/_InlineTermsAgreement.scss"; | ||||||
| @import "./views/toasts/_AnalyticsToast.scss"; | @import "./views/toasts/_AnalyticsToast.scss"; | ||||||
|  | @import "./views/toasts/_IncomingCallToast.scss"; | ||||||
| @import "./views/toasts/_NonUrgentEchoFailureToast.scss"; | @import "./views/toasts/_NonUrgentEchoFailureToast.scss"; | ||||||
| @import "./views/verification/_VerificationShowSas.scss"; | @import "./views/verification/_VerificationShowSas.scss"; | ||||||
| @import "./views/voip/_CallContainer.scss"; | @import "./views/voip/_CallContainer.scss"; | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ limitations under the License. | |||||||
|         margin: 0 4px; |         margin: 0 4px; | ||||||
|         grid-row: 2 / 4; |         grid-row: 2 / 4; | ||||||
|         grid-column: 1; |         grid-column: 1; | ||||||
|         background-color: $dark-panel-bg-color; |         background-color: $toast-bg-color; | ||||||
|         box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); |         box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); | ||||||
|         border-radius: 8px; |         border-radius: 8px; | ||||||
|     } |     } | ||||||
| @ -37,7 +37,7 @@ limitations under the License. | |||||||
|         grid-row: 1 / 3; |         grid-row: 1 / 3; | ||||||
|         grid-column: 1; |         grid-column: 1; | ||||||
|         color: $primary-fg-color; |         color: $primary-fg-color; | ||||||
|         background-color: $dark-panel-bg-color; |         background-color: $toast-bg-color; | ||||||
|         box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); |         box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); | ||||||
|         border-radius: 8px; |         border-radius: 8px; | ||||||
|         overflow: hidden; |         overflow: hidden; | ||||||
|  | |||||||
							
								
								
									
										149
									
								
								res/css/views/toasts/_IncomingCallToast.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								res/css/views/toasts/_IncomingCallToast.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,149 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2020 The Matrix.org Foundation C.I.C. | ||||||
|  | Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com> | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | .mx_IncomingCallToast { | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: row; | ||||||
|  |     pointer-events: initial; // restore pointer events so the user can accept/decline | ||||||
|  | 
 | ||||||
|  |     .mx_IncomingCallToast_content { | ||||||
|  |         display: flex; | ||||||
|  |         flex-direction: column; | ||||||
|  |         margin-left: 8px; | ||||||
|  | 
 | ||||||
|  |         .mx_CallEvent_caller { | ||||||
|  |             font-weight: bold; | ||||||
|  |             font-size: $font-15px; | ||||||
|  |             line-height: $font-18px; | ||||||
|  | 
 | ||||||
|  |             margin-top: 2px; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .mx_CallEvent_type { | ||||||
|  |             font-size: $font-12px; | ||||||
|  |             line-height: $font-15px; | ||||||
|  |             color: $tertiary-fg-color; | ||||||
|  | 
 | ||||||
|  |             margin-top: 4px; | ||||||
|  |             margin-bottom: 6px; | ||||||
|  | 
 | ||||||
|  |             display: flex; | ||||||
|  |             flex-direction: row; | ||||||
|  |             align-items: center; | ||||||
|  | 
 | ||||||
|  |             .mx_CallEvent_type_icon { | ||||||
|  |                 height: 16px; | ||||||
|  |                 width: 16px; | ||||||
|  |                 margin-right: 6px; | ||||||
|  | 
 | ||||||
|  |                 &::before { | ||||||
|  |                     content: ''; | ||||||
|  |                     position: absolute; | ||||||
|  |                     height: inherit; | ||||||
|  |                     width: inherit; | ||||||
|  |                     background-color: $tertiary-fg-color; | ||||||
|  |                     mask-repeat: no-repeat; | ||||||
|  |                     mask-size: contain; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         &.mx_IncomingCallToast_content_voice { | ||||||
|  |             .mx_CallEvent_type .mx_CallEvent_type_icon::before, | ||||||
|  |             .mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before { | ||||||
|  |                 mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         &.mx_IncomingCallToast_content_video { | ||||||
|  |             .mx_CallEvent_type .mx_CallEvent_type_icon::before, | ||||||
|  |             .mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before { | ||||||
|  |                 mask-image: url('$(res)/img/element-icons/call/video-call.svg'); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .mx_IncomingCallToast_buttons { | ||||||
|  |             margin-top: 8px; | ||||||
|  |             display: flex; | ||||||
|  |             flex-direction: row; | ||||||
|  |             gap: 12px; | ||||||
|  | 
 | ||||||
|  |             .mx_IncomingCallToast_button { | ||||||
|  |                 height: 24px; | ||||||
|  |                 padding: 0px 8px; | ||||||
|  |                 flex-shrink: 0; | ||||||
|  |                 flex-grow: 1; | ||||||
|  |                 margin-right: 0; | ||||||
|  |                 font-size: $font-15px; | ||||||
|  |                 line-height: $font-24px; | ||||||
|  | 
 | ||||||
|  |                 span { | ||||||
|  |                     padding: 8px 0; | ||||||
|  |                     display: flex; | ||||||
|  |                     align-items: center; | ||||||
|  | 
 | ||||||
|  |                     &::before { | ||||||
|  |                         content: ''; | ||||||
|  |                         display: inline-block; | ||||||
|  |                         background-color: $button-fg-color; | ||||||
|  |                         mask-position: center; | ||||||
|  |                         mask-repeat: no-repeat; | ||||||
|  |                         margin-right: 8px; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 &.mx_IncomingCallToast_button_accept span::before { | ||||||
|  |                     mask-size: 13px; | ||||||
|  |                     width: 13px; | ||||||
|  |                     height: 13px; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 &.mx_IncomingCallToast_button_decline span::before { | ||||||
|  |                     mask-image: url('$(res)/img/element-icons/call/hangup.svg'); | ||||||
|  |                     mask-size: 16px; | ||||||
|  |                     width: 16px; | ||||||
|  |                     height: 16px; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .mx_IncomingCallToast_iconButton { | ||||||
|  |         display: flex; | ||||||
|  |         height: 20px; | ||||||
|  |         width: 20px; | ||||||
|  | 
 | ||||||
|  |         &::before { | ||||||
|  |             content: ''; | ||||||
|  | 
 | ||||||
|  |             height: inherit; | ||||||
|  |             width: inherit; | ||||||
|  |             background-color: $tertiary-fg-color; | ||||||
|  |             mask-repeat: no-repeat; | ||||||
|  |             mask-size: contain; | ||||||
|  |             mask-position: center; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .mx_IncomingCallToast_silence::before { | ||||||
|  |         mask-image: url('$(res)/img/voip/silence.svg'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .mx_IncomingCallToast_unSilence::before { | ||||||
|  |         mask-image: url('$(res)/img/voip/un-silence.svg'); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -43,84 +43,4 @@ limitations under the License. | |||||||
|     .mx_AppTile_persistedWrapper div { |     .mx_AppTile_persistedWrapper div { | ||||||
|         min-width: 350px; |         min-width: 350px; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     .mx_IncomingCallBox { |  | ||||||
|         min-width: 250px; |  | ||||||
|         background-color: $voipcall-plinth-color; |  | ||||||
|         padding: 8px; |  | ||||||
|         box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); |  | ||||||
|         border-radius: 8px; |  | ||||||
| 
 |  | ||||||
|         pointer-events: initial; // restore pointer events so the user can accept/decline |  | ||||||
|         cursor: pointer; |  | ||||||
| 
 |  | ||||||
|         .mx_IncomingCallBox_CallerInfo { |  | ||||||
|             display: flex; |  | ||||||
|             direction: row; |  | ||||||
| 
 |  | ||||||
|             img, .mx_BaseAvatar_initial { |  | ||||||
|                 margin: 8px; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             > div { |  | ||||||
|                 display: flex; |  | ||||||
|                 flex-direction: column; |  | ||||||
| 
 |  | ||||||
|                 justify-content: center; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             h1, p { |  | ||||||
|                 margin: 0px; |  | ||||||
|                 padding: 0px; |  | ||||||
|                 font-size: $font-14px; |  | ||||||
|                 line-height: $font-16px; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             h1 { |  | ||||||
|                 font-weight: bold; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         .mx_IncomingCallBox_buttons { |  | ||||||
|             padding: 8px; |  | ||||||
|             display: flex; |  | ||||||
|             flex-direction: row; |  | ||||||
| 
 |  | ||||||
|             > .mx_IncomingCallBox_spacer { |  | ||||||
|                 width: 8px; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             > * { |  | ||||||
|                 flex-shrink: 0; |  | ||||||
|                 flex-grow: 1; |  | ||||||
|                 margin-right: 0; |  | ||||||
|                 font-size: $font-15px; |  | ||||||
|                 line-height: $font-24px; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         .mx_IncomingCallBox_iconButton { |  | ||||||
|             position: absolute; |  | ||||||
|             right: 8px; |  | ||||||
| 
 |  | ||||||
|             &::before { |  | ||||||
|                 content: ''; |  | ||||||
| 
 |  | ||||||
|                 height: 20px; |  | ||||||
|                 width: 20px; |  | ||||||
|                 background-color: $icon-button-color; |  | ||||||
|                 mask-repeat: no-repeat; |  | ||||||
|                 mask-size: contain; |  | ||||||
|                 mask-position: center; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         .mx_IncomingCallBox_silence::before { |  | ||||||
|             mask-image: url('$(res)/img/voip/silence.svg'); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         .mx_IncomingCallBox_unSilence::before { |  | ||||||
|             mask-image: url('$(res)/img/voip/un-silence.svg'); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ limitations under the License. | |||||||
| .mx_CallView_pip { | .mx_CallView_pip { | ||||||
|     width: 320px; |     width: 320px; | ||||||
|     padding-bottom: 8px; |     padding-bottom: 8px; | ||||||
|     background-color: $voipcall-plinth-color; |     background-color: $toast-bg-color; | ||||||
|     box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20); |     box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20); | ||||||
|     border-radius: 8px; |     border-radius: 8px; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -115,8 +115,8 @@ $eventtile-meta-color: $roomtopic-color; | |||||||
| $header-divider-color: $header-panel-text-primary-color; | $header-divider-color: $header-panel-text-primary-color; | ||||||
| $composer-e2e-icon-color: $header-panel-text-primary-color; | $composer-e2e-icon-color: $header-panel-text-primary-color; | ||||||
| 
 | 
 | ||||||
| // this probably shouldn't have it's own colour | $quinary-content-color: #394049; | ||||||
| $voipcall-plinth-color: #394049; | $toast-bg-color: $quinary-content-color; | ||||||
| 
 | 
 | ||||||
| // ******************** | // ******************** | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -111,8 +111,8 @@ $eventtile-meta-color: $roomtopic-color; | |||||||
| $header-divider-color: $header-panel-text-primary-color; | $header-divider-color: $header-panel-text-primary-color; | ||||||
| $composer-e2e-icon-color: $header-panel-text-primary-color; | $composer-e2e-icon-color: $header-panel-text-primary-color; | ||||||
| 
 | 
 | ||||||
| // this probably shouldn't have it's own colour | $quinary-content-color: #394049; | ||||||
| $voipcall-plinth-color: #394049; | $toast-bg-color: $quinary-content-color; | ||||||
| 
 | 
 | ||||||
| // ******************** | // ******************** | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -181,7 +181,7 @@ $eventtile-meta-color: $roomtopic-color; | |||||||
| $composer-e2e-icon-color: #91a1c0; | $composer-e2e-icon-color: #91a1c0; | ||||||
| $header-divider-color: #91a1c0; | $header-divider-color: #91a1c0; | ||||||
| 
 | 
 | ||||||
| // this probably shouldn't have it's own colour | $toast-bg-color: $system-light; | ||||||
| $voipcall-plinth-color: $system-light; | $voipcall-plinth-color: $system-light; | ||||||
| 
 | 
 | ||||||
| // ******************** | // ******************** | ||||||
|  | |||||||
| @ -170,7 +170,7 @@ $eventtile-meta-color: $roomtopic-color; | |||||||
| $composer-e2e-icon-color: #91A1C0; | $composer-e2e-icon-color: #91A1C0; | ||||||
| $header-divider-color: #91A1C0; | $header-divider-color: #91A1C0; | ||||||
| 
 | 
 | ||||||
| // this probably shouldn't have it's own colour | $toast-bg-color: $system-light; | ||||||
| $voipcall-plinth-color: $system-light; | $voipcall-plinth-color: $system-light; | ||||||
| 
 | 
 | ||||||
| // ******************** | // ******************** | ||||||
|  | |||||||
| @ -86,6 +86,9 @@ import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/ | |||||||
| import EventEmitter from 'events'; | import EventEmitter from 'events'; | ||||||
| import SdkConfig from './SdkConfig'; | import SdkConfig from './SdkConfig'; | ||||||
| import { ensureDMExists, findDMForUser } from './createRoom'; | import { ensureDMExists, findDMForUser } from './createRoom'; | ||||||
|  | import { getIncomingCallToastKey } from './toasts/IncomingCallToast'; | ||||||
|  | import ToastStore from './stores/ToastStore'; | ||||||
|  | import IncomingCallToast from "./toasts/IncomingCallToast"; | ||||||
| 
 | 
 | ||||||
| export const PROTOCOL_PSTN = 'm.protocol.pstn'; | export const PROTOCOL_PSTN = 'm.protocol.pstn'; | ||||||
| export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; | export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; | ||||||
| @ -624,6 +627,19 @@ export default class CallHandler extends EventEmitter { | |||||||
|             `Call state in ${mappedRoomId} changed to ${status}`, |             `Call state in ${mappedRoomId} changed to ${status}`, | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|  |         const toastKey = getIncomingCallToastKey(call.callId); | ||||||
|  |         if (status === CallState.Ringing) { | ||||||
|  |             ToastStore.sharedInstance().addOrReplaceToast({ | ||||||
|  |                 key: toastKey, | ||||||
|  |                 priority: 100, | ||||||
|  |                 component: IncomingCallToast, | ||||||
|  |                 bodyClassName: "mx_IncomingCallToast", | ||||||
|  |                 props: { call }, | ||||||
|  |             }); | ||||||
|  |         } else { | ||||||
|  |             ToastStore.sharedInstance().dismissToast(toastKey); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         dis.dispatch({ |         dis.dispatch({ | ||||||
|             action: 'call_state', |             action: 'call_state', | ||||||
|             room_id: mappedRoomId, |             room_id: mappedRoomId, | ||||||
|  | |||||||
| @ -58,28 +58,39 @@ export default class ToastContainer extends React.Component<{}, IState> { | |||||||
|         let containerClasses; |         let containerClasses; | ||||||
|         if (totalCount !== 0) { |         if (totalCount !== 0) { | ||||||
|             const topToast = this.state.toasts[0]; |             const topToast = this.state.toasts[0]; | ||||||
|             const { title, icon, key, component, className, props } = topToast; |             const { title, icon, key, component, className, bodyClassName, props } = topToast; | ||||||
|             const toastClasses = classNames("mx_Toast_toast", { |             const bodyClasses = classNames("mx_Toast_body", bodyClassName); | ||||||
|  |             const toastClasses = classNames("mx_Toast_toast", className, { | ||||||
|                 "mx_Toast_hasIcon": icon, |                 "mx_Toast_hasIcon": icon, | ||||||
|                 [`mx_Toast_icon_${icon}`]: icon, |                 [`mx_Toast_icon_${icon}`]: icon, | ||||||
|             }, className); |             }); | ||||||
| 
 |  | ||||||
|             let countIndicator; |  | ||||||
|             if (isStacked || this.state.countSeen > 0) { |  | ||||||
|                 countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             const toastProps = Object.assign({}, props, { |             const toastProps = Object.assign({}, props, { | ||||||
|                 key, |                 key, | ||||||
|                 toastKey: key, |                 toastKey: key, | ||||||
|             }); |             }); | ||||||
|             toast = (<div className={toastClasses}> |             const content = React.createElement(component, toastProps); | ||||||
|                 <div className="mx_Toast_title"> | 
 | ||||||
|                     <h2>{ title }</h2> |             let countIndicator; | ||||||
|                     <span>{ countIndicator }</span> |             if (title && isStacked || this.state.countSeen > 0) { | ||||||
|  |                 countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             let titleElement; | ||||||
|  |             if (title) { | ||||||
|  |                 titleElement = ( | ||||||
|  |                     <div className="mx_Toast_title"> | ||||||
|  |                         <h2>{ title }</h2> | ||||||
|  |                         <span>{ countIndicator }</span> | ||||||
|  |                     </div> | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             toast = ( | ||||||
|  |                 <div className={toastClasses}> | ||||||
|  |                     { titleElement } | ||||||
|  |                     <div className={bodyClasses}>{ content }</div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div className="mx_Toast_body">{ React.createElement(component, toastProps) }</div> |             ); | ||||||
|             </div>); |  | ||||||
| 
 | 
 | ||||||
|             containerClasses = classNames("mx_ToastContainer", { |             containerClasses = classNames("mx_ToastContainer", { | ||||||
|                 "mx_ToastContainer_stacked": isStacked, |                 "mx_ToastContainer_stacked": isStacked, | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| /* | /* | ||||||
| Copyright 2020 The Matrix.org Foundation C.I.C. | Copyright 2020 The Matrix.org Foundation C.I.C. | ||||||
|  | Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com> | ||||||
| 
 | 
 | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
| you may not use this file except in compliance with the License. | you may not use this file except in compliance with the License. | ||||||
| @ -15,7 +16,6 @@ limitations under the License. | |||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import IncomingCallBox from './IncomingCallBox'; |  | ||||||
| import CallPreview from './CallPreview'; | import CallPreview from './CallPreview'; | ||||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; | import { replaceableComponent } from "../../../utils/replaceableComponent"; | ||||||
| 
 | 
 | ||||||
| @ -31,7 +31,6 @@ interface IState { | |||||||
| export default class CallContainer extends React.PureComponent<IProps, IState> { | export default class CallContainer extends React.PureComponent<IProps, IState> { | ||||||
|     public render() { |     public render() { | ||||||
|         return <div className="mx_CallContainer"> |         return <div className="mx_CallContainer"> | ||||||
|             <IncomingCallBox /> |  | ||||||
|             <CallPreview /> |             <CallPreview /> | ||||||
|         </div>; |         </div>; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,176 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2015, 2016 OpenMarket Ltd |  | ||||||
| Copyright 2018 New Vector Ltd |  | ||||||
| Copyright 2019, 2020 The Matrix.org Foundation C.I.C. |  | ||||||
| 
 |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
| 
 |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0
 |  | ||||||
| 
 |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| import React from 'react'; |  | ||||||
| import { MatrixClientPeg } from '../../../MatrixClientPeg'; |  | ||||||
| import dis from '../../../dispatcher/dispatcher'; |  | ||||||
| import { _t } from '../../../languageHandler'; |  | ||||||
| import { ActionPayload } from '../../../dispatcher/payloads'; |  | ||||||
| import CallHandler, { CallHandlerEvent } from '../../../CallHandler'; |  | ||||||
| import RoomAvatar from '../avatars/RoomAvatar'; |  | ||||||
| import AccessibleButton from '../elements/AccessibleButton'; |  | ||||||
| import { CallState } from 'matrix-js-sdk/src/webrtc/call'; |  | ||||||
| import { replaceableComponent } from "../../../utils/replaceableComponent"; |  | ||||||
| import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; |  | ||||||
| import classNames from 'classnames'; |  | ||||||
| 
 |  | ||||||
| interface IProps { |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| interface IState { |  | ||||||
|     incomingCall: any; |  | ||||||
|     silenced: boolean; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @replaceableComponent("views.voip.IncomingCallBox") |  | ||||||
| export default class IncomingCallBox extends React.Component<IProps, IState> { |  | ||||||
|     private dispatcherRef: string; |  | ||||||
| 
 |  | ||||||
|     constructor(props: IProps) { |  | ||||||
|         super(props); |  | ||||||
| 
 |  | ||||||
|         this.dispatcherRef = dis.register(this.onAction); |  | ||||||
|         this.state = { |  | ||||||
|             incomingCall: null, |  | ||||||
|             silenced: false, |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     componentDidMount = () => { |  | ||||||
|         CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     public componentWillUnmount() { |  | ||||||
|         dis.unregister(this.dispatcherRef); |  | ||||||
|         CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private onAction = (payload: ActionPayload) => { |  | ||||||
|         switch (payload.action) { |  | ||||||
|             case 'call_state': { |  | ||||||
|                 const call = CallHandler.sharedInstance().getCallForRoom(payload.room_id); |  | ||||||
|                 if (call && call.state === CallState.Ringing) { |  | ||||||
|                     this.setState({ |  | ||||||
|                         incomingCall: call, |  | ||||||
|                         silenced: false, // Reset silenced state for new call
 |  | ||||||
|                     }); |  | ||||||
|                 } else { |  | ||||||
|                     this.setState({ |  | ||||||
|                         incomingCall: null, |  | ||||||
|                     }); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     private onSilencedCallsChanged = () => { |  | ||||||
|         const callId = this.state.incomingCall?.callId; |  | ||||||
|         if (!callId) return; |  | ||||||
|         this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(callId) }); |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     private onAnswerClick: React.MouseEventHandler = (e) => { |  | ||||||
|         e.stopPropagation(); |  | ||||||
|         dis.dispatch({ |  | ||||||
|             action: 'answer', |  | ||||||
|             room_id: CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall), |  | ||||||
|         }); |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     private onRejectClick: React.MouseEventHandler = (e) => { |  | ||||||
|         e.stopPropagation(); |  | ||||||
|         dis.dispatch({ |  | ||||||
|             action: 'reject', |  | ||||||
|             room_id: CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall), |  | ||||||
|         }); |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     private onSilenceClick: React.MouseEventHandler = (e) => { |  | ||||||
|         e.stopPropagation(); |  | ||||||
|         const callId = this.state.incomingCall.callId; |  | ||||||
|         this.state.silenced ? |  | ||||||
|             CallHandler.sharedInstance().unSilenceCall(callId): |  | ||||||
|             CallHandler.sharedInstance().silenceCall(callId); |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     public render() { |  | ||||||
|         if (!this.state.incomingCall) { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let room = null; |  | ||||||
|         if (this.state.incomingCall) { |  | ||||||
|             room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const caller = room ? room.name : _t("Unknown caller"); |  | ||||||
| 
 |  | ||||||
|         let incomingCallText = null; |  | ||||||
|         if (this.state.incomingCall) { |  | ||||||
|             if (this.state.incomingCall.type === "voice") { |  | ||||||
|                 incomingCallText = _t("Incoming voice call"); |  | ||||||
|             } else if (this.state.incomingCall.type === "video") { |  | ||||||
|                 incomingCallText = _t("Incoming video call"); |  | ||||||
|             } else { |  | ||||||
|                 incomingCallText = _t("Incoming call"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const silenceClass = classNames({ |  | ||||||
|             "mx_IncomingCallBox_iconButton": true, |  | ||||||
|             "mx_IncomingCallBox_unSilence": this.state.silenced, |  | ||||||
|             "mx_IncomingCallBox_silence": !this.state.silenced, |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         return <div className="mx_IncomingCallBox"> |  | ||||||
|             <div className="mx_IncomingCallBox_CallerInfo"> |  | ||||||
|                 <RoomAvatar |  | ||||||
|                     room={room} |  | ||||||
|                     height={32} |  | ||||||
|                     width={32} |  | ||||||
|                 /> |  | ||||||
|                 <div> |  | ||||||
|                     <h1>{ caller }</h1> |  | ||||||
|                     <p>{ incomingCallText }</p> |  | ||||||
|                 </div> |  | ||||||
|                 <AccessibleTooltipButton |  | ||||||
|                     className={silenceClass} |  | ||||||
|                     onClick={this.onSilenceClick} |  | ||||||
|                     title={this.state.silenced ? _t("Sound on"): _t("Silence call")} |  | ||||||
|                 /> |  | ||||||
|             </div> |  | ||||||
|             <div className="mx_IncomingCallBox_buttons"> |  | ||||||
|                 <AccessibleButton |  | ||||||
|                     className="mx_IncomingCallBox_decline" |  | ||||||
|                     onClick={this.onRejectClick} |  | ||||||
|                     kind="danger" |  | ||||||
|                 > |  | ||||||
|                     { _t("Decline") } |  | ||||||
|                 </AccessibleButton> |  | ||||||
|                 <div className="mx_IncomingCallBox_spacer" /> |  | ||||||
|                 <AccessibleButton |  | ||||||
|                     className="mx_IncomingCallBox_accept" |  | ||||||
|                     onClick={this.onAnswerClick} |  | ||||||
|                     kind="primary" |  | ||||||
|                 > |  | ||||||
|                     { _t("Accept") } |  | ||||||
|                 </AccessibleButton> |  | ||||||
|             </div> |  | ||||||
|         </div>; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -734,6 +734,13 @@ | |||||||
|     "Notifications": "Notifications", |     "Notifications": "Notifications", | ||||||
|     "Enable desktop notifications": "Enable desktop notifications", |     "Enable desktop notifications": "Enable desktop notifications", | ||||||
|     "Enable": "Enable", |     "Enable": "Enable", | ||||||
|  |     "Unknown caller": "Unknown caller", | ||||||
|  |     "Voice call": "Voice call", | ||||||
|  |     "Video call": "Video call", | ||||||
|  |     "Decline": "Decline", | ||||||
|  |     "Accept": "Accept", | ||||||
|  |     "Sound on": "Sound on", | ||||||
|  |     "Silence call": "Silence call", | ||||||
|     "Use app for a better experience": "Use app for a better experience", |     "Use app for a better experience": "Use app for a better experience", | ||||||
|     "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.", |     "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.", | ||||||
|     "Use app": "Use app", |     "Use app": "Use app", | ||||||
| @ -912,14 +919,6 @@ | |||||||
|     "Fill Screen": "Fill Screen", |     "Fill Screen": "Fill Screen", | ||||||
|     "Return to call": "Return to call", |     "Return to call": "Return to call", | ||||||
|     "%(name)s on hold": "%(name)s on hold", |     "%(name)s on hold": "%(name)s on hold", | ||||||
|     "Unknown caller": "Unknown caller", |  | ||||||
|     "Incoming voice call": "Incoming voice call", |  | ||||||
|     "Incoming video call": "Incoming video call", |  | ||||||
|     "Incoming call": "Incoming call", |  | ||||||
|     "Sound on": "Sound on", |  | ||||||
|     "Silence call": "Silence call", |  | ||||||
|     "Decline": "Decline", |  | ||||||
|     "Accept": "Accept", |  | ||||||
|     "The other party cancelled the verification.": "The other party cancelled the verification.", |     "The other party cancelled the verification.": "The other party cancelled the verification.", | ||||||
|     "Verified!": "Verified!", |     "Verified!": "Verified!", | ||||||
|     "You've successfully verified this user.": "You've successfully verified this user.", |     "You've successfully verified this user.": "You've successfully verified this user.", | ||||||
| @ -1582,8 +1581,6 @@ | |||||||
|     "Hide Widgets": "Hide Widgets", |     "Hide Widgets": "Hide Widgets", | ||||||
|     "Show Widgets": "Show Widgets", |     "Show Widgets": "Show Widgets", | ||||||
|     "Search": "Search", |     "Search": "Search", | ||||||
|     "Voice call": "Voice call", |  | ||||||
|     "Video call": "Video call", |  | ||||||
|     "Invites": "Invites", |     "Invites": "Invites", | ||||||
|     "Favourites": "Favourites", |     "Favourites": "Favourites", | ||||||
|     "People": "People", |     "People": "People", | ||||||
|  | |||||||
| @ -22,10 +22,11 @@ export interface IToast<C extends ComponentClass> { | |||||||
|     key: string; |     key: string; | ||||||
|     // higher priority number will be shown on top of lower priority
 |     // higher priority number will be shown on top of lower priority
 | ||||||
|     priority: number; |     priority: number; | ||||||
|     title: string; |     title?: string; | ||||||
|     icon?: string; |     icon?: string; | ||||||
|     component: C; |     component: C; | ||||||
|     className?: string; |     className?: string; | ||||||
|  |     bodyClassName?: string; | ||||||
|     props?: Omit<React.ComponentProps<C>, "toastKey">; // toastKey is injected by ToastContainer
 |     props?: Omit<React.ComponentProps<C>, "toastKey">; // toastKey is injected by ToastContainer
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										140
									
								
								src/toasts/IncomingCallToast.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/toasts/IncomingCallToast.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2015, 2016 OpenMarket Ltd | ||||||
|  | Copyright 2018 New Vector Ltd | ||||||
|  | Copyright 2019, 2020 The Matrix.org Foundation C.I.C. | ||||||
|  | Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com> | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | import React from 'react'; | ||||||
|  | import { CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; | ||||||
|  | import classNames from 'classnames'; | ||||||
|  | import { replaceableComponent } from '../utils/replaceableComponent'; | ||||||
|  | import CallHandler, { CallHandlerEvent } from '../CallHandler'; | ||||||
|  | import dis from '../dispatcher/dispatcher'; | ||||||
|  | import { MatrixClientPeg } from '../MatrixClientPeg'; | ||||||
|  | import { _t } from '../languageHandler'; | ||||||
|  | import RoomAvatar from '../components/views/avatars/RoomAvatar'; | ||||||
|  | import AccessibleTooltipButton from '../components/views/elements/AccessibleTooltipButton'; | ||||||
|  | import AccessibleButton from '../components/views/elements/AccessibleButton'; | ||||||
|  | 
 | ||||||
|  | export const getIncomingCallToastKey = (callId: string) => `call_${callId}`; | ||||||
|  | 
 | ||||||
|  | interface IProps { | ||||||
|  |     call: MatrixCall; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface IState { | ||||||
|  |     silenced: boolean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @replaceableComponent("views.voip.IncomingCallToast") | ||||||
|  | export default class IncomingCallToast extends React.Component<IProps, IState> { | ||||||
|  |     constructor(props: IProps) { | ||||||
|  |         super(props); | ||||||
|  | 
 | ||||||
|  |         this.state = { | ||||||
|  |             silenced: false, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public componentDidMount = (): void => { | ||||||
|  |         CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     public componentWillUnmount(): void { | ||||||
|  |         CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private onSilencedCallsChanged = (): void => { | ||||||
|  |         this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(this.props.call.callId) }); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     private onAnswerClick= (e: React.MouseEvent): void => { | ||||||
|  |         e.stopPropagation(); | ||||||
|  |         dis.dispatch({ | ||||||
|  |             action: 'answer', | ||||||
|  |             room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call), | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     private onRejectClick= (e: React.MouseEvent): void => { | ||||||
|  |         e.stopPropagation(); | ||||||
|  |         dis.dispatch({ | ||||||
|  |             action: 'reject', | ||||||
|  |             room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call), | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     private onSilenceClick = (e: React.MouseEvent): void => { | ||||||
|  |         e.stopPropagation(); | ||||||
|  |         const callId = this.props.call.callId; | ||||||
|  |         this.state.silenced ? | ||||||
|  |             CallHandler.sharedInstance().unSilenceCall(callId) : | ||||||
|  |             CallHandler.sharedInstance().silenceCall(callId); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     public render() { | ||||||
|  |         const call = this.props.call; | ||||||
|  |         const room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call)); | ||||||
|  |         const isVoice = call.type === CallType.Voice; | ||||||
|  | 
 | ||||||
|  |         const contentClass = classNames("mx_IncomingCallToast_content", { | ||||||
|  |             "mx_IncomingCallToast_content_voice": isVoice, | ||||||
|  |             "mx_IncomingCallToast_content_video": !isVoice, | ||||||
|  |         }); | ||||||
|  |         const silenceClass = classNames("mx_IncomingCallToast_iconButton", { | ||||||
|  |             "mx_IncomingCallToast_unSilence": this.state.silenced, | ||||||
|  |             "mx_IncomingCallToast_silence": !this.state.silenced, | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return <React.Fragment> | ||||||
|  |             <RoomAvatar | ||||||
|  |                 room={room} | ||||||
|  |                 height={32} | ||||||
|  |                 width={32} | ||||||
|  |             /> | ||||||
|  |             <div className={contentClass}> | ||||||
|  |                 <span className="mx_CallEvent_caller"> | ||||||
|  |                     { room ? room.name : _t("Unknown caller") } | ||||||
|  |                 </span> | ||||||
|  |                 <div className="mx_CallEvent_type"> | ||||||
|  |                     <div className="mx_CallEvent_type_icon" /> | ||||||
|  |                     { isVoice ? _t("Voice call") : _t("Video call") } | ||||||
|  |                 </div> | ||||||
|  |                 <div className="mx_IncomingCallToast_buttons"> | ||||||
|  |                     <AccessibleButton | ||||||
|  |                         className="mx_IncomingCallToast_button mx_IncomingCallToast_button_decline" | ||||||
|  |                         onClick={this.onRejectClick} | ||||||
|  |                         kind="danger" | ||||||
|  |                     > | ||||||
|  |                         <span> { _t("Decline") } </span> | ||||||
|  |                     </AccessibleButton> | ||||||
|  |                     <AccessibleButton | ||||||
|  |                         className="mx_IncomingCallToast_button mx_IncomingCallToast_button_accept" | ||||||
|  |                         onClick={this.onAnswerClick} | ||||||
|  |                         kind="primary" | ||||||
|  |                     > | ||||||
|  |                         <span> { _t("Accept") } </span> | ||||||
|  |                     </AccessibleButton> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |             <AccessibleTooltipButton | ||||||
|  |                 className={silenceClass} | ||||||
|  |                 onClick={this.onSilenceClick} | ||||||
|  |                 title={this.state.silenced ? _t("Sound on") : _t("Silence call")} | ||||||
|  |             /> | ||||||
|  |         </React.Fragment>; | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user