Fixed focus issue in contact editor

This commit is contained in:
Sylvain Berfini 2023-08-18 12:10:55 +02:00
parent 4a98610b67
commit 715e6dc8be
8 changed files with 174 additions and 78 deletions

View file

@ -19,20 +19,26 @@
*/
package org.linphone.ui.main.contacts.fragment
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.navigation.navGraphViewModels
import kotlinx.coroutines.launch
import org.linphone.BR
import org.linphone.R
import org.linphone.core.tools.Log
import org.linphone.databinding.ContactNewOrEditFragmentBinding
import org.linphone.ui.main.MainActivity
import org.linphone.ui.main.contacts.model.NewOrEditNumberOrAddressModel
import org.linphone.ui.main.contacts.viewmodel.ContactNewOrEditViewModel
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.FileUtils
@ -114,10 +120,57 @@ class EditContactFragment : GenericFragment() {
}
viewModel.friendFoundEvent.observe(viewLifecycleOwner) {
it.consume {
startPostponedEnterTransition()
it.consume { found ->
if (found) {
binding.sipAddresses.removeAllViews()
for (items in viewModel.sipAddresses) {
addCell(items)
}
binding.phoneNumbers.removeAllViews()
for (items in viewModel.phoneNumbers) {
addCell(items)
}
startPostponedEnterTransition()
}
}
}
viewModel.addNewNumberOrAddressFieldEvent.observe(viewLifecycleOwner) {
it.consume { model ->
addCell(model)
}
}
viewModel.removeNewNumberOrAddressFieldEvent.observe(viewLifecycleOwner) {
it.consume { model ->
removeCell(model)
}
}
}
private fun addCell(model: NewOrEditNumberOrAddressModel) {
val inflater = requireContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val parent = if (model.isSip) binding.sipAddresses else binding.phoneNumbers
val cellBinding = DataBindingUtil.inflate<ViewDataBinding>(
inflater,
R.layout.contact_new_or_edit_cell,
parent,
false
)
cellBinding.setVariable(BR.model, model)
cellBinding.lifecycleOwner = (requireActivity() as MainActivity)
parent.addView(cellBinding.root)
}
private fun removeCell(model: NewOrEditNumberOrAddressModel) {
val parent = if (model.isSip) binding.sipAddresses else binding.phoneNumbers
parent.removeAllViews()
val source = if (model.isSip) viewModel.sipAddresses else viewModel.phoneNumbers
for (items in source) {
addCell(items)
}
}
private fun pickImage() {

View file

@ -19,19 +19,25 @@
*/
package org.linphone.ui.main.contacts.fragment
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
import kotlinx.coroutines.launch
import org.linphone.BR
import org.linphone.R
import org.linphone.core.tools.Log
import org.linphone.databinding.ContactNewOrEditFragmentBinding
import org.linphone.ui.main.MainActivity
import org.linphone.ui.main.contacts.model.NewOrEditNumberOrAddressModel
import org.linphone.ui.main.contacts.viewmodel.ContactNewOrEditViewModel
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.Event
@ -106,6 +112,56 @@ class NewContactFragment : GenericFragment() {
}
}
}
viewModel.friendFoundEvent.observe(viewLifecycleOwner) {
it.consume {
binding.sipAddresses.removeAllViews()
for (items in viewModel.sipAddresses) {
addCell(items)
}
binding.phoneNumbers.removeAllViews()
for (items in viewModel.phoneNumbers) {
addCell(items)
}
}
}
viewModel.addNewNumberOrAddressFieldEvent.observe(viewLifecycleOwner) {
it.consume { model ->
addCell(model)
}
}
viewModel.removeNewNumberOrAddressFieldEvent.observe(viewLifecycleOwner) {
it.consume { model ->
removeCell(model)
}
}
}
private fun addCell(model: NewOrEditNumberOrAddressModel) {
val inflater = requireContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val parent = if (model.isSip) binding.sipAddresses else binding.phoneNumbers
val cellBinding = DataBindingUtil.inflate<ViewDataBinding>(
inflater,
R.layout.contact_new_or_edit_cell,
parent,
false
)
cellBinding.setVariable(BR.model, model)
cellBinding.lifecycleOwner = (requireActivity() as MainActivity)
parent.addView(cellBinding.root)
}
private fun removeCell(model: NewOrEditNumberOrAddressModel) {
val parent = if (model.isSip) binding.sipAddresses else binding.phoneNumbers
parent.removeAllViews()
val source = if (model.isSip) viewModel.sipAddresses else viewModel.phoneNumbers
for (items in source) {
addCell(items)
}
}
private fun pickImage() {

View file

@ -43,9 +43,9 @@ class ContactNewOrEditViewModel() : ViewModel() {
val lastName = MutableLiveData<String>()
val sipAddresses = MutableLiveData<ArrayList<NewOrEditNumberOrAddressModel>>()
val sipAddresses = ArrayList<NewOrEditNumberOrAddressModel>()
val phoneNumbers = MutableLiveData<ArrayList<NewOrEditNumberOrAddressModel>>()
val phoneNumbers = ArrayList<NewOrEditNumberOrAddressModel>()
val company = MutableLiveData<String>()
@ -57,6 +57,10 @@ class ContactNewOrEditViewModel() : ViewModel() {
val friendFoundEvent = MutableLiveData<Event<Boolean>>()
val addNewNumberOrAddressFieldEvent = MutableLiveData<Event<NewOrEditNumberOrAddressModel>>()
val removeNewNumberOrAddressFieldEvent = MutableLiveData<Event<NewOrEditNumberOrAddressModel>>()
fun findFriendByRefKey(refKey: String?) {
// UI thread
coreContext.postOnCoreThread { core ->
@ -68,9 +72,6 @@ class ContactNewOrEditViewModel() : ViewModel() {
val exists = !friend.refKey.isNullOrEmpty()
isEdit.postValue(exists)
val addresses = arrayListOf<NewOrEditNumberOrAddressModel>()
val numbers = arrayListOf<NewOrEditNumberOrAddressModel>()
if (exists) {
Log.i("$TAG Found friend [$friend] using ref key [$refKey]")
val vCard = friend.vcard
@ -84,45 +85,22 @@ class ContactNewOrEditViewModel() : ViewModel() {
picturePath.postValue(friend.photo)
for (address in friend.addresses) {
addresses.add(
NewOrEditNumberOrAddressModel(address.asStringUriOnly(), true, { }, { model ->
removeModel(model)
})
)
addSipAddress(address.asStringUriOnly())
}
for (number in friend.phoneNumbers) {
numbers.add(
NewOrEditNumberOrAddressModel(number, false, { }, { model ->
removeModel(model)
})
)
addPhoneNumber(number)
}
company.postValue(friend.organization)
jobTitle.postValue(friend.jobTitle)
friendFoundEvent.postValue(Event(true))
} else if (refKey.orEmpty().isNotEmpty()) {
Log.e("$TAG No friend found using ref key [$refKey]")
}
addresses.add(
NewOrEditNumberOrAddressModel("", true, {
addNewModel(true)
}, { model ->
removeModel(model)
})
)
numbers.add(
NewOrEditNumberOrAddressModel("", false, {
addNewModel(false)
}, { model ->
removeModel(model)
})
)
addSipAddress()
addPhoneNumber()
sipAddresses.postValue(addresses)
phoneNumbers.postValue(numbers)
friendFoundEvent.postValue(Event(exists))
}
}
@ -159,7 +137,7 @@ class ContactNewOrEditViewModel() : ViewModel() {
for (address in friend.addresses) {
friend.removeAddress(address)
}
for (address in sipAddresses.value.orEmpty()) {
for (address in sipAddresses) {
val data = address.value.value
if (!data.isNullOrEmpty()) {
val parsedAddress = core.interpretUrl(data, true)
@ -172,7 +150,7 @@ class ContactNewOrEditViewModel() : ViewModel() {
for (number in friend.phoneNumbers) {
friend.removePhoneNumber(number)
}
for (number in phoneNumbers.value.orEmpty()) {
for (number in phoneNumbers) {
val data = number.value.value
if (!data.isNullOrEmpty()) {
friend.addPhoneNumber(data)
@ -201,43 +179,45 @@ class ContactNewOrEditViewModel() : ViewModel() {
}
}
private fun addNewModel(isSip: Boolean) {
// UI thread
// TODO FIXME: causes focus issues
val list = arrayListOf<NewOrEditNumberOrAddressModel>()
val source = if (isSip) sipAddresses.value.orEmpty() else phoneNumbers.value.orEmpty()
private fun addSipAddress(address: String = "", requestFieldToBeAddedInUi: Boolean = false) {
// Core thread
val newModel = NewOrEditNumberOrAddressModel(address, true, {
if (address.isEmpty()) {
addSipAddress(requestFieldToBeAddedInUi = true)
}
}, { model ->
removeModel(model)
})
sipAddresses.add(newModel)
list.addAll(source)
list.add(
NewOrEditNumberOrAddressModel("", isSip, {
addNewModel(isSip)
}, { model ->
removeModel(model)
})
)
if (requestFieldToBeAddedInUi) {
addNewNumberOrAddressFieldEvent.postValue(Event(newModel))
}
}
if (isSip) {
sipAddresses.value = list
} else {
phoneNumbers.value = list
private fun addPhoneNumber(number: String = "", requestFieldToBeAddedInUi: Boolean = false) {
// Core thread
val newModel = NewOrEditNumberOrAddressModel(number, false, {
if (number.isEmpty()) {
addPhoneNumber(requestFieldToBeAddedInUi = true)
}
}, { model ->
removeModel(model)
})
phoneNumbers.add(newModel)
if (requestFieldToBeAddedInUi) {
addNewNumberOrAddressFieldEvent.postValue(Event(newModel))
}
}
private fun removeModel(model: NewOrEditNumberOrAddressModel) {
// UI thread
val list = arrayListOf<NewOrEditNumberOrAddressModel>()
val source = if (model.isSip) sipAddresses.value.orEmpty() else phoneNumbers.value.orEmpty()
for (item in source) {
if (item != model) {
list.add(item)
}
}
if (model.isSip) {
sipAddresses.value = list
sipAddresses.remove(model)
} else {
phoneNumbers.value = list
phoneNumbers.remove(model)
}
removeNewNumberOrAddressFieldEvent.value = Event(model)
}
}

View file

@ -7,7 +7,7 @@
android:viewportHeight="7">
<path
android:name="path"
android:pathData="M 11.11 6.481 C 11.331 6.238 11.331 5.866 11.11 5.623 L 6.594 0.654 C 6.276 0.303 5.724 0.303 5.406 0.654 L 1.722 4.707 L 1.722 2.232 C 1.722 1.894 1.449 1.621 1.111 1.621 C 0.774 1.621 0.5 1.894 0.5 2.232 L 0.5 6.556 C 0.5 6.801 0.699 7 0.944 7 L 4.716 7 C 5.088 7 5.389 6.699 5.389 6.328 C 5.389 5.956 5.088 5.655 4.716 5.655 L 2.584 5.655 L 6 1.896 L 10.166 6.481 C 10.419 6.759 10.857 6.759 11.11 6.481 Z"
android:pathData="M 11.11 0.519 C 11.331 0.762 11.331 1.134 11.11 1.377 L 6.594 6.346 C 6.276 6.697 5.724 6.697 5.406 6.346 L 1.722 2.293 L 1.722 4.768 C 1.722 5.106 1.449 5.379 1.111 5.379 C 0.774 5.379 0.5 5.106 0.5 4.768 L 0.5 0.444 C 0.5 0.199 0.699 0 0.944 0 L 4.716 0 C 5.088 0 5.389 0.301 5.389 0.672 C 5.389 1.044 5.088 1.345 4.716 1.345 L 2.584 1.345 L 6 5.104 L 10.166 0.519 C 10.419 0.241 10.857 0.241 11.11 0.519 Z"
android:fillColor="#dd5f5f"
android:strokeWidth="1"/>
</vector>

View file

@ -6,8 +6,8 @@
android:viewportWidth="12"
android:viewportHeight="7">
<path
android:name="path"
android:pathData="M 0.89 0.519 C 0.669 0.762 0.669 1.134 0.89 1.377 L 5.406 6.346 C 5.724 6.697 6.276 6.697 6.594 6.346 L 10.278 2.293 L 10.278 4.768 C 10.278 5.106 10.551 5.379 10.889 5.379 C 11.226 5.379 11.5 5.106 11.5 4.768 L 11.5 0.444 C 11.5 0.199 11.301 0 11.056 0 L 7.284 0 C 6.912 0 6.611 0.301 6.611 0.672 C 6.611 1.044 6.912 1.345 7.284 1.345 L 9.416 1.345 L 6 5.104 L 1.834 0.519 C 1.581 0.241 1.143 0.241 0.89 0.519 Z"
android:name="path_1"
android:pathData="M 0.89 6.481 C 0.669 6.238 0.669 5.866 0.89 5.623 L 5.406 0.654 C 5.724 0.303 6.276 0.303 6.594 0.654 L 10.278 4.707 L 10.278 2.232 C 10.278 1.894 10.551 1.621 10.889 1.621 C 11.226 1.621 11.5 1.894 11.5 2.232 L 11.5 6.556 C 11.5 6.801 11.301 7 11.056 7 L 7.284 7 C 6.912 7 6.611 6.699 6.611 6.328 C 6.611 5.956 6.912 5.655 7.284 5.655 L 9.416 5.655 L 6 1.896 L 1.834 6.481 C 1.581 6.759 1.143 6.759 0.89 6.481 Z"
android:fillColor="#dd5f5f"
android:strokeWidth="1"/>
</vector>

View file

@ -6,6 +6,7 @@
<data>
<import type="android.view.View" />
<import type="android.graphics.Typeface" />
<import type="android.text.InputType" />
<variable
name="model"
type="org.linphone.ui.main.contacts.model.NewOrEditNumberOrAddressModel" />
@ -44,6 +45,8 @@
android:textSize="14sp"
android:textColor="@color/gray_9"
android:background="@drawable/shape_edit_text_background"
android:maxLines="1"
android:inputType="@{model.isSip ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS : InputType.TYPE_CLASS_PHONE}"
app:layout_constraintTop_toBottomOf="@id/label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/remove"/>

View file

@ -140,7 +140,9 @@
android:text="@={viewModel.firstName, default=`John`}"
android:textSize="14sp"
android:textColor="@color/gray_9"
android:maxLines="1"
android:background="@drawable/shape_edit_text_background"
android:inputType="text|textPersonName"
app:layout_constraintTop_toBottomOf="@id/first_name_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
@ -172,7 +174,9 @@
android:text="@={viewModel.lastName, default=`Doe`}"
android:textSize="14sp"
android:textColor="@color/gray_9"
android:maxLines="1"
android:background="@drawable/shape_edit_text_background"
android:inputType="text|textPersonName"
app:layout_constraintTop_toBottomOf="@id/last_name_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
@ -181,32 +185,28 @@
android:id="@+id/sip_addresses"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_marginTop="30dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/last_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
entries="@{viewModel.sipAddresses}"
layout="@{@layout/contact_new_or_edit_cell}"/>
app:layout_constraintEnd_toEndOf="parent"/>
<LinearLayout
android:id="@+id/phone_numbers"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_marginTop="30dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/sip_addresses"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
entries="@{viewModel.phoneNumbers}"
layout="@{@layout/contact_new_or_edit_cell}"/>
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/company_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_marginTop="30dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="Company"
@ -228,7 +228,9 @@
android:text="@={viewModel.company, default=`Belledonne Comm`}"
android:textSize="14sp"
android:textColor="@color/gray_9"
android:maxLines="1"
android:background="@drawable/shape_edit_text_background"
android:inputType="text"
app:layout_constraintTop_toBottomOf="@id/company_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
@ -260,7 +262,9 @@
android:text="@={viewModel.jobTitle, default=`Android dev`}"
android:textSize="14sp"
android:textColor="@color/gray_9"
android:maxLines="1"
android:background="@drawable/shape_edit_text_background"
android:inputType="text"
app:layout_constraintTop_toBottomOf="@id/job_title_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

View file

@ -3,7 +3,7 @@
<color name="transparent_color">#00000000</color>
<color name="primary_color">#FF5E00</color>
<color name="primary_color_disabled">#FFEACB</color>
<color name="primary_color_disabled">#4DFE5E00</color>
<color name="primary_color_pressed">#B72D00</color>
<color name="black">#000000</color>