diff --git a/app/src/main/java/org/linphone/contacts/ContactsManager.kt b/app/src/main/java/org/linphone/contacts/ContactsManager.kt index 3044650c9..f6161255f 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contacts/ContactsManager.kt @@ -354,6 +354,7 @@ fun Friend.getListOfSipAddressesAndPhoneNumbers(listener: ContactNumberOrAddress for (address in addresses) { val data = ContactNumberOrAddressModel( + this, address, address.asStringUriOnly(), true, // SIP addresses are always enabled @@ -385,6 +386,7 @@ fun Friend.getListOfSipAddressesAndPhoneNumbers(listener: ContactNumberOrAddress address.clean() // To remove ;user=phone presenceAddress = address val data = ContactNumberOrAddressModel( + this, address, address.asStringUriOnly(), true, // SIP addresses are always enabled @@ -412,6 +414,7 @@ fun Friend.getListOfSipAddressesAndPhoneNumbers(listener: ContactNumberOrAddress number.label ?: "" ) val data = ContactNumberOrAddressModel( + this, address, number.phoneNumber, enablePhoneNumbers, diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/StartConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/StartConversationFragment.kt index 0c9342bc3..b3a995c1a 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/StartConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/StartConversationFragment.kt @@ -19,34 +19,30 @@ */ package org.linphone.ui.main.chat.fragment -import android.app.Dialog import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.UiThread +import androidx.annotation.WorkerThread import androidx.core.view.doOnPreDraw import androidx.navigation.navGraphViewModels import androidx.recyclerview.widget.LinearLayoutManager -import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R -import org.linphone.contacts.getListOfSipAddressesAndPhoneNumbers +import org.linphone.core.Address +import org.linphone.core.Friend import org.linphone.core.tools.Log import org.linphone.databinding.StartChatFragmentBinding import org.linphone.ui.main.MainActivity import org.linphone.ui.main.chat.viewmodel.StartConversationViewModel -import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener -import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel -import org.linphone.ui.main.contacts.model.NumberOrAddressPickerDialogModel -import org.linphone.ui.main.fragment.GenericFragment +import org.linphone.ui.main.contacts.model.ContactAvatarModel +import org.linphone.ui.main.fragment.GenericAddressPickerFragment import org.linphone.ui.main.history.adapter.ContactsAndSuggestionsListAdapter -import org.linphone.ui.main.history.model.ContactOrSuggestionModel -import org.linphone.ui.main.model.isInSecureMode -import org.linphone.utils.DialogUtils +import org.linphone.ui.main.model.SelectedAddressModel import org.linphone.utils.Event @UiThread -class StartConversationFragment : GenericFragment() { +class StartConversationFragment : GenericAddressPickerFragment() { companion object { private const val TAG = "[Start Conversation Fragment]" } @@ -59,27 +55,6 @@ class StartConversationFragment : GenericFragment() { private lateinit var adapter: ContactsAndSuggestionsListAdapter - private val listener = object : ContactNumberOrAddressClickListener { - @UiThread - override fun onClicked(model: ContactNumberOrAddressModel) { - val address = model.address - coreContext.postOnCoreThread { - if (address != null) { - Log.i( - "$TAG Creating a 1-1 conversation with [${model.address.asStringUriOnly()}]" - ) - viewModel.createOneToOneChatRoomWith(model.address) - } - } - } - - @UiThread - override fun onLongPress(model: ContactNumberOrAddressModel) { - } - } - - private var numberOrAddressPickerDialog: Dialog? = null - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -106,7 +81,7 @@ class StartConversationFragment : GenericFragment() { adapter.contactClickedEvent.observe(viewLifecycleOwner) { it.consume { model -> - createChatRoom(model) + handleClickOnContactModel(model) } } @@ -154,69 +129,16 @@ class StartConversationFragment : GenericFragment() { } } - override fun onPause() { - super.onPause() - - numberOrAddressPickerDialog?.dismiss() - numberOrAddressPickerDialog = null - } - - private fun createChatRoom(model: ContactOrSuggestionModel) { - coreContext.postOnCoreThread { core -> - val friend = model.friend - if (friend == null) { - Log.i("$TAG Friend is null, creating conversation with [${model.address}]") - viewModel.createOneToOneChatRoomWith(model.address) - return@postOnCoreThread - } - - val addressesCount = friend.addresses.size - val numbersCount = friend.phoneNumbers.size - - // Do not consider phone numbers if default account is in secure mode - val enablePhoneNumbers = core.defaultAccount?.isInSecureMode() != true - - if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) { - Log.i( - "$TAG Only 1 SIP address found for contact [${friend.name}], creating conversation directly" - ) - val address = friend.addresses.first() - viewModel.createOneToOneChatRoomWith(address) - } else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) { - val number = friend.phoneNumbers.first() - val address = core.interpretUrl(number, true) - if (address != null) { - Log.i( - "$TAG Only 1 phone number found for contact [${friend.name}], creating conversation directly" - ) - viewModel.createOneToOneChatRoomWith(address) - } else { - Log.e("$TAG Failed to interpret phone number [$number] as SIP address") - } - } else { - val list = friend.getListOfSipAddressesAndPhoneNumbers(listener) - Log.i( - "$TAG [${list.size}] numbers or addresses found for contact [${friend.name}], showing selection dialog" - ) - - coreContext.postOnMainThread { - val numberOrAddressModel = NumberOrAddressPickerDialogModel(list) - val dialog = - DialogUtils.getNumberOrAddressPickerDialog( - requireActivity(), - numberOrAddressModel - ) - numberOrAddressPickerDialog = dialog - - numberOrAddressModel.dismissEvent.observe(viewLifecycleOwner) { event -> - event.consume { - dialog.dismiss() - } - } - - dialog.show() - } + @WorkerThread + override fun onAddressSelected(address: Address, friend: Friend) { + if (viewModel.multipleSelectionMode.value == true) { + val avatarModel = ContactAvatarModel(friend) + val model = SelectedAddressModel(address, avatarModel) { + viewModel.removeAddressModelFromSelection(it) } + viewModel.addAddressModelToSelection(model) + } else { + viewModel.createOneToOneChatRoomWith(address) } } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt index 7459ff0cd..cb247fafd 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/StartConversationViewModel.kt @@ -22,8 +22,7 @@ package org.linphone.ui.main.chat.viewmodel import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import java.util.ArrayList +import kotlin.collections.ArrayList import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R @@ -39,11 +38,12 @@ import org.linphone.core.tools.Log import org.linphone.ui.main.contacts.model.ContactAvatarModel import org.linphone.ui.main.history.model.ContactOrSuggestionModel import org.linphone.ui.main.model.isInSecureMode +import org.linphone.ui.main.viewmodel.AddressSelectionViewModel import org.linphone.utils.AppUtils import org.linphone.utils.Event import org.linphone.utils.LinphoneUtils -class StartConversationViewModel @UiThread constructor() : ViewModel() { +class StartConversationViewModel @UiThread constructor() : AddressSelectionViewModel() { companion object { private const val TAG = "[Start Conversation ViewModel]" } diff --git a/app/src/main/java/org/linphone/ui/main/contacts/model/ContactNumberOrAddressModel.kt b/app/src/main/java/org/linphone/ui/main/contacts/model/ContactNumberOrAddressModel.kt index 435e01660..3b6349c6b 100644 --- a/app/src/main/java/org/linphone/ui/main/contacts/model/ContactNumberOrAddressModel.kt +++ b/app/src/main/java/org/linphone/ui/main/contacts/model/ContactNumberOrAddressModel.kt @@ -23,8 +23,10 @@ import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import org.linphone.core.Address +import org.linphone.core.Friend class ContactNumberOrAddressModel @WorkerThread constructor( + val friend: Friend, val address: Address?, val displayedValue: String, val isEnabled: Boolean, diff --git a/app/src/main/java/org/linphone/ui/main/fragment/GenericAddressPickerFragment.kt b/app/src/main/java/org/linphone/ui/main/fragment/GenericAddressPickerFragment.kt new file mode 100644 index 000000000..f52177536 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/fragment/GenericAddressPickerFragment.kt @@ -0,0 +1,133 @@ +/* + * 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 . + */ +package org.linphone.ui.main.fragment + +import android.app.Dialog +import androidx.annotation.UiThread +import androidx.annotation.WorkerThread +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.contacts.getListOfSipAddressesAndPhoneNumbers +import org.linphone.core.Address +import org.linphone.core.Friend +import org.linphone.core.tools.Log +import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener +import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel +import org.linphone.ui.main.contacts.model.NumberOrAddressPickerDialogModel +import org.linphone.ui.main.history.model.ContactOrSuggestionModel +import org.linphone.ui.main.model.isInSecureMode +import org.linphone.utils.DialogUtils + +@UiThread +abstract class GenericAddressPickerFragment : GenericFragment() { + companion object { + private const val TAG = "[Generic Address Picker Fragment]" + } + + private var numberOrAddressPickerDialog: Dialog? = null + + @WorkerThread + abstract fun onAddressSelected(address: Address, friend: Friend) + + override fun onPause() { + super.onPause() + + numberOrAddressPickerDialog?.dismiss() + numberOrAddressPickerDialog = null + } + + private val listener = object : ContactNumberOrAddressClickListener { + @UiThread + override fun onClicked(model: ContactNumberOrAddressModel) { + val address = model.address + coreContext.postOnCoreThread { + if (address != null) { + Log.i( + "$TAG Selected address [${model.address.asStringUriOnly()}] from friend [${model.friend.name}]" + ) + onAddressSelected(model.address, model.friend) + } + } + + numberOrAddressPickerDialog?.dismiss() + numberOrAddressPickerDialog = null + } + + @UiThread + override fun onLongPress(model: ContactNumberOrAddressModel) { + } + } + + protected fun handleClickOnContactModel(model: ContactOrSuggestionModel) { + coreContext.postOnCoreThread { core -> + val friend = model.friend + if (friend == null) { + Log.i("$TAG Friend is null, using address [${model.address}]") + val fakeFriend = core.createFriend() + fakeFriend.addAddress(model.address) + onAddressSelected(model.address, fakeFriend) + return@postOnCoreThread + } + + val addressesCount = friend.addresses.size + val numbersCount = friend.phoneNumbers.size + + // Do not consider phone numbers if default account is in secure mode + val enablePhoneNumbers = core.defaultAccount?.isInSecureMode() != true + + if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) { + val address = friend.addresses.first() + Log.i("$TAG Only 1 SIP address found for contact [${friend.name}], using it") + onAddressSelected(address, friend) + } else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) { + val number = friend.phoneNumbers.first() + val address = core.interpretUrl(number, true) + if (address != null) { + Log.i("$TAG Only 1 phone number found for contact [${friend.name}], using it") + onAddressSelected(address, friend) + } else { + Log.e("$TAG Failed to interpret phone number [$number] as SIP address") + } + } else { + val list = friend.getListOfSipAddressesAndPhoneNumbers(listener) + Log.i( + "$TAG [${list.size}] numbers or addresses found for contact [${friend.name}], showing selection dialog" + ) + + coreContext.postOnMainThread { + val numberOrAddressModel = NumberOrAddressPickerDialogModel(list) + val dialog = + DialogUtils.getNumberOrAddressPickerDialog( + requireActivity(), + numberOrAddressModel + ) + numberOrAddressPickerDialog = dialog + + numberOrAddressModel.dismissEvent.observe(viewLifecycleOwner) { event -> + event.consume { + dialog.dismiss() + } + } + + dialog.show() + } + } + } + } +} diff --git a/app/src/main/java/org/linphone/ui/main/history/adapter/ContactsAndSuggestionsListAdapter.kt b/app/src/main/java/org/linphone/ui/main/history/adapter/ContactsAndSuggestionsListAdapter.kt index d9af1a94a..613e1f0be 100644 --- a/app/src/main/java/org/linphone/ui/main/history/adapter/ContactsAndSuggestionsListAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/history/adapter/ContactsAndSuggestionsListAdapter.kt @@ -31,8 +31,6 @@ class ContactsAndSuggestionsListAdapter( private const val SUGGESTION_TYPE = 1 } - var selectedAdapterPosition = -1 - val contactClickedEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -108,8 +106,6 @@ class ContactsAndSuggestionsListAdapter( lifecycleOwner = viewLifecycleOwner - binding.root.isSelected = bindingAdapterPosition == selectedAdapterPosition - binding.setOnClickListener { contactClickedEvent.value = Event(contactOrSuggestionModel) } @@ -129,8 +125,6 @@ class ContactsAndSuggestionsListAdapter( lifecycleOwner = viewLifecycleOwner - binding.root.isSelected = bindingAdapterPosition == selectedAdapterPosition - binding.setOnClickListener { contactClickedEvent.value = Event(contactOrSuggestionModel) } diff --git a/app/src/main/java/org/linphone/ui/main/history/fragment/StartCallFragment.kt b/app/src/main/java/org/linphone/ui/main/history/fragment/StartCallFragment.kt index 54c36aac8..133989719 100644 --- a/app/src/main/java/org/linphone/ui/main/history/fragment/StartCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/history/fragment/StartCallFragment.kt @@ -19,30 +19,25 @@ */ package org.linphone.ui.main.history.fragment -import android.app.Dialog import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.UiThread +import androidx.annotation.WorkerThread import androidx.core.view.doOnPreDraw import androidx.navigation.navGraphViewModels import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetBehavior import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R -import org.linphone.contacts.getListOfSipAddressesAndPhoneNumbers +import org.linphone.core.Address +import org.linphone.core.Friend import org.linphone.core.tools.Log import org.linphone.databinding.StartCallFragmentBinding -import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener -import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel -import org.linphone.ui.main.contacts.model.NumberOrAddressPickerDialogModel -import org.linphone.ui.main.fragment.GenericFragment +import org.linphone.ui.main.fragment.GenericAddressPickerFragment import org.linphone.ui.main.history.adapter.ContactsAndSuggestionsListAdapter -import org.linphone.ui.main.history.model.ContactOrSuggestionModel import org.linphone.ui.main.history.viewmodel.StartCallViewModel -import org.linphone.ui.main.model.isInSecureMode -import org.linphone.utils.DialogUtils import org.linphone.utils.RecyclerViewHeaderDecoration import org.linphone.utils.addCharacterAtPosition import org.linphone.utils.hideKeyboard @@ -51,7 +46,7 @@ import org.linphone.utils.setKeyboardInsetListener import org.linphone.utils.showKeyboard @UiThread -class StartCallFragment : GenericFragment() { +class StartCallFragment : GenericAddressPickerFragment() { companion object { private const val TAG = "[Start Call Fragment]" } @@ -64,24 +59,6 @@ class StartCallFragment : GenericFragment() { private lateinit var adapter: ContactsAndSuggestionsListAdapter - private val listener = object : ContactNumberOrAddressClickListener { - @UiThread - override fun onClicked(model: ContactNumberOrAddressModel) { - val address = model.address - if (address != null) { - coreContext.postOnCoreThread { - coreContext.startCall(address) - } - } - } - - @UiThread - override fun onLongPress(model: ContactNumberOrAddressModel) { - } - } - - private var numberOrAddressPickerDialog: Dialog? = null - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -117,7 +94,7 @@ class StartCallFragment : GenericFragment() { adapter.contactClickedEvent.observe(viewLifecycleOwner) { it.consume { model -> - startCall(model) + handleClickOnContactModel(model) } } @@ -187,71 +164,14 @@ class StartCallFragment : GenericFragment() { } } + @WorkerThread + override fun onAddressSelected(address: Address, friend: Friend) { + coreContext.startCall(address) + } + override fun onPause() { super.onPause() viewModel.isNumpadVisible.value = false - - numberOrAddressPickerDialog?.dismiss() - numberOrAddressPickerDialog = null - } - - private fun startCall(model: ContactOrSuggestionModel) { - coreContext.postOnCoreThread { core -> - val friend = model.friend - if (friend == null) { - Log.i("$TAG Friend is null, starting call with [${model.address}]") - coreContext.startCall(model.address) - return@postOnCoreThread - } - - val addressesCount = friend.addresses.size - val numbersCount = friend.phoneNumbers.size - - // Do not consider phone numbers if default account is in secure mode - val enablePhoneNumbers = core.defaultAccount?.isInSecureMode() != true - - if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) { - Log.i( - "$TAG Only 1 SIP address found for contact [${friend.name}], starting call directly" - ) - val address = friend.addresses.first() - coreContext.startCall(address) - } else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) { - val number = friend.phoneNumbers.first() - val address = core.interpretUrl(number, true) - if (address != null) { - Log.i( - "$TAG Only 1 phone number found for contact [${friend.name}], starting call directly" - ) - coreContext.startCall(address) - } else { - Log.e("$TAG Failed to interpret phone number [$number] as SIP address") - } - } else { - val list = friend.getListOfSipAddressesAndPhoneNumbers(listener) - Log.i( - "$TAG [${list.size}] numbers or addresses found for contact [${friend.name}], showing selection dialog" - ) - - coreContext.postOnMainThread { - val numberOrAddressModel = NumberOrAddressPickerDialogModel(list) - val dialog = - DialogUtils.getNumberOrAddressPickerDialog( - requireActivity(), - numberOrAddressModel - ) - numberOrAddressPickerDialog = dialog - - numberOrAddressModel.dismissEvent.observe(viewLifecycleOwner) { event -> - event.consume { - dialog.dismiss() - } - } - - dialog.show() - } - } - } } } diff --git a/app/src/main/java/org/linphone/ui/main/model/SelectedAddressModel.kt b/app/src/main/java/org/linphone/ui/main/model/SelectedAddressModel.kt new file mode 100644 index 000000000..391f331db --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/model/SelectedAddressModel.kt @@ -0,0 +1,40 @@ +/* + * 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 . + */ +package org.linphone.ui.main.model + +import androidx.annotation.UiThread +import androidx.annotation.WorkerThread +import org.linphone.core.Address +import org.linphone.ui.main.contacts.model.ContactAvatarModel + +class SelectedAddressModel @WorkerThread constructor( + val address: Address, + val avatarModel: ContactAvatarModel?, + private val onRemovedFromSelection: ((model: SelectedAddressModel) -> Unit)? = null +) { + companion object { + private const val TAG = "[Selected Address Model]" + } + + @UiThread + fun removeFromSelection() { + onRemovedFromSelection?.invoke(this) + } +} diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/AddressSelectionViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/AddressSelectionViewModel.kt new file mode 100644 index 000000000..50beebc2b --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/AddressSelectionViewModel.kt @@ -0,0 +1,104 @@ +/* + * 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 . + */ +package org.linphone.ui.main.viewmodel + +import androidx.annotation.UiThread +import androidx.annotation.WorkerThread +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.linphone.R +import org.linphone.mediastream.Log +import org.linphone.ui.main.model.SelectedAddressModel +import org.linphone.utils.AppUtils + +abstract class AddressSelectionViewModel @UiThread constructor() : ViewModel() { + companion object { + private const val TAG = "[Address Selection ViewModel]" + } + + val multipleSelectionMode = MutableLiveData() + + val selection = MutableLiveData>() + + val selectionCount = MutableLiveData() + + init { + multipleSelectionMode.value = false + } + + @UiThread + fun switchToMultipleSelectionMode() { + Log.i("$$TAG Multiple selection mode ON") + multipleSelectionMode.value = true + } + + @WorkerThread + fun addAddressModelToSelection(model: SelectedAddressModel) { + val actual = selection.value.orEmpty() + if (actual.find { + it.address.weakEqual(model.address) + } == null + ) { + Log.i("$TAG Adding [${model.address.asStringUriOnly()}] address to selection") + + val list = arrayListOf() + list.addAll(actual) + list.add(model) + + selectionCount.postValue( + AppUtils.getStringWithPlural( + R.plurals.selection_count_label, + list.size, + list.size.toString() + ) + ) + selection.postValue(list) + } else { + Log.w("$TAG Address is already in selection, doing nothing") + } + } + + @WorkerThread + fun removeAddressModelFromSelection(model: SelectedAddressModel) { + val actual = selection.value.orEmpty() + if (actual.find { + it.address.weakEqual(model.address) + } != null + ) { + Log.i("$TAG Removing [${model.address.asStringUriOnly()}] address from selection") + + val list = arrayListOf() + list.addAll(actual) + model.avatarModel?.destroy() + list.remove(model) + + selectionCount.postValue( + AppUtils.getStringWithPlural( + R.plurals.selection_count_label, + list.size, + list.size.toString() + ) + ) + selection.postValue(list) + } else { + Log.w("$TAG Address isn't in selection, doing nothing") + } + } +} diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt index 436ccd5f8..4a69c886c 100644 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt @@ -80,8 +80,8 @@ fun setEntries( entries: List?, layoutId: Int ) { + viewGroup.removeAllViews() if (!entries.isNullOrEmpty()) { - viewGroup.removeAllViews() val inflater = viewGroup.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater for (entry in entries) { val binding = DataBindingUtil.inflate( diff --git a/app/src/main/res/drawable/shape_circle_gray_100_background.xml b/app/src/main/res/drawable/shape_circle_gray_100_background.xml new file mode 100644 index 000000000..ec4482f7b --- /dev/null +++ b/app/src/main/res/drawable/shape_circle_gray_100_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/address_selected_list_cell.xml b/app/src/main/res/layout/address_selected_list_cell.xml new file mode 100644 index 000000000..665764635 --- /dev/null +++ b/app/src/main/res/layout/address_selected_list_cell.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/start_chat_fragment.xml b/app/src/main/res/layout/start_chat_fragment.xml index 5c5e6ad82..dcd12a34a 100644 --- a/app/src/main/res/layout/start_chat_fragment.xml +++ b/app/src/main/res/layout/start_chat_fragment.xml @@ -43,10 +43,23 @@ android:layout_marginStart="10dp" android:layout_marginEnd="10dp" android:text="@string/new_conversation_title" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@id/create_group" app:layout_constraintStart_toEndOf="@id/back" app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/multiple_selection" /> + android:visibility="@{viewModel.hideGroupChatButton || viewModel.multipleSelectionMode || viewModel.searchFilter.length() > 0 ? View.GONE : View.VISIBLE}" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1bba1ef72..547cb346d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,11 @@ next Start + + %s selected + %s selected + + &appName; active calls notifications &appName; incoming calls notifications &appName; service notification