Fixed issue with contact editor since we switched to Friend as original value from addressbook will be lost in Friend causing edition/removal in native addressbook to silently fail
This commit is contained in:
parent
11f36dcb63
commit
ee40995f34
5 changed files with 103 additions and 61 deletions
|
@ -43,6 +43,7 @@ Group changes to describe their impact on the project, as follows:
|
||||||
- Show service notification sooner to prevent crash if Core creation takes too long
|
- Show service notification sooner to prevent crash if Core creation takes too long
|
||||||
- Incoming call screen not being showed up to user (& screen staying off) when using app in Samsung secure folder
|
- Incoming call screen not being showed up to user (& screen staying off) when using app in Samsung secure folder
|
||||||
- One to one chat room creation process waiting indefinitely if chat room already exists
|
- One to one chat room creation process waiting indefinitely if chat room already exists
|
||||||
|
- Contact edition (SIP addresses & phone numbers) not working due to original value being lost in Friend parsing
|
||||||
- "Blinking" in some views when presence is being received
|
- "Blinking" in some views when presence is being received
|
||||||
- Trying to keep the preferred driver (OpenSLES / AAudio) when switching device
|
- Trying to keep the preferred driver (OpenSLES / AAudio) when switching device
|
||||||
- Issues when storing presence in native contacts + potentially duplicated SIP addresses in contact details
|
- Issues when storing presence in native contacts + potentially duplicated SIP addresses in contact details
|
||||||
|
|
|
@ -506,6 +506,7 @@ internal fun MasterContactsFragment.clearDisplayedContact() {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ContactEditorFragment.navigateToContact(id: String) {
|
internal fun ContactEditorFragment.navigateToContact(id: String) {
|
||||||
|
if (findNavController().currentDestination?.id == R.id.contactEditorFragment) {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putString("id", id)
|
bundle.putString("id", id)
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
|
@ -513,6 +514,7 @@ internal fun ContactEditorFragment.navigateToContact(id: String) {
|
||||||
bundle,
|
bundle,
|
||||||
popupTo(R.id.contactEditorFragment, true)
|
popupTo(R.id.contactEditorFragment, true)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun DetailContactFragment.navigateToChatRoom(args: Bundle?) {
|
internal fun DetailContactFragment.navigateToChatRoom(args: Bundle?) {
|
||||||
|
|
|
@ -17,43 +17,32 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package org.linphone.activities.main.contact.viewmodels
|
package org.linphone.activities.main.contact.data
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.media.ExifInterface
|
import android.media.ExifInterface
|
||||||
import android.provider.ContactsContract
|
import android.provider.ContactsContract
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||||
import org.linphone.activities.main.contact.data.NumberOrAddressEditorData
|
import org.linphone.R
|
||||||
import org.linphone.contact.*
|
import org.linphone.contact.*
|
||||||
import org.linphone.core.ChatRoomSecurityLevel
|
import org.linphone.core.ChatRoomSecurityLevel
|
||||||
import org.linphone.core.Friend
|
import org.linphone.core.Friend
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
|
import org.linphone.utils.AppUtils
|
||||||
import org.linphone.utils.ImageUtils
|
import org.linphone.utils.ImageUtils
|
||||||
import org.linphone.utils.PermissionHelper
|
import org.linphone.utils.PermissionHelper
|
||||||
|
|
||||||
class ContactEditorViewModelFactory(private val friend: Friend?) :
|
class ContactEditorData(val friend: Friend?) : ContactDataInterface {
|
||||||
ViewModelProvider.NewInstanceFactory() {
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
|
||||||
return ContactEditorViewModel(friend) as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface {
|
|
||||||
override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
|
override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
|
||||||
override val displayName: MutableLiveData<String> = MutableLiveData<String>()
|
override val displayName: MutableLiveData<String> = MutableLiveData<String>()
|
||||||
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
|
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
|
||||||
override val coroutineScope: CoroutineScope = viewModelScope
|
override val coroutineScope: CoroutineScope = coreContext.coroutineScope
|
||||||
|
|
||||||
val firstName = MutableLiveData<String>()
|
val firstName = MutableLiveData<String>()
|
||||||
|
|
||||||
|
@ -74,20 +63,20 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
|
||||||
var syncAccountType: String? = null
|
var syncAccountType: String? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (c != null) {
|
if (friend != null) {
|
||||||
contact.value = c!!
|
contact.value = friend!!
|
||||||
displayName.value = c.name ?: ""
|
displayName.value = friend.name ?: ""
|
||||||
} else {
|
} else {
|
||||||
displayName.value = ""
|
displayName.value = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
organization.value = c?.organization ?: ""
|
organization.value = friend?.organization ?: ""
|
||||||
|
|
||||||
firstName.value = ""
|
firstName.value = ""
|
||||||
lastName.value = ""
|
lastName.value = ""
|
||||||
val vCard = c?.vcard
|
val refKey = friend?.refKey
|
||||||
|
val vCard = friend?.vcard
|
||||||
if (vCard?.familyName.isNullOrEmpty() && vCard?.givenName.isNullOrEmpty()) {
|
if (vCard?.familyName.isNullOrEmpty() && vCard?.givenName.isNullOrEmpty()) {
|
||||||
val refKey = c?.refKey
|
|
||||||
if (refKey != null) {
|
if (refKey != null) {
|
||||||
Log.w("[Contact Editor] vCard first & last name not filled-in yet, doing it now")
|
Log.w("[Contact Editor] vCard first & last name not filled-in yet, doing it now")
|
||||||
fetchFirstAndLastNames(refKey)
|
fetchFirstAndLastNames(refKey)
|
||||||
|
@ -99,11 +88,11 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
|
||||||
lastName.value = vCard?.familyName
|
lastName.value = vCard?.familyName
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNumbersAndAddresses()
|
updateNumbersAndAddresses(refKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save(): Friend {
|
fun save(): Friend {
|
||||||
var contact = c
|
var contact = friend
|
||||||
var created = false
|
var created = false
|
||||||
|
|
||||||
if (contact == null) {
|
if (contact == null) {
|
||||||
|
@ -144,7 +133,7 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
|
||||||
}
|
}
|
||||||
for (address in addresses.value.orEmpty()) {
|
for (address in addresses.value.orEmpty()) {
|
||||||
val sipAddress = address.newValue.value.orEmpty()
|
val sipAddress = address.newValue.value.orEmpty()
|
||||||
if (sipAddress.isEmpty()) continue
|
if (sipAddress.isEmpty() || address.toRemove.value == true) continue
|
||||||
|
|
||||||
val parsed = coreContext.core.interpretUrl(sipAddress, false)
|
val parsed = coreContext.core.interpretUrl(sipAddress, false)
|
||||||
if (parsed != null) contact.addAddress(parsed)
|
if (parsed != null) contact.addAddress(parsed)
|
||||||
|
@ -155,7 +144,7 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
|
||||||
}
|
}
|
||||||
for (phone in numbers.value.orEmpty()) {
|
for (phone in numbers.value.orEmpty()) {
|
||||||
val phoneNumber = phone.newValue.value.orEmpty()
|
val phoneNumber = phone.newValue.value.orEmpty()
|
||||||
if (phoneNumber.isEmpty()) continue
|
if (phoneNumber.isEmpty() || phone.toRemove.value == true) continue
|
||||||
|
|
||||||
contact.addPhoneNumber(phoneNumber)
|
contact.addPhoneNumber(phoneNumber)
|
||||||
}
|
}
|
||||||
|
@ -228,20 +217,71 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
|
||||||
numbers.value = list
|
numbers.value = list
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNumbersAndAddresses() {
|
private fun updateNumbersAndAddresses(contactId: String?) {
|
||||||
val phoneNumbers = arrayListOf<NumberOrAddressEditorData>()
|
val phoneNumbers = arrayListOf<NumberOrAddressEditorData>()
|
||||||
for (number in c?.phoneNumbers.orEmpty()) {
|
val sipAddresses = arrayListOf<NumberOrAddressEditorData>()
|
||||||
|
var fetched = false
|
||||||
|
|
||||||
|
if (contactId != null) {
|
||||||
|
try {
|
||||||
|
// Try to get real values from contact to ensure edition/removal in native address book will go well
|
||||||
|
val cursor = coreContext.context.contentResolver.query(
|
||||||
|
ContactsContract.Data.CONTENT_URI,
|
||||||
|
arrayOf(
|
||||||
|
ContactsContract.Data.MIMETYPE,
|
||||||
|
ContactsContract.CommonDataKinds.Phone.NUMBER
|
||||||
|
),
|
||||||
|
ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?",
|
||||||
|
arrayOf(contactId),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
val linphoneMime = AppUtils.getString(R.string.linphone_address_mime_type)
|
||||||
|
val mime: String? =
|
||||||
|
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE))
|
||||||
|
if (mime == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) {
|
||||||
|
val data1: String? =
|
||||||
|
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER))
|
||||||
|
if (data1 != null) {
|
||||||
|
phoneNumbers.add(NumberOrAddressEditorData(data1, false))
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
mime == ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE ||
|
||||||
|
mime == linphoneMime
|
||||||
|
) {
|
||||||
|
val data1: String? =
|
||||||
|
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS))
|
||||||
|
if (data1 != null) {
|
||||||
|
sipAddresses.add(NumberOrAddressEditorData(data1, true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor?.close()
|
||||||
|
fetched = true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("[Contact Editor] Failed to sip addresses & phone number: $e")
|
||||||
|
fetched = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fetched) {
|
||||||
|
Log.w("[Contact Editor] Fall-backing to friend info (might be inaccurate and thus edition/removal might fail)")
|
||||||
|
for (number in friend?.phoneNumbers.orEmpty()) {
|
||||||
phoneNumbers.add(NumberOrAddressEditorData(number, false))
|
phoneNumbers.add(NumberOrAddressEditorData(number, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (address in friend?.addresses.orEmpty()) {
|
||||||
|
sipAddresses.add(NumberOrAddressEditorData(address.asStringUriOnly(), true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (phoneNumbers.isEmpty()) {
|
if (phoneNumbers.isEmpty()) {
|
||||||
phoneNumbers.add(NumberOrAddressEditorData("", false))
|
phoneNumbers.add(NumberOrAddressEditorData("", false))
|
||||||
}
|
}
|
||||||
numbers.value = phoneNumbers
|
numbers.value = phoneNumbers
|
||||||
|
|
||||||
val sipAddresses = arrayListOf<NumberOrAddressEditorData>()
|
|
||||||
for (address in c?.addresses.orEmpty()) {
|
|
||||||
sipAddresses.add(NumberOrAddressEditorData(address.asStringUriOnly(), true))
|
|
||||||
}
|
|
||||||
if (sipAddresses.isEmpty()) {
|
if (sipAddresses.isEmpty()) {
|
||||||
sipAddresses.add(NumberOrAddressEditorData("", true))
|
sipAddresses.add(NumberOrAddressEditorData("", true))
|
||||||
}
|
}
|
||||||
|
@ -268,14 +308,14 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
|
||||||
val givenName: String? =
|
val givenName: String? =
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME))
|
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME))
|
||||||
if (!givenName.isNullOrEmpty()) {
|
if (!givenName.isNullOrEmpty()) {
|
||||||
c?.vcard?.givenName = givenName
|
friend?.vcard?.givenName = givenName
|
||||||
firstName.value = givenName!!
|
firstName.value = givenName!!
|
||||||
}
|
}
|
||||||
|
|
||||||
val familyName: String? =
|
val familyName: String? =
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME))
|
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME))
|
||||||
if (!familyName.isNullOrEmpty()) {
|
if (!familyName.isNullOrEmpty()) {
|
||||||
c?.vcard?.familyName = familyName
|
friend?.vcard?.familyName = familyName
|
||||||
lastName.value = familyName!!
|
lastName.value = familyName!!
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,7 +27,6 @@ import android.os.Parcelable
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -35,6 +34,7 @@ import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||||
import org.linphone.R
|
import org.linphone.R
|
||||||
import org.linphone.activities.GenericFragment
|
import org.linphone.activities.GenericFragment
|
||||||
import org.linphone.activities.main.MainActivity
|
import org.linphone.activities.main.MainActivity
|
||||||
|
import org.linphone.activities.main.contact.data.ContactEditorData
|
||||||
import org.linphone.activities.main.contact.data.NumberOrAddressEditorData
|
import org.linphone.activities.main.contact.data.NumberOrAddressEditorData
|
||||||
import org.linphone.activities.main.contact.viewmodels.*
|
import org.linphone.activities.main.contact.viewmodels.*
|
||||||
import org.linphone.activities.navigateToContact
|
import org.linphone.activities.navigateToContact
|
||||||
|
@ -44,7 +44,7 @@ import org.linphone.utils.FileUtils
|
||||||
import org.linphone.utils.PermissionHelper
|
import org.linphone.utils.PermissionHelper
|
||||||
|
|
||||||
class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), SyncAccountPickerFragment.SyncAccountPickedListener {
|
class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), SyncAccountPickerFragment.SyncAccountPickedListener {
|
||||||
private lateinit var viewModel: ContactEditorViewModel
|
private lateinit var data: ContactEditorData
|
||||||
private var temporaryPicturePath: File? = null
|
private var temporaryPicturePath: File? = null
|
||||||
|
|
||||||
override fun getLayoutId(): Int = R.layout.contact_editor_fragment
|
override fun getLayoutId(): Int = R.layout.contact_editor_fragment
|
||||||
|
@ -54,11 +54,10 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
viewModel = ViewModelProvider(
|
val contact = sharedViewModel.selectedContact.value
|
||||||
this,
|
// TODO: FIXME: contact can be const! Find a way to get it not-const!
|
||||||
ContactEditorViewModelFactory(sharedViewModel.selectedContact.value)
|
data = ContactEditorData(contact)
|
||||||
)[ContactEditorViewModel::class.java]
|
binding.viewModel = data
|
||||||
binding.viewModel = viewModel
|
|
||||||
|
|
||||||
useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false
|
useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false
|
||||||
|
|
||||||
|
@ -67,10 +66,10 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.setSaveChangesClickListener {
|
binding.setSaveChangesClickListener {
|
||||||
viewModel.syncAccountName = null
|
data.syncAccountName = null
|
||||||
viewModel.syncAccountType = null
|
data.syncAccountType = null
|
||||||
|
|
||||||
if (viewModel.c == null && corePreferences.showNewContactAccountDialog) {
|
if (data.friend == null && corePreferences.showNewContactAccountDialog) {
|
||||||
Log.i("[Contact Editor] New contact, ask user where to store it")
|
Log.i("[Contact Editor] New contact, ask user where to store it")
|
||||||
SyncAccountPickerFragment(this).show(childFragmentManager, "SyncAccountPicker")
|
SyncAccountPickerFragment(this).show(childFragmentManager, "SyncAccountPicker")
|
||||||
} else {
|
} else {
|
||||||
|
@ -85,9 +84,9 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
|
||||||
newSipUri.newValue.value = sipUri
|
newSipUri.newValue.value = sipUri
|
||||||
|
|
||||||
val list = arrayListOf<NumberOrAddressEditorData>()
|
val list = arrayListOf<NumberOrAddressEditorData>()
|
||||||
list.addAll(viewModel.addresses.value.orEmpty())
|
list.addAll(data.addresses.value.orEmpty())
|
||||||
list.add(newSipUri)
|
list.add(newSipUri)
|
||||||
viewModel.addresses.value = list
|
data.addresses.value = list
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PermissionHelper.required(requireContext()).hasWriteContactsPermission()) {
|
if (!PermissionHelper.required(requireContext()).hasWriteContactsPermission()) {
|
||||||
|
@ -98,8 +97,8 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
|
||||||
|
|
||||||
override fun onSyncAccountClicked(name: String?, type: String?) {
|
override fun onSyncAccountClicked(name: String?, type: String?) {
|
||||||
Log.i("[Contact Editor] Using account $name / $type")
|
Log.i("[Contact Editor] Using account $name / $type")
|
||||||
viewModel.syncAccountName = name
|
data.syncAccountName = name
|
||||||
viewModel.syncAccountType = type
|
data.syncAccountType = type
|
||||||
saveContact()
|
saveContact()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,19 +121,19 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath)
|
val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(intent, temporaryPicturePath)
|
||||||
if (contactImageFilePath != null) {
|
if (contactImageFilePath != null) {
|
||||||
viewModel.setPictureFromPath(contactImageFilePath)
|
data.setPictureFromPath(contactImageFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveContact() {
|
private fun saveContact() {
|
||||||
val savedContact = viewModel.save()
|
val savedContact = data.save()
|
||||||
val id = savedContact.refKey
|
val id = savedContact.refKey
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
Log.i("[Contact Editor] Displaying contact $savedContact")
|
Log.i("[Contact Editor] Displaying contact $savedContact")
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
type="android.view.View.OnClickListener"/>
|
type="android.view.View.OnClickListener"/>
|
||||||
<variable
|
<variable
|
||||||
name="viewModel"
|
name="viewModel"
|
||||||
type="org.linphone.activities.main.contact.viewmodels.ContactEditorViewModel" />
|
type="org.linphone.activities.main.contact.data.ContactEditorData" />
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
|
|
Loading…
Reference in a new issue