webrtc: show connection information, mark call as missed if it ends while pending (#672)

This commit is contained in:
Evgeny Poberezkin
2022-05-20 07:43:44 +01:00
committed by GitHub
parent d50ebbd061
commit 9c9f6d8443
10 changed files with 101 additions and 50 deletions
@@ -513,7 +513,7 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
onConfirm = {
if (chatModel.activeCallInvitation.value == null) {
AlertManager.shared.hideAlert()
AlertManager.shared.showAlertMsg(generalGetString(R.string.call_already_ended))
withApi { AlertManager.shared.showAlertMsg(generalGetString(R.string.call_already_ended)) }
} else {
chatModel.activeCallInvitation.value = null
chatModel.activeCall.value = Call(
@@ -546,18 +546,20 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
}
}
is CR.CallExtraInfo -> {
withCall(r, r.contact) { call ->
withCall(r, r.contact) { _ ->
chatModel.callCommand.value = WCallCommand.Ice(iceCandidates = r.extraInfo.rtcIceCandidates)
}
}
is CR.CallEnded -> {
withCall(r, r.contact) { call ->
withCall(r, r.contact) { _ ->
chatModel.callCommand.value = WCallCommand.End
chatModel.activeCall.value = null
chatModel.activeCallInvitation.value = null
chatModel.callCommand.value = null
chatModel.showCallView.value = false
withApi {
chatModel.activeCall.value = null
chatModel.callCommand.value = null
}
}
chatModel.activeCallInvitation.value = null
chatModel.showCallView.value = false
}
else ->
Log.d(TAG , "unsupported event: ${r.responseType}")
@@ -836,7 +838,7 @@ sealed class CC {
is ApiSendCallAnswer -> "/_call answer @${contact.apiId} ${json.encodeToString(answer)}"
is ApiSendCallExtraInfo -> "/_call extra @${contact.apiId} ${json.encodeToString(extraInfo)}"
is ApiEndCall -> "/_call end @${contact.apiId}"
is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus}"
is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}"
is ApiChatRead -> "/_read chat ${chatRef(type, id)} from=${range.from} to=${range.to}"
is ReceiveFile -> "/freceive $fileId"
}
@@ -3,6 +3,7 @@ package chat.simplex.app.views.call
import android.Manifest
import android.content.ClipData
import android.content.ClipboardManager
import android.graphics.fonts.FontStyle
import android.os.Build
import android.service.controls.templates.ControlButton
import android.util.Log
@@ -26,6 +27,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -189,7 +191,7 @@ private fun ActiveCallOverlayLayout(
private fun ControlButton(call: Call, icon: ImageVector, @StringRes iconText: Int, action: () -> Unit) {
if (call.hasMedia) {
IconButton(onClick = action) {
Icon(icon, stringResource(iconText), tint = Color.White, modifier = Modifier.size(40.dp))
Icon(icon, stringResource(iconText), tint = Color(0xFFFFFFD8), modifier = Modifier.size(40.dp))
}
} else {
Spacer(Modifier.size(40.dp))
@@ -207,10 +209,16 @@ private fun ToggleAudioButton(call: Call, toggleAudio: () -> Unit) {
@Composable
private fun CallInfoView(call: Call, alignment: Alignment.Horizontal) {
@Composable fun InfoText(text: String, style: TextStyle = MaterialTheme.typography.body2) =
Text(text, color = Color(0xFFFFFFD8), style = style)
Column(horizontalAlignment = alignment) {
Text(call.contact.chatViewName, color = Color.White, style = MaterialTheme.typography.body2)
Text(call.callState.text, color = Color.White, style = MaterialTheme.typography.body2)
Text(call.encryptionStatus, color = Color.White, style = MaterialTheme.typography.body2)
InfoText(call.contact.chatViewName, style = MaterialTheme.typography.h2)
InfoText(call.callState.text)
val connInfo =
if (call.connectionInfo == null) ""
else " (${call.connectionInfo.text})"
InfoText(call.encryptionStatus + connInfo)
}
}
@@ -390,7 +398,8 @@ fun PreviewActiveCallOverlayVideo() {
contact = Contact.sampleData,
callState = CallState.Negotiated,
localMedia = CallMediaType.Video,
peerMedia = CallMediaType.Video
peerMedia = CallMediaType.Video,
connectionInfo = ConnectionInfo(RTCIceCandidate(RTCIceCandidateType.Host), RTCIceCandidate(RTCIceCandidateType.Host))
),
dismiss = {},
toggleAudio = {},
@@ -409,7 +418,8 @@ fun PreviewActiveCallOverlayAudio() {
contact = Contact.sampleData,
callState = CallState.Negotiated,
localMedia = CallMediaType.Audio,
peerMedia = CallMediaType.Audio
peerMedia = CallMediaType.Audio,
connectionInfo = ConnectionInfo(RTCIceCandidate(RTCIceCandidateType.Host), RTCIceCandidate(RTCIceCandidateType.Host))
),
dismiss = {},
toggleAudio = {},
@@ -96,7 +96,16 @@ sealed class WCallResponse {
})
}
@Serializable class CallCapabilities(val encryption: Boolean)
@Serializable class ConnectionInfo(val localCandidate: RTCIceCandidate?, val remoteCandidate: RTCIceCandidate)
@Serializable class ConnectionInfo(val localCandidate: RTCIceCandidate?, val remoteCandidate: RTCIceCandidate) {
val text: String @Composable get() = when {
localCandidate?.candidateType == RTCIceCandidateType.Host && remoteCandidate?.candidateType == RTCIceCandidateType.Host ->
stringResource(R.string.call_connection_peer_to_peer)
localCandidate?.candidateType == RTCIceCandidateType.Relay && remoteCandidate?.candidateType == RTCIceCandidateType.Relay ->
stringResource(R.string.call_connection_via_relay)
else ->
"${localCandidate?.candidateType?.value ?: "unknown"} / ${remoteCandidate?.candidateType?.value ?: "unknown"}"
}
}
// https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate
@Serializable class RTCIceCandidate(val candidateType: RTCIceCandidateType?)
// https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer
@@ -104,19 +113,19 @@ sealed class WCallResponse {
// https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate/type
@Serializable
enum class RTCIceCandidateType {
@SerialName("host") Host,
@SerialName("srflx") ServerReflexive,
@SerialName("prflx") PeerReflexive,
@SerialName("relay") Relay
enum class RTCIceCandidateType(val value: String) {
@SerialName("host") Host("host"),
@SerialName("srflx") ServerReflexive("srflx"),
@SerialName("prflx") PeerReflexive("prflx"),
@SerialName("relay") Relay("relay")
}
@Serializable
enum class WebRTCCallStatus {
@SerialName("connected") Connected,
@SerialName("connecting") Connecting,
@SerialName("disconnected") Disconnected,
@SerialName("failed") Failed
enum class WebRTCCallStatus(val value: String) {
@SerialName("connected") Connected("connected"),
@SerialName("connecting") Connecting("connecting"),
@SerialName("disconnected") Disconnected("disconnected"),
@SerialName("failed") Failed("failed")
}
@Serializable
@@ -3,6 +3,7 @@ package chat.simplex.app.views.chat
import android.content.res.Configuration
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
@@ -18,12 +19,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.TAG
@@ -174,42 +177,42 @@ fun ChatLayout(
@Composable
fun ChatInfoToolbar(chat: Chat, back: () -> Unit, info: () -> Unit, startCall: (CallMediaType) -> Unit) {
@Composable fun toolbarButton(icon: ImageVector, @StringRes textId: Int, modifier: Modifier = Modifier.padding(0.dp), onClick: () -> Unit) {
IconButton(onClick, modifier = modifier) {
Icon(icon, stringResource(textId), tint = MaterialTheme.colors.primary)
}
}
Column {
Box(
Modifier
.fillMaxWidth()
.height(52.dp)
.background(if (isSystemInDarkTheme()) ToolbarDark else ToolbarLight)
.padding(horizontal = 8.dp),
.padding(horizontal = 4.dp),
contentAlignment = Alignment.CenterStart,
) {
IconButton(onClick = back) {
Icon(
Icons.Outlined.ArrowBackIos,
stringResource(R.string.back),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
}
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
IconButton(onClick = { startCall(CallMediaType.Video) }) {
Icon(
Icons.Outlined.Videocam,
"video call",
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
val cInfo = chat.chatInfo
toolbarButton(Icons.Outlined.ArrowBackIos, R.string.back, onClick = back)
if (cInfo is ChatInfo.Direct) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
Box(Modifier.width(85.dp), contentAlignment = Alignment.CenterStart) {
toolbarButton(Icons.Outlined.Phone, R.string.icon_descr_audio_call) {
startCall(CallMediaType.Audio)
}
}
toolbarButton(Icons.Outlined.Videocam, R.string.icon_descr_video_call) {
startCall(CallMediaType.Video)
}
}
}
Row(
Modifier
.padding(horizontal = 68.dp)
.padding(horizontal = 80.dp)
.fillMaxWidth()
.clickable(onClick = info),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
val cInfo = chat.chatInfo
ChatInfoImage(cInfo, size = 40.dp)
Column(
Modifier.padding(start = 8.dp),
@@ -295,7 +295,7 @@
<string name="callstatus_accepted">принятый звонок</string>
<string name="callstatus_connecting">соединяется…</string>
<string name="callstatus_in_progress">активный звонок</string>
<string name="callstatus_ended">звонок завершён <xliff:g id="duration" example="01:15">%1$s!</xliff:g></string>
<string name="callstatus_ended">звонок завершён <xliff:g id="duration" example="01:15">%1$s</xliff:g></string>
<string name="callstatus_error">ошибка соединения</string>
<!-- CallState -->
@@ -347,15 +347,19 @@
<string name="encrypted_video_call">e2e encrypted video call</string>
<string name="audio_call_no_encryption">audio call (not e2e encrypted)</string>
<string name="encrypted_audio_call">e2e encrypted audio call</string>
<string name="if_you_accept_this_call_your_ip_address_visible">If you accept this call, your IP address might be visible to your contact, unless you connect via relay.</string>
<string name="if_you_accept_this_call_your_ip_address_visible">If you accept this call and you don\'t use relay, your IP address might be visible to your contact.</string>
<string name="answer">Answer</string>
<string name="call_already_ended">Call already ended!</string>
<string name="icon_descr_video_call">video call</string>
<string name="icon_descr_audio_call">audio call</string>
<!-- Call overlay -->
<string name="status_e2e_encrypted">e2e encrypted</string>
<string name="status_no_e2e_encryption">no e2e encryption</string>
<string name="status_contact_has_e2e_encryption">contact has e2e encryption</string>
<string name="status_contact_has_no_e2e_encryption">contact has no e2e encryption</string>
<string name="call_connection_peer_to_peer">peer-to-peer</string>
<string name="call_connection_via_relay">via relay</string>
<string name="icon_descr_hang_up">Hang up</string>
<string name="icon_descr_video_off">Video off</string>
<string name="icon_descr_video_on">Video on</string>
@@ -296,7 +296,7 @@
<string name="callstatus_accepted">accepted</string>
<string name="callstatus_connecting">connecting…</string>
<string name="callstatus_in_progress">in progress</string>
<string name="callstatus_ended">ended <xliff:g id="duration" example="01:15">%1$s!</xliff:g></string>
<string name="callstatus_ended">ended <xliff:g id="duration" example="01:15">%1$s</xliff:g></string>
<string name="callstatus_error">error</string>
<!-- CallState -->
@@ -348,15 +348,19 @@
<string name="encrypted_video_call">e2e encrypted video call</string>
<string name="audio_call_no_encryption">audio call (not e2e encrypted)</string>
<string name="encrypted_audio_call">e2e encrypted audio call</string>
<string name="if_you_accept_this_call_your_ip_address_visible">If you accept this call, your IP address might be visible to your contact, unless you connect via relay.</string>
<string name="if_you_accept_this_call_your_ip_address_visible">If you accept this call and you don\'t use relay, your IP address might be visible to your contact.</string>
<string name="answer">Answer</string>
<string name="call_already_ended">Call already ended!</string>
<string name="icon_descr_video_call">video call</string>
<string name="icon_descr_audio_call">audio call</string>
<!-- Call overlay -->
<string name="status_e2e_encrypted">e2e encrypted</string>
<string name="status_no_e2e_encryption">no e2e encryption</string>
<string name="status_contact_has_e2e_encryption">contact has e2e encryption</string>
<string name="status_contact_has_no_e2e_encryption">contact has no e2e encryption</string>
<string name="call_connection_peer_to_peer">peer-to-peer</string>
<string name="call_connection_via_relay">via relay</string>
<string name="icon_descr_hang_up">Hang up</string>
<string name="icon_descr_video_off">Video off</string>
<string name="icon_descr_video_on">Video on</string>