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