mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Fixed performances issue by replacing suggestions recyclerview by linearlayout...
This commit is contained in:
parent
3c94068910
commit
ab151cc409
10 changed files with 312 additions and 321 deletions
|
|
@ -1,66 +0,0 @@
|
|||
package org.linphone.ui.main.calls.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.linphone.R
|
||||
import org.linphone.databinding.ContactListCellBinding
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
import org.linphone.utils.Event
|
||||
|
||||
class SuggestionsListAdapter(
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : ListAdapter<ContactAvatarModel, RecyclerView.ViewHolder>(SuggestionDiffCallback()) {
|
||||
val contactClickedEvent: MutableLiveData<Event<ContactAvatarModel>> by lazy {
|
||||
MutableLiveData<Event<ContactAvatarModel>>()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val binding: ContactListCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
R.layout.contact_list_cell,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
(holder as ViewHolder).bind(getItem(position))
|
||||
}
|
||||
|
||||
inner class ViewHolder(
|
||||
val binding: ContactListCellBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
@UiThread
|
||||
fun bind(contactModel: ContactAvatarModel) {
|
||||
with(binding) {
|
||||
model = contactModel
|
||||
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
|
||||
binding.setOnClickListener {
|
||||
contactClickedEvent.value = Event(contactModel)
|
||||
}
|
||||
|
||||
executePendingBindings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionDiffCallback : DiffUtil.ItemCallback<ContactAvatarModel>() {
|
||||
override fun areItemsTheSame(oldItem: ContactAvatarModel, newItem: ContactAvatarModel): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ContactAvatarModel, newItem: ContactAvatarModel): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,9 +32,7 @@ import org.linphone.LinphoneApplication.Companion.coreContext
|
|||
import org.linphone.R
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.databinding.CallStartFragmentBinding
|
||||
import org.linphone.ui.main.calls.adapter.SuggestionsListAdapter
|
||||
import org.linphone.ui.main.calls.viewmodel.StartCallViewModel
|
||||
import org.linphone.ui.main.calls.viewmodel.SuggestionsListViewModel
|
||||
import org.linphone.ui.main.contacts.adapter.ContactsListAdapter
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener
|
||||
|
|
@ -61,15 +59,7 @@ class StartCallFragment : GenericFragment() {
|
|||
R.id.startCallFragment
|
||||
)
|
||||
|
||||
private val suggestionsListViewModel: SuggestionsListViewModel by navGraphViewModels(
|
||||
R.id.startCallFragment
|
||||
)
|
||||
|
||||
private lateinit var contactsAdapter: ContactsListAdapter
|
||||
private lateinit var suggestionsAdapter: SuggestionsListAdapter
|
||||
|
||||
private var contactsListReady = false
|
||||
private var suggestionsListReady = false
|
||||
|
||||
private val listener = object : ContactNumberOrAddressClickListener {
|
||||
@UiThread
|
||||
|
|
@ -122,59 +112,32 @@ class StartCallFragment : GenericFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
binding.contactsList.layoutManager = LinearLayoutManager(requireContext())
|
||||
|
||||
suggestionsAdapter = SuggestionsListAdapter(viewLifecycleOwner)
|
||||
binding.suggestionsList.setHasFixedSize(true)
|
||||
binding.suggestionsList.adapter = suggestionsAdapter
|
||||
|
||||
suggestionsAdapter.contactClickedEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { model ->
|
||||
startCall(model)
|
||||
viewModel.onSuggestionClickedEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { address ->
|
||||
coreContext.postOnCoreThread {
|
||||
coreContext.startCall(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.suggestionsList.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.contactsList.layoutManager = LinearLayoutManager(requireContext())
|
||||
|
||||
contactsListViewModel.contactsList.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
Log.i("$TAG Contacts list is ready with [${it.size}] items")
|
||||
contactsAdapter.submitList(it) {
|
||||
// Otherwise list won't show until keyboard is opened for example...
|
||||
binding.contactsList.requestLayout()
|
||||
}
|
||||
contactsAdapter.submitList(it)
|
||||
viewModel.emptyContactsList.value = it.isEmpty()
|
||||
|
||||
if (suggestionsListReady && !contactsListReady) {
|
||||
Log.i("$TAG Suggestions list is also ready, start postponed enter transition")
|
||||
(view.parent as? ViewGroup)?.doOnPreDraw {
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
Log.i("$TAG Suggestions list is also ready, start postponed enter transition")
|
||||
(view.parent as? ViewGroup)?.doOnPreDraw {
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
contactsListReady = true
|
||||
}
|
||||
|
||||
suggestionsListViewModel.suggestionsList.observe(viewLifecycleOwner) {
|
||||
Log.i("$TAG Suggestions list is ready with [${it.size}] items")
|
||||
suggestionsAdapter.submitList(it) {
|
||||
// Otherwise list won't show until keyboard is opened for example...
|
||||
binding.suggestionsList.requestLayout()
|
||||
}
|
||||
viewModel.emptySuggestionsList.value = it.isEmpty()
|
||||
|
||||
if (contactsListReady && !suggestionsListReady) {
|
||||
Log.i("$TAG Contacts list is also ready, start postponed enter transition")
|
||||
(view.parent as? ViewGroup)?.doOnPreDraw {
|
||||
startPostponedEnterTransition()
|
||||
}
|
||||
}
|
||||
suggestionsListReady = true
|
||||
}
|
||||
|
||||
viewModel.searchFilter.observe(viewLifecycleOwner) { filter ->
|
||||
contactsListViewModel.applyFilter(filter)
|
||||
suggestionsListViewModel.applyFilter(filter)
|
||||
viewModel.applyFilter(filter)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.ui.main.calls.model
|
||||
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.linphone.core.Address
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class SuggestionModel @WorkerThread constructor(
|
||||
val address: Address,
|
||||
private val onClicked: ((Address) -> Unit)? = null
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "[Suggestion Model]"
|
||||
}
|
||||
|
||||
val name = LinphoneUtils.getDisplayName(address)
|
||||
|
||||
val initials = LinphoneUtils.getInitials(name)
|
||||
|
||||
@UiThread
|
||||
fun onClicked() {
|
||||
onClicked?.invoke(address)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,18 +20,183 @@
|
|||
package org.linphone.ui.main.calls.viewmodel
|
||||
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import java.util.ArrayList
|
||||
import org.linphone.LinphoneApplication
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.contacts.ContactsListener
|
||||
import org.linphone.core.Address
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.core.MagicSearch
|
||||
import org.linphone.core.MagicSearchListenerStub
|
||||
import org.linphone.core.SearchResult
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.calls.model.SuggestionModel
|
||||
import org.linphone.ui.main.model.isInSecureMode
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class StartCallViewModel @UiThread constructor() : ViewModel() {
|
||||
companion object {
|
||||
private const val TAG = "[Start Call ViewModel]"
|
||||
}
|
||||
|
||||
val searchFilter = MutableLiveData<String>()
|
||||
|
||||
val emptyContactsList = MutableLiveData<Boolean>()
|
||||
|
||||
val emptySuggestionsList = MutableLiveData<Boolean>()
|
||||
val suggestionsList = MutableLiveData<ArrayList<SuggestionModel>>()
|
||||
|
||||
val onSuggestionClickedEvent: MutableLiveData<Event<Address>> by lazy {
|
||||
MutableLiveData<Event<Address>>()
|
||||
}
|
||||
|
||||
private var currentFilter = ""
|
||||
private var previousFilter = "NotSet"
|
||||
private var limitSearchToLinphoneAccounts = true
|
||||
|
||||
private lateinit var magicSearch: MagicSearch
|
||||
|
||||
private val magicSearchListener = object : MagicSearchListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onSearchResultsReceived(magicSearch: MagicSearch) {
|
||||
Log.i("$TAG Magic search contacts available")
|
||||
processMagicSearchResults(magicSearch.lastSearch)
|
||||
}
|
||||
}
|
||||
|
||||
private val contactsListener = object : ContactsListener {
|
||||
@WorkerThread
|
||||
override fun onContactsLoaded() {
|
||||
Log.i("$TAG Contacts have been (re)loaded, updating list")
|
||||
applyFilter(
|
||||
currentFilter,
|
||||
if (limitSearchToLinphoneAccounts) LinphoneApplication.corePreferences.defaultDomain else "",
|
||||
MagicSearch.Source.CallLogs.toInt() or MagicSearch.Source.ChatRooms.toInt() or MagicSearch.Source.Request.toInt(),
|
||||
MagicSearch.Aggregation.Friend
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
val defaultAccount = core.defaultAccount
|
||||
limitSearchToLinphoneAccounts = defaultAccount?.isInSecureMode() ?: false
|
||||
|
||||
coreContext.contactsManager.addListener(contactsListener)
|
||||
magicSearch = core.createMagicSearch()
|
||||
magicSearch.limitedSearch = false
|
||||
magicSearch.addListener(magicSearchListener)
|
||||
}
|
||||
|
||||
applyFilter(currentFilter)
|
||||
}
|
||||
|
||||
@UiThread
|
||||
override fun onCleared() {
|
||||
coreContext.postOnCoreThread {
|
||||
magicSearch.removeListener(magicSearchListener)
|
||||
coreContext.contactsManager.removeListener(contactsListener)
|
||||
}
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun clearFilter() {
|
||||
searchFilter.value = ""
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun processMagicSearchResults(results: Array<SearchResult>) {
|
||||
Log.i("$TAG Processing [${results.size}] results")
|
||||
|
||||
val list = arrayListOf<SuggestionModel>()
|
||||
for (result in results) {
|
||||
val address = result.address
|
||||
|
||||
if (address != null) {
|
||||
val friend = coreContext.core.findFriend(address)
|
||||
// We don't want Friends here as they would also be in contacts list
|
||||
if (friend == null) {
|
||||
// If user-input generated result (always last) already exists, don't show it again
|
||||
if (result.sourceFlags == MagicSearch.Source.Request.toInt()) {
|
||||
val found = list.find {
|
||||
it.address.weakEqual(address)
|
||||
}
|
||||
if (found != null) {
|
||||
Log.i(
|
||||
"$TAG Result generated from user input is a duplicate of an existing solution, preventing double"
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
val model = SuggestionModel(address) {
|
||||
onSuggestionClickedEvent.value = Event(it)
|
||||
}
|
||||
list.add(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suggestionsList.postValue(list)
|
||||
Log.i("$TAG Processed [${results.size}] results, extracted [${list.size}] suggestions")
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun applyFilter(filter: String) {
|
||||
coreContext.postOnCoreThread {
|
||||
applyFilter(
|
||||
filter,
|
||||
if (limitSearchToLinphoneAccounts) LinphoneApplication.corePreferences.defaultDomain else "",
|
||||
MagicSearch.Source.CallLogs.toInt() or MagicSearch.Source.ChatRooms.toInt() or MagicSearch.Source.Request.toInt(),
|
||||
MagicSearch.Aggregation.Friend
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun applyFilter(
|
||||
filter: String,
|
||||
domain: String,
|
||||
sources: Int,
|
||||
aggregation: MagicSearch.Aggregation
|
||||
) {
|
||||
if (previousFilter.isNotEmpty() && (
|
||||
previousFilter.length > filter.length ||
|
||||
(previousFilter.length == filter.length && previousFilter != filter)
|
||||
)
|
||||
) {
|
||||
magicSearch.resetSearchCache()
|
||||
}
|
||||
currentFilter = filter
|
||||
previousFilter = filter
|
||||
|
||||
Log.i(
|
||||
"$TAG Asking Magic search for contacts matching filter [$filter], domain [$domain] and in sources [$sources]"
|
||||
)
|
||||
magicSearch.getContactsListAsync(
|
||||
filter,
|
||||
domain,
|
||||
sources,
|
||||
aggregation
|
||||
)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun createFriendFromSearchResult(searchResult: SearchResult): Friend {
|
||||
val friend = coreContext.core.createFriend()
|
||||
|
||||
val address = searchResult.address
|
||||
if (address != null) {
|
||||
friend.address = address
|
||||
|
||||
friend.name = LinphoneUtils.getDisplayName(address)
|
||||
friend.refKey = address.asStringUriOnly().hashCode().toString()
|
||||
}
|
||||
|
||||
return friend
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,190 +0,0 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.ui.main.calls.viewmodel
|
||||
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import java.util.ArrayList
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.contacts.ContactsListener
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.core.MagicSearch
|
||||
import org.linphone.core.MagicSearchListenerStub
|
||||
import org.linphone.core.SearchResult
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||
import org.linphone.ui.main.model.isInSecureMode
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class SuggestionsListViewModel @UiThread constructor() : ViewModel() {
|
||||
companion object {
|
||||
private const val TAG = "[Suggestions List ViewModel]"
|
||||
}
|
||||
|
||||
val suggestionsList = MutableLiveData<ArrayList<ContactAvatarModel>>()
|
||||
|
||||
private var currentFilter = ""
|
||||
private var previousFilter = "NotSet"
|
||||
private var limitSearchToLinphoneAccounts = true
|
||||
|
||||
private lateinit var magicSearch: MagicSearch
|
||||
|
||||
private val magicSearchListener = object : MagicSearchListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onSearchResultsReceived(magicSearch: MagicSearch) {
|
||||
Log.i("$TAG Magic search contacts available")
|
||||
processMagicSearchResults(magicSearch.lastSearch)
|
||||
}
|
||||
}
|
||||
|
||||
private val contactsListener = object : ContactsListener {
|
||||
@WorkerThread
|
||||
override fun onContactsLoaded() {
|
||||
Log.i("$TAG Contacts have been (re)loaded, updating list")
|
||||
applyFilter(
|
||||
currentFilter,
|
||||
if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "",
|
||||
MagicSearch.Source.CallLogs.toInt() or MagicSearch.Source.ChatRooms.toInt() or MagicSearch.Source.Request.toInt(),
|
||||
MagicSearch.Aggregation.Friend
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
val defaultAccount = core.defaultAccount
|
||||
limitSearchToLinphoneAccounts = defaultAccount?.isInSecureMode() ?: false
|
||||
|
||||
coreContext.contactsManager.addListener(contactsListener)
|
||||
magicSearch = core.createMagicSearch()
|
||||
magicSearch.limitedSearch = false
|
||||
magicSearch.addListener(magicSearchListener)
|
||||
}
|
||||
|
||||
applyFilter(currentFilter)
|
||||
}
|
||||
|
||||
@UiThread
|
||||
override fun onCleared() {
|
||||
coreContext.postOnCoreThread {
|
||||
magicSearch.removeListener(magicSearchListener)
|
||||
coreContext.contactsManager.removeListener(contactsListener)
|
||||
}
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun processMagicSearchResults(results: Array<SearchResult>) {
|
||||
Log.i("$TAG Processing [${results.size}] results")
|
||||
suggestionsList.value.orEmpty().forEach(ContactAvatarModel::destroy)
|
||||
|
||||
val list = arrayListOf<ContactAvatarModel>()
|
||||
|
||||
for (result in results) {
|
||||
val address = result.address
|
||||
|
||||
if (address != null) {
|
||||
val friend = coreContext.core.findFriend(address)
|
||||
// We don't want Friends here as they would also be in contacts list
|
||||
if (friend == null) {
|
||||
// If user-input generated result (always last) already exists, don't show it again
|
||||
if (result.sourceFlags == MagicSearch.Source.Request.toInt()) {
|
||||
val found = list.find {
|
||||
it.friend.address?.weakEqual(address) == true
|
||||
}
|
||||
if (found != null) {
|
||||
Log.i(
|
||||
"$TAG Result generated from user input is a duplicate of an existing solution, preventing double"
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
val fakeFriend = createFriendFromSearchResult(result)
|
||||
val model = ContactAvatarModel(fakeFriend)
|
||||
model.noAlphabet.postValue(true)
|
||||
|
||||
list.add(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suggestionsList.postValue(list)
|
||||
Log.i("$TAG Processed [${results.size}] results, extracted [${list.size}] suggestions")
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun applyFilter(filter: String) {
|
||||
coreContext.postOnCoreThread {
|
||||
applyFilter(
|
||||
filter,
|
||||
if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "",
|
||||
MagicSearch.Source.CallLogs.toInt() or MagicSearch.Source.ChatRooms.toInt() or MagicSearch.Source.Request.toInt(),
|
||||
MagicSearch.Aggregation.Friend
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun applyFilter(
|
||||
filter: String,
|
||||
domain: String,
|
||||
sources: Int,
|
||||
aggregation: MagicSearch.Aggregation
|
||||
) {
|
||||
if (previousFilter.isNotEmpty() && (
|
||||
previousFilter.length > filter.length ||
|
||||
(previousFilter.length == filter.length && previousFilter != filter)
|
||||
)
|
||||
) {
|
||||
magicSearch.resetSearchCache()
|
||||
}
|
||||
currentFilter = filter
|
||||
previousFilter = filter
|
||||
|
||||
Log.i(
|
||||
"$TAG Asking Magic search for contacts matching filter [$filter], domain [$domain] and in sources [$sources]"
|
||||
)
|
||||
magicSearch.getContactsListAsync(
|
||||
filter,
|
||||
domain,
|
||||
sources,
|
||||
aggregation
|
||||
)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun createFriendFromSearchResult(searchResult: SearchResult): Friend {
|
||||
val friend = coreContext.core.createFriend()
|
||||
|
||||
val address = searchResult.address
|
||||
if (address != null) {
|
||||
friend.address = address
|
||||
|
||||
friend.name = LinphoneUtils.getDisplayName(address)
|
||||
friend.refKey = address.asStringUriOnly().hashCode().toString()
|
||||
}
|
||||
|
||||
return friend
|
||||
}
|
||||
}
|
||||
|
|
@ -51,8 +51,6 @@ class ContactAvatarModel @WorkerThread constructor(val friend: Friend) {
|
|||
|
||||
val firstContactStartingByThatLetter = MutableLiveData<Boolean>()
|
||||
|
||||
val noAlphabet = MutableLiveData<Boolean>()
|
||||
|
||||
val showTrust = MutableLiveData<Boolean>()
|
||||
|
||||
private val friendListener = object : FriendListenerStub() {
|
||||
|
|
|
|||
|
|
@ -201,6 +201,12 @@ fun ImageView.setPresenceIcon(presence: ConsolidatedPresence?) {
|
|||
setImageResource(icon)
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@BindingAdapter("avatarInitials")
|
||||
fun AvatarView.loadInitials(initials: String?) {
|
||||
avatarInitials = initials.orEmpty()
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@BindingAdapter("accountAvatar")
|
||||
fun AvatarView.loadAccountAvatar(account: AccountModel?) {
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:src="@drawable/illu"
|
||||
android:visibility="gone"
|
||||
android:visibility="@{viewModel.emptyContactsList && viewModel.suggestionsList.size() == 0 ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintBottom_toTopOf="@id/no_contacts_nor_suggestion_label"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="No suggestion and no contact for the moment..."
|
||||
android:textSize="16sp"
|
||||
android:visibility="gone"
|
||||
android:visibility="@{viewModel.emptyContactsList && viewModel.suggestionsList.size() == 0 ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/no_contacts_nor_suggestion_image" />
|
||||
|
|
@ -169,7 +169,7 @@
|
|||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/all_contacts_label"
|
||||
style="@style/default_text_style_800"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
|
|
@ -180,23 +180,26 @@
|
|||
android:visibility="@{viewModel.emptyContactsList ? View.GONE : View.VISIBLE}"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/group_call_icon"
|
||||
app:layout_constraintBottom_toTopOf="@id/contacts_list"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/contacts_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:visibility="@{viewModel.emptyContactsList ? View.GONE : View.VISIBLE}"
|
||||
app:layout_constrainedHeight="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/all_contacts_label"
|
||||
app:layout_constraintBottom_toTopOf="@id/suggestions_label" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/suggestions_label"
|
||||
style="@style/default_text_style_800"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
|
|
@ -204,17 +207,22 @@
|
|||
android:padding="5dp"
|
||||
android:text="Suggestions"
|
||||
android:textSize="16sp"
|
||||
android:visibility="@{viewModel.emptySuggestionsList ? View.GONE : View.VISIBLE}"
|
||||
android:visibility="@{viewModel.suggestionsList.size() == 0 ? View.GONE : View.VISIBLE}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/contacts_list"
|
||||
app:layout_constraintBottom_toTopOf="@id/suggestions_list" />
|
||||
app:layout_constraintBottom_toTopOf="@id/suggestions_list"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
<LinearLayout
|
||||
android:id="@+id/suggestions_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:visibility="@{viewModel.emptySuggestionsList ? View.GONE : View.VISIBLE}"
|
||||
android:orientation="vertical"
|
||||
entries="@{viewModel.suggestionsList}"
|
||||
layout="@{@layout/call_suggestion_list_cell}"
|
||||
app:layout_constrainedHeight="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/suggestions_label"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
|
|
|
|||
64
app/src/main/res/layout/call_suggestion_list_cell.xml
Normal file
64
app/src/main/res/layout/call_suggestion_list_cell.xml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="model"
|
||||
type="org.linphone.ui.main.calls.model.SuggestionModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:onClick="@{() -> model.onClicked()}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/cell_background"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp">
|
||||
|
||||
<io.getstream.avatarview.AvatarView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="@dimen/avatar_list_cell_size"
|
||||
android:layout_height="@dimen/avatar_list_cell_size"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@drawable/shape_avatar_background"
|
||||
avatarInitials="@{model.initials, default=`JD`}"
|
||||
app:avatarViewPlaceholder="@drawable/contact_avatar"
|
||||
app:avatarViewInitialsBackgroundColor="@color/blue_outgoing_message"
|
||||
app:avatarViewInitialsTextColor="@color/gray_9"
|
||||
app:avatarViewInitialsTextSize="16sp"
|
||||
app:avatarViewInitialsTextStyle="bold"
|
||||
app:avatarViewShape="circle"
|
||||
app:avatarViewBorderWidth="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style"
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{model.name, default=`John Doe`}"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginStart="10dp"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:background="@color/blue_outgoing_message"
|
||||
app:layout_constraintStart_toStartOf="@id/name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="@{model.firstLetter, default=`A`}"
|
||||
android:visibility="@{model.noAlphabet ? View.GONE : model.firstContactStartingByThatLetter ? View.VISIBLE : View.INVISIBLE}"
|
||||
android:visibility="@{model.firstContactStartingByThatLetter ? View.VISIBLE : View.INVISIBLE}"
|
||||
android:textColor="@color/gray_10"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue