mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Added first version of ZRTP SAS confirmation dialog
This commit is contained in:
parent
c4b0bf0ee0
commit
f368114b88
27 changed files with 720 additions and 237 deletions
|
|
@ -11,21 +11,21 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import org.linphone.R
|
||||
import org.linphone.databinding.ContactFavouriteListCellBinding
|
||||
import org.linphone.databinding.ContactListCellBinding
|
||||
import org.linphone.ui.main.contacts.model.ContactModel
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
import org.linphone.utils.Event
|
||||
|
||||
class ContactsListAdapter(
|
||||
private val viewLifecycleOwner: LifecycleOwner,
|
||||
private val favourites: Boolean
|
||||
) : ListAdapter<ContactModel, RecyclerView.ViewHolder>(ContactDiffCallback()) {
|
||||
) : ListAdapter<ContactAvatarModel, RecyclerView.ViewHolder>(ContactDiffCallback()) {
|
||||
var selectedAdapterPosition = -1
|
||||
|
||||
val contactClickedEvent: MutableLiveData<Event<ContactModel>> by lazy {
|
||||
MutableLiveData<Event<ContactModel>>()
|
||||
val contactClickedEvent: MutableLiveData<Event<ContactAvatarModel>> by lazy {
|
||||
MutableLiveData<Event<ContactAvatarModel>>()
|
||||
}
|
||||
|
||||
val contactLongClickedEvent: MutableLiveData<Event<ContactModel>> by lazy {
|
||||
MutableLiveData<Event<ContactModel>>()
|
||||
val contactLongClickedEvent: MutableLiveData<Event<ContactAvatarModel>> by lazy {
|
||||
MutableLiveData<Event<ContactAvatarModel>>()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
|
|
@ -64,7 +64,7 @@ class ContactsListAdapter(
|
|||
inner class ViewHolder(
|
||||
val binding: ContactListCellBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(contactModel: ContactModel) {
|
||||
fun bind(contactModel: ContactAvatarModel) {
|
||||
with(binding) {
|
||||
model = contactModel
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ class ContactsListAdapter(
|
|||
inner class FavouriteViewHolder(
|
||||
val binding: ContactFavouriteListCellBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(contactModel: ContactModel) {
|
||||
fun bind(contactModel: ContactAvatarModel) {
|
||||
with(binding) {
|
||||
model = contactModel
|
||||
|
||||
|
|
@ -116,12 +116,12 @@ class ContactsListAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private class ContactDiffCallback : DiffUtil.ItemCallback<ContactModel>() {
|
||||
override fun areItemsTheSame(oldItem: ContactModel, newItem: ContactModel): Boolean {
|
||||
private class ContactDiffCallback : DiffUtil.ItemCallback<ContactAvatarModel>() {
|
||||
override fun areItemsTheSame(oldItem: ContactAvatarModel, newItem: ContactAvatarModel): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ContactModel, newItem: ContactModel): Boolean {
|
||||
override fun areContentsTheSame(oldItem: ContactAvatarModel, newItem: ContactAvatarModel): Boolean {
|
||||
return oldItem.showFirstLetter.value == newItem.showFirstLetter.value
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import org.linphone.core.Friend
|
|||
import org.linphone.core.FriendListenerStub
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class ContactModel(val friend: Friend) {
|
||||
class ContactAvatarModel(val friend: Friend) {
|
||||
val id = friend.refKey
|
||||
|
||||
val initials = LinphoneUtils.getInitials(friend.name.orEmpty())
|
||||
|
|
@ -31,7 +31,6 @@ class NumberOrAddressPickerDialogModel(viewModel: ContactViewModel) : ViewModel(
|
|||
|
||||
init {
|
||||
sipAddressesAndPhoneNumbers.value = viewModel.sipAddressesAndPhoneNumbers.value
|
||||
// TODO: set listener
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.ViewModel
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.core.Address
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
import org.linphone.ui.main.contacts.model.ContactDeviceModel
|
||||
import org.linphone.ui.main.contacts.model.ContactModel
|
||||
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener
|
||||
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel
|
||||
import org.linphone.utils.Event
|
||||
|
||||
class ContactViewModel : ViewModel() {
|
||||
val contact = MutableLiveData<ContactModel>()
|
||||
val contact = MutableLiveData<ContactAvatarModel>()
|
||||
|
||||
val sipAddressesAndPhoneNumbers = MutableLiveData<ArrayList<ContactNumberOrAddressModel>>()
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ class ContactViewModel : ViewModel() {
|
|||
coreContext.postOnCoreThread { core ->
|
||||
val friend = coreContext.contactsManager.findContactById(refKey)
|
||||
if (friend != null) {
|
||||
contact.postValue(ContactModel(friend))
|
||||
contact.postValue(ContactAvatarModel(friend))
|
||||
val organization = friend.organization
|
||||
if (!organization.isNullOrEmpty()) {
|
||||
company.postValue(organization)
|
||||
|
|
|
|||
|
|
@ -28,13 +28,13 @@ import org.linphone.core.MagicSearch
|
|||
import org.linphone.core.MagicSearchListenerStub
|
||||
import org.linphone.core.SearchResult
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.contacts.model.ContactModel
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
import org.linphone.ui.main.viewmodel.TopBarViewModel
|
||||
|
||||
class ContactsListViewModel : TopBarViewModel() {
|
||||
val contactsList = MutableLiveData<ArrayList<ContactModel>>()
|
||||
val contactsList = MutableLiveData<ArrayList<ContactAvatarModel>>()
|
||||
|
||||
val favourites = MutableLiveData<ArrayList<ContactModel>>()
|
||||
val favourites = MutableLiveData<ArrayList<ContactAvatarModel>>()
|
||||
|
||||
val showFavourites = MutableLiveData<Boolean>()
|
||||
|
||||
|
|
@ -88,10 +88,10 @@ class ContactsListViewModel : TopBarViewModel() {
|
|||
fun processMagicSearchResults(results: Array<SearchResult>) {
|
||||
// Core thread
|
||||
Log.i("[Contacts List] Processing ${results.size} results")
|
||||
contactsList.value.orEmpty().forEach(ContactModel::destroy)
|
||||
contactsList.value.orEmpty().forEach(ContactAvatarModel::destroy)
|
||||
|
||||
val list = arrayListOf<ContactModel>()
|
||||
val favouritesList = arrayListOf<ContactModel>()
|
||||
val list = arrayListOf<ContactAvatarModel>()
|
||||
val favouritesList = arrayListOf<ContactAvatarModel>()
|
||||
var previousLetter = ""
|
||||
|
||||
for (result in results) {
|
||||
|
|
@ -100,13 +100,13 @@ class ContactsListViewModel : TopBarViewModel() {
|
|||
var currentLetter = ""
|
||||
val model = if (friend != null) {
|
||||
currentLetter = friend.name?.get(0).toString()
|
||||
ContactModel(friend)
|
||||
ContactAvatarModel(friend)
|
||||
} else {
|
||||
Log.w("[Contacts] SearchResult [$result] has no Friend!")
|
||||
val fakeFriend =
|
||||
createFriendFromSearchResult(result)
|
||||
currentLetter = fakeFriend.name?.get(0).toString()
|
||||
ContactModel(fakeFriend)
|
||||
ContactAvatarModel(fakeFriend)
|
||||
}
|
||||
|
||||
val displayLetter = previousLetter.isEmpty() || currentLetter != previousLetter
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
package org.linphone.ui.voip.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -27,12 +28,15 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import org.linphone.R
|
||||
import org.linphone.databinding.VoipActiveCallFragmentBinding
|
||||
import org.linphone.ui.main.fragment.GenericFragment
|
||||
import org.linphone.ui.voip.viewmodel.CallViewModel
|
||||
import org.linphone.ui.voip.model.ZrtpSasConfirmationDialogModel
|
||||
import org.linphone.ui.voip.viewmodel.CurrentCallViewModel
|
||||
import org.linphone.utils.DialogUtils
|
||||
import org.linphone.utils.slideInToastFromTop
|
||||
|
||||
class ActiveCallFragment : GenericFragment() {
|
||||
private lateinit var binding: VoipActiveCallFragmentBinding
|
||||
|
||||
private lateinit var callViewModel: CallViewModel
|
||||
private lateinit var callViewModel: CurrentCallViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
|
@ -47,7 +51,7 @@ class ActiveCallFragment : GenericFragment() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
callViewModel = requireActivity().run {
|
||||
ViewModelProvider(this)[CallViewModel::class.java]
|
||||
ViewModelProvider(this)[CurrentCallViewModel::class.java]
|
||||
}
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
|
|
@ -60,7 +64,43 @@ class ActiveCallFragment : GenericFragment() {
|
|||
}*/
|
||||
}
|
||||
|
||||
binding.blueToast.icon.setImageResource(R.drawable.trusted)
|
||||
binding.blueToast.message.text = "This call can be trusted"
|
||||
callViewModel.isRemoteDeviceTrusted.observe(viewLifecycleOwner) { trusted ->
|
||||
if (trusted) {
|
||||
binding.blueToast.message.text = "This call can be trusted"
|
||||
binding.blueToast.icon.setImageResource(R.drawable.trusted)
|
||||
binding.blueToast.root.slideInToastFromTop(binding.root as ViewGroup, true)
|
||||
} else if (binding.blueToast.root.visibility == View.VISIBLE) {
|
||||
binding.blueToast.root.slideInToastFromTop(binding.root as ViewGroup, false)
|
||||
}
|
||||
}
|
||||
|
||||
callViewModel.startCallChronometerEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { duration ->
|
||||
binding.chronometer.base = SystemClock.elapsedRealtime() - (1000 * duration)
|
||||
binding.chronometer.start()
|
||||
}
|
||||
}
|
||||
|
||||
callViewModel.showZrtpSasDialogEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { pair ->
|
||||
val model = ZrtpSasConfirmationDialogModel(pair.first, pair.second)
|
||||
val dialog = DialogUtils.getZrtpSasConfirmationDialog(requireActivity(), model)
|
||||
|
||||
model.dismissEvent.observe(viewLifecycleOwner) { event ->
|
||||
event.consume {
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
model.trustVerified.observe(viewLifecycleOwner) { event ->
|
||||
event.consume { verified ->
|
||||
callViewModel.updateZrtpSas(verified)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,18 +20,19 @@
|
|||
package org.linphone.ui.voip.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.linphone.databinding.VoipIncomingCallFragmentBinding
|
||||
import org.linphone.ui.main.fragment.GenericFragment
|
||||
import org.linphone.ui.voip.viewmodel.CallViewModel
|
||||
import org.linphone.ui.voip.viewmodel.CurrentCallViewModel
|
||||
|
||||
class IncomingCallFragment : GenericFragment() {
|
||||
private lateinit var binding: VoipIncomingCallFragmentBinding
|
||||
|
||||
private lateinit var callViewModel: CallViewModel
|
||||
private lateinit var callViewModel: CurrentCallViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
|
@ -46,10 +47,17 @@ class IncomingCallFragment : GenericFragment() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
callViewModel = requireActivity().run {
|
||||
ViewModelProvider(this)[CallViewModel::class.java]
|
||||
ViewModelProvider(this)[CurrentCallViewModel::class.java]
|
||||
}
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.viewModel = callViewModel
|
||||
|
||||
callViewModel.startCallChronometerEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { duration ->
|
||||
binding.chronometer.base = SystemClock.elapsedRealtime() - (1000 * duration)
|
||||
binding.chronometer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,18 +20,19 @@
|
|||
package org.linphone.ui.voip.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.linphone.databinding.VoipOutgoingCallFragmentBinding
|
||||
import org.linphone.ui.main.fragment.GenericFragment
|
||||
import org.linphone.ui.voip.viewmodel.CallViewModel
|
||||
import org.linphone.ui.voip.viewmodel.CurrentCallViewModel
|
||||
|
||||
class OutgoingCallFragment : GenericFragment() {
|
||||
private lateinit var binding: VoipOutgoingCallFragmentBinding
|
||||
|
||||
private lateinit var callViewModel: CallViewModel
|
||||
private lateinit var callViewModel: CurrentCallViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
|
@ -46,12 +47,17 @@ class OutgoingCallFragment : GenericFragment() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
callViewModel = requireActivity().run {
|
||||
ViewModelProvider(this)[CallViewModel::class.java]
|
||||
ViewModelProvider(this)[CurrentCallViewModel::class.java]
|
||||
}
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.viewModel = callViewModel
|
||||
|
||||
binding.chronometer.start()
|
||||
callViewModel.startCallChronometerEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { duration ->
|
||||
binding.chronometer.base = SystemClock.elapsedRealtime() - (1000 * duration)
|
||||
binding.chronometer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.ui.voip.model
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import java.util.Random
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.Event
|
||||
|
||||
class ZrtpSasConfirmationDialogModel(
|
||||
private val authTokenToRead: String,
|
||||
private val authTokenToListen: String
|
||||
) : ViewModel() {
|
||||
companion object {
|
||||
const val TAG = "[ZRTP SAS Confirmation Dialog]"
|
||||
const val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
}
|
||||
|
||||
val message = MutableLiveData<String>()
|
||||
val letters1 = MutableLiveData<String>()
|
||||
val letters2 = MutableLiveData<String>()
|
||||
val letters3 = MutableLiveData<String>()
|
||||
val letters4 = MutableLiveData<String>()
|
||||
|
||||
val trustVerified = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
val dismissEvent = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
init {
|
||||
message.value = "Dites $authTokenToRead et cliquez sur les lettres données par votre interlocuteur :"
|
||||
|
||||
// TODO: improve algo
|
||||
val rnd = Random()
|
||||
val randomLetters1 = "${alphabet[rnd.nextInt(alphabet.length)]}${alphabet[
|
||||
rnd.nextInt(
|
||||
alphabet.length
|
||||
)
|
||||
]}"
|
||||
val randomLetters2 = "${alphabet[rnd.nextInt(alphabet.length)]}${alphabet[
|
||||
rnd.nextInt(
|
||||
alphabet.length
|
||||
)
|
||||
]}"
|
||||
val randomLetters3 = "${alphabet[rnd.nextInt(alphabet.length)]}${alphabet[
|
||||
rnd.nextInt(
|
||||
alphabet.length
|
||||
)
|
||||
]}"
|
||||
val randomLetters4 = "${alphabet[rnd.nextInt(alphabet.length)]}${alphabet[
|
||||
rnd.nextInt(
|
||||
alphabet.length
|
||||
)
|
||||
]}"
|
||||
|
||||
val correctLetters = rnd.nextInt(4)
|
||||
letters1.value = if (correctLetters == 0) authTokenToListen else randomLetters1
|
||||
letters2.value = if (correctLetters == 1) authTokenToListen else randomLetters2
|
||||
letters3.value = if (correctLetters == 2) authTokenToListen else randomLetters3
|
||||
letters4.value = if (correctLetters == 3) authTokenToListen else randomLetters4
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
dismissEvent.value = Event(true)
|
||||
}
|
||||
|
||||
fun lettersClicked(letters: MutableLiveData<String>) {
|
||||
val verified = letters.value == authTokenToListen
|
||||
Log.i(
|
||||
"$TAG User clicked on ${if (verified) "right" else "wrong"} letters"
|
||||
)
|
||||
trustVerified.value = Event(verified)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.ui.voip.viewmodel
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.contacts.model.ContactModel
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class CallViewModel() : ViewModel() {
|
||||
companion object {
|
||||
const val TAG = "[Call ViewModel]"
|
||||
}
|
||||
|
||||
val contact = MutableLiveData<ContactModel>()
|
||||
|
||||
val displayedName = MutableLiveData<String>()
|
||||
|
||||
val displayedAddress = MutableLiveData<String>()
|
||||
|
||||
val isVideoEnabled = MutableLiveData<Boolean>()
|
||||
|
||||
val isOutgoing = MutableLiveData<Boolean>()
|
||||
|
||||
val isActionsMenuExpanded = MutableLiveData<Boolean>()
|
||||
|
||||
val extraActionsMenuTranslateY = MutableLiveData<Float>()
|
||||
|
||||
private val extraActionsMenuHeight = coreContext.context.resources.getDimension(
|
||||
R.dimen.in_call_extra_actions_menu_height
|
||||
)
|
||||
private val extraButtonsMenuAnimator: ValueAnimator by lazy {
|
||||
ValueAnimator.ofFloat(
|
||||
extraActionsMenuHeight,
|
||||
0f
|
||||
).apply {
|
||||
addUpdateListener {
|
||||
val value = it.animatedValue as Float
|
||||
extraActionsMenuTranslateY.value = value
|
||||
}
|
||||
duration = 500
|
||||
}
|
||||
}
|
||||
|
||||
val toggleExtraActionMenuVisibilityEvent = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
private lateinit var call: Call
|
||||
|
||||
init {
|
||||
isVideoEnabled.value = false
|
||||
isActionsMenuExpanded.value = false
|
||||
extraActionsMenuTranslateY.value = extraActionsMenuHeight
|
||||
|
||||
coreContext.postOnCoreThread { core ->
|
||||
val currentCall = core.currentCall ?: core.calls.firstOrNull()
|
||||
|
||||
if (currentCall != null) {
|
||||
call = currentCall
|
||||
Log.i("$TAG Found call [$call]")
|
||||
|
||||
if (call.state == Call.State.StreamsRunning) {
|
||||
isVideoEnabled.postValue(call.currentParams.isVideoEnabled)
|
||||
} else {
|
||||
isVideoEnabled.postValue(call.params.isVideoEnabled)
|
||||
}
|
||||
isOutgoing.postValue(call.dir == Call.Dir.Outgoing)
|
||||
|
||||
val address = call.remoteAddress
|
||||
address.clean()
|
||||
displayedAddress.postValue(address.asStringUriOnly())
|
||||
|
||||
val friend = core.findFriend(address)
|
||||
if (friend != null) {
|
||||
displayedName.postValue(friend.name)
|
||||
contact.postValue(ContactModel(friend))
|
||||
} else {
|
||||
displayedName.postValue(LinphoneUtils.getDisplayName(address))
|
||||
}
|
||||
} else {
|
||||
Log.e("$TAG Failed to find outgoing call!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun hangUp() {
|
||||
// UI thread
|
||||
coreContext.postOnCoreThread {
|
||||
Log.i("$TAG Terminating call [$call]")
|
||||
call.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleExpandActionsMenu() {
|
||||
// UI thread
|
||||
isActionsMenuExpanded.value = isActionsMenuExpanded.value == false
|
||||
|
||||
if (isActionsMenuExpanded.value == true) {
|
||||
extraButtonsMenuAnimator.start()
|
||||
} else {
|
||||
extraButtonsMenuAnimator.reverse()
|
||||
}
|
||||
// toggleExtraActionMenuVisibilityEvent.value = Event(isActionsMenuExpanded.value == true)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.ui.voip.viewmodel
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import java.util.Locale
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.core.CallListenerStub
|
||||
import org.linphone.core.MediaEncryption
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class CurrentCallViewModel() : ViewModel() {
|
||||
companion object {
|
||||
const val TAG = "[Call ViewModel]"
|
||||
}
|
||||
|
||||
val contact = MutableLiveData<ContactAvatarModel>()
|
||||
|
||||
val displayedName = MutableLiveData<String>()
|
||||
|
||||
val displayedAddress = MutableLiveData<String>()
|
||||
|
||||
val isVideoEnabled = MutableLiveData<Boolean>()
|
||||
|
||||
val isOutgoing = MutableLiveData<Boolean>()
|
||||
|
||||
val isMicrophoneMuted = MutableLiveData<Boolean>()
|
||||
|
||||
val isRemoteDeviceTrusted = MutableLiveData<Boolean>()
|
||||
|
||||
val showZrtpSasDialogEvent: MutableLiveData<Event<Pair<String, String>>> by lazy {
|
||||
MutableLiveData<Event<Pair<String, String>>>()
|
||||
}
|
||||
|
||||
val startCallChronometerEvent = MutableLiveData<Event<Int>>()
|
||||
|
||||
// Extras actions
|
||||
|
||||
val isActionsMenuExpanded = MutableLiveData<Boolean>()
|
||||
|
||||
val extraActionsMenuTranslateY = MutableLiveData<Float>()
|
||||
|
||||
private val extraActionsMenuHeight = coreContext.context.resources.getDimension(
|
||||
R.dimen.in_call_extra_actions_menu_height
|
||||
)
|
||||
private val extraButtonsMenuAnimator: ValueAnimator by lazy {
|
||||
ValueAnimator.ofFloat(
|
||||
extraActionsMenuHeight,
|
||||
0f
|
||||
).apply {
|
||||
addUpdateListener {
|
||||
val value = it.animatedValue as Float
|
||||
extraActionsMenuTranslateY.value = value
|
||||
}
|
||||
duration = 500
|
||||
}
|
||||
}
|
||||
|
||||
val toggleExtraActionMenuVisibilityEvent = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
private lateinit var call: Call
|
||||
|
||||
private val callListener = object : CallListenerStub() {
|
||||
override fun onEncryptionChanged(call: Call, on: Boolean, authenticationToken: String?) {
|
||||
updateEncryption()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
isVideoEnabled.value = false
|
||||
isMicrophoneMuted.value = false
|
||||
isActionsMenuExpanded.value = false
|
||||
extraActionsMenuTranslateY.value = extraActionsMenuHeight
|
||||
|
||||
coreContext.postOnCoreThread { core ->
|
||||
val currentCall = core.currentCall ?: core.calls.firstOrNull()
|
||||
|
||||
if (currentCall != null) {
|
||||
call = currentCall
|
||||
Log.i("$TAG Found call [$call]")
|
||||
configureCall(call)
|
||||
} else {
|
||||
Log.e("$TAG Failed to find outgoing call!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
||||
coreContext.postOnCoreThread {
|
||||
if (::call.isInitialized) {
|
||||
call.removeListener(callListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun hangUp() {
|
||||
// UI thread
|
||||
coreContext.postOnCoreThread {
|
||||
Log.i("$TAG Terminating call [$call]")
|
||||
call.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateZrtpSas(verified: Boolean) {
|
||||
// UI thread
|
||||
coreContext.postOnCoreThread {
|
||||
if (::call.isInitialized) {
|
||||
call.authenticationTokenVerified = verified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleMuteMicrophone() {
|
||||
// UI thread
|
||||
// TODO: check record audio permission
|
||||
coreContext.postOnCoreThread {
|
||||
call.microphoneMuted = !call.microphoneMuted
|
||||
isMicrophoneMuted.postValue(call.microphoneMuted)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeAudioOutputDevice() {
|
||||
// UI thread
|
||||
// TODO: display list of all output devices
|
||||
}
|
||||
|
||||
fun toggleVideo() {
|
||||
// UI thread
|
||||
// TODO: check video permission
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
fun toggleExpandActionsMenu() {
|
||||
// UI thread
|
||||
isActionsMenuExpanded.value = isActionsMenuExpanded.value == false
|
||||
|
||||
if (isActionsMenuExpanded.value == true) {
|
||||
extraButtonsMenuAnimator.start()
|
||||
} else {
|
||||
extraButtonsMenuAnimator.reverse()
|
||||
}
|
||||
// toggleExtraActionMenuVisibilityEvent.value = Event(isActionsMenuExpanded.value == true)
|
||||
}
|
||||
|
||||
fun forceShowZrtpSasDialog() {
|
||||
val authToken = call.authenticationToken
|
||||
if (authToken.orEmpty().isNotEmpty()) {
|
||||
showZrtpSasDialog(authToken!!.uppercase(Locale.getDefault()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showZrtpSasDialog(authToken: String) {
|
||||
val toRead: String
|
||||
val toListen: String
|
||||
when (call.dir) {
|
||||
Call.Dir.Incoming -> {
|
||||
toRead = authToken.substring(0, 2)
|
||||
toListen = authToken.substring(2)
|
||||
}
|
||||
else -> {
|
||||
toRead = authToken.substring(2)
|
||||
toListen = authToken.substring(0, 2)
|
||||
}
|
||||
}
|
||||
showZrtpSasDialogEvent.postValue(Event(Pair(toRead, toListen)))
|
||||
}
|
||||
|
||||
private fun updateEncryption() {
|
||||
// Core thread
|
||||
when (call.currentParams.mediaEncryption) {
|
||||
MediaEncryption.ZRTP -> {
|
||||
val authToken = call.authenticationToken
|
||||
val deviceIsTrusted = call.authenticationTokenVerified && authToken != null
|
||||
Log.i(
|
||||
"$TAG Current call media encryption is ZRTP, auth token is ${if (deviceIsTrusted) "trusted" else "not trusted yet"}"
|
||||
)
|
||||
isRemoteDeviceTrusted.postValue(deviceIsTrusted)
|
||||
|
||||
if (!deviceIsTrusted && authToken.orEmpty().isNotEmpty()) {
|
||||
Log.i("$TAG Showing ZRTP SAS confirmation dialog")
|
||||
showZrtpSasDialog(authToken!!.uppercase(Locale.getDefault()))
|
||||
}
|
||||
}
|
||||
MediaEncryption.SRTP, MediaEncryption.DTLS -> {
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureCall(call: Call) {
|
||||
// Core thread
|
||||
call.addListener(callListener)
|
||||
|
||||
if (LinphoneUtils.isCallOutgoing(call.state)) {
|
||||
isVideoEnabled.postValue(call.params.isVideoEnabled)
|
||||
} else {
|
||||
isVideoEnabled.postValue(call.currentParams.isVideoEnabled)
|
||||
}
|
||||
|
||||
isMicrophoneMuted.postValue(call.microphoneMuted)
|
||||
isOutgoing.postValue(call.dir == Call.Dir.Outgoing)
|
||||
|
||||
val address = call.remoteAddress
|
||||
address.clean()
|
||||
displayedAddress.postValue(address.asStringUriOnly())
|
||||
|
||||
val friend = call.core.findFriend(address)
|
||||
if (friend != null) {
|
||||
displayedName.postValue(friend.name)
|
||||
contact.postValue(ContactAvatarModel(friend))
|
||||
} else {
|
||||
displayedName.postValue(LinphoneUtils.getDisplayName(address))
|
||||
}
|
||||
|
||||
updateEncryption()
|
||||
startCallChronometerEvent.postValue(Event(call.duration))
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,20 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
fun View.slideInToastFromTop(
|
||||
root: ViewGroup,
|
||||
visible: Boolean
|
||||
) {
|
||||
val view = this
|
||||
|
||||
val transition: Transition = Slide(Gravity.TOP)
|
||||
transition.duration = 600
|
||||
transition.addTarget(view)
|
||||
|
||||
TransitionManager.beginDelayedTransition(root, transition)
|
||||
view.visibility = if (visible) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
fun View.slideInToastFromTopForDuration(
|
||||
root: ViewGroup,
|
||||
lifecycleScope: LifecycleCoroutineScope,
|
||||
|
|
@ -55,15 +69,3 @@ fun View.slideInToastFromTopForDuration(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun View.slideInExtraActionsMenu(
|
||||
root: ViewGroup,
|
||||
visibility: Int
|
||||
) {
|
||||
val transition: Transition = Slide(Gravity.BOTTOM)
|
||||
transition.duration = 600
|
||||
transition.addTarget(this)
|
||||
|
||||
TransitionManager.beginDelayedTransition(root, transition)
|
||||
this.translationY = 0f
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ import org.linphone.R
|
|||
import org.linphone.contacts.ContactData
|
||||
import org.linphone.core.ConsolidatedPresence
|
||||
import org.linphone.ui.main.MainActivity
|
||||
import org.linphone.ui.main.contacts.model.ContactModel
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
|
||||
/**
|
||||
* This file contains all the data binding necessary for the app
|
||||
|
|
@ -142,7 +142,7 @@ fun loadContactPictureWithCoil2(imageView: ImageView, contact: ContactData?) {
|
|||
}
|
||||
|
||||
@BindingAdapter("contactAvatar")
|
||||
fun AvatarView.loadContactPicture(contact: ContactModel?) {
|
||||
fun AvatarView.loadContactPicture(contact: ContactAvatarModel?) {
|
||||
if (contact == null) {
|
||||
loadImage(R.drawable.contact_avatar)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -29,8 +29,10 @@ import android.view.WindowManager
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import org.linphone.R
|
||||
import org.linphone.databinding.DialogConfirmZrtpSasBinding
|
||||
import org.linphone.databinding.DialogPickNumberOrAddressBinding
|
||||
import org.linphone.ui.main.contacts.model.NumberOrAddressPickerDialogModel
|
||||
import org.linphone.ui.voip.model.ZrtpSasConfirmationDialogModel
|
||||
|
||||
class DialogUtils {
|
||||
companion object {
|
||||
|
|
@ -50,6 +52,34 @@ class DialogUtils {
|
|||
binding.viewModel = viewModel
|
||||
dialog.setContentView(binding.root)
|
||||
|
||||
val d: Drawable = ColorDrawable(
|
||||
ContextCompat.getColor(dialog.context, R.color.dialog_background)
|
||||
)
|
||||
d.alpha = 100
|
||||
dialog.window
|
||||
?.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
dialog.window?.setBackgroundDrawable(d)
|
||||
return dialog
|
||||
}
|
||||
fun getZrtpSasConfirmationDialog(
|
||||
context: Context,
|
||||
viewModel: ZrtpSasConfirmationDialogModel
|
||||
): Dialog {
|
||||
val dialog = Dialog(context)
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
||||
val binding: DialogConfirmZrtpSasBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(context),
|
||||
R.layout.dialog_confirm_zrtp_sas,
|
||||
null,
|
||||
false
|
||||
)
|
||||
binding.viewModel = viewModel
|
||||
dialog.setContentView(binding.root)
|
||||
|
||||
val d: Drawable = ColorDrawable(
|
||||
ContextCompat.getColor(dialog.context, R.color.dialog_background)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,16 +19,12 @@
|
|||
*/
|
||||
package org.linphone.utils
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import androidx.emoji2.text.EmojiCompat
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.core.Address
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.core.ChatRoom
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.core.tools.Log
|
||||
|
||||
class LinphoneUtils {
|
||||
|
|
@ -74,18 +70,6 @@ class LinphoneUtils {
|
|||
return initials
|
||||
}
|
||||
|
||||
private fun getChatRoomId(localAddress: Address, remoteAddress: Address): String {
|
||||
val localSipUri = localAddress.clone()
|
||||
localSipUri.clean()
|
||||
val remoteSipUri = remoteAddress.clone()
|
||||
remoteSipUri.clean()
|
||||
return "${localSipUri.asStringUriOnly()}~${remoteSipUri.asStringUriOnly()}"
|
||||
}
|
||||
|
||||
fun getChatRoomId(chatRoom: ChatRoom): String {
|
||||
return getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress)
|
||||
}
|
||||
|
||||
fun getDisplayName(address: Address?): String {
|
||||
if (address == null) return "[null]"
|
||||
if (address.displayName == null) {
|
||||
|
|
@ -102,41 +86,23 @@ class LinphoneUtils {
|
|||
return address.displayName ?: address.username ?: address.asString()
|
||||
}
|
||||
|
||||
fun Friend.getPictureUri(thumbnailPreferred: Boolean = false): Uri? {
|
||||
val refKey = refKey
|
||||
if (refKey != null) {
|
||||
try {
|
||||
val lookupUri = ContentUris.withAppendedId(
|
||||
ContactsContract.Contacts.CONTENT_URI,
|
||||
refKey.toLong()
|
||||
)
|
||||
|
||||
if (!thumbnailPreferred) {
|
||||
val pictureUri = Uri.withAppendedPath(
|
||||
lookupUri,
|
||||
ContactsContract.Contacts.Photo.DISPLAY_PHOTO
|
||||
)
|
||||
// Check that the URI points to a real file
|
||||
val contentResolver = coreContext.context.contentResolver
|
||||
try {
|
||||
if (contentResolver.openAssetFileDescriptor(pictureUri, "r") != null) {
|
||||
return pictureUri
|
||||
}
|
||||
} catch (ioe: IOException) { }
|
||||
}
|
||||
|
||||
// Fallback to thumbnail if high res picture isn't available
|
||||
return Uri.withAppendedPath(
|
||||
lookupUri,
|
||||
ContactsContract.Contacts.Photo.CONTENT_DIRECTORY
|
||||
)
|
||||
} catch (e: Exception) { }
|
||||
} else if (photo != null) {
|
||||
try {
|
||||
return Uri.parse(photo)
|
||||
} catch (e: Exception) { }
|
||||
fun isCallOutgoing(callState: Call.State): Boolean {
|
||||
return when (callState) {
|
||||
Call.State.OutgoingInit, Call.State.OutgoingProgress, Call.State.OutgoingRinging, Call.State.OutgoingEarlyMedia -> true
|
||||
else -> false
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getChatRoomId(localAddress: Address, remoteAddress: Address): String {
|
||||
val localSipUri = localAddress.clone()
|
||||
localSipUri.clean()
|
||||
val remoteSipUri = remoteAddress.clone()
|
||||
remoteSipUri.clean()
|
||||
return "${localSipUri.asStringUriOnly()}~${remoteSipUri.asStringUriOnly()}"
|
||||
}
|
||||
|
||||
fun getChatRoomId(chatRoom: ChatRoom): String {
|
||||
return getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
app/src/main/res/drawable/microphone_muted.xml
Normal file
13
app/src/main/res/drawable/microphone_muted.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:name="path"
|
||||
android:pathData="M 10.8 4.9 C 10.8 4.24 11.34 3.7 12 3.7 C 12.66 3.7 13.2 4.24 13.2 4.9 L 13.19 8.81 L 15 10.6 L 15 5 C 15 3.34 13.66 2 12 2 C 10.46 2 9.21 3.16 9.04 4.65 L 10.8 6.41 L 10.8 4.9 Z M 19 11 L 17.3 11 C 17.3 11.58 17.2 12.13 17.03 12.64 L 18.3 13.91 C 18.74 13.03 19 12.04 19 11 Z M 4.41 2.86 L 3 4.27 L 9 10.27 L 9 11 C 9 12.66 10.34 14 12 14 C 12.23 14 12.44 13.97 12.65 13.92 L 14.31 15.58 C 13.6 15.91 12.81 16.1 12 16.1 C 9.24 16.1 6.7 14 6.7 11 L 5 11 C 5 14.41 7.72 17.23 11 17.72 L 11 21 L 13 21 L 13 17.72 C 13.91 17.59 14.77 17.27 15.55 16.82 L 19.75 21.02 L 21.16 19.61 L 4.41 2.86 Z"
|
||||
android:fillColor="#000000"
|
||||
android:strokeWidth="1"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<size android:width="70dp" android:height="70dp" />
|
||||
<solid android:color="@color/white"/>
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<corners android:radius="48dp" />
|
||||
<stroke android:color="@color/red_danger" android:width="1dp" />
|
||||
<solid android:color="@color/white"/>
|
||||
</shape>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="model"
|
||||
type="org.linphone.ui.main.contacts.model.ContactModel" />
|
||||
type="org.linphone.ui.main.contacts.model.ContactAvatarModel" />
|
||||
<variable
|
||||
name="onClickListener"
|
||||
type="View.OnClickListener" />
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<import type="android.graphics.Typeface" />
|
||||
<variable
|
||||
name="model"
|
||||
type="org.linphone.ui.main.contacts.model.ContactModel" />
|
||||
type="org.linphone.ui.main.contacts.model.ContactAvatarModel" />
|
||||
<variable
|
||||
name="onClickListener"
|
||||
type="View.OnClickListener" />
|
||||
|
|
|
|||
194
app/src/main/res/layout/dialog_confirm_zrtp_sas.xml
Normal file
194
app/src/main/res/layout/dialog_confirm_zrtp_sas.xml
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
<import type="android.graphics.Typeface" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.voip.model.ZrtpSasConfirmationDialogModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:onClick="@{() -> viewModel.dismiss()}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/in_call_black"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/dialog_background_shadow"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:src="@drawable/shape_dialog_orange_shadow"
|
||||
app:layout_constraintBottom_toBottomOf="@id/anchor"
|
||||
app:layout_constraintEnd_toEndOf="@id/dialog_background"
|
||||
app:layout_constraintStart_toStartOf="@id/dialog_background"
|
||||
app:layout_constraintTop_toTopOf="@id/dialog_background" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/dialog_background"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:src="@drawable/shape_dialog_background"
|
||||
app:layout_constraintBottom_toBottomOf="@id/anchor"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/title"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style_800"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:paddingTop="25dp"
|
||||
android:text="Vérifiez l'appareil"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintBottom_toTopOf="@id/message"
|
||||
app:layout_constraintStart_toStartOf="@id/dialog_background"
|
||||
app:layout_constraintEnd_toEndOf="@id/dialog_background"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@{viewModel.message}"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/letters_1"
|
||||
app:layout_constraintStart_toStartOf="@id/dialog_background"
|
||||
app:layout_constraintEnd_toEndOf="@id/dialog_background"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.lettersClicked(viewModel.letters1)}"
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/letters_1"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:text="@{viewModel.letters1, default=`RV`}"
|
||||
android:textSize="32sp"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/shape_circle_white_background"
|
||||
android:elevation="5dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/letters_2"
|
||||
app:layout_constraintTop_toBottomOf="@id/message"
|
||||
app:layout_constraintBottom_toTopOf="@id/letters_3"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.lettersClicked(viewModel.letters2)}"
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/letters_2"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:text="@{viewModel.letters2, default=`PT`}"
|
||||
android:textSize="32sp"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/shape_circle_white_background"
|
||||
android:elevation="5dp"
|
||||
app:layout_constraintStart_toEndOf="@id/letters_1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/letters_1"
|
||||
app:layout_constraintBottom_toBottomOf="@id/letters_1"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.lettersClicked(viewModel.letters3)}"
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/letters_3"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:text="@{viewModel.letters3, default=`BB`}"
|
||||
android:textSize="32sp"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/shape_circle_white_background"
|
||||
android:elevation="5dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/letters_4"
|
||||
app:layout_constraintTop_toBottomOf="@id/letters_1"
|
||||
app:layout_constraintBottom_toTopOf="@id/skip"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.lettersClicked(viewModel.letters4)}"
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/letters_4"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:text="@{viewModel.letters4, default=`NM`}"
|
||||
android:textSize="32sp"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/shape_circle_white_background"
|
||||
android:elevation="5dp"
|
||||
app:layout_constraintStart_toEndOf="@id/letters_3"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/letters_3"
|
||||
app:layout_constraintBottom_toBottomOf="@id/letters_3"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.dismiss()}"
|
||||
style="@style/default_text_style_600"
|
||||
android:id="@+id/skip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="45dp"
|
||||
android:text="Skip"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/letters_3"
|
||||
app:layout_constraintBottom_toTopOf="@id/nothing_matches"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:onClick="@{() -> viewModel.dismiss()}"
|
||||
style="@style/default_text_style_600"
|
||||
android:id="@+id/nothing_matches"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="45dp"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:paddingBottom="13dp"
|
||||
android:paddingTop="13dp"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/shape_dialog_red_button_background"
|
||||
android:text="Letters don't match!"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/red_danger"
|
||||
app:layout_constraintStart_toStartOf="@id/dialog_background"
|
||||
app:layout_constraintEnd_toEndOf="@id/dialog_background"
|
||||
app:layout_constraintTop_toBottomOf="@id/skip"
|
||||
app:layout_constraintBottom_toTopOf="@id/anchor"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/anchor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/nothing_matches"
|
||||
app:layout_constraintStart_toStartOf="@id/dialog_background"
|
||||
app:layout_constraintEnd_toEndOf="@id/dialog_background"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.voip.viewmodel.CallViewModel" />
|
||||
type="org.linphone.ui.voip.viewmodel.CurrentCallViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
@ -92,7 +92,9 @@
|
|||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<include
|
||||
android:onClick="@{() -> viewModel.forceShowZrtpSasDialog()}"
|
||||
android:id="@+id/blue_toast"
|
||||
android:visibility="gone"
|
||||
layout="@layout/toast_blue"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.voip.viewmodel.CallViewModel" />
|
||||
type="org.linphone.ui.voip.viewmodel.CurrentCallViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.voip.viewmodel.CallViewModel" />
|
||||
type="org.linphone.ui.voip.viewmodel.CurrentCallViewModel" />
|
||||
<variable
|
||||
name="showExpandToggle"
|
||||
type="Boolean" />
|
||||
|
|
@ -48,6 +48,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:onClick="@{() -> viewModel.toggleVideo()}"
|
||||
android:id="@+id/toggle_video"
|
||||
android:enabled="@{viewModel.isVideoEnabled}"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -60,12 +61,13 @@
|
|||
app:layout_constraintEnd_toStartOf="@id/toggle_mute_mic" />
|
||||
|
||||
<ImageView
|
||||
android:onClick="@{() -> viewModel.toggleMuteMicrophone()}"
|
||||
android:id="@+id/toggle_mute_mic"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:padding="15dp"
|
||||
android:src="@drawable/microphone"
|
||||
android:src="@{viewModel.isMicrophoneMuted ? @drawable/microphone_muted : @drawable/microphone, default=@drawable/microphone}"
|
||||
android:background="@drawable/in_call_button_background"
|
||||
app:tint="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.voip.viewmodel.CallViewModel" />
|
||||
type="org.linphone.ui.voip.viewmodel.CurrentCallViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.voip.viewmodel.CallViewModel" />
|
||||
type="org.linphone.ui.voip.viewmodel.CurrentCallViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.voip.viewmodel.CallViewModel" />
|
||||
type="org.linphone.ui.voip.viewmodel.CurrentCallViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue