diff --git a/CHANGELOG.md b/CHANGELOG.md
index 852976508..eb6c991e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
- 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
+- 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
- 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
diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt
index a3275454c..f6a2377b4 100644
--- a/app/src/main/java/org/linphone/activities/Navigation.kt
+++ b/app/src/main/java/org/linphone/activities/Navigation.kt
@@ -506,13 +506,15 @@ internal fun MasterContactsFragment.clearDisplayedContact() {
}
internal fun ContactEditorFragment.navigateToContact(id: String) {
- val bundle = Bundle()
- bundle.putString("id", id)
- findNavController().navigate(
- R.id.action_contactEditorFragment_to_detailContactFragment,
- bundle,
- popupTo(R.id.contactEditorFragment, true)
- )
+ if (findNavController().currentDestination?.id == R.id.contactEditorFragment) {
+ val bundle = Bundle()
+ bundle.putString("id", id)
+ findNavController().navigate(
+ R.id.action_contactEditorFragment_to_detailContactFragment,
+ bundle,
+ popupTo(R.id.contactEditorFragment, true)
+ )
+ }
}
internal fun DetailContactFragment.navigateToChatRoom(args: Bundle?) {
diff --git a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactEditorViewModel.kt b/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt
similarity index 72%
rename from app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactEditorViewModel.kt
rename to app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt
index 7518aa1bf..f64f07d26 100644
--- a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactEditorViewModel.kt
+++ b/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt
@@ -17,43 +17,32 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.linphone.activities.main.contact.viewmodels
+package org.linphone.activities.main.contact.data
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.ExifInterface
import android.provider.ContactsContract
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewModelScope
import java.io.ByteArrayOutputStream
import java.io.IOException
import kotlinx.coroutines.CoroutineScope
import org.linphone.LinphoneApplication.Companion.coreContext
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.core.ChatRoomSecurityLevel
import org.linphone.core.Friend
import org.linphone.core.tools.Log
+import org.linphone.utils.AppUtils
import org.linphone.utils.ImageUtils
import org.linphone.utils.PermissionHelper
-class ContactEditorViewModelFactory(private val friend: Friend?) :
- ViewModelProvider.NewInstanceFactory() {
-
- @Suppress("UNCHECKED_CAST")
- override fun create(modelClass: Class): T {
- return ContactEditorViewModel(friend) as T
- }
-}
-
-class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface {
+class ContactEditorData(val friend: Friend?) : ContactDataInterface {
override val contact: MutableLiveData = MutableLiveData()
override val displayName: MutableLiveData = MutableLiveData()
override val securityLevel: MutableLiveData = MutableLiveData()
- override val coroutineScope: CoroutineScope = viewModelScope
+ override val coroutineScope: CoroutineScope = coreContext.coroutineScope
val firstName = MutableLiveData()
@@ -74,20 +63,20 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
var syncAccountType: String? = null
init {
- if (c != null) {
- contact.value = c!!
- displayName.value = c.name ?: ""
+ if (friend != null) {
+ contact.value = friend!!
+ displayName.value = friend.name ?: ""
} else {
displayName.value = ""
}
- organization.value = c?.organization ?: ""
+ organization.value = friend?.organization ?: ""
firstName.value = ""
lastName.value = ""
- val vCard = c?.vcard
+ val refKey = friend?.refKey
+ val vCard = friend?.vcard
if (vCard?.familyName.isNullOrEmpty() && vCard?.givenName.isNullOrEmpty()) {
- val refKey = c?.refKey
if (refKey != null) {
Log.w("[Contact Editor] vCard first & last name not filled-in yet, doing it now")
fetchFirstAndLastNames(refKey)
@@ -99,11 +88,11 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
lastName.value = vCard?.familyName
}
- updateNumbersAndAddresses()
+ updateNumbersAndAddresses(refKey)
}
fun save(): Friend {
- var contact = c
+ var contact = friend
var created = false
if (contact == null) {
@@ -144,7 +133,7 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
}
for (address in addresses.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)
if (parsed != null) contact.addAddress(parsed)
@@ -155,7 +144,7 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
}
for (phone in numbers.value.orEmpty()) {
val phoneNumber = phone.newValue.value.orEmpty()
- if (phoneNumber.isEmpty()) continue
+ if (phoneNumber.isEmpty() || phone.toRemove.value == true) continue
contact.addPhoneNumber(phoneNumber)
}
@@ -228,20 +217,71 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
numbers.value = list
}
- private fun updateNumbersAndAddresses() {
+ private fun updateNumbersAndAddresses(contactId: String?) {
val phoneNumbers = arrayListOf()
- for (number in c?.phoneNumbers.orEmpty()) {
- phoneNumbers.add(NumberOrAddressEditorData(number, false))
+ val sipAddresses = arrayListOf()
+ 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))
+ }
+
+ for (address in friend?.addresses.orEmpty()) {
+ sipAddresses.add(NumberOrAddressEditorData(address.asStringUriOnly(), true))
+ }
+ }
+
if (phoneNumbers.isEmpty()) {
phoneNumbers.add(NumberOrAddressEditorData("", false))
}
numbers.value = phoneNumbers
- val sipAddresses = arrayListOf()
- for (address in c?.addresses.orEmpty()) {
- sipAddresses.add(NumberOrAddressEditorData(address.asStringUriOnly(), true))
- }
if (sipAddresses.isEmpty()) {
sipAddresses.add(NumberOrAddressEditorData("", true))
}
@@ -268,14 +308,14 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface
val givenName: String? =
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME))
if (!givenName.isNullOrEmpty()) {
- c?.vcard?.givenName = givenName
+ friend?.vcard?.givenName = givenName
firstName.value = givenName!!
}
val familyName: String? =
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME))
if (!familyName.isNullOrEmpty()) {
- c?.vcard?.familyName = familyName
+ friend?.vcard?.familyName = familyName
lastName.value = familyName!!
}
}
diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt
index 1f2efd3f8..cdce1f3ff 100644
--- a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt
+++ b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt
@@ -27,7 +27,6 @@ import android.os.Parcelable
import android.provider.MediaStore
import android.view.View
import androidx.core.content.FileProvider
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import java.io.File
import kotlinx.coroutines.launch
@@ -35,6 +34,7 @@ import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.activities.GenericFragment
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.viewmodels.*
import org.linphone.activities.navigateToContact
@@ -44,7 +44,7 @@ import org.linphone.utils.FileUtils
import org.linphone.utils.PermissionHelper
class ContactEditorFragment : GenericFragment(), SyncAccountPickerFragment.SyncAccountPickedListener {
- private lateinit var viewModel: ContactEditorViewModel
+ private lateinit var data: ContactEditorData
private var temporaryPicturePath: File? = null
override fun getLayoutId(): Int = R.layout.contact_editor_fragment
@@ -54,11 +54,10 @@ class ContactEditorFragment : GenericFragment(), S
binding.lifecycleOwner = viewLifecycleOwner
- viewModel = ViewModelProvider(
- this,
- ContactEditorViewModelFactory(sharedViewModel.selectedContact.value)
- )[ContactEditorViewModel::class.java]
- binding.viewModel = viewModel
+ val contact = sharedViewModel.selectedContact.value
+ // TODO: FIXME: contact can be const! Find a way to get it not-const!
+ data = ContactEditorData(contact)
+ binding.viewModel = data
useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false
@@ -67,10 +66,10 @@ class ContactEditorFragment : GenericFragment(), S
}
binding.setSaveChangesClickListener {
- viewModel.syncAccountName = null
- viewModel.syncAccountType = null
+ data.syncAccountName = 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")
SyncAccountPickerFragment(this).show(childFragmentManager, "SyncAccountPicker")
} else {
@@ -85,9 +84,9 @@ class ContactEditorFragment : GenericFragment(), S
newSipUri.newValue.value = sipUri
val list = arrayListOf()
- list.addAll(viewModel.addresses.value.orEmpty())
+ list.addAll(data.addresses.value.orEmpty())
list.add(newSipUri)
- viewModel.addresses.value = list
+ data.addresses.value = list
}
if (!PermissionHelper.required(requireContext()).hasWriteContactsPermission()) {
@@ -98,8 +97,8 @@ class ContactEditorFragment : GenericFragment(), S
override fun onSyncAccountClicked(name: String?, type: String?) {
Log.i("[Contact Editor] Using account $name / $type")
- viewModel.syncAccountName = name
- viewModel.syncAccountType = type
+ data.syncAccountName = name
+ data.syncAccountType = type
saveContact()
}
@@ -122,19 +121,19 @@ class ContactEditorFragment : GenericFragment(), S
}
@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) {
lifecycleScope.launch {
- val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath)
+ val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(intent, temporaryPicturePath)
if (contactImageFilePath != null) {
- viewModel.setPictureFromPath(contactImageFilePath)
+ data.setPictureFromPath(contactImageFilePath)
}
}
}
}
private fun saveContact() {
- val savedContact = viewModel.save()
+ val savedContact = data.save()
val id = savedContact.refKey
if (id != null) {
Log.i("[Contact Editor] Displaying contact $savedContact")
diff --git a/app/src/main/res/layout/contact_editor_fragment.xml b/app/src/main/res/layout/contact_editor_fragment.xml
index 76c888b37..235d4c0fc 100644
--- a/app/src/main/res/layout/contact_editor_fragment.xml
+++ b/app/src/main/res/layout/contact_editor_fragment.xml
@@ -12,7 +12,7 @@
type="android.view.View.OnClickListener"/>
+ type="org.linphone.activities.main.contact.data.ContactEditorData" />