From 9c9391c95bc19013e0984623e7333f99390d4acc Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 15 Sep 2023 15:20:44 +0200 Subject: [PATCH] Merged contacts & suggestions in the same list --- .../ContactsAndSuggestionsListAdapter.kt | 146 ++++++++++++++++++ .../main/calls/fragment/StartCallFragment.kt | 65 ++++---- ...onModel.kt => ContactOrSuggestionModel.kt} | 9 +- .../calls/viewmodel/StartCallViewModel.kt | 58 ++++--- .../utils/RecyclerViewHeaderDecoration.kt | 98 ++++++++++++ .../account_profile_secure_mode_fragment.xml | 2 +- .../res/layout/assistant_login_fragment.xml | 2 +- .../layout/assistant_register_fragment.xml | 2 +- .../layout/assistant_secure_mode_fragment.xml | 2 +- ...third_party_sip_account_login_fragment.xml | 2 +- ...ird_party_sip_account_warning_fragment.xml | 2 +- .../main/res/layout/call_start_fragment.xml | 73 ++------- .../res/layout/call_suggestion_list_cell.xml | 7 +- .../call_suggestion_list_decoration.xml | 9 ++ app/src/main/res/layout/contact_list_cell.xml | 1 - .../layout/contact_new_or_edit_fragment.xml | 2 +- app/src/main/res/layout/settings_fragment.xml | 2 +- app/src/main/res/values/dimen.xml | 2 + app/src/main/res/values/strings.xml | 1 + 19 files changed, 360 insertions(+), 125 deletions(-) create mode 100644 app/src/main/java/org/linphone/ui/main/calls/adapter/ContactsAndSuggestionsListAdapter.kt rename app/src/main/java/org/linphone/ui/main/calls/model/{SuggestionModel.kt => ContactOrSuggestionModel.kt} (81%) create mode 100644 app/src/main/java/org/linphone/utils/RecyclerViewHeaderDecoration.kt create mode 100644 app/src/main/res/layout/call_suggestion_list_decoration.xml diff --git a/app/src/main/java/org/linphone/ui/main/calls/adapter/ContactsAndSuggestionsListAdapter.kt b/app/src/main/java/org/linphone/ui/main/calls/adapter/ContactsAndSuggestionsListAdapter.kt new file mode 100644 index 000000000..4e6332d85 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/calls/adapter/ContactsAndSuggestionsListAdapter.kt @@ -0,0 +1,146 @@ +package org.linphone.ui.main.calls.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +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.CallSuggestionListCellBinding +import org.linphone.databinding.ContactListCellBinding +import org.linphone.ui.main.calls.model.ContactOrSuggestionModel +import org.linphone.utils.Event +import org.linphone.utils.HeaderAdapter + +class ContactsAndSuggestionsListAdapter( + private val viewLifecycleOwner: LifecycleOwner +) : ListAdapter( + ContactOrSuggestionDiffCallback() +), + HeaderAdapter { + companion object { + private const val CONTACT_TYPE = 0 + private const val SUGGESTION_TYPE = 1 + } + + var selectedAdapterPosition = -1 + + val contactClickedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + override fun displayHeaderForPosition(position: Int): Boolean { + val model = getItem(position) + if (model.friend == null) { + if (position == 0) { + return true + } + val previousModel = getItem(position - 1) + return previousModel.friend != null + } + return false + } + + override fun getHeaderViewForPosition(context: Context, position: Int): View { + return LayoutInflater.from(context).inflate(R.layout.call_suggestion_list_decoration, null) + } + + override fun getItemViewType(position: Int): Int { + val model = getItem(position) + return if (model.friend == null) SUGGESTION_TYPE else CONTACT_TYPE + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + CONTACT_TYPE -> { + val binding: ContactListCellBinding = DataBindingUtil.inflate( + LayoutInflater.from(parent.context), + R.layout.contact_list_cell, + parent, + false + ) + ContactViewHolder(binding) + } + else -> { + val binding: CallSuggestionListCellBinding = DataBindingUtil.inflate( + LayoutInflater.from(parent.context), + R.layout.call_suggestion_list_cell, + parent, + false + ) + SuggestionViewHolder(binding) + } + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (getItemViewType(position)) { + CONTACT_TYPE -> (holder as ContactViewHolder).bind(getItem(position)) + else -> (holder as SuggestionViewHolder).bind(getItem(position)) + } + } + + inner class ContactViewHolder( + val binding: ContactListCellBinding + ) : RecyclerView.ViewHolder(binding.root) { + @UiThread + fun bind(contactOrSuggestionModel: ContactOrSuggestionModel) { + with(binding) { + model = contactOrSuggestionModel.contactAvatarModel + + lifecycleOwner = viewLifecycleOwner + + binding.root.isSelected = bindingAdapterPosition == selectedAdapterPosition + + binding.setOnClickListener { + contactClickedEvent.value = Event(contactOrSuggestionModel) + } + + executePendingBindings() + } + } + } + + inner class SuggestionViewHolder( + val binding: CallSuggestionListCellBinding + ) : RecyclerView.ViewHolder(binding.root) { + @UiThread + fun bind(contactOrSuggestionModel: ContactOrSuggestionModel) { + with(binding) { + model = contactOrSuggestionModel + + lifecycleOwner = viewLifecycleOwner + + binding.root.isSelected = bindingAdapterPosition == selectedAdapterPosition + + binding.setOnClickListener { + contactClickedEvent.value = Event(contactOrSuggestionModel) + } + + executePendingBindings() + } + } + } + + private class ContactOrSuggestionDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ContactOrSuggestionModel, + newItem: ContactOrSuggestionModel + ): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame( + oldItem: ContactOrSuggestionModel, + newItem: ContactOrSuggestionModel + ): Boolean { + return false + } + } +} diff --git a/app/src/main/java/org/linphone/ui/main/calls/fragment/StartCallFragment.kt b/app/src/main/java/org/linphone/ui/main/calls/fragment/StartCallFragment.kt index 546ceafc5..27463e7b4 100644 --- a/app/src/main/java/org/linphone/ui/main/calls/fragment/StartCallFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/calls/fragment/StartCallFragment.kt @@ -34,16 +34,16 @@ import org.linphone.R import org.linphone.contacts.getListOfSipAddressesAndPhoneNumbers import org.linphone.core.tools.Log import org.linphone.databinding.CallStartFragmentBinding +import org.linphone.ui.main.calls.adapter.ContactsAndSuggestionsListAdapter +import org.linphone.ui.main.calls.model.ContactOrSuggestionModel import org.linphone.ui.main.calls.viewmodel.StartCallViewModel -import org.linphone.ui.main.contacts.adapter.ContactsListAdapter -import org.linphone.ui.main.contacts.model.ContactAvatarModel 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.contacts.viewmodel.ContactsListViewModel import org.linphone.ui.main.fragment.GenericFragment import org.linphone.ui.main.model.isInSecureMode import org.linphone.utils.DialogUtils +import org.linphone.utils.RecyclerViewHeaderDecoration import org.linphone.utils.hideKeyboard import org.linphone.utils.setKeyboardInsetListener import org.linphone.utils.showKeyboard @@ -60,11 +60,7 @@ class StartCallFragment : GenericFragment() { R.id.main_nav_graph ) - private val contactsListViewModel: ContactsListViewModel by navGraphViewModels( - R.id.main_nav_graph - ) - - private lateinit var contactsAdapter: ContactsListAdapter + private lateinit var adapter: ContactsAndSuggestionsListAdapter private val listener = object : ContactNumberOrAddressClickListener { @UiThread @@ -108,42 +104,37 @@ class StartCallFragment : GenericFragment() { viewModel.hideNumpad() } - contactsAdapter = ContactsListAdapter(viewLifecycleOwner, disableLongClick = true) - binding.contactsList.setHasFixedSize(true) - binding.contactsList.adapter = contactsAdapter + adapter = ContactsAndSuggestionsListAdapter(viewLifecycleOwner) + binding.contactsAndSuggestionsList.setHasFixedSize(true) + binding.contactsAndSuggestionsList.adapter = adapter - contactsAdapter.contactClickedEvent.observe(viewLifecycleOwner) { + val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) + binding.contactsAndSuggestionsList.addItemDecoration(headerItemDecoration) + + adapter.contactClickedEvent.observe(viewLifecycleOwner) { it.consume { model -> startCall(model) } } - viewModel.onSuggestionClickedEvent.observe(viewLifecycleOwner) { - it.consume { address -> - coreContext.postOnCoreThread { - coreContext.startCall(address) - } - } - } + binding.contactsAndSuggestionsList.layoutManager = LinearLayoutManager(requireContext()) - binding.contactsList.layoutManager = LinearLayoutManager(requireContext()) - - contactsListViewModel.contactsList.observe( + viewModel.contactsAndSuggestionsList.observe( viewLifecycleOwner ) { - Log.i("$TAG Contacts list is ready with [${it.size}] items") - contactsAdapter.submitList(it) - viewModel.emptyContactsList.value = it.isEmpty() + Log.i("$TAG Contacts & suggestions list is ready with [${it.size}] items") + val count = adapter.itemCount + adapter.submitList(it) - Log.i("$TAG Suggestions list is also ready, start postponed enter transition") - (view.parent as? ViewGroup)?.doOnPreDraw { - startPostponedEnterTransition() + if (count == 0 && it.isNotEmpty()) { + (view.parent as? ViewGroup)?.doOnPreDraw { + startPostponedEnterTransition() + } } } viewModel.searchFilter.observe(viewLifecycleOwner) { filter -> val trimmed = filter.trim() - contactsListViewModel.applyFilter(trimmed) viewModel.applyFilter(trimmed) } @@ -205,9 +196,14 @@ class StartCallFragment : GenericFragment() { numberOrAddressPickerDialog = null } - private fun startCall(model: ContactAvatarModel) { + private fun startCall(model: ContactOrSuggestionModel) { coreContext.postOnCoreThread { core -> val friend = model.friend + if (friend == null) { + coreContext.startCall(model.address) + return@postOnCoreThread + } + val addressesCount = friend.addresses.size val numbersCount = friend.phoneNumbers.size @@ -238,12 +234,15 @@ class StartCallFragment : GenericFragment() { ) coreContext.postOnMainThread { - val model = NumberOrAddressPickerDialogModel(list) + val numberOrAddressModel = NumberOrAddressPickerDialogModel(list) val dialog = - DialogUtils.getNumberOrAddressPickerDialog(requireActivity(), model) + DialogUtils.getNumberOrAddressPickerDialog( + requireActivity(), + numberOrAddressModel + ) numberOrAddressPickerDialog = dialog - model.dismissEvent.observe(viewLifecycleOwner) { event -> + numberOrAddressModel.dismissEvent.observe(viewLifecycleOwner) { event -> event.consume { dialog.dismiss() } diff --git a/app/src/main/java/org/linphone/ui/main/calls/model/SuggestionModel.kt b/app/src/main/java/org/linphone/ui/main/calls/model/ContactOrSuggestionModel.kt similarity index 81% rename from app/src/main/java/org/linphone/ui/main/calls/model/SuggestionModel.kt rename to app/src/main/java/org/linphone/ui/main/calls/model/ContactOrSuggestionModel.kt index 377542b78..7367ddd3a 100644 --- a/app/src/main/java/org/linphone/ui/main/calls/model/SuggestionModel.kt +++ b/app/src/main/java/org/linphone/ui/main/calls/model/ContactOrSuggestionModel.kt @@ -22,20 +22,27 @@ package org.linphone.ui.main.calls.model import androidx.annotation.UiThread import androidx.annotation.WorkerThread import org.linphone.core.Address +import org.linphone.core.Friend +import org.linphone.ui.main.contacts.model.ContactAvatarModel import org.linphone.utils.LinphoneUtils -class SuggestionModel @WorkerThread constructor( +class ContactOrSuggestionModel @WorkerThread constructor( val address: Address, + val friend: Friend? = null, private val onClicked: ((Address) -> Unit)? = null ) { companion object { private const val TAG = "[Suggestion Model]" } + val id = friend?.refKey ?: address.asStringUriOnly().hashCode() + val name = LinphoneUtils.getDisplayName(address) val initials = LinphoneUtils.getInitials(name) + lateinit var contactAvatarModel: ContactAvatarModel + @UiThread fun onClicked() { onClicked?.invoke(address) diff --git a/app/src/main/java/org/linphone/ui/main/calls/viewmodel/StartCallViewModel.kt b/app/src/main/java/org/linphone/ui/main/calls/viewmodel/StartCallViewModel.kt index 47f5b3246..49536b339 100644 --- a/app/src/main/java/org/linphone/ui/main/calls/viewmodel/StartCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/calls/viewmodel/StartCallViewModel.kt @@ -30,13 +30,13 @@ import kotlinx.coroutines.launch import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.contacts.ContactsManager.ContactsListener -import org.linphone.core.Address 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.ContactOrSuggestionModel import org.linphone.ui.main.calls.model.NumpadModel -import org.linphone.ui.main.calls.model.SuggestionModel +import org.linphone.ui.main.contacts.model.ContactAvatarModel import org.linphone.ui.main.model.isInSecureMode import org.linphone.utils.Event @@ -47,9 +47,7 @@ class StartCallViewModel @UiThread constructor() : ViewModel() { val searchFilter = MutableLiveData() - val emptyContactsList = MutableLiveData() - - val suggestionsList = MutableLiveData>() + val contactsAndSuggestionsList = MutableLiveData>() val numpadModel: NumpadModel @@ -67,10 +65,6 @@ class StartCallViewModel @UiThread constructor() : ViewModel() { MutableLiveData>() } - val onSuggestionClickedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - private var currentFilter = "" private var previousFilter = "NotSet" private var limitSearchToLinphoneAccounts = true @@ -92,8 +86,8 @@ class StartCallViewModel @UiThread constructor() : ViewModel() { applyFilter( currentFilter, if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "", - MagicSearch.Source.CallLogs.toInt() or MagicSearch.Source.ChatRooms.toInt() or MagicSearch.Source.Request.toInt(), - MagicSearch.Aggregation.None + MagicSearch.Source.All.toInt(), + MagicSearch.Aggregation.Friend ) } } @@ -119,7 +113,7 @@ class StartCallViewModel @UiThread constructor() : ViewModel() { val address = core.interpretUrl(suggestion, true) if (address != null) { Log.i("$TAG Calling [${address.asStringUriOnly()}]") - onSuggestionClickedEvent.postValue(Event(address)) + coreContext.startCall(address) } else { Log.e("$TAG Failed to parse [$suggestion] as SIP address") } @@ -174,17 +168,32 @@ class StartCallViewModel @UiThread constructor() : ViewModel() { fun processMagicSearchResults(results: Array) { Log.i("$TAG Processing [${results.size}] results") - val list = arrayListOf() + val contactsList = arrayListOf() + val suggestionsList = arrayListOf() + var previousLetter = "" + 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 (friend != null) { + val model = ContactOrSuggestionModel(address, friend) + model.contactAvatarModel = ContactAvatarModel(friend) + + val currentLetter = friend.name?.get(0).toString() + val displayLetter = previousLetter.isEmpty() || currentLetter != previousLetter + if (currentLetter != previousLetter) { + previousLetter = currentLetter + } + model.contactAvatarModel.firstContactStartingByThatLetter.postValue( + displayLetter + ) + + contactsList.add(model) + } else { // 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 { + val found = suggestionsList.find { it.address.weakEqual(address) } if (found != null) { @@ -195,15 +204,18 @@ class StartCallViewModel @UiThread constructor() : ViewModel() { } } - val model = SuggestionModel(address) { - onSuggestionClickedEvent.value = Event(it) + val model = ContactOrSuggestionModel(address) { + coreContext.startCall(address) } - list.add(model) + suggestionsList.add(model) } } } - suggestionsList.postValue(list) + val list = arrayListOf() + list.addAll(contactsList) + list.addAll(suggestionsList) + contactsAndSuggestionsList.postValue(list) Log.i("$TAG Processed [${results.size}] results, extracted [${list.size}] suggestions") } @@ -213,8 +225,8 @@ class StartCallViewModel @UiThread constructor() : ViewModel() { applyFilter( filter, if (limitSearchToLinphoneAccounts) corePreferences.defaultDomain else "", - MagicSearch.Source.CallLogs.toInt() or MagicSearch.Source.ChatRooms.toInt() or MagicSearch.Source.Request.toInt(), - MagicSearch.Aggregation.None + MagicSearch.Source.All.toInt(), + MagicSearch.Aggregation.Friend ) } } diff --git a/app/src/main/java/org/linphone/utils/RecyclerViewHeaderDecoration.kt b/app/src/main/java/org/linphone/utils/RecyclerViewHeaderDecoration.kt new file mode 100644 index 000000000..4b598a4e7 --- /dev/null +++ b/app/src/main/java/org/linphone/utils/RecyclerViewHeaderDecoration.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2010-2020 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.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.util.SparseArray +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView + +class RecyclerViewHeaderDecoration(private val context: Context, private val adapter: HeaderAdapter) : RecyclerView.ItemDecoration() { + private val headers: SparseArray = SparseArray() + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + val position = (view.layoutParams as RecyclerView.LayoutParams).bindingAdapterPosition + + if (position != RecyclerView.NO_POSITION && adapter.displayHeaderForPosition(position)) { + val headerView: View = adapter.getHeaderViewForPosition(view.context, position) + headers.put(position, headerView) + measureHeaderView(headerView, parent) + outRect.top = headerView.height + } else { + headers.remove(position) + } + } + + private fun measureHeaderView(view: View, parent: ViewGroup) { + if (view.layoutParams == null) { + view.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY) + val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.EXACTLY) + val childWidth = ViewGroup.getChildMeasureSpec( + widthSpec, + parent.paddingLeft + parent.paddingRight, + view.layoutParams.width + ) + val childHeight = ViewGroup.getChildMeasureSpec( + heightSpec, + parent.paddingTop + parent.paddingBottom, + view.layoutParams.height + ) + + view.measure(childWidth, childHeight) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + for (i in 0 until parent.childCount) { + val child = parent.getChildAt(i) + val position = parent.getChildAdapterPosition(child) + if (position != RecyclerView.NO_POSITION && adapter.displayHeaderForPosition(position)) { + canvas.save() + val headerView: View = headers.get(position) ?: adapter.getHeaderViewForPosition( + context, + position + ) + canvas.translate(0f, child.y - headerView.height) + headerView.draw(canvas) + canvas.restore() + } + } + } +} + +interface HeaderAdapter { + fun displayHeaderForPosition(position: Int): Boolean + + fun getHeaderViewForPosition(context: Context, position: Int): View +} diff --git a/app/src/main/res/layout/account_profile_secure_mode_fragment.xml b/app/src/main/res/layout/account_profile_secure_mode_fragment.xml index 5d7a6e15f..38bd9a712 100644 --- a/app/src/main/res/layout/account_profile_secure_mode_fragment.xml +++ b/app/src/main/res/layout/account_profile_secure_mode_fragment.xml @@ -251,7 +251,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="32dp" - android:layout_marginBottom="32dp" + android:layout_marginBottom="@dimen/screen_bottom_margin" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:paddingStart="20dp" diff --git a/app/src/main/res/layout/assistant_login_fragment.xml b/app/src/main/res/layout/assistant_login_fragment.xml index fcc27d5b8..faa82b37c 100644 --- a/app/src/main/res/layout/assistant_login_fragment.xml +++ b/app/src/main/res/layout/assistant_login_fragment.xml @@ -280,7 +280,7 @@ android:layout_height="wrap_content" android:layout_marginTop="40dp" android:layout_marginStart="20dp" - android:layout_marginBottom="32dp" + android:layout_marginBottom="@dimen/screen_bottom_margin" android:paddingStart="20dp" android:paddingEnd="20dp" android:text="@string/assistant_account_register" diff --git a/app/src/main/res/layout/assistant_register_fragment.xml b/app/src/main/res/layout/assistant_register_fragment.xml index 30dfd020d..e4eae8322 100644 --- a/app/src/main/res/layout/assistant_register_fragment.xml +++ b/app/src/main/res/layout/assistant_register_fragment.xml @@ -300,7 +300,7 @@ android:layout_height="wrap_content" android:layout_marginTop="34dp" android:layout_marginStart="20dp" - android:layout_marginBottom="32dp" + android:layout_marginBottom="@dimen/screen_bottom_margin" android:paddingStart="20dp" android:paddingEnd="20dp" android:text="@string/assistant_account_login" diff --git a/app/src/main/res/layout/assistant_secure_mode_fragment.xml b/app/src/main/res/layout/assistant_secure_mode_fragment.xml index 0ba3a64de..5dcc91c63 100644 --- a/app/src/main/res/layout/assistant_secure_mode_fragment.xml +++ b/app/src/main/res/layout/assistant_secure_mode_fragment.xml @@ -266,7 +266,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="32dp" - android:layout_marginBottom="32dp" + android:layout_marginBottom="@dimen/screen_bottom_margin" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:paddingStart="20dp" diff --git a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml index 9cff69964..459a10f47 100644 --- a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml +++ b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml @@ -234,7 +234,7 @@ android:layout_marginTop="32dp" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" - android:layout_marginBottom="32dp" + android:layout_marginBottom="@dimen/screen_bottom_margin" android:paddingStart="20dp" android:paddingEnd="20dp" android:text="@string/assistant_account_login" diff --git a/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml b/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml index b73b12e13..d32e61ab3 100644 --- a/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml +++ b/app/src/main/res/layout/assistant_third_party_sip_account_warning_fragment.xml @@ -147,7 +147,7 @@ android:layout_marginTop="32dp" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" - android:layout_marginBottom="32dp" + android:layout_marginBottom="@dimen/screen_bottom_margin" android:paddingStart="20dp" android:paddingEnd="20dp" android:text="@string/assistant_third_party_sip_account_warning_ok" diff --git a/app/src/main/res/layout/call_start_fragment.xml b/app/src/main/res/layout/call_start_fragment.xml index 647f39e75..bf4263a20 100644 --- a/app/src/main/res/layout/call_start_fragment.xml +++ b/app/src/main/res/layout/call_start_fragment.xml @@ -81,6 +81,8 @@ android:paddingBottom="10dp" android:text="@={viewModel.searchFilter}" android:textSize="14sp" + app:layout_constraintHeight_min="48dp" + app:layout_constraintWidth_max="@dimen/text_input_max_width" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/title" /> @@ -159,17 +161,17 @@ + app:layout_constraintBottom_toBottomOf="parent" /> + app:layout_constraintBottom_toTopOf="@id/contacts_and_suggestions_list"/> - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/contacts_and_suggestions_label" + app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/app/src/main/res/layout/call_suggestion_list_cell.xml b/app/src/main/res/layout/call_suggestion_list_cell.xml index bb1900ec3..d4ab0f052 100644 --- a/app/src/main/res/layout/call_suggestion_list_cell.xml +++ b/app/src/main/res/layout/call_suggestion_list_cell.xml @@ -5,13 +5,16 @@ + + type="org.linphone.ui.main.calls.model.ContactOrSuggestionModel" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/contact_list_cell.xml b/app/src/main/res/layout/contact_list_cell.xml index 1539222cf..66357d31a 100644 --- a/app/src/main/res/layout/contact_list_cell.xml +++ b/app/src/main/res/layout/contact_list_cell.xml @@ -23,7 +23,6 @@ android:onLongClick="@{onLongClickListener}" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/white" android:layout_marginStart="4dp" android:layout_marginEnd="16dp"> 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 493ade7cd..eb2048fd5 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 @@ -257,7 +257,7 @@ android:layout_marginTop="5dp" android:paddingStart="20dp" android:paddingEnd="20dp" - android:layout_marginBottom="32dp" + android:layout_marginBottom="@dimen/screen_bottom_margin" android:text="@={viewModel.jobTitle, default=`Android dev`}" android:textSize="14sp" android:textColor="@color/gray_9" diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml index f998bba2b..16e13c125 100644 --- a/app/src/main/res/layout/settings_fragment.xml +++ b/app/src/main/res/layout/settings_fragment.xml @@ -492,7 +492,7 @@ android:layout_marginStart="26dp" android:layout_marginEnd="26dp" android:layout_marginTop="16dp" - android:layout_marginBottom="32dp" + android:layout_marginBottom="@dimen/screen_bottom_margin" android:text="@string/settings_advanced_title" android:drawableEnd="@drawable/caret_right" android:drawableTint="@color/gray_9" diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 27c15034a..e11b683a6 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -2,6 +2,8 @@ 0dp + 32dp + 75dp 400dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7707f845c..c8750e55c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -208,6 +208,7 @@ Search contact or history call Create a group call No suggestion and no contact for the moment… + Contacts Suggestions No call for the moment…