Started call logs

This commit is contained in:
Sylvain Berfini 2023-08-13 12:25:58 +02:00
parent 571fa8c885
commit 0a83695c45
27 changed files with 865 additions and 19 deletions

17
.idea/deploymentTargetDropDown.xml generated Normal file
View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_3a_API_34_extension_level_7_x86_64.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-08-13T08:18:53.498566591Z" />
</component>
</project>

View file

@ -132,6 +132,7 @@ class CoreContext(val context: Context) : HandlerThread("Core Thread") {
forceZRTP: Boolean = false,
localAddress: Address? = null
) {
// Core thread
if (!core.isNetworkReachable) {
Log.e("[Context] Network unreachable, abort outgoing call")
return

View file

@ -0,0 +1,92 @@
package org.linphone.ui.main.calls.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
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.main.calls.model.CallLogModel
import org.linphone.utils.Event
class CallsListAdapter(
private val viewLifecycleOwner: LifecycleOwner
) : ListAdapter<CallLogModel, RecyclerView.ViewHolder>(CallLogDiffCallback()) {
var selectedAdapterPosition = -1
val callLogClickedEvent: MutableLiveData<Event<CallLogModel>> by lazy {
MutableLiveData<Event<CallLogModel>>()
}
val callLogLongClickedEvent: MutableLiveData<Event<CallLogModel>> by lazy {
MutableLiveData<Event<CallLogModel>>()
}
val callLogCallBackClickedEvent: MutableLiveData<Event<CallLogModel>> by lazy {
MutableLiveData<Event<CallLogModel>>()
}
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) {
fun bind(callLogModel: CallLogModel) {
with(binding) {
model = callLogModel
lifecycleOwner = viewLifecycleOwner
binding.root.isSelected = bindingAdapterPosition == selectedAdapterPosition
binding.setOnClickListener {
callLogClickedEvent.value = Event(callLogModel)
}
binding.setOnLongClickListener {
selectedAdapterPosition = bindingAdapterPosition
binding.root.isSelected = true
callLogLongClickedEvent.value = Event(callLogModel)
true
}
binding.setOnCallClickListener {
callLogCallBackClickedEvent.value = Event(callLogModel)
}
executePendingBindings()
}
}
}
}
private class CallLogDiffCallback : DiffUtil.ItemCallback<CallLogModel>() {
override fun areItemsTheSame(oldItem: CallLogModel, newItem: CallLogModel): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: CallLogModel, newItem: CallLogModel): Boolean {
return oldItem.avatarModel.id == newItem.avatarModel.id
}
}

View file

@ -0,0 +1,84 @@
/*
* 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.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.navArgs
import org.linphone.core.tools.Log
import org.linphone.databinding.CallFragmentBinding
import org.linphone.ui.main.calls.viewmodel.CallLogViewModel
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.Event
class CallFragment : GenericFragment() {
private lateinit var binding: CallFragmentBinding
private lateinit var viewModel: CallLogViewModel
private val args: CallFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = CallFragmentBinding.inflate(layoutInflater)
return binding.root
}
override fun goBack() {
sharedViewModel.closeSlidingPaneEvent.value = Event(true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
binding.lifecycleOwner = viewLifecycleOwner
viewModel = ViewModelProvider(this)[CallLogViewModel::class.java]
binding.viewModel = viewModel
val callId = args.callId
Log.i("[Call Fragment] Looking up for call log with call id [$callId]")
viewModel.findCallLogByCallId(callId)
binding.setBackClickListener {
goBack()
}
sharedViewModel.isSlidingPaneSlideable.observe(viewLifecycleOwner) { slideable ->
viewModel.showBackButton.value = slideable
}
viewModel.callLogFoundEvent.observe(viewLifecycleOwner) {
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
sharedViewModel.openSlidingPaneEvent.value = Event(true)
}
}
}

View file

@ -28,6 +28,7 @@ import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.slidingpanelayout.widget.SlidingPaneLayout
import org.linphone.R
import org.linphone.core.tools.Log
import org.linphone.databinding.CallsFragmentBinding
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.SlidingPaneBackPressedCallback
@ -77,6 +78,19 @@ class CallsFragment : GenericFragment() {
}
}
sharedViewModel.showCallLogEvent.observe(
viewLifecycleOwner
) {
it.consume { callId ->
Log.i("[Calls Fragment] Displaying call log with call ID [$callId]")
val navController = binding.callsRightNavContainer.findNavController()
val action = CallFragmentDirections.actionGlobalCallFragment(
callId
)
navController.navigate(action)
}
}
sharedViewModel.navigateToConversationsEvent.observe(viewLifecycleOwner) {
it.consume {
if (findNavController().currentDestination?.id == R.id.callsFragment) {

View file

@ -28,11 +28,15 @@ import android.view.animation.Animation
import android.view.animation.AnimationUtils
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.databinding.CallsListFragmentBinding
import org.linphone.ui.main.MainActivity
import org.linphone.ui.main.calls.adapter.CallsListAdapter
import org.linphone.ui.main.calls.viewmodel.CallsListViewModel
import org.linphone.ui.main.fragment.GenericFragment
import org.linphone.utils.Event
import org.linphone.utils.setKeyboardInsetListener
class CallsListFragment : GenericFragment() {
@ -43,6 +47,8 @@ class CallsListFragment : GenericFragment() {
R.id.callsListFragment
)
private lateinit var adapter: CallsListAdapter
override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
if (findNavController().currentDestination?.id == R.id.newContactFragment) {
// Holds fragment in place while new contact fragment slides over it
@ -65,13 +71,50 @@ class CallsListFragment : GenericFragment() {
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = listViewModel
postponeEnterTransition()
binding.root.setKeyboardInsetListener { keyboardVisible ->
val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE
listViewModel.bottomNavBarVisible.value = !portraitOrientation || !keyboardVisible
}
adapter = CallsListAdapter(viewLifecycleOwner)
binding.callsList.setHasFixedSize(true)
binding.callsList.adapter = adapter
adapter.callLogLongClickedEvent.observe(viewLifecycleOwner) {
it.consume { model ->
val modalBottomSheet = CallsListMenuDialogFragment(model.callLog) {
adapter.resetSelection()
}
modalBottomSheet.show(parentFragmentManager, CallsListMenuDialogFragment.TAG)
}
}
adapter.callLogClickedEvent.observe(viewLifecycleOwner) {
it.consume { model ->
sharedViewModel.showCallLogEvent.value = Event(model.id ?: "")
}
}
adapter.callLogCallBackClickedEvent.observe(viewLifecycleOwner) {
it.consume { model ->
coreContext.postOnCoreThread {
coreContext.startCall(model.address)
}
}
}
val layoutManager = LinearLayoutManager(requireContext())
binding.callsList.layoutManager = layoutManager
binding.setOnAvatarClickListener {
(requireActivity() as MainActivity).toggleDrawerMenu()
}
listViewModel.callLogs.observe(viewLifecycleOwner) {
adapter.submitList(it)
startPostponedEnterTransition()
}
}
}

View file

@ -19,6 +19,39 @@
*/
package org.linphone.ui.main.calls.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.core.CallLog
import org.linphone.databinding.CallsListLongPressMenuBinding
class CallsListMenuDialogFragment : BottomSheetDialogFragment()
class CallsListMenuDialogFragment(
private val calLog: CallLog,
private val onDismiss: (() -> Unit)? = null
) : BottomSheetDialogFragment() {
companion object {
const val TAG = "CallsListMenuDialogFragment"
}
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)
return view.root
}
}

View file

@ -0,0 +1,38 @@
package org.linphone.ui.main.calls.model
import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.Call.Dir
import org.linphone.core.CallLog
import org.linphone.ui.main.contacts.model.ContactAvatarModel
import org.linphone.utils.TimestampUtils
class CallLogModel(val callLog: CallLog) {
val id = callLog.callId ?: callLog.refKey
val address = if (callLog.dir == Dir.Outgoing) callLog.remoteAddress else callLog.fromAddress
val avatarModel: ContactAvatarModel
val isOutgoing = MutableLiveData<Boolean>()
val dateTime = MutableLiveData<String>()
init {
// Core thread
isOutgoing.postValue(callLog.dir == Dir.Outgoing)
dateTime.postValue(
TimestampUtils.toString(callLog.startDate, shortDate = false, hideYear = false)
)
val friend = coreContext.core.findFriend(address)
if (friend != null) {
avatarModel = ContactAvatarModel(friend)
} else {
val fakeFriend = coreContext.core.createFriend()
fakeFriend.address = address
avatarModel = ContactAvatarModel(fakeFriend)
}
}
}

View file

@ -0,0 +1,38 @@
package org.linphone.ui.main.calls.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.ui.main.calls.model.CallLogModel
import org.linphone.utils.Event
class CallLogViewModel : ViewModel() {
val callLogModel = MutableLiveData<CallLogModel>()
val showBackButton = MutableLiveData<Boolean>()
val callLogFoundEvent = MutableLiveData<Event<Boolean>>()
fun findCallLogByCallId(callId: String) {
// UI thread
coreContext.postOnCoreThread { core ->
val callLog = core.findCallLogFromCallId(callId)
if (callLog != null) {
callLogModel.postValue(CallLogModel(callLog))
callLogFoundEvent.postValue(Event(true))
}
}
}
fun startAudioCall() {
// TODO
}
fun startVideoCall() {
// TODO
}
fun sendMessage() {
// TODO
}
}

View file

@ -19,11 +19,28 @@
*/
package org.linphone.ui.main.calls.viewmodel
import androidx.lifecycle.MutableLiveData
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.ui.main.calls.model.CallLogModel
import org.linphone.ui.main.viewmodel.TopBarViewModel
class CallsListViewModel : TopBarViewModel() {
val callLogs = MutableLiveData<ArrayList<CallLogModel>>()
init {
title.value = "Calls"
bottomNavBarVisible.value = true
coreContext.postOnCoreThread { core ->
val list = arrayListOf<CallLogModel>()
// TODO : filter depending on currently selected account
for (callLog in core.callLogs) {
val model = CallLogModel(callLog)
list.add(model)
}
callLogs.postValue(list)
}
}
}

View file

@ -30,7 +30,6 @@ import androidx.core.view.doOnPreDraw
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import androidx.transition.ChangeBounds
import org.linphone.R
import org.linphone.core.tools.Log
import org.linphone.databinding.ContactFragmentBinding
@ -48,11 +47,6 @@ class ContactFragment : GenericFragment() {
private val args: ContactFragmentArgs by navArgs()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = ChangeBounds()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,

View file

@ -43,4 +43,8 @@ class SharedMainViewModel : ViewModel() {
/* Contacts related */
val showContactEvent = MutableLiveData<Event<String>>()
/* Call logs related */
val showCallLogEvent = MutableLiveData<Event<String>>()
}

View file

@ -0,0 +1,13 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="12dp"
android:height="7dp"
android:viewportWidth="12"
android:viewportHeight="7">
<path
android:name="path"
android:pathData="M 11.11 6.481 C 11.331 6.238 11.331 5.866 11.11 5.623 L 6.594 0.654 C 6.276 0.303 5.724 0.303 5.406 0.654 L 1.722 4.707 L 1.722 2.232 C 1.722 1.894 1.449 1.621 1.111 1.621 C 0.774 1.621 0.5 1.894 0.5 2.232 L 0.5 6.556 C 0.5 6.801 0.699 7 0.944 7 L 4.716 7 C 5.088 7 5.389 6.699 5.389 6.328 C 5.389 5.956 5.088 5.655 4.716 5.655 L 2.584 5.655 L 6 1.896 L 10.166 6.481 C 10.419 6.759 10.857 6.759 11.11 6.481 Z"
android:fillColor="#dd5f5f"
android:strokeWidth="1"/>
</vector>

View file

@ -0,0 +1,15 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="29dp"
android:height="28dp"
android:viewportWidth="29"
android:viewportHeight="28">
<group android:name="group">
<path
android:name="path"
android:pathData="M 13.532 7 L 19.167 12.635 L 19.167 25.667 L 5.167 25.667 L 5.167 7 L 13.532 7 Z M 14.5 4.667 L 5.167 4.667 C 3.883 4.667 2.833 5.717 2.833 7 L 2.833 25.667 C 2.833 26.95 3.883 28 5.167 28 L 19.167 28 C 20.45 28 21.5 26.95 21.5 25.667 L 21.5 11.667 L 14.5 4.667 Z M 12.167 18.667 C 13.45 18.667 14.5 17.617 14.5 16.333 C 14.5 15.05 13.45 14 12.167 14 C 10.883 14 9.833 15.05 9.833 16.333 C 9.833 17.617 10.883 18.667 12.167 18.667 Z M 16.833 22.668 C 16.833 21.723 16.273 20.883 15.41 20.51 C 14.418 20.078 13.322 19.833 12.167 19.833 C 11.012 19.833 9.915 20.078 8.923 20.51 C 8.06 20.883 7.5 21.723 7.5 22.668 L 7.5 23.333 L 16.833 23.333 L 16.833 22.668 Z M 22.667 0 L 22.667 3.5 L 26.167 3.5 L 26.167 5.833 L 22.667 5.833 L 22.667 9.333 L 20.333 9.333 L 20.333 5.833 L 16.833 5.833 L 16.833 3.5 L 20.333 3.5 L 20.333 0 L 22.667 0 Z"
android:fillColor="#000"
android:strokeWidth="1"/>
</group>
</vector>

View file

@ -0,0 +1,13 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="12dp"
android:height="7dp"
android:viewportWidth="12"
android:viewportHeight="7">
<path
android:name="path"
android:pathData="M 0.89 0.519 C 0.669 0.762 0.669 1.134 0.89 1.377 L 5.406 6.346 C 5.724 6.697 6.276 6.697 6.594 6.346 L 10.278 2.293 L 10.278 4.768 C 10.278 5.106 10.551 5.379 10.889 5.379 C 11.226 5.379 11.5 5.106 11.5 4.768 L 11.5 0.444 C 11.5 0.199 11.301 0 11.056 0 L 7.284 0 C 6.912 0 6.611 0.301 6.611 0.672 C 6.611 1.044 6.912 1.345 7.284 1.345 L 9.416 1.345 L 6 5.104 L 1.834 0.519 C 1.581 0.241 1.143 0.241 0.89 0.519 Z"
android:fillColor="#dd5f5f"
android:strokeWidth="1"/>
</vector>

View file

@ -45,6 +45,17 @@
app:layout_constraintStart_toEndOf="@id/bottom_nav_bar"
app:layout_constraintTop_toBottomOf="@id/top_bar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/calls_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
app:layout_constraintStart_toEndOf="@id/bottom_nav_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/top_bar"
app:layout_constraintBottom_toBottomOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/bottom_nav_bar"
android:name="org.linphone.ui.main.fragment.BottomNavBarFragment"

View file

@ -154,7 +154,7 @@
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/add"
android:src="@drawable/new_contact"
app:tint="@color/gray_8"
app:backgroundTint="@color/white"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -18,9 +18,7 @@
android:onClick="@{onClickListener}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:background="@drawable/cell_background">
android:background="@drawable/shape_conversation_cell_background">
<io.getstream.avatarview.AvatarView
android:id="@+id/avatar"
@ -88,8 +86,6 @@
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/dot_menu"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
app:tint="@color/gray_1"

View file

@ -12,7 +12,8 @@
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"

View file

@ -12,7 +12,8 @@
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"

View file

@ -0,0 +1,217 @@
<?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"
xmlns:bind="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<import type="org.linphone.core.ConsolidatedPresence" />
<variable
name="backClickListener"
type="View.OnClickListener" />
<variable
name="viewModel"
type="org.linphone.ui.main.calls.viewmodel.CallLogViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:animateLayoutChanges="true">
<ImageView
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginStart="10dp"
android:adjustViewBounds="true"
android:padding="5dp"
android:onClick="@{backClickListener}"
android:visibility="@{viewModel.showBackButton ? View.VISIBLE : View.GONE}"
android:src="@drawable/back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/menu"
app:layout_constraintBottom_toBottomOf="@id/menu"/>
<ImageView
android:id="@+id/menu"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:adjustViewBounds="true"
android:padding="5dp"
android:src="@drawable/dot_menu"
app:tint="@color/primary_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@color/gray_7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/menu"
app:layout_constraintBottom_toBottomOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<io.getstream.avatarview.AvatarView
android:id="@+id/avatar"
android:layout_width="@dimen/avatar_big_size"
android:layout_height="@dimen/avatar_big_size"
android:layout_marginTop="8dp"
android:adjustViewBounds="true"
contactAvatar="@{viewModel.callLogModel.avatarModel}"
app:avatarViewBorderWidth="0dp"
app:avatarViewIndicatorBorderColor="@color/white"
app:avatarViewIndicatorBorderSizeCriteria="8"
app:avatarViewIndicatorEnabled="true"
app:avatarViewIndicatorPosition="bottomRight"
app:avatarViewIndicatorSizeCriteria="7"
app:avatarViewInitialsBackgroundColor="@color/blue_outgoing_message"
app:avatarViewInitialsTextColor="@color/gray_9"
app:avatarViewInitialsTextSize="21sp"
app:avatarViewInitialsTextStyle="bold"
app:avatarViewPlaceholder="@drawable/contact_avatar"
app:avatarViewShape="circle"
app:layout_constraintEnd_toEndOf="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:layout_marginTop="10dp"
android:text="@{viewModel.callLogModel.avatarModel.name, default=`John Doe`}"
android:textColor="@color/gray_8"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/avatar" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/status"
android:visibility="@{viewModel.callLogModel.avatarModel.presenceStatus == ConsolidatedPresence.Offline ? View.GONE : View.VISIBLE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.callLogModel.avatarModel.presenceStatus == ConsolidatedPresence.Online ? `En ligne` : `Absent`, default=`En ligne`}"
android:textColor="@{viewModel.callLogModel.avatarModel.presenceStatus == ConsolidatedPresence.Online ? @color/green_online : @color/green_online, default=@color/green_online}"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/name" />
<ImageView
android:onClick="@{() -> viewModel.startAudioCall()}"
android:id="@+id/call"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginTop="20dp"
android:background="@drawable/shape_button_round"
android:padding="16dp"
android:src="@drawable/calls"
app:tint="@color/gray_1"
app:layout_constraintEnd_toStartOf="@id/chat"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:onClick="@{() -> viewModel.startAudioCall()}"
android:id="@+id/call_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Call"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/call"
app:layout_constraintStart_toStartOf="@id/call"
app:layout_constraintEnd_toEndOf="@id/call"/>
<ImageView
android:onClick="@{() -> viewModel.sendMessage()}"
android:id="@+id/chat"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginTop="20dp"
android:background="@drawable/shape_button_round"
android:padding="16dp"
android:src="@drawable/chat"
app:tint="@color/gray_1"
app:layout_constraintEnd_toStartOf="@id/video_call"
app:layout_constraintStart_toEndOf="@id/call"
app:layout_constraintTop_toBottomOf="@id/status" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:onClick="@{() -> viewModel.sendMessage()}"
android:id="@+id/chat_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Message"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/chat"
app:layout_constraintStart_toStartOf="@id/chat"
app:layout_constraintEnd_toEndOf="@id/chat"/>
<ImageView
android:onClick="@{() -> viewModel.startVideoCall()}"
android:id="@+id/video_call"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginTop="20dp"
android:background="@drawable/shape_button_round"
android:padding="16dp"
android:src="@drawable/video_call"
app:tint="@color/gray_1"
app:layout_constraintStart_toEndOf="@id/chat"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/status" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:onClick="@{() -> viewModel.startVideoCall()}"
android:id="@+id/video_call_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Video Call"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/video_call"
app:layout_constraintStart_toStartOf="@id/video_call"
app:layout_constraintEnd_toEndOf="@id/video_call"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<include
android:id="@+id/green_toast"
android:visibility="gone"
layout="@layout/toast_green"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
app:layout_constraintTop_toTopOf="@id/scrollView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,113 @@
<?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" />
<import type="android.graphics.Typeface" />
<variable
name="model"
type="org.linphone.ui.main.calls.model.CallLogModel" />
<variable
name="onClickListener"
type="View.OnClickListener" />
<variable
name="onLongClickListener"
type="View.OnLongClickListener" />
<variable
name="onCallClickListener"
type="View.OnClickListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:onClick="@{onClickListener}"
android:onLongClickListener="@{onLongClickListener}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/cell_background">
<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_marginStart="12dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:adjustViewBounds="true"
contactAvatar="@{model.avatarModel}"
app:avatarViewInitials="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="2dp"
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:layout_marginTop="5dp"
android:layout_marginStart="10dp"
android:text="@{model.avatarModel.name, default=`John Doe`}"
android:textSize="14sp"
android:textColor="@color/gray_8"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/date_time"/>
<ImageView
android:id="@+id/call_status"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginBottom="10dp"
android:src="@{model.isOutgoing() ? @drawable/outgoing_call : @drawable/incoming_call, default=@drawable/outgoing_call}"
app:layout_constraintStart_toStartOf="@id/name"
app:layout_constraintTop_toBottomOf="@id/name"
app:layout_constraintBottom_toTopOf="@id/separator" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_300"
android:id="@+id/date_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginStart="5dp"
android:gravity="center"
android:text="@{model.dateTime, default=`13 aout 2023`}"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/call_status"
app:layout_constraintTop_toBottomOf="@id/name"
app:layout_constraintBottom_toTopOf="@id/separator"/>
<ImageView
android:onClick="@{onCallClickListener}"
android:id="@+id/call"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:src="@drawable/calls"
app:tint="@color/gray_1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/name"
app:layout_constraintBottom_toBottomOf="@id/date_time" />
<View
android:id="@+id/separator"
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>

View file

@ -40,6 +40,17 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/top_bar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/calls_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/top_bar"
app:layout_constraintBottom_toTopOf="@id/bottom_nav_bar" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/bottom_nav_bar"
android:name="org.linphone.ui.main.fragment.BottomNavBarFragment"
@ -57,7 +68,7 @@
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/add"
android:src="@drawable/new_call"
app:tint="@color/gray_8"
app:backgroundTint="@color/white"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -0,0 +1,67 @@
<?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="newContactClickListener"
type="View.OnClickListener" />
<variable
name="copyNumberClickListener"
type="View.OnClickListener" />
<variable
name="deleteClickListener"
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/favorite"
android:onClick="@{newContactClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Ajouter aux contacts"
style="@style/context_menu_action_label_style"
android:background="@color/gray_2"
android:layout_marginBottom="1dp"
android:drawableStart="@drawable/new_contact"
app:layout_constraintBottom_toTopOf="@id/share"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/share"
android:onClick="@{copyNumberClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Copier le numéro"
style="@style/context_menu_action_label_style"
android:background="@color/gray_2"
android:layout_marginBottom="1dp"
android:drawableStart="@drawable/copy"
app:layout_constraintBottom_toTopOf="@id/delete"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/delete"
android:onClick="@{deleteClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Supprimer"
style="@style/context_menu_danger_action_label_style"
android:background="@color/gray_2"
android:layout_marginBottom="1dp"
android:drawableStart="@drawable/delete"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -22,8 +22,6 @@
android:onLongClick="@{onLongClickListener}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:background="@drawable/cell_background">
<androidx.appcompat.widget.AppCompatTextView

View file

@ -151,7 +151,7 @@
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/add"
android:src="@drawable/new_contact"
app:tint="@color/gray_8"
app:backgroundTint="@color/white"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -11,4 +11,19 @@
android:label="EmptyFragment"
tools:layout="@layout/empty_fragment"/>
<fragment
android:id="@+id/callFragment"
android:name="org.linphone.ui.main.calls.fragment.CallFragment"
android:label="CallFragment"
tools:layout="@layout/call_fragment">
<argument
android:name="callId"
app:argType="string" />
</fragment>
<action
android:id="@+id/action_global_callFragment"
app:destination="@id/callFragment"
app:launchSingleTop="true" />
</navigation>