Added long press context menu to phone number & sip address in contact details

This commit is contained in:
Sylvain Berfini 2023-08-08 16:09:25 +02:00
parent 7c0f9585e7
commit e147efd358
12 changed files with 151 additions and 52 deletions

View file

@ -19,15 +19,18 @@
*/
package org.linphone.ui.contacts
import androidx.lifecycle.MutableLiveData
import org.linphone.core.Address
class ContactNumberOrAddressData(
class ContactNumberOrAddressModel(
val address: Address?,
val displayedValue: String,
private val listener: ContactNumberOrAddressClickListener,
val isSip: Boolean = true,
val label: String = ""
) {
val selected = MutableLiveData<Boolean>()
fun startCall() {
address ?: return
listener.onCall(address)
@ -42,6 +45,12 @@ class ContactNumberOrAddressData(
address ?: return
listener.onChat(address)
}
fun onLongPress(): Boolean {
selected.value = true
listener.onLongPress(this)
return true
}
}
interface ContactNumberOrAddressClickListener {
@ -50,4 +59,6 @@ interface ContactNumberOrAddressClickListener {
fun onVideoCall(address: Address)
fun onChat(address: Address)
fun onLongPress(model: ContactNumberOrAddressModel)
}

View file

@ -74,10 +74,22 @@ class ContactFragment : GenericFragment() {
viewModel.showBackButton.value = slideable
}
viewModel.contact.observe(viewLifecycleOwner) {
viewModel.contactFoundEvent.observe(viewLifecycleOwner) {
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
sharedViewModel.openSlidingPaneEvent.value = Event(true)
}
sharedViewModel.openSlidingPaneEvent.value = Event(true)
}
viewModel.showLongPressMenuForNumberOrAddress.observe(viewLifecycleOwner) {
it.consume { model ->
val modalBottomSheet = ContactNumberOrAddressMenuDialogFragment() {
model.selected.value = false
}
modalBottomSheet.show(
parentFragmentManager,
ContactNumberOrAddressMenuDialogFragment.TAG
)
}
}
}

View file

@ -0,0 +1,56 @@
/*
* 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.contacts.fragment
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.linphone.databinding.ContactNumberOrAddressLongPressMenuBinding
class ContactNumberOrAddressMenuDialogFragment(
private val onDismiss: (() -> Unit)? = null
) : BottomSheetDialogFragment() {
companion object {
const val TAG = "ContactNumberOrAddressMenuDialogFragment"
}
override fun onCancel(dialog: DialogInterface) {
onDismiss?.invoke()
super.onCancel(dialog)
}
override fun onDismiss(dialog: DialogInterface) {
onDismiss?.invoke()
super.onDismiss(dialog)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = ContactNumberOrAddressLongPressMenuBinding.inflate(layoutInflater)
return view.root
}
}

View file

@ -24,14 +24,14 @@ import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Address
import org.linphone.ui.contacts.ContactNumberOrAddressClickListener
import org.linphone.ui.contacts.ContactNumberOrAddressData
import org.linphone.ui.contacts.ContactNumberOrAddressModel
import org.linphone.ui.contacts.model.ContactModel
import org.linphone.utils.Event
class ContactViewModel : ViewModel() {
val contact = MutableLiveData<ContactModel>()
val sipAddressesAndPhoneNumbers = MutableLiveData<ArrayList<ContactNumberOrAddressData>>()
val sipAddressesAndPhoneNumbers = MutableLiveData<ArrayList<ContactNumberOrAddressModel>>()
val company = MutableLiveData<String>()
@ -43,7 +43,11 @@ class ContactViewModel : ViewModel() {
val showDevicesTrust = MutableLiveData<Boolean>()
val friendFoundEvent = MutableLiveData<Event<Boolean>>()
val contactFoundEvent = MutableLiveData<Event<Boolean>>()
val showLongPressMenuForNumberOrAddress: MutableLiveData<Event<ContactNumberOrAddressModel>> by lazy {
MutableLiveData<Event<ContactNumberOrAddressModel>>()
}
val listener = object : ContactNumberOrAddressClickListener {
override fun onCall(address: Address) {
@ -57,6 +61,10 @@ class ContactViewModel : ViewModel() {
override fun onChat(address: Address) {
// UI thread
}
override fun onLongPress(model: ContactNumberOrAddressModel) {
showLongPressMenuForNumberOrAddress.value = Event(model)
}
}
init {
@ -69,15 +77,16 @@ class ContactViewModel : ViewModel() {
coreContext.postOnCoreThread { core ->
val friend = coreContext.contactsManager.findContactById(refKey)
if (friend != null) {
contact.postValue(ContactModel(friend))
val organization = friend.organization
if (!organization.isNullOrEmpty()) {
company.postValue(organization)
showCompany.postValue(true)
}
val addressesAndNumbers = arrayListOf<ContactNumberOrAddressData>()
val addressesAndNumbers = arrayListOf<ContactNumberOrAddressModel>()
for (address in friend.addresses) {
val data = ContactNumberOrAddressData(
val data = ContactNumberOrAddressModel(
address,
address.asStringUriOnly(),
listener,
@ -87,7 +96,7 @@ class ContactViewModel : ViewModel() {
}
for (number in friend.phoneNumbersWithLabel) {
val address = core.interpretUrl(number.phoneNumber, true)
val data = ContactNumberOrAddressData(
val data = ContactNumberOrAddressModel(
address,
number.phoneNumber,
listener,
@ -97,7 +106,7 @@ class ContactViewModel : ViewModel() {
addressesAndNumbers.add(data)
}
sipAddressesAndPhoneNumbers.postValue(addressesAndNumbers)
contact.postValue(ContactModel(friend))
contactFoundEvent.postValue(Event(true))
}
}
}

View file

@ -37,11 +37,11 @@ abstract class GenericFragment : Fragment() {
override fun handleOnBackPressed() {
try {
val navController = findNavController()
Log.i("[Generic Fragment] ${getFragmentRealClassName()} handleOnBackPressed")
Log.d("[Generic Fragment] ${getFragmentRealClassName()} handleOnBackPressed")
if (!navController.popBackStack()) {
Log.i("[Generic Fragment] ${getFragmentRealClassName()} couldn't pop")
Log.d("[Generic Fragment] ${getFragmentRealClassName()} couldn't pop")
if (!navController.navigateUp()) {
Log.i(
Log.d(
"[Generic Fragment] ${getFragmentRealClassName()} couldn't navigate up"
)
// Disable this callback & start a new back press event
@ -65,7 +65,7 @@ abstract class GenericFragment : Fragment() {
}
sharedViewModel.isSlidingPaneSlideable.observe(viewLifecycleOwner) {
Log.i(
Log.d(
"[Generic Fragment] ${getFragmentRealClassName()} shared main VM sliding pane has changed"
)
onBackPressedCallback.isEnabled = backPressedCallBackEnabled()
@ -94,11 +94,11 @@ abstract class GenericFragment : Fragment() {
}
private fun setupBackPressCallback() {
Log.i("[Generic Fragment] ${getFragmentRealClassName()} setupBackPressCallback")
Log.d("[Generic Fragment] ${getFragmentRealClassName()} setupBackPressCallback")
val backButton = view?.findViewById<ImageView>(R.id.back)
if (backButton != null && backButton.visibility == View.VISIBLE) {
Log.i("[Generic Fragment] ${getFragmentRealClassName()} found back button")
Log.d("[Generic Fragment] ${getFragmentRealClassName()} found back button")
// If popping navigation back stack entry would bring us to an "empty" fragment
// then don't do it if sliding pane layout isn't "flat"
onBackPressedCallback.isEnabled = backPressedCallBackEnabled()
@ -120,15 +120,15 @@ abstract class GenericFragment : Fragment() {
if (findNavController().graph.id == R.id.main_nav_graph) return false
val isSlidingPaneFlat = sharedViewModel.isSlidingPaneSlideable.value == false
Log.i(
Log.d(
"[Generic Fragment] ${getFragmentRealClassName()} isSlidingPaneFlat ? $isSlidingPaneFlat"
)
val isPreviousFragmentEmpty = findNavController().previousBackStackEntry?.destination?.id == R.id.emptyFragment
Log.i(
Log.d(
"[Generic Fragment] ${getFragmentRealClassName()} isPreviousFragmentEmpty ? $isPreviousFragmentEmpty"
)
val popBackStack = isSlidingPaneFlat || !isPreviousFragmentEmpty
Log.i("[Generic Fragment] ${getFragmentRealClassName()} popBackStack ? $popBackStack")
Log.d("[Generic Fragment] ${getFragmentRealClassName()} popBackStack ? $popBackStack")
return popBackStack
}
}

View file

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:topLeftRadius="30dp" android:topRightRadius="30dp" />
<solid android:color="@color/white"/>
</shape>

View file

@ -80,6 +80,7 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/conversations"
android:enabled="false"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableTop="@drawable/conversations"

View file

@ -85,6 +85,7 @@
<androidx.appcompat.widget.AppCompatTextView
android:onClick="@{onConversationsClicked}"
android:enabled="false"
android:id="@+id/conversations"
android:layout_width="0dp"
android:layout_height="wrap_content"

View file

@ -74,15 +74,6 @@
app:constraint_referenced_ids="info_background, company_label, company"
android:visibility="@{viewModel.showCompany ? View.VISIBLE : View.GONE}" />
<ImageView
android:id="@+id/background"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<io.getstream.avatarview.AvatarView
android:id="@+id/avatar"
android:layout_width="72dp"
@ -338,7 +329,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="17dp"
android:layout_marginEnd="17dp"
android:gravity="left"
android:text="Edit"
style="@style/context_menu_action_label"
android:drawableLeft="@drawable/edit"
@ -362,7 +352,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="17dp"
android:layout_marginEnd="17dp"
android:gravity="left"
android:text="Add to favourites"
style="@style/context_menu_action_label"
android:drawableLeft="@drawable/favorite"
@ -386,7 +375,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="17dp"
android:layout_marginEnd="17dp"
android:gravity="left"
android:text="Share"
style="@style/context_menu_action_label"
android:drawableLeft="@drawable/share"
@ -410,7 +398,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="17dp"
android:layout_marginEnd="17dp"
android:gravity="left"
android:text="Invite"
style="@style/context_menu_action_label"
android:drawableLeft="@drawable/invite"
@ -434,7 +421,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="17dp"
android:layout_marginEnd="17dp"
android:gravity="left"
android:text="Delete"
style="@style/context_menu_danger_action_label"
android:drawableLeft="@drawable/delete"

View file

@ -8,25 +8,16 @@
<import type="android.graphics.Typeface" />
<variable
name="model"
type="org.linphone.ui.contacts.ContactNumberOrAddressData" />
type="org.linphone.ui.contacts.ContactNumberOrAddressModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:onLongClick="@{() -> model.onLongPress()}"
android:selected="@{model.selected}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/cell_background">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/calls"
app:tint="@color/gray_9"
android:layout_marginStart="10dp"
app:layout_constraintTop_toTopOf="@id/header"
app:layout_constraintBottom_toBottomOf="@id/number_or_address"
app:layout_constraintStart_toStartOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/header"
android:layout_width="wrap_content"
@ -39,7 +30,7 @@
android:textSize="14sp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
@ -52,14 +43,14 @@
android:textSize="14sp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header"/>
<ImageView
android:id="@+id/copy"
android:id="@+id/call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/copy"
android:src="@drawable/calls"
app:tint="@color/gray_9"
android:layout_marginEnd="10dp"
app:layout_constraintTop_toTopOf="@id/header"

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<variable
name="copyNumberOrAddressClickListener"
type="View.OnClickListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/separator">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/copy"
android:onClick="@{copyNumberOrAddressClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Copier le numéro"
style="@style/context_menu_action_label"
android:background="@color/gray_2"
android:layout_marginBottom="1dp"
android:drawableLeft="@drawable/copy"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -3,7 +3,7 @@
<style name="context_menu_action_label">
<item name="android:textSize">17sp</item>
<item name="android:textColor">@color/gray_1</item>
<item name="android:gravity">center</item>
<item name="android:gravity">start</item>
<item name="android:padding">20dp</item>
<item name="android:drawableTint">@color/gray_9</item>
<item name="android:drawablePadding">8dp</item>
@ -11,7 +11,7 @@
<style name="context_menu_danger_action_label">
<item name="android:textSize">17sp</item>
<item name="android:textColor">@color/red_danger</item>
<item name="android:gravity">center</item>
<item name="android:gravity">start</item>
<item name="android:padding">20dp</item>
<item name="android:drawableTint">@color/red_danger</item>
<item name="android:drawablePadding">8dp</item>