From 715e6dc8be9ed074f8d5dc3e95b8493d94479dd3 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 18 Aug 2023 12:10:55 +0200 Subject: [PATCH] Fixed focus issue in contact editor --- .../contacts/fragment/EditContactFragment.kt | 57 +++++++++- .../contacts/fragment/NewContactFragment.kt | 56 +++++++++ .../viewmodel/ContactNewOrEditViewModel.kt | 106 +++++++----------- .../res/drawable/incoming_call_rejected.xml | 2 +- .../res/drawable/outgoing_call_rejected.xml | 4 +- .../res/layout/contact_new_or_edit_cell.xml | 3 + .../layout/contact_new_or_edit_fragment.xml | 22 ++-- app/src/main/res/values/colors.xml | 2 +- 8 files changed, 174 insertions(+), 78 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt index a20f65965..3144e588e 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/EditContactFragment.kt @@ -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( + 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() { diff --git a/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt b/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt index 7ec7d4f11..bfb1182c0 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/fragment/NewContactFragment.kt @@ -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( + 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() { diff --git a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt index 99fd99ad4..e01d0d78b 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/viewmodel/ContactNewOrEditViewModel.kt @@ -43,9 +43,9 @@ class ContactNewOrEditViewModel() : ViewModel() { val lastName = MutableLiveData() - val sipAddresses = MutableLiveData>() + val sipAddresses = ArrayList() - val phoneNumbers = MutableLiveData>() + val phoneNumbers = ArrayList() val company = MutableLiveData() @@ -57,6 +57,10 @@ class ContactNewOrEditViewModel() : ViewModel() { val friendFoundEvent = MutableLiveData>() + val addNewNumberOrAddressFieldEvent = MutableLiveData>() + + val removeNewNumberOrAddressFieldEvent = MutableLiveData>() + 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() - val numbers = arrayListOf() - 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() - 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() - 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) } } diff --git a/app/src/main/res/drawable/incoming_call_rejected.xml b/app/src/main/res/drawable/incoming_call_rejected.xml index 10a82b3a8..7e682b80e 100644 --- a/app/src/main/res/drawable/incoming_call_rejected.xml +++ b/app/src/main/res/drawable/incoming_call_rejected.xml @@ -7,7 +7,7 @@ android:viewportHeight="7"> diff --git a/app/src/main/res/drawable/outgoing_call_rejected.xml b/app/src/main/res/drawable/outgoing_call_rejected.xml index c89423e4c..f964fdf9a 100644 --- a/app/src/main/res/drawable/outgoing_call_rejected.xml +++ b/app/src/main/res/drawable/outgoing_call_rejected.xml @@ -6,8 +6,8 @@ android:viewportWidth="12" android:viewportHeight="7"> diff --git a/app/src/main/res/layout/contact_new_or_edit_cell.xml b/app/src/main/res/layout/contact_new_or_edit_cell.xml index 584702ceb..6397f1a0f 100644 --- a/app/src/main/res/layout/contact_new_or_edit_cell.xml +++ b/app/src/main/res/layout/contact_new_or_edit_cell.xml @@ -6,6 +6,7 @@ + @@ -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"/> diff --git a/app/src/main/res/layout/contact_new_or_edit_fragment.xml b/app/src/main/res/layout/contact_new_or_edit_fragment.xml index 0e5799347..3ba1c8d8e 100644 --- a/app/src/main/res/layout/contact_new_or_edit_fragment.xml +++ b/app/src/main/res/layout/contact_new_or_edit_fragment.xml @@ -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"/> + 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"/> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8b5c45e28..f8dbaba4d 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,7 +3,7 @@ #00000000 #FF5E00 - #FFEACB + #4DFE5E00 #B72D00 #000000