diff --git a/app/build.gradle b/app/build.gradle index 50a6a4ca0..f1d3aadc9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,7 +77,7 @@ dependencies { implementation "androidx.core:core-ktx:1.12.0" implementation "androidx.core:core-telecom:1.0.0-alpha02" implementation "androidx.media:media:1.6.0" - implementation "androidx.recyclerview:recyclerview:1.3.1" + implementation "androidx.recyclerview:recyclerview:1.3.2" implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" implementation "androidx.window:window:1.1.0" diff --git a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt index 733acd17d..6bb8b0e21 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationEventAdapter.kt @@ -137,6 +137,10 @@ class ConversationEventAdapter( with(binding) { model = message + setShowDeliveryInfoClickListener { + showDeliveryForChatMessageModelEvent.value = Event(message) + } + lifecycleOwner = viewLifecycleOwner executePendingBindings() } diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt index d0ecb681e..49793c5e9 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -43,13 +43,17 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayout.OnTabSelectedListener import kotlinx.coroutines.launch import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R +import org.linphone.core.ChatMessage import org.linphone.core.tools.Log import org.linphone.databinding.ChatConversationFragmentBinding import org.linphone.databinding.ChatConversationLongPressMenuBinding import org.linphone.ui.main.chat.adapter.ConversationEventAdapter +import org.linphone.ui.main.chat.model.ChatMessageDeliveryModel import org.linphone.ui.main.chat.model.ChatMessageModel import org.linphone.ui.main.chat.viewmodel.ConversationViewModel import org.linphone.ui.main.fragment.GenericFragment @@ -177,13 +181,9 @@ class ConversationFragment : GenericFragment() { emojisBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED emojisBottomSheetBehavior.isDraggable = false // To allow scrolling through the emojis - val imdnBottomSheetBehavior = BottomSheetBehavior.from(binding.deliveryBottomSheet.root) - imdnBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED - adapter.showDeliveryForChatMessageModelEvent.observe(viewLifecycleOwner) { it.consume { model -> - binding.deliveryBottomSheet.model = model - imdnBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED + showDeliveryBottomSheetDialog(model) } } @@ -303,4 +303,62 @@ class ConversationFragment : GenericFragment() { dialog.window?.setBackgroundDrawable(d) dialog.show() } + + @UiThread + private fun showDeliveryBottomSheetDialog(chatMessageModel: ChatMessageModel) { + val deliveryBottomSheetBehavior = BottomSheetBehavior.from(binding.messageDelivery.root) + if (deliveryBottomSheetBehavior.state == BottomSheetBehavior.STATE_HALF_EXPANDED) { + deliveryBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED + } + + binding.messageDelivery.setHandleClickedListener { + deliveryBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED + } + + coreContext.postOnCoreThread { + val model = ChatMessageDeliveryModel(chatMessageModel.chatMessage) + + coreContext.postOnMainThread { + binding.messageDelivery.tabs.removeAllTabs() + binding.messageDelivery.tabs.addTab( + binding.messageDelivery.tabs.newTab().setText(model.readLabel.value).setId( + ChatMessage.State.Displayed.toInt() + ) + ) + binding.messageDelivery.tabs.addTab( + binding.messageDelivery.tabs.newTab().setText(model.receivedLabel.value).setId( + ChatMessage.State.DeliveredToUser.toInt() + ) + ) + binding.messageDelivery.tabs.addTab( + binding.messageDelivery.tabs.newTab().setText(model.sentLabel.value).setId( + ChatMessage.State.Delivered.toInt() + ) + ) + binding.messageDelivery.tabs.addTab( + binding.messageDelivery.tabs.newTab().setText(model.errorLabel.value).setId( + ChatMessage.State.NotDelivered.toInt() + ) + ) + + binding.messageDelivery.tabs.setOnTabSelectedListener(object : OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab?) { + val state = tab?.id ?: ChatMessage.State.Displayed.toInt() + model.computeListForState(ChatMessage.State.fromInt(state)) + } + + override fun onTabUnselected(tab: TabLayout.Tab?) { + } + + override fun onTabReselected(tab: TabLayout.Tab?) { + } + }) + + binding.messageDelivery.model = model + + binding.messageDelivery.root.visibility = View.VISIBLE + deliveryBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED + } + } + } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/MessageDeliveryDialogFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/MessageDeliveryDialogFragment.kt new file mode 100644 index 000000000..54f2518fc --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/MessageDeliveryDialogFragment.kt @@ -0,0 +1,51 @@ +/* + * 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.main.chat.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.UiThread +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.linphone.databinding.ChatMessageDeliveryBottomSheetBinding +import org.linphone.ui.main.chat.model.ChatMessageDeliveryModel +import org.linphone.ui.main.chat.model.ChatMessageModel + +@UiThread +class MessageDeliveryDialogFragment( + chatMessageModel: ChatMessageModel +) : BottomSheetDialogFragment() { + companion object { + const val TAG = "MessageDeliveryDialogFragment" + } + + val model = ChatMessageDeliveryModel(chatMessageModel.chatMessage) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val view = ChatMessageDeliveryBottomSheetBinding.inflate(layoutInflater) + view.model = model + return view.root + } +} diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageDeliveryModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageDeliveryModel.kt index eba841549..1365e17c7 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageDeliveryModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageDeliveryModel.kt @@ -1,18 +1,102 @@ package org.linphone.ui.main.chat.model +import androidx.annotation.UiThread import androidx.annotation.WorkerThread -import org.linphone.LinphoneApplication -import org.linphone.core.ParticipantImdnState -import org.linphone.utils.TimestampUtils +import androidx.lifecycle.MutableLiveData +import org.linphone.R +import org.linphone.core.ChatMessage +import org.linphone.core.ChatMessage.State +import org.linphone.utils.AppUtils class ChatMessageDeliveryModel @WorkerThread constructor( - imdnState: ParticipantImdnState + private val chatMessage: ChatMessage ) { - val address = imdnState.participant.address + companion object { + private const val TAG = "[Chat Message Delivery Model]" + } - val avatarModel = LinphoneApplication.coreContext.contactsManager.getContactAvatarModelForAddress( - address - ) + val readLabel = MutableLiveData() - val time = TimestampUtils.toString(imdnState.stateChangeTime) + val receivedLabel = MutableLiveData() + + val sentLabel = MutableLiveData() + + val errorLabel = MutableLiveData() + + val deliveryModels = MutableLiveData>() + + private val displayedModels = arrayListOf() + + private val deliveredModels = arrayListOf() + + private val sentModels = arrayListOf() + + private val errorModels = arrayListOf() + + init { + computeDeliveryStatus() + } + + @UiThread + fun computeListForState(state: State) { + when (state) { + State.DeliveredToUser -> { + deliveryModels.value = deliveredModels + } + State.Delivered -> { + deliveryModels.value = sentModels + } + State.NotDelivered -> { + deliveryModels.value = errorModels + } + else -> { + deliveryModels.value = displayedModels + } + } + } + + @WorkerThread + private fun computeDeliveryStatus() { + for (participant in chatMessage.getParticipantsByImdnState(State.Displayed)) { + displayedModels.add(ChatMessageParticipantDeliveryModel(participant)) + } + readLabel.postValue( + AppUtils.getFormattedString( + R.string.message_delivery_info_read_title, + displayedModels.size.toString() + ) + ) + + for (participant in chatMessage.getParticipantsByImdnState(State.DeliveredToUser)) { + deliveredModels.add(ChatMessageParticipantDeliveryModel(participant)) + } + receivedLabel.postValue( + AppUtils.getFormattedString( + R.string.message_delivery_info_received_title, + deliveredModels.size.toString() + ) + ) + + for (participant in chatMessage.getParticipantsByImdnState(State.Delivered)) { + sentModels.add(ChatMessageParticipantDeliveryModel(participant)) + } + sentLabel.postValue( + AppUtils.getFormattedString( + R.string.message_delivery_info_sent_title, + sentModels.size.toString() + ) + ) + + for (participant in chatMessage.getParticipantsByImdnState(State.NotDelivered)) { + errorModels.add(ChatMessageParticipantDeliveryModel(participant)) + } + errorLabel.postValue( + AppUtils.getFormattedString( + R.string.message_delivery_info_error_title, + errorModels.size.toString() + ) + ) + + deliveryModels.postValue(displayedModels) + } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt index ae5658920..04a4c8a42 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageModel.kt @@ -59,8 +59,6 @@ class ChatMessageModel @WorkerThread constructor( val chatRoomIsReadOnly = chatMessage.chatRoom.isReadOnly - val deliveryModels = MutableLiveData>() - val dismissLongPressMenuEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -87,8 +85,6 @@ class ChatMessageModel @WorkerThread constructor( init { chatMessage.addListener(chatMessageListener) computeStatusIcon(chatMessage.state) - - computeDeliveryStatus() } @WorkerThread @@ -127,15 +123,4 @@ class ChatMessageModel @WorkerThread constructor( } statusIcon.postValue(icon) } - - @WorkerThread - private fun computeDeliveryStatus() { - val list = arrayListOf() - - /*for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.Displayed)) { - list.add(ChatMessageDeliveryModel(participant)) - }*/ - - deliveryModels.postValue(list) - } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageParticipantDeliveryModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageParticipantDeliveryModel.kt new file mode 100644 index 000000000..d1ccb4b36 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ChatMessageParticipantDeliveryModel.kt @@ -0,0 +1,18 @@ +package org.linphone.ui.main.chat.model + +import androidx.annotation.WorkerThread +import org.linphone.LinphoneApplication +import org.linphone.core.ParticipantImdnState +import org.linphone.utils.TimestampUtils + +class ChatMessageParticipantDeliveryModel @WorkerThread constructor( + imdnState: ParticipantImdnState +) { + val address = imdnState.participant.address + + val avatarModel = LinphoneApplication.coreContext.contactsManager.getContactAvatarModelForAddress( + address + ) + + val time = TimestampUtils.toString(imdnState.stateChangeTime) +} diff --git a/app/src/main/res/drawable/shape_bottom_sheet_background.xml b/app/src/main/res/drawable/shape_bottom_sheet_gray_100_background.xml similarity index 100% rename from app/src/main/res/drawable/shape_bottom_sheet_background.xml rename to app/src/main/res/drawable/shape_bottom_sheet_gray_100_background.xml diff --git a/app/src/main/res/drawable/shape_bottom_sheet_white_background.xml b/app/src/main/res/drawable/shape_bottom_sheet_white_background.xml new file mode 100644 index 000000000..8675a28f4 --- /dev/null +++ b/app/src/main/res/drawable/shape_bottom_sheet_white_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_chat_bubble_incoming_first.xml b/app/src/main/res/drawable/shape_chat_bubble_incoming_first.xml index 0dd826c00..e82f7df3c 100644 --- a/app/src/main/res/drawable/shape_chat_bubble_incoming_first.xml +++ b/app/src/main/res/drawable/shape_chat_bubble_incoming_first.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_chat_bubble_incoming_full.xml b/app/src/main/res/drawable/shape_chat_bubble_incoming_full.xml index 18bb74022..df3a98fea 100644 --- a/app/src/main/res/drawable/shape_chat_bubble_incoming_full.xml +++ b/app/src/main/res/drawable/shape_chat_bubble_incoming_full.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_bubble_outgoing.xml b/app/src/main/res/layout/chat_bubble_outgoing.xml index ad21828fe..103bcc85b 100644 --- a/app/src/main/res/layout/chat_bubble_outgoing.xml +++ b/app/src/main/res/layout/chat_bubble_outgoing.xml @@ -9,6 +9,9 @@ + @@ -75,6 +78,7 @@ @@ -203,7 +203,7 @@ android:layout_height="wrap_content" android:layout_marginStart="10dp" android:paddingBottom="5dp" - android:background="@color/gray_100" + android:background="@color/white" android:text="@{viewModel.composingLabel, default=`John Doe is composing...`}" android:textSize="12sp" android:textColor="@color/gray_main2_400" @@ -223,7 +223,8 @@ layout="@layout/chat_conversation_send_area"/> diff --git a/app/src/main/res/layout/chat_delivery_list_cell.xml b/app/src/main/res/layout/chat_delivery_list_cell.xml index 4e99c4c10..ed2b59ae5 100644 --- a/app/src/main/res/layout/chat_delivery_list_cell.xml +++ b/app/src/main/res/layout/chat_delivery_list_cell.xml @@ -9,7 +9,7 @@ + type="org.linphone.ui.main.chat.model.ChatMessageParticipantDeliveryModel" /> + type="org.linphone.ui.main.chat.model.ChatMessageDeliveryModel" /> @@ -39,40 +38,25 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/transparent_color" + android:layout_marginBottom="16dp" app:layout_constraintTop_toBottomOf="@id/handle" + app:layout_constraintBottom_toTopOf="@id/scrollview" app:tabMode="fixed" + app:tabUnboundedRipple="true" + app:tabRippleColor="@color/orange_main_100" app:tabGravity="fill" app:tabPadding="0dp" app:tabInlineLabel="true" + app:tabIndicatorColor="@color/orange_main_500" app:tabTextColor="@color/gray_main2_400" - app:tabSelectedTextColor="@color/gray_main2_600"> - - - - - - - - - - + app:tabSelectedTextColor="@color/gray_main2_600" /> + android:layout_height="250dp" + app:layout_constraintHeight_min="50dp" + app:layout_constraintBottom_toBottomOf="parent"> Give admin rights Remove admin rights + Read (%s) + Received (%s) + Sent (%s) + Error (%s) + No meeting for the moment… New meeting Meeting