Add ability to make new call

This commit is contained in:
2020-03-14 23:18:27 -04:00
parent 70aaa79e69
commit 44e8489742
15 changed files with 132 additions and 91 deletions

View File

@@ -25,14 +25,14 @@
left: 2px;
}
.answer {
.answer, .makeCall {
composes: button;
background-color: #45bd57;
}
.answer:hover {
.answer:hover, .makeCall:hover {
background-image: linear-gradient(#84b68c, #5fb86c);
}
.answer:after {
.answer:after, .makeCall:after{
content: url(/phone-solid.svg);
transform: scale(0.65);
}

View File

@@ -1,6 +0,0 @@
import React from 'react';
const DialBox = () => {
return;
}
export default DialBox;

View File

@@ -1,11 +0,0 @@
import React from 'react';
import { DeviceState } from '../../hooks/voice-state';
export default function DisplayDeviceState({deviceState}: {deviceState: DeviceState})
{
return(
<div>
Device is currently {deviceState}.
</div>
);
}

View File

@@ -2,47 +2,47 @@ import React from 'react';
import { Voice } from '../../wrappers/voice';
import { voiceToken } from '../../wrappers/default-config';
import Button from './Button/button';
import DeviceState from './display-device-state';
import DtmfButtons from './DtmfButtons/dtmf-buttons';
import DeviceState from './DeviceState/display-device-state';
import DialButtons from './DialButtons/dial-buttons';
import DialBox from './DialBox/dial-box';
export default function Phone({voice:{ startDevice, answer, reject, hangup, dial, dtmf, voiceState} }: {voice: Voice})
export default function Phone({voice:{ startDevice, answer, reject, hangup, dialDigit, makeCall, voiceState} }: {voice: Voice})
{
if (voiceState.deviceState === "Offline")
{
return (
<div className="Phone">
<DeviceState deviceState = {voiceState.deviceState}/>
<Button title = "connect" onClick = {() => startDevice(voiceToken)}/>
</div>
);
}
else if(voiceState.deviceState === "Ringing")
{
return (
<div className="Phone">
<DeviceState deviceState = {voiceState.deviceState}/>
<Button title = "reject" onClick = {() => reject()}/>
<Button title = "answer" onClick = {() => answer()}/>
</div>
);
}
else if(voiceState.deviceState === "Connected")
{
return (
<div className="Phone">
<DeviceState deviceState = {voiceState.deviceState}/>
<DtmfButtons dtmf = {dtmf} />
<Button title = "hangup" onClick = {() => hangup()}/>
</div>
);
}
else
{
return (
<div className="Phone">
<DeviceState deviceState = {voiceState.deviceState}/>
</div>
);
}
switch (voiceState.deviceState)
{
case "Offline":
return (
<div className="Phone">
<DeviceState deviceState = {voiceState.deviceState} phoneNumber = {voiceState.callPhoneNumber}/>
<Button title = "connect" onClick = {() => startDevice(voiceToken)}/>
</div>
);
case "Ringing":
return (
<div className="Phone">
<DeviceState deviceState = {voiceState.deviceState} phoneNumber = {voiceState.callPhoneNumber}/>
<Button title = "reject" onClick = {() => reject()}/>
<Button title = "answer" onClick = {() => answer()}/>
</div>
);
case "Connected":
return (
<div className="Phone">
<DeviceState deviceState = {voiceState.deviceState} phoneNumber = {voiceState.callPhoneNumber}/>
<DialBox dialDigits = {voiceState.dialDigits} />
<DialButtons action = {dialDigit} />
<Button title = "hangup" onClick = {() => hangup()}/>
</div>
);
default:
return (
<div className="Phone">
<DeviceState deviceState = {voiceState.deviceState} phoneNumber = {voiceState.callPhoneNumber}/>
<DialBox dialDigits = {voiceState.dialDigits} />
<DialButtons action = {dialDigit} />
<Button title = "makeCall" onClick = {() => makeCall()}/>
</div>
);
}
}

View File

@@ -8,11 +8,13 @@ export interface VoiceState
callCreatedTime: number;
callEstablishedTime: number;
callPhoneNumber: string;
dialDigits: string;
}
export interface VoiceStateManager
{
onDeviceStateUpdate(state: DeviceState, phoneNumber?: string): void;
setDialDigits(digits: string): void;
voiceState: VoiceState;
}
@@ -23,6 +25,9 @@ export default function useVoiceState(): VoiceStateManager
const [callEstablishedTime, setCallEstablishedTime] = useState<number>(0);
const [callPhoneNumber, setCallPhoneNumber] = useState<string>("");
// Used for DTMF and number to dial
const [dialDigits, setDialDigits] = useState<string>("");
const onDeviceStateUpdate = (state: DeviceState, phoneNumber?: string) =>
{
@@ -39,15 +44,20 @@ export default function useVoiceState(): VoiceStateManager
setDeviceState(state);
if (phoneNumber) { setCallPhoneNumber(phoneNumber); }
// Wipe of the dialing digits on every state change.
setDialDigits("");
}
return {
onDeviceStateUpdate,
setDialDigits,
voiceState: {
deviceState,
callCreatedTime,
callEstablishedTime,
callPhoneNumber
callPhoneNumber,
dialDigits
}
}
}

View File

@@ -3,10 +3,10 @@ import { defaultDelay } from "./mock-device";
export class MockConnection implements Connection
{
private from = "+16133713909";
private to = "+123456789";
private from: string = "+16133713909";
private to: string = "+123456789";
public parameters: ConnectionParameters = { From: this.from, To: this.to } as ConnectionParameters;
public get parameters() { return { From: this.from, To: this.to } as ConnectionParameters; }
public customParameters: Map<string, string> = new Map();
public options: object = {};
@@ -14,6 +14,16 @@ export class MockConnection implements Connection
private readonly handlers = {} as {[key in ConnectionEvent]: ((mute?: boolean) => void)[]};
private muted: boolean = false;
constructor(phoneNumber?: string)
{
if (phoneNumber)
{
this.to = phoneNumber;
this.from = "";
this.executeHandler("accept");
}
}
private executeHandler(handlerName: ConnectionEvent)
{
setTimeout(() => this.handlers[handlerName]?.forEach(handler => handler()), defaultDelay);

View File

@@ -8,23 +8,31 @@ export class MockDevice implements Device
public audio: DeviceAudio = {} as DeviceAudio;
private deviceStatus: DeviceStatus = "offline";
private readonly handlers = {} as {[key in DeviceEvent]: ((connection?: Connection) => void)[]};
private currentConnection: Connection | undefined;
constructor(token: string, params?: SetupParams)
{
setTimeout(() => this.handlers["ready"].forEach(handler => handler()), defaultDelay);
setTimeout(() => this.handlers["incoming"].forEach(handler => handler(new MockConnection())), 2000);
setTimeout(() =>
{
this.currentConnection = new MockConnection();
this.handlers["incoming"].forEach(handler => handler(this.currentConnection));
},
2000);
}
public setup(token: string, params?: SetupParams): void {}
public connect(params: any, audioConstraints?: any): Connection
{
return {} as Connection;
this.currentConnection = new MockConnection(params.To);
return this.currentConnection;
}
public activeConnection(): Connection
public activeConnection(): Connection | undefined
{
return {} as Connection;
return this.currentConnection;
}
public destroy(): void

View File

@@ -9,8 +9,8 @@ export interface Voice
answer(): void;
reject(): void;
hangup(): void;
dial(phoneNumber: string): void;
dtmf(digit: string): void;
dialDigit(digit: string): void;
makeCall(): void;
voiceState: VoiceState;
}
@@ -72,20 +72,27 @@ export default function useVoice()
device?.disconnectAll();
}
const dial = (phoneNumber: string) =>
const dialDigit = (digit: string) =>
{
const connection = device?.connect({To: phoneNumber});
connection?.on("accept", () => state.onDeviceStateUpdate("Connected"));
connection?.on("disconnect", () => state.onDeviceStateUpdate("Disconnected"));
connection?.on("mute", muted => state.onDeviceStateUpdate(muted ? "Muted" : "Connected"));
setConnection(connection);
if (state.voiceState.deviceState === "Connected")
{
connection?.sendDigits(digit);
}
state.setDialDigits(state.voiceState.dialDigits + digit);
}
const dtmf = (digit: string) =>
const makeCall = () =>
{
connection?.sendDigits(digit);
if (state.voiceState.dialDigits)
{
const connection = device?.connect({To: state.voiceState.dialDigits});
connection?.on("accept", () => state.onDeviceStateUpdate("Connected", connection.parameters.To));
connection?.on("disconnect", () => state.onDeviceStateUpdate("Disconnected"));
connection?.on("mute", muted => state.onDeviceStateUpdate(muted ? "Muted" : "Connected"));
setConnection(connection);
}
}
return {
@@ -93,8 +100,8 @@ export default function useVoice()
answer,
reject,
hangup,
dial,
dtmf,
dialDigit,
makeCall,
voiceState: state.voiceState,
} as Voice;
}

View File

@@ -0,0 +1,4 @@
.phoneNumber {
font-size: 15px;
font-family: Arial, Helvetica, sans-serif;
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
import { DeviceState } from '../../../hooks/voice-state';
import styles from './display-device-state.module.css';
export default function DisplayDeviceState({deviceState, phoneNumber}: {deviceState: DeviceState, phoneNumber: string})
{
return(
<div>
Device is currently {deviceState}.
<p className={styles.phoneNumber} >{phoneNumber}</p>
</div>
);
}

View File

@@ -0,0 +1,7 @@
import React from 'react';
import style from './dial-box.module.css';
export default function DialBox({dialDigits} : {dialDigits: string})
{
return <div>{dialDigits}</div>;
}

View File

@@ -1,4 +1,4 @@
.dtmf-button {
.dial-button {
border-radius: 50%;
border: 1px solid black;
width: 70px;
@@ -7,7 +7,7 @@
background-color: white;
}
.dtmf-button:hover {
.dial-button:hover {
background-color: lightgray;
}

View File

@@ -1,15 +1,14 @@
import React from 'react';
import { chunk } from 'lodash';
import { Voice } from '../../../wrappers/voice';
import styles from './dtmf-buttons.module.css';
import styles from './dial-buttons.module.css';
interface DtmfButton
interface DialButton
{
title: string;
subTitle:string;
}
const buttons: DtmfButton[] = [
const buttons: DialButton[] = [
{title: "1", subTitle: " "},
{title: "2", subTitle: "abc"},
{title: "3", subTitle: "efg"},
@@ -24,13 +23,13 @@ const buttons: DtmfButton[] = [
{title: "#", subTitle: " "},
];
export default function DtmfButtons({dtmf}: {dtmf: Voice["dtmf"]}) {
export default function DialButtons({action}: {action: (digit: string) => void}) {
return (
<div>
{ chunk(buttons, 3).map((buttonRow, rowIndex) =>
<div key={rowIndex}>
{ buttonRow.map(({title, subTitle}, index) =>
<button key={title} className={styles["dtmf-button"]} onClick = {() => dtmf(title)}>
<button key={title} className={styles["dial-button"]} onClick = {() => action(title)}>
<div className={styles["title"]}>{ title }</div>
<div className={styles["sub-title"]}>{ subTitle }</div>
</button>) }

View File

@@ -91,7 +91,7 @@ declare module "twilio-client"
constructor(token: string, params?: SetupParams);
public setup(token: string, params?: SetupParams): void;
public connect(params: any, audioConstraints?: any): Connection;
public activeConnection(): Connection;
public activeConnection(): Connection | undefined;
public destroy(): void;
public disconnectAll(): void;
public status(): DeviceStatus;