From 8269228b8a9ccc96b79ccd67897880a933a1ffcd Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 12 Oct 2023 14:43:55 +0200 Subject: [PATCH] Update some info dynamically --- .../chat/fragment/ConversationFragment.kt | 10 +- .../ui/main/chat/model/ConversationModel.kt | 127 ++++++++++++++---- .../viewmodel/ConversationsListViewModel.kt | 3 + .../main/meetings/fragment/MeetingFragment.kt | 50 +++---- .../linphone/ui/main/model/AccountModel.kt | 29 +++- .../chat_conversation_long_press_menu.xml | 2 +- app/src/main/res/layout/chat_list_cell.xml | 2 +- app/src/main/res/values/dimen.xml | 3 +- 8 files changed, 167 insertions(+), 59 deletions(-) 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 59daa1f17..2cb1048e9 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 @@ -125,7 +125,7 @@ class ConversationFragment : GenericFragment() { } adapter = ConversationEventAdapter(viewLifecycleOwner) - binding.eventsList.setHasFixedSize(false) + binding.eventsList.setHasFixedSize(true) binding.eventsList.adapter = adapter val layoutManager = LinearLayoutManager(requireContext()) @@ -142,9 +142,11 @@ class ConversationFragment : GenericFragment() { adapter.submitList(items) Log.i("$TAG Events (messages) list updated with [${items.size}] items") - (view.parent as? ViewGroup)?.doOnPreDraw { - startPostponedEnterTransition() - sharedViewModel.openSlidingPaneEvent.value = Event(true) + if (currentCount == 0 && items.isNotEmpty()) { + (view.parent as? ViewGroup)?.doOnPreDraw { + startPostponedEnterTransition() + sharedViewModel.openSlidingPaneEvent.value = Event(true) + } } if (currentCount < items.size) { diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt index 039efa696..821034c25 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/ConversationModel.kt @@ -24,9 +24,13 @@ import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R +import org.linphone.core.Address import org.linphone.core.ChatMessage +import org.linphone.core.ChatMessageListenerStub import org.linphone.core.ChatRoom import org.linphone.core.ChatRoom.Capabilities +import org.linphone.core.ChatRoomListenerStub +import org.linphone.core.EventLog import org.linphone.core.Friend import org.linphone.core.tools.Log import org.linphone.ui.main.contacts.model.ContactAvatarModel @@ -62,7 +66,7 @@ class ConversationModel @WorkerThread constructor(private val chatRoom: ChatRoom val composingLabel = MutableLiveData() - val lastMessage = MutableLiveData() + val lastMessageText = MutableLiveData() val lastMessageIcon = MutableLiveData() @@ -76,9 +80,47 @@ class ConversationModel @WorkerThread constructor(private val chatRoom: ChatRoom val groupAvatarModel: GroupAvatarModel + private var lastMessage: ChatMessage? = null + + private val chatRoomListener = object : ChatRoomListenerStub() { + @WorkerThread + override fun onIsComposingReceived( + chatRoom: ChatRoom, + remoteAddress: Address, + isComposing: Boolean + ) { + computeComposingLabel() + } + + @WorkerThread + override fun onMessagesReceived(chatRoom: ChatRoom, chatMessages: Array) { + updateLastMessage() + } + + @WorkerThread + override fun onChatMessageSending(chatRoom: ChatRoom, eventLog: EventLog) { + updateLastMessage() + } + + @WorkerThread + override fun onChatRoomRead(chatRoom: ChatRoom) { + unreadMessageCount.postValue(chatRoom.unreadMessagesCount) + } + } + + private val chatMessageListener = object : ChatMessageListenerStub() { + @WorkerThread + override fun onMsgStateChanged(message: ChatMessage, state: ChatMessage.State?) { + updateLastMessageStatus(message) + } + } + init { + chatRoom.addListener(chatRoomListener) + subject.postValue(chatRoom.subject) lastUpdateTime.postValue(chatRoom.lastUpdateTime) + computeComposingLabel() val friends = arrayListOf() val address = if (chatRoom.hasCapability(Capabilities.Basic.toInt())) { @@ -123,6 +165,14 @@ class ConversationModel @WorkerThread constructor(private val chatRoom: ChatRoom unreadMessageCount.postValue(chatRoom.unreadMessagesCount) } + @WorkerThread + fun destroy() { + lastMessage?.removeListener(chatMessageListener) + lastMessage = null + + chatRoom.removeListener(chatRoomListener) + } + @UiThread fun markAsRead() { coreContext.postOnCoreThread { @@ -171,37 +221,55 @@ class ConversationModel @WorkerThread constructor(private val chatRoom: ChatRoom } } + @WorkerThread + private fun updateLastMessageStatus(message: ChatMessage) { + val text = LinphoneUtils.getTextDescribingMessage(message) + lastMessageText.postValue(text) + + val isOutgoing = message.isOutgoing + isLastMessageOutgoing.postValue(isOutgoing) + if (isOutgoing) { + val icon = when (message.state) { + ChatMessage.State.Displayed -> { + R.drawable.checks + } + + ChatMessage.State.DeliveredToUser -> { + R.drawable.check + } + + ChatMessage.State.Delivered -> { + R.drawable.sent + } + + ChatMessage.State.InProgress, ChatMessage.State.FileTransferInProgress -> { + R.drawable.in_progress + } + + ChatMessage.State.NotDelivered, ChatMessage.State.FileTransferError -> { + R.drawable.warning_circle + } + + else -> { + R.drawable.info + } + } + lastMessageIcon.postValue(icon) + } + } + @WorkerThread private fun updateLastMessage() { + lastMessage?.removeListener(chatMessageListener) + lastMessage = null + val message = chatRoom.lastMessageInHistory if (message != null) { - val text = LinphoneUtils.getTextDescribingMessage(message) - lastMessage.postValue(text) + updateLastMessageStatus(message) - val isOutgoing = message.isOutgoing - isLastMessageOutgoing.postValue(isOutgoing) - if (isOutgoing) { - val icon = when (message.state) { - ChatMessage.State.Displayed -> { - R.drawable.checks - } - ChatMessage.State.DeliveredToUser -> { - R.drawable.check - } - ChatMessage.State.Delivered -> { - R.drawable.sent - } - ChatMessage.State.InProgress, ChatMessage.State.FileTransferInProgress -> { - R.drawable.in_progress - } - ChatMessage.State.NotDelivered, ChatMessage.State.FileTransferError -> { - R.drawable.warning_circle - } - else -> { - R.drawable.info - } - } - lastMessageIcon.postValue(icon) + if (message.isOutgoing && message.state != ChatMessage.State.Displayed) { + message.addListener(chatMessageListener) + lastMessage = message } } else { Log.w("$TAG No last message to display for chat room [$id]") @@ -225,4 +293,9 @@ class ConversationModel @WorkerThread constructor(private val chatRoom: ChatRoom } dateTime.postValue(humanReadableTimestamp) } + + @WorkerThread + private fun computeComposingLabel() { + // TODO + } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt index 7f9099f81..db6ab696d 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt @@ -87,6 +87,7 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod super.onCleared() coreContext.postOnCoreThread { core -> + conversations.value.orEmpty().forEach(ConversationModel::destroy) coreContext.contactsManager.removeListener(contactsListener) core.removeListener(coreListener) } @@ -103,6 +104,8 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod @WorkerThread private fun computeChatRoomsList(filter: String) { + conversations.value.orEmpty().forEach(ConversationModel::destroy) + if (conversations.value.orEmpty().isEmpty()) { fetchInProgress.postValue(true) } diff --git a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingFragment.kt b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingFragment.kt index 88432c833..a825529b9 100644 --- a/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/meetings/fragment/MeetingFragment.kt @@ -95,29 +95,7 @@ class MeetingFragment : GenericFragment() { } binding.setShareClickListener { - val intent = Intent(Intent.ACTION_EDIT) - intent.type = "vnd.android.cursor.item/event" - intent.putExtra(CalendarContract.Events.TITLE, viewModel.subject.value) - - val description = viewModel.description.value.orEmpty() - if (description.isNotEmpty()) { - intent.putExtra(CalendarContract.Events.DESCRIPTION, description) - } - - intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, viewModel.startTimeStamp.value) - intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, viewModel.endTimeStamp.value) - - intent.putExtra(CalendarContract.Events.CUSTOM_APP_URI, viewModel.sipUri.value) - intent.putExtra( - CalendarContract.Events.CUSTOM_APP_PACKAGE, - requireContext().packageName - ) - - try { - startActivity(intent) - } catch (exception: ActivityNotFoundException) { - Log.e("$TAG No activity found to handle intent: $exception") - } + shareMeetingInfoAsCalendarEvent() } binding.setMenuClickListener { @@ -180,4 +158,30 @@ class MeetingFragment : GenericFragment() { popupWindow.elevation = 20f popupWindow.showAsDropDown(binding.menu, 0, 0, Gravity.BOTTOM) } + + private fun shareMeetingInfoAsCalendarEvent() { + val intent = Intent(Intent.ACTION_EDIT) + intent.type = "vnd.android.cursor.item/event" + intent.putExtra(CalendarContract.Events.TITLE, viewModel.subject.value) + + val description = viewModel.description.value.orEmpty() + if (description.isNotEmpty()) { + intent.putExtra(CalendarContract.Events.DESCRIPTION, description) + } + + intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, viewModel.startTimeStamp.value) + intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, viewModel.endTimeStamp.value) + + intent.putExtra(CalendarContract.Events.CUSTOM_APP_URI, viewModel.sipUri.value) + intent.putExtra( + CalendarContract.Events.CUSTOM_APP_PACKAGE, + requireContext().packageName + ) + + try { + startActivity(intent) + } catch (exception: ActivityNotFoundException) { + Log.e("${MeetingFragment.TAG} No activity found to handle intent: $exception") + } + } } diff --git a/app/src/main/java/org/linphone/ui/main/model/AccountModel.kt b/app/src/main/java/org/linphone/ui/main/model/AccountModel.kt index 6a160aba0..d3872c058 100644 --- a/app/src/main/java/org/linphone/ui/main/model/AccountModel.kt +++ b/app/src/main/java/org/linphone/ui/main/model/AccountModel.kt @@ -27,6 +27,10 @@ import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.R import org.linphone.core.Account import org.linphone.core.AccountListenerStub +import org.linphone.core.ChatMessage +import org.linphone.core.ChatRoom +import org.linphone.core.Core +import org.linphone.core.CoreListenerStub import org.linphone.core.RegistrationState import org.linphone.core.tools.Log import org.linphone.utils.AppUtils @@ -75,8 +79,25 @@ class AccountModel @WorkerThread constructor( } } + private val coreListener = object : CoreListenerStub() { + @WorkerThread + override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) { + computeNotificationsCount() + } + + @WorkerThread + override fun onMessagesReceived( + core: Core, + chatRoom: ChatRoom, + messages: Array + ) { + computeNotificationsCount() + } + } + init { account.addListener(accountListener) + coreContext.core.addListener(coreListener) showTrust.postValue(account.isInSecureMode()) @@ -85,6 +106,7 @@ class AccountModel @WorkerThread constructor( @WorkerThread fun destroy() { + coreContext.core.removeListener(coreListener) account.removeListener(accountListener) } @@ -139,7 +161,7 @@ class AccountModel @WorkerThread constructor( } isDefault.postValue(coreContext.core.defaultAccount == account) - notificationsCount.postValue(account.unreadChatMessageCount + account.missedCallsCount) + computeNotificationsCount() val state = account.state registrationState.postValue(state) @@ -187,6 +209,11 @@ class AccountModel @WorkerThread constructor( } registrationStateSummary.postValue(summary) } + + @WorkerThread + private fun computeNotificationsCount() { + notificationsCount.postValue(account.unreadChatMessageCount + account.missedCallsCount) + } } fun Account.isInSecureMode(): Boolean { diff --git a/app/src/main/res/layout/chat_conversation_long_press_menu.xml b/app/src/main/res/layout/chat_conversation_long_press_menu.xml index 5065b1db3..fe0ef19c6 100644 --- a/app/src/main/res/layout/chat_conversation_long_press_menu.xml +++ b/app/src/main/res/layout/chat_conversation_long_press_menu.xml @@ -24,7 +24,7 @@ 4dp 16dp - 60dp - 105dp + 110dp \ No newline at end of file