diff --git a/app/src/main/java/org/linphone/ui/call/adapter/CallsListAdapter.kt b/app/src/main/java/org/linphone/ui/call/adapter/CallsListAdapter.kt
new file mode 100644
index 000000000..71917372d
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/call/adapter/CallsListAdapter.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.call.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.CallListCellBinding
+import org.linphone.ui.call.model.CallModel
+import org.linphone.utils.Event
+
+class CallsListAdapter(private val viewLifecycleOwner: LifecycleOwner) :
+ ListAdapter(CallDiffCallback()) {
+ var selectedAdapterPosition = -1
+
+ val callClickedEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+
+ val callLongClickedEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ val binding: CallListCellBinding = DataBindingUtil.inflate(
+ LayoutInflater.from(parent.context),
+ R.layout.call_list_cell,
+ parent,
+ false
+ )
+ return ViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ (holder as ViewHolder).bind(getItem(position))
+ }
+
+ fun resetSelection() {
+ notifyItemChanged(selectedAdapterPosition)
+ selectedAdapterPosition = -1
+ }
+
+ inner class ViewHolder(
+ val binding: CallListCellBinding
+ ) : RecyclerView.ViewHolder(binding.root) {
+ @UiThread
+ fun bind(callModel: CallModel) {
+ with(binding) {
+ model = callModel
+
+ lifecycleOwner = viewLifecycleOwner
+
+ binding.root.isSelected = bindingAdapterPosition == selectedAdapterPosition
+
+ binding.setOnClickListener {
+ callClickedEvent.value = Event(callModel)
+ }
+
+ binding.setOnLongClickListener {
+ selectedAdapterPosition = bindingAdapterPosition
+ binding.root.isSelected = true
+ callLongClickedEvent.value = Event(callModel)
+ true
+ }
+
+ executePendingBindings()
+ }
+ }
+ }
+
+ private class CallDiffCallback : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: CallModel, newItem: CallModel): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(oldItem: CallModel, newItem: CallModel): Boolean {
+ return false
+ }
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt
index 0726d1aac..1aba0b716 100644
--- a/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt
+++ b/app/src/main/java/org/linphone/ui/call/fragment/ActiveCallFragment.kt
@@ -40,6 +40,7 @@ import org.linphone.core.tools.Log
import org.linphone.databinding.CallActiveFragmentBinding
import org.linphone.ui.call.CallActivity
import org.linphone.ui.call.model.ZrtpSasConfirmationDialogModel
+import org.linphone.ui.call.viewmodel.CallsViewModel
import org.linphone.ui.call.viewmodel.CurrentCallViewModel
import org.linphone.ui.call.viewmodel.SharedCallViewModel
import org.linphone.utils.AppUtils
@@ -56,6 +57,8 @@ class ActiveCallFragment : GenericCallFragment() {
private lateinit var callViewModel: CurrentCallViewModel
+ private lateinit var callsViewModel: CallsViewModel
+
// For moving video preview purposes
private var previewX: Float = 0f
@@ -107,8 +110,13 @@ class ActiveCallFragment : GenericCallFragment() {
ViewModelProvider(this)[CurrentCallViewModel::class.java]
}
+ callsViewModel = requireActivity().run {
+ ViewModelProvider(this)[CallsViewModel::class.java]
+ }
+
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = callViewModel
+ binding.callsViewModel = callsViewModel
val bottomSheetBehavior = BottomSheetBehavior.from(binding.bottomBar.root)
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
diff --git a/app/src/main/java/org/linphone/ui/call/fragment/CallMenuDialogFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/CallMenuDialogFragment.kt
new file mode 100644
index 000000000..00f469b8c
--- /dev/null
+++ b/app/src/main/java/org/linphone/ui/call/fragment/CallMenuDialogFragment.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.call.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.CallsListLongPressMenuBinding
+import org.linphone.ui.call.model.CallModel
+
+class CallMenuDialogFragment(
+ private val callModel: CallModel,
+ private val onDismiss: (() -> Unit)? = null
+) : BottomSheetDialogFragment() {
+ companion object {
+ const val TAG = "CallMenuDialogFragment"
+ }
+
+ 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 = CallsListLongPressMenuBinding.inflate(layoutInflater)
+
+ view.setHangUpClickListener {
+ callModel.hangUp()
+ dismiss()
+ }
+
+ return view.root
+ }
+}
diff --git a/app/src/main/java/org/linphone/ui/call/fragment/CallsListFragment.kt b/app/src/main/java/org/linphone/ui/call/fragment/CallsListFragment.kt
index cfeb4839c..2cabff9ea 100644
--- a/app/src/main/java/org/linphone/ui/call/fragment/CallsListFragment.kt
+++ b/app/src/main/java/org/linphone/ui/call/fragment/CallsListFragment.kt
@@ -23,10 +23,12 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
-import androidx.navigation.navGraphViewModels
-import org.linphone.R
+import androidx.recyclerview.widget.LinearLayoutManager
+import org.linphone.core.tools.Log
import org.linphone.databinding.CallsListFragmentBinding
+import org.linphone.ui.call.adapter.CallsListAdapter
import org.linphone.ui.call.viewmodel.CallsViewModel
class CallsListFragment : GenericCallFragment() {
@@ -36,9 +38,9 @@ class CallsListFragment : GenericCallFragment() {
private lateinit var binding: CallsListFragmentBinding
- private val viewModel: CallsViewModel by navGraphViewModels(
- R.id.call_nav_graph
- )
+ private lateinit var viewModel: CallsViewModel
+
+ private lateinit var adapter: CallsListAdapter
override fun onCreateView(
inflater: LayoutInflater,
@@ -52,11 +54,43 @@ class CallsListFragment : GenericCallFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ viewModel = requireActivity().run {
+ ViewModelProvider(this)[CallsViewModel::class.java]
+ }
+
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
+ adapter = CallsListAdapter(viewLifecycleOwner)
+ binding.callsList.setHasFixedSize(true)
+ binding.callsList.adapter = adapter
+
+ val layoutManager = LinearLayoutManager(requireContext())
+ binding.callsList.layoutManager = layoutManager
+
+ adapter.callLongClickedEvent.observe(viewLifecycleOwner) {
+ it.consume { model ->
+ val modalBottomSheet = CallMenuDialogFragment(model) {
+ // onDismiss
+ adapter.resetSelection()
+ }
+ modalBottomSheet.show(parentFragmentManager, CallMenuDialogFragment.TAG)
+ }
+ }
+
+ adapter.callClickedEvent.observe(viewLifecycleOwner) {
+ it.consume { model ->
+ model.togglePauseResume()
+ }
+ }
+
binding.setBackClickListener {
findNavController().popBackStack()
}
+
+ viewModel.calls.observe(viewLifecycleOwner) {
+ Log.i("$TAG Calls list updated with [${it.size}] items")
+ adapter.submitList(it)
+ }
}
}
diff --git a/app/src/main/java/org/linphone/ui/call/model/CallModel.kt b/app/src/main/java/org/linphone/ui/call/model/CallModel.kt
index ead266f46..c4de2c22b 100644
--- a/app/src/main/java/org/linphone/ui/call/model/CallModel.kt
+++ b/app/src/main/java/org/linphone/ui/call/model/CallModel.kt
@@ -19,15 +19,23 @@
*/
package org.linphone.ui.call.model
+import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Call
import org.linphone.core.CallListenerStub
+import org.linphone.core.tools.Log
import org.linphone.ui.main.contacts.model.ContactAvatarModel
import org.linphone.utils.LinphoneUtils
class CallModel @WorkerThread constructor(val call: Call) {
+ companion object {
+ private const val TAG = "[Call Model]"
+ }
+
+ val id = call.callLog.callId
+
val displayName = MutableLiveData()
val state = MutableLiveData()
@@ -61,4 +69,26 @@ class CallModel @WorkerThread constructor(val call: Call) {
fun destroy() {
call.removeListener(callListener)
}
+
+ @WorkerThread
+ fun togglePauseResume() {
+ when (call.state) {
+ Call.State.Paused -> {
+ Log.i("$TAG Trying to resume call [${call.remoteAddress.asStringUriOnly()}]")
+ call.resume()
+ }
+ else -> {
+ Log.i("$TAG Trying to resume call [${call.remoteAddress.asStringUriOnly()}]")
+ call.pause()
+ }
+ }
+ }
+
+ @UiThread
+ fun hangUp() {
+ coreContext.postOnCoreThread {
+ Log.i("$TAG Terminating call [${call.remoteAddress.asStringUriOnly()}]")
+ call.terminate()
+ }
+ }
}
diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CallsViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CallsViewModel.kt
index 5afd5644c..8027a3dd7 100644
--- a/app/src/main/java/org/linphone/ui/call/viewmodel/CallsViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CallsViewModel.kt
@@ -37,6 +37,7 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
companion object {
private const val TAG = "[Calls ViewModel]"
+ // Keys are hardcoded in SDK
private const val ALERT_NETWORK_TYPE_KEY = "network-type"
private const val ALERT_NETWORK_TYPE_WIFI = "wifi"
private const val ALERT_NETWORK_TYPE_CELLULAR = "mobile"
@@ -44,6 +45,8 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
val calls = MutableLiveData>()
+ val callsCount = MutableLiveData()
+
val goToActiveCallEvent = MutableLiveData>()
val showIncomingCallEvent = MutableLiveData>()
@@ -111,6 +114,7 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
val model = CallModel(call)
list.add(model)
calls.postValue(list)
+ callsCount.postValue(list.size)
}
} else {
if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) {
@@ -176,6 +180,7 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
list.add(model)
}
calls.postValue(list)
+ callsCount.postValue(list.size)
val currentCall = core.currentCall ?: core.calls.first()
@@ -204,6 +209,7 @@ class CallsViewModel @UiThread constructor() : ViewModel() {
coreContext.postOnCoreThread { core ->
calls.value.orEmpty().forEach(CallModel::destroy)
+ callsCount.postValue(0)
core.removeListener(coreListener)
}
}
diff --git a/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryMenuDialogFragment.kt b/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryMenuDialogFragment.kt
index c3af0c466..bb8d993b0 100644
--- a/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryMenuDialogFragment.kt
+++ b/app/src/main/java/org/linphone/ui/main/history/fragment/HistoryMenuDialogFragment.kt
@@ -38,7 +38,7 @@ class HistoryMenuDialogFragment(
private val onDeleteCallLog: (() -> Unit)? = null
) : BottomSheetDialogFragment() {
companion object {
- const val TAG = "CallsListMenuDialogFragment"
+ const val TAG = "HistoryMenuDialogFragment"
}
override fun onCancel(dialog: DialogInterface) {
diff --git a/app/src/main/res/layout-land/call_extra_actions.xml b/app/src/main/res/layout-land/call_extra_actions.xml
index 825bae7f5..ae8aea999 100644
--- a/app/src/main/res/layout-land/call_extra_actions.xml
+++ b/app/src/main/res/layout-land/call_extra_actions.xml
@@ -17,6 +17,9 @@
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/calls_list"/>
+
diff --git a/app/src/main/res/layout/call_extra_actions.xml b/app/src/main/res/layout/call_extra_actions.xml
index 9f9f8e954..7e9b41c5e 100644
--- a/app/src/main/res/layout/call_extra_actions.xml
+++ b/app/src/main/res/layout/call_extra_actions.xml
@@ -17,6 +17,9 @@
+
+
+
+ app:layout_constraintStart_toStartOf="@id/calls_list"
+ app:layout_constraintEnd_toEndOf="@id/calls_list" />
diff --git a/app/src/main/res/layout/call_list_cell.xml b/app/src/main/res/layout/call_list_cell.xml
index a3dda8d77..eafff4c77 100644
--- a/app/src/main/res/layout/call_list_cell.xml
+++ b/app/src/main/res/layout/call_list_cell.xml
@@ -5,27 +5,25 @@
+
+
-
-
+ android:layout_marginEnd="16dp"
+ android:background="@drawable/primary_cell_background">
-
+ app:layout_constraintBottom_toBottomOf="parent"/>
diff --git a/app/src/main/res/layout/calls_list_long_press_menu.xml b/app/src/main/res/layout/calls_list_long_press_menu.xml
new file mode 100644
index 000000000..f847f9c40
--- /dev/null
+++ b/app/src/main/res/layout/calls_list_long_press_menu.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/history_fragment.xml b/app/src/main/res/layout/history_fragment.xml
index f6342b074..b31826fac 100644
--- a/app/src/main/res/layout/history_fragment.xml
+++ b/app/src/main/res/layout/history_fragment.xml
@@ -12,7 +12,7 @@
android:layout_height="match_parent">
Pause
Pause
Record
+ Hang up
In progress
Ringing
Incoming