android: types and messages for webrtc calls (#609)
* android: webrtc calls * string localizations, more types
This commit is contained in:
committed by
GitHub
parent
29990765e7
commit
fcb5c69281
@@ -17,8 +17,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.work.*
|
||||
import chat.simplex.app.model.ChatModel
|
||||
import chat.simplex.app.model.NtfManager
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.SplashView
|
||||
import chat.simplex.app.views.WelcomeView
|
||||
@@ -29,7 +28,6 @@ import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.newchat.connectViaUri
|
||||
import chat.simplex.app.views.newchat.withUriAction
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
//import kotlinx.serialization.decodeFromString
|
||||
|
||||
class MainActivity: ComponentActivity() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.ui.text.style.TextDecoration
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.ui.theme.SecretColor
|
||||
import chat.simplex.app.ui.theme.SimplexBlue
|
||||
import chat.simplex.app.views.call.*
|
||||
import chat.simplex.app.views.helpers.generalGetString
|
||||
import kotlinx.datetime.*
|
||||
import kotlinx.serialization.*
|
||||
@@ -19,23 +20,29 @@ import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.*
|
||||
|
||||
class ChatModel(val controller: ChatController) {
|
||||
var currentUser = mutableStateOf<User?>(null)
|
||||
var userCreated = mutableStateOf<Boolean?>(null)
|
||||
var chats = mutableStateListOf<Chat>()
|
||||
var chatId = mutableStateOf<String?>(null)
|
||||
var chatItems = mutableStateListOf<ChatItem>()
|
||||
val currentUser = mutableStateOf<User?>(null)
|
||||
val userCreated = mutableStateOf<Boolean?>(null)
|
||||
val chats = mutableStateListOf<Chat>()
|
||||
val chatId = mutableStateOf<String?>(null)
|
||||
val chatItems = mutableStateListOf<ChatItem>()
|
||||
|
||||
var connReqInvitation: String? = null
|
||||
var terminalItems = mutableStateListOf<TerminalItem>()
|
||||
var userAddress = mutableStateOf<String?>(null)
|
||||
var userSMPServers = mutableStateOf<(List<String>)?>(null)
|
||||
val terminalItems = mutableStateListOf<TerminalItem>()
|
||||
val userAddress = mutableStateOf<String?>(null)
|
||||
val userSMPServers = mutableStateOf<(List<String>)?>(null)
|
||||
|
||||
// set when app opened from external intent
|
||||
var clearOverlays = mutableStateOf<Boolean>(false)
|
||||
val clearOverlays = mutableStateOf<Boolean>(false)
|
||||
|
||||
// set when app is opened via contact or invitation URI
|
||||
var appOpenUrl = mutableStateOf<Uri?>(null)
|
||||
var runServiceInBackground = mutableStateOf(true)
|
||||
val appOpenUrl = mutableStateOf<Uri?>(null)
|
||||
val runServiceInBackground = mutableStateOf(true)
|
||||
|
||||
// current WebRTC call
|
||||
val callInvitations = mutableStateMapOf<String, CallInvitation>()
|
||||
val activeCallInvitation = mutableStateOf<ContactRef?>(null)
|
||||
val activeCall = mutableStateOf<Call?>(null)
|
||||
val callCommand = mutableStateOf<WCallCommand?>(null)
|
||||
|
||||
fun updateUserProfile(profile: Profile) {
|
||||
val user = currentUser.value
|
||||
@@ -654,6 +661,13 @@ data class ChatItem (
|
||||
else -> false
|
||||
}
|
||||
|
||||
val isCall: Boolean get() =
|
||||
when (content) {
|
||||
is CIContent.SndCall -> true
|
||||
is CIContent.RcvCall -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getSampleData(
|
||||
id: Long = 1,
|
||||
@@ -709,26 +723,16 @@ data class ChatItem (
|
||||
|
||||
@Serializable
|
||||
sealed class CIDirection {
|
||||
abstract val sent: Boolean
|
||||
@Serializable @SerialName("directSnd") class DirectSnd: CIDirection()
|
||||
@Serializable @SerialName("directRcv") class DirectRcv: CIDirection()
|
||||
@Serializable @SerialName("groupSnd") class GroupSnd: CIDirection()
|
||||
@Serializable @SerialName("groupRcv") class GroupRcv(val groupMember: GroupMember): CIDirection()
|
||||
|
||||
@Serializable @SerialName("directSnd")
|
||||
class DirectSnd: CIDirection() {
|
||||
override val sent get() = true
|
||||
}
|
||||
|
||||
@Serializable @SerialName("directRcv")
|
||||
class DirectRcv: CIDirection() {
|
||||
override val sent get() = false
|
||||
}
|
||||
|
||||
@Serializable @SerialName("groupSnd")
|
||||
class GroupSnd: CIDirection() {
|
||||
override val sent get() = true
|
||||
}
|
||||
|
||||
@Serializable @SerialName("groupRcv")
|
||||
class GroupRcv(val groupMember: GroupMember): CIDirection() {
|
||||
override val sent get() = false
|
||||
val sent: Boolean get() = when(this) {
|
||||
is DirectSnd -> true
|
||||
is DirectRcv -> false
|
||||
is GroupSnd -> true
|
||||
is GroupRcv -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,23 +779,12 @@ fun getTimestampText(t: Instant): String {
|
||||
|
||||
@Serializable
|
||||
sealed class CIStatus {
|
||||
@Serializable @SerialName("sndNew")
|
||||
class SndNew: CIStatus()
|
||||
|
||||
@Serializable @SerialName("sndSent")
|
||||
class SndSent: CIStatus()
|
||||
|
||||
@Serializable @SerialName("sndErrorAuth")
|
||||
class SndErrorAuth: CIStatus()
|
||||
|
||||
@Serializable @SerialName("sndError")
|
||||
class SndError(val agentError: AgentErrorType): CIStatus()
|
||||
|
||||
@Serializable @SerialName("rcvNew")
|
||||
class RcvNew: CIStatus()
|
||||
|
||||
@Serializable @SerialName("rcvRead")
|
||||
class RcvRead: CIStatus()
|
||||
@Serializable @SerialName("sndNew") class SndNew: CIStatus()
|
||||
@Serializable @SerialName("sndSent") class SndSent: CIStatus()
|
||||
@Serializable @SerialName("sndErrorAuth") class SndErrorAuth: CIStatus()
|
||||
@Serializable @SerialName("sndError") class SndError(val agentError: AgentErrorType): CIStatus()
|
||||
@Serializable @SerialName("rcvNew") class RcvNew: CIStatus()
|
||||
@Serializable @SerialName("rcvRead") class RcvRead: CIStatus()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -806,29 +799,22 @@ interface ItemContent {
|
||||
|
||||
@Serializable
|
||||
sealed class CIContent: ItemContent {
|
||||
abstract override val text: String
|
||||
abstract val msgContent: MsgContent?
|
||||
|
||||
@Serializable @SerialName("sndMsgContent")
|
||||
class SndMsgContent(override val msgContent: MsgContent): CIContent() {
|
||||
override val text get() = msgContent.text
|
||||
}
|
||||
@Serializable @SerialName("sndMsgContent") class SndMsgContent(override val msgContent: MsgContent): CIContent()
|
||||
@Serializable @SerialName("rcvMsgContent") class RcvMsgContent(override val msgContent: MsgContent): CIContent()
|
||||
@Serializable @SerialName("sndDeleted") class SndDeleted(val deleteMode: CIDeleteMode): CIContent() { override val msgContent get() = null }
|
||||
@Serializable @SerialName("rcvDeleted") class RcvDeleted(val deleteMode: CIDeleteMode): CIContent() { override val msgContent get() = null }
|
||||
@Serializable @SerialName("sndCall") class SndCall(val status: CICallStatus, val duration: Int): CIContent() { override val msgContent get() = null }
|
||||
@Serializable @SerialName("rcvCall") class RcvCall(val status: CICallStatus, val duration: Int): CIContent() { override val msgContent get() = null }
|
||||
|
||||
@Serializable @SerialName("rcvMsgContent")
|
||||
class RcvMsgContent(override val msgContent: MsgContent): CIContent() {
|
||||
override val text get() = msgContent.text
|
||||
}
|
||||
|
||||
@Serializable @SerialName("sndDeleted")
|
||||
class SndDeleted(val deleteMode: CIDeleteMode): CIContent() {
|
||||
override val text get() = generalGetString(R.string.deleted_description)
|
||||
override val msgContent get() = null
|
||||
}
|
||||
|
||||
@Serializable @SerialName("rcvDeleted")
|
||||
class RcvDeleted(val deleteMode: CIDeleteMode): CIContent() {
|
||||
override val text get() = generalGetString(R.string.deleted_description)
|
||||
override val msgContent get() = null
|
||||
override val text: String get() = when(this) {
|
||||
is SndMsgContent -> msgContent.text
|
||||
is RcvMsgContent -> msgContent.text
|
||||
is SndDeleted -> generalGetString(R.string.deleted_description)
|
||||
is RcvDeleted -> generalGetString(R.string.deleted_description)
|
||||
is SndCall -> status.text(duration)
|
||||
is RcvCall -> status.text(duration)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,20 +890,11 @@ enum class CIFileStatus {
|
||||
sealed class MsgContent {
|
||||
abstract val text: String
|
||||
|
||||
@Serializable(with = MsgContentSerializer::class)
|
||||
class MCText(override val text: String): MsgContent()
|
||||
|
||||
@Serializable(with = MsgContentSerializer::class)
|
||||
class MCLink(override val text: String, val preview: LinkPreview): MsgContent()
|
||||
|
||||
@Serializable(with = MsgContentSerializer::class)
|
||||
class MCImage(override val text: String, val image: String): MsgContent()
|
||||
|
||||
@Serializable(with = MsgContentSerializer::class)
|
||||
class MCFile(override val text: String): MsgContent()
|
||||
|
||||
@Serializable(with = MsgContentSerializer::class)
|
||||
class MCUnknown(val type: String? = null, override val text: String, val json: JsonElement): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCText(override val text: String): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCLink(override val text: String, val preview: LinkPreview): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCImage(override val text: String, val image: String): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCFile(override val text: String): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCUnknown(val type: String? = null, override val text: String, val json: JsonElement): MsgContent()
|
||||
|
||||
val cmdString: String get() = when (this) {
|
||||
is MCText -> "text $text"
|
||||
@@ -1073,3 +1050,28 @@ class SndFileTransfer() {}
|
||||
|
||||
@Serializable
|
||||
class FileTransferMeta() {}
|
||||
|
||||
@Serializable
|
||||
enum class CICallStatus {
|
||||
@SerialName("pending") Pending,
|
||||
@SerialName("missed") Missed,
|
||||
@SerialName("rejected") Rejected,
|
||||
@SerialName("accepted") Accepted,
|
||||
@SerialName("negotiated") Negotiated,
|
||||
@SerialName("progress") Progress,
|
||||
@SerialName("ended") Ended,
|
||||
@SerialName("error") Error;
|
||||
|
||||
fun text(sec: Int): String = when (this) {
|
||||
Pending -> generalGetString(R.string.callstatus_calling)
|
||||
Missed -> generalGetString(R.string.callstatus_missed)
|
||||
Rejected -> generalGetString(R.string.callstatus_rejected)
|
||||
Accepted -> generalGetString(R.string.callstatus_accepted)
|
||||
Negotiated -> generalGetString(R.string.callstatus_connecting)
|
||||
Progress -> generalGetString(R.string.callstatus_in_progress)
|
||||
Ended -> String.format(generalGetString(R.string.callstatus_ended), duration(sec))
|
||||
Error -> generalGetString(R.string.callstatus_error)
|
||||
}
|
||||
|
||||
fun duration(sec: Int): String = "%02d:%02d".format(sec / 60, sec % 60)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
|
||||
val chats = apiGetChats()
|
||||
chatModel.chats.clear()
|
||||
chatModel.chats.addAll(chats)
|
||||
chatModel.currentUser = mutableStateOf(user)
|
||||
chatModel.currentUser.value = user
|
||||
chatModel.userCreated.value = true
|
||||
Log.d(TAG, "started chat")
|
||||
} catch(e: Error) {
|
||||
@@ -713,7 +713,7 @@ class APIResponse(val resp: CR, val corr: String? = null) {
|
||||
json.decodeFromString(str)
|
||||
} catch(e: Exception) {
|
||||
try {
|
||||
Log.d(TAG, e.localizedMessage)
|
||||
Log.d(TAG, e.localizedMessage ?: "")
|
||||
val data = json.parseToJsonElement(str).jsonObject
|
||||
APIResponse(
|
||||
resp = CR.Response(data["resp"]!!.jsonObject["type"]?.toString() ?: "invalid", json.encodeToString(data)),
|
||||
|
||||
@@ -27,7 +27,7 @@ import chat.simplex.app.TAG
|
||||
import chat.simplex.app.views.helpers.TextEditor
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
|
||||
//@SuppressLint("JavascriptInterface")
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Composable
|
||||
fun VideoCallView(close: () -> Unit) {
|
||||
BackHandler(onBack = close)
|
||||
@@ -57,8 +57,7 @@ fun VideoCallView(close: () -> Unit) {
|
||||
}
|
||||
}
|
||||
val localContext = LocalContext.current
|
||||
val iceCandidateCommand = remember { mutableStateOf("") }
|
||||
val commandToShow = remember { mutableStateOf("processCommand({type: 'start', media: 'video', aesKey: 'FwW+t6UbnwHoapYOfN4mUBUuqR7UtvYWxW16iBqM29U='})") }
|
||||
val commandToShow = remember { mutableStateOf("processCommand({command: {type: 'start', media: 'video'}})") } //, aesKey: 'FwW+t6UbnwHoapYOfN4mUBUuqR7UtvYWxW16iBqM29U='})") }
|
||||
val assetLoader = WebViewAssetLoader.Builder()
|
||||
.addPathHandler("/assets/www/", WebViewAssetLoader.AssetsPathHandler(localContext))
|
||||
.build()
|
||||
@@ -96,9 +95,7 @@ fun VideoCallView(close: () -> Unit) {
|
||||
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
|
||||
val rtnValue = super.onConsoleMessage(consoleMessage)
|
||||
val msg = consoleMessage?.message() as String
|
||||
if (msg.startsWith("{\"action\":\"processIceCandidates\"")) {
|
||||
iceCandidateCommand.value = "processCommand($msg)"
|
||||
} else if (msg.startsWith("{")) {
|
||||
if (msg.startsWith("{")) {
|
||||
commandToShow.value = "processCommand($msg)"
|
||||
}
|
||||
return rtnValue
|
||||
@@ -142,15 +139,9 @@ fun VideoCallView(close: () -> Unit) {
|
||||
wv.evaluateJavascript(commandToShow.value, null)
|
||||
commandToShow.value = ""
|
||||
}) {Text("Send")}
|
||||
Button( onClick = {
|
||||
commandToShow.value = iceCandidateCommand.value
|
||||
}) {Text("ICE")}
|
||||
Button( onClick = {
|
||||
commandToShow.value = ""
|
||||
}) {Text("Clear")}
|
||||
Button( onClick = {
|
||||
wv.evaluateJavascript("endCall()", null)
|
||||
}) {Text("End Call")}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,47 @@
|
||||
package chat.simplex.app.views.call
|
||||
|
||||
import chat.simplex.app.model.CR
|
||||
import chat.simplex.app.model.User
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.views.helpers.generalGetString
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
class Call(
|
||||
val contact: Contact,
|
||||
val callState: CallState,
|
||||
val localMedia: CallMediaType,
|
||||
val localCapabilities: CallCapabilities? = null,
|
||||
val peerMedia: CallMediaType? = null,
|
||||
val sharedKey: String? = null,
|
||||
val audioEnabled: Boolean = true,
|
||||
val videoEnabled: Boolean = localMedia == CallMediaType.Video
|
||||
) {
|
||||
val encrypted: Boolean get() = (localCapabilities?.encryption ?: false) && sharedKey != null
|
||||
}
|
||||
|
||||
enum class CallState {
|
||||
WaitCapabilities,
|
||||
InvitationSent,
|
||||
InvitationReceived,
|
||||
OfferSent,
|
||||
OfferReceived,
|
||||
Negotiated,
|
||||
Connected;
|
||||
|
||||
val text: String get() = when(this) {
|
||||
WaitCapabilities -> generalGetString(R.string.callstate_starting)
|
||||
InvitationSent -> generalGetString(R.string.callstate_waiting_for_answer)
|
||||
InvitationReceived -> generalGetString(R.string.callstate_starting)
|
||||
OfferSent -> generalGetString(R.string.callstate_waiting_for_confirmation)
|
||||
OfferReceived -> generalGetString(R.string.callstate_received_answer)
|
||||
Negotiated -> generalGetString(R.string.callstate_connecting)
|
||||
Connected -> generalGetString(R.string.callstate_connected)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable class WVAPICall(val corrId: Int? = null, val command: WCallCommand)
|
||||
@Serializable class WVAPIMessage(val corrId: Int? = null, val resp: WCallResponse, val command: WCallCommand?)
|
||||
|
||||
@Serializable
|
||||
sealed class WCallCommand {
|
||||
@Serializable @SerialName("capabilities") class Capabilities(): WCallCommand()
|
||||
@@ -12,6 +49,7 @@ sealed class WCallCommand {
|
||||
@Serializable @SerialName("accept") class Accept(val offer: String, val iceCandidates: List<String>, val media: CallMediaType, val aesKey: String? = null): WCallCommand()
|
||||
@Serializable @SerialName("answer") class Answer (val answer: String, val iceCandidates: List<String>): WCallCommand()
|
||||
@Serializable @SerialName("ice") class Ice(val iceCandidates: List<String>): WCallCommand()
|
||||
@Serializable @SerialName("media") class Media(val media: CallMediaType, val enable: Boolean): WCallCommand()
|
||||
@Serializable @SerialName("end") class End(): WCallCommand()
|
||||
}
|
||||
|
||||
@@ -30,17 +68,12 @@ sealed class WCallResponse {
|
||||
@Serializable class Invalid(val str: String): WCallResponse()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class WebRTCCallOffer(val callType: CallType, val rtcSession: WebRTCSession)
|
||||
|
||||
@Serializable
|
||||
class WebRTCSession(val rtcSession: String, val rtcIceCandidates: List<String>)
|
||||
|
||||
@Serializable
|
||||
class WebRTCExtraInfo(val rtcIceCandidates: List<String>)
|
||||
|
||||
@Serializable
|
||||
class CallType(val media: CallMediaType, val capabilities: CallCapabilities)
|
||||
@Serializable class WebRTCCallOffer(val callType: CallType, val rtcSession: WebRTCSession)
|
||||
@Serializable class WebRTCSession(val rtcSession: String, val rtcIceCandidates: List<String>)
|
||||
@Serializable class WebRTCExtraInfo(val rtcIceCandidates: List<String>)
|
||||
@Serializable class CallType(val media: CallMediaType, val capabilities: CallCapabilities)
|
||||
@Serializable class CallInvitation(val peerMedia: CallMediaType?, val sharedKey: String?)
|
||||
@Serializable class CallCapabilities(val encryption: Boolean)
|
||||
|
||||
enum class WebRTCCallStatus(val status: String) {
|
||||
Connected("connected"),
|
||||
@@ -54,9 +87,6 @@ enum class CallMediaType(val media: String) {
|
||||
Audio("audio")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class CallCapabilities(val encryption: Boolean)
|
||||
|
||||
@Serializable
|
||||
class ConnectionState(
|
||||
val connectionState: String,
|
||||
|
||||
@@ -62,6 +62,8 @@ fun ChatItemView(
|
||||
}
|
||||
} else if (cItem.isDeletedContent) {
|
||||
DeletedItemView(cItem, showMember = showMember)
|
||||
} else if (cItem.isCall) {
|
||||
FramedItemView(user, cItem, uriHandler, showMember = showMember, showMenu, receiveFile)
|
||||
}
|
||||
if (cItem.isMsgContent) {
|
||||
DropdownMenu(
|
||||
|
||||
@@ -39,7 +39,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
suspend fun openChat(chatModel: ChatModel, cInfo: ChatInfo) {
|
||||
val chat = chatModel.controller.apiGetChat(cInfo.chatType, cInfo.apiId)
|
||||
if (chat != null) {
|
||||
chatModel.chatItems = chat.chatItems.toMutableStateList()
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatItems.addAll(chat.chatItems)
|
||||
chatModel.chatId.value = cInfo.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,4 +275,21 @@
|
||||
<string name="you_can_also_connect_by_clicking_the_link">Вы также можете соединиться, открыв ссылку там, где вы её получили. Если ссылка откроется в браузере, нажмите кнопку <b>Open in mobile app</b>.</string>
|
||||
<string name="add_contact_to_start_new_chat">Добавьте контакт, чтобы начать разговор:</string>
|
||||
|
||||
<!-- CICallStatus -->
|
||||
<string name="callstatus_calling">входящий звонок…</string>
|
||||
<string name="callstatus_missed">пропущенный звонок</string>
|
||||
<string name="callstatus_rejected">отклоненный звонок</string>
|
||||
<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_error">ошибка соединения</string>
|
||||
|
||||
<!-- CallState -->
|
||||
<string name="callstate_starting">инициализация…</string>
|
||||
<string name="callstate_waiting_for_answer">ожидается ответ…</string>
|
||||
<string name="callstate_waiting_for_confirmation">ожидается подтверждение…</string>
|
||||
<string name="callstate_received_answer">получен ответ…</string>
|
||||
<string name="callstate_connecting">соединяется…</string>
|
||||
<string name="callstate_connected">соединено</string>
|
||||
</resources>
|
||||
|
||||
@@ -276,4 +276,21 @@
|
||||
<string name="colored">colored</string>
|
||||
<string name="secret">secret</string>
|
||||
|
||||
<!-- CICallStatus -->
|
||||
<string name="callstatus_calling">calling…</string>
|
||||
<string name="callstatus_missed">missed</string>
|
||||
<string name="callstatus_rejected">rejected</string>
|
||||
<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_error">error</string>
|
||||
|
||||
<!-- CallState -->
|
||||
<string name="callstate_starting">starting…</string>
|
||||
<string name="callstate_waiting_for_answer">waiting for answer…</string>
|
||||
<string name="callstate_waiting_for_confirmation">waiting for confirmation…</string>
|
||||
<string name="callstate_received_answer">received answer…</string>
|
||||
<string name="callstate_connecting">connecting…</string>
|
||||
<string name="callstate_connected">connected</string>
|
||||
</resources>
|
||||
|
||||
@@ -725,7 +725,6 @@ enum MsgContent {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO define Encodable
|
||||
extension MsgContent: Decodable {
|
||||
init(from decoder: Decoder) throws {
|
||||
do {
|
||||
@@ -863,15 +862,14 @@ enum CICallStatus: String, Decodable {
|
||||
|
||||
func text(_ sec: Int) -> String {
|
||||
switch self {
|
||||
case .pending: return "calling..."
|
||||
case .negotiated: return "connecting..."
|
||||
case .progress: return "in progress"
|
||||
case .ended: return "ended \(duration(sec))"
|
||||
default: return self.rawValue
|
||||
case .pending: return NSLocalizedString("calling…", comment: "call status")
|
||||
case .missed: return NSLocalizedString("missed…", comment: "call status")
|
||||
case .rejected: return NSLocalizedString("rejected", comment: "call status")
|
||||
case .accepted: return NSLocalizedString("accepted", comment: "call status")
|
||||
case .negotiated: return NSLocalizedString("connecting…", comment: "call status")
|
||||
case .progress: return NSLocalizedString("in progress", comment: "call status")
|
||||
case .ended: return String.localizedStringWithFormat(NSLocalizedString("ended %02d:%02d", comment: "call status"), sec / 60, sec % 60)
|
||||
case .error: return NSLocalizedString("error", comment: "call status")
|
||||
}
|
||||
}
|
||||
|
||||
func duration(_ sec: Int) -> String {
|
||||
String(format: "%02d:%02d", sec / 60, sec % 60)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,12 +81,12 @@ enum CallState {
|
||||
|
||||
var text: LocalizedStringKey {
|
||||
switch self {
|
||||
case .waitCapabilities: return "starting..."
|
||||
case .invitationSent: return "waiting for answer..."
|
||||
case .invitationReceived: return "starting..."
|
||||
case .offerSent: return "waiting for confirmation..."
|
||||
case .offerReceived: return "received answer..."
|
||||
case .negotiated: return "connecting..."
|
||||
case .waitCapabilities: return "starting…"
|
||||
case .invitationSent: return "waiting for answer…"
|
||||
case .invitationReceived: return "starting…"
|
||||
case .offerSent: return "waiting for confirmation…"
|
||||
case .offerReceived: return "received answer…"
|
||||
case .negotiated: return "connecting…"
|
||||
case .connected: return "connected"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,14 +91,15 @@ struct WebRTCView: UIViewRepresentable {
|
||||
}
|
||||
|
||||
//struct CallViewDebug: View {
|
||||
// @State var coordinator: WebRTCCoordinator? = nil
|
||||
// @State var commandStr = ""
|
||||
// @State private var webViewMsg: WCallResponse? = nil
|
||||
// @State private var coordinator: WebRTCCoordinator? = nil
|
||||
// @State private var commandStr = ""
|
||||
// @State private var webViewReady: Bool = false
|
||||
// @State private var webViewMsg: WVAPIMessage? = nil
|
||||
// @FocusState private var keyboardVisible: Bool
|
||||
//
|
||||
// var body: some View {
|
||||
// VStack(spacing: 30) {
|
||||
// WebRTCView(coordinator: $coordinator, webViewMsg: $webViewMsg).frame(maxHeight: 260)
|
||||
// WebRTCView(coordinator: $coordinator, webViewReady: $webViewReady, webViewMsg: $webViewMsg).frame(maxHeight: 260)
|
||||
// .onChange(of: webViewMsg) { _ in
|
||||
// if let resp = webViewMsg {
|
||||
// commandStr = encodeJSON(resp)
|
||||
@@ -126,13 +127,9 @@ struct WebRTCView: UIViewRepresentable {
|
||||
// commandStr = ""
|
||||
// }
|
||||
// Button("Send") {
|
||||
// do {
|
||||
// if let c = coordinator,
|
||||
// let command: WCallCommand = decodeJSON(commandStr) {
|
||||
// c.sendCommand(command: command)
|
||||
// }
|
||||
// } catch {
|
||||
// print(error)
|
||||
// if let c = coordinator,
|
||||
// let command: WCallCommand = decodeJSON(commandStr) {
|
||||
// c.sendCommand(command: command)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user