diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt deleted file mode 100644 index c726c64e93..0000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Nextcloud Talk - Android Client - * - * SPDX-FileCopyrightText: 2023 Julius Linus - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package com.nextcloud.talk.adapters.messages - -import android.content.Context -import android.graphics.drawable.Drawable -import android.view.View -import autodagger.AutoInjector -import coil.Coil.imageLoader -import coil.request.ImageRequest -import coil.target.Target -import coil.transform.CircleCropTransformation -import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.databinding.CallStartedMessageBinding -import com.nextcloud.talk.chat.data.model.ChatMessage -import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.users.UserManager -import com.nextcloud.talk.utils.ApiUtils -import com.stfalcon.chatkit.messages.MessageHolders -import javax.inject.Inject - -@AutoInjector(NextcloudTalkApplication::class) -class CallStartedViewHolder(incomingView: View, payload: Any) : - MessageHolders.BaseIncomingMessageViewHolder(incomingView, payload) { - private val binding: CallStartedMessageBinding = CallStartedMessageBinding.bind(incomingView) - - @Inject - lateinit var context: Context - - @Inject - lateinit var userManager: UserManager - - @Inject - lateinit var viewThemeUtils: ViewThemeUtils - - private lateinit var messageInterface: CallStartedMessageInterface - - override fun onBind(message: ChatMessage) { - super.onBind(message) - NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) - themeBackground() - setUpAvatarProfile(message) - binding.callAuthorChip.text = message.actorDisplayName - binding.joinVideoCall.setOnClickListener { messageInterface.joinVideoCall() } - binding.joinAudioCall.setOnClickListener { messageInterface.joinAudioCall() } - } - - private fun themeBackground() { - binding.callStartedBackground.apply { - viewThemeUtils.talk.themeOutgoingMessageBubble(this, grouped = true, false) - } - - binding.callAuthorChip.apply { - viewThemeUtils.material.colorChipBackground(this) - } - } - - private fun setUpAvatarProfile(message: ChatMessage) { - val user = userManager.currentUser.blockingGet() - val url: String = if (message.actorType == "guests" || message.actorType == "guest") { - ApiUtils.getUrlForGuestAvatar( - user!!.baseUrl!!, - message.actorDisplayName, - true - ) - } else { - ApiUtils.getUrlForAvatar(user!!.baseUrl!!, message.actorDisplayName, false) - } - - val imageRequest: ImageRequest = ImageRequest.Builder(context) - .data(url) - .crossfade(true) - .transformations(CircleCropTransformation()) - .target(object : Target { - override fun onStart(placeholder: Drawable?) { - // unused atm - } - - override fun onError(error: Drawable?) { - // unused atm - } - - override fun onSuccess(result: Drawable) { - binding.callAuthorChip.chipIcon = result - } - }) - .build() - - imageLoader(context).enqueue(imageRequest) - } - - fun assignCallStartedMessageInterface(inf: CallStartedMessageInterface) { - messageInterface = inf - } - - override fun viewDetached() { - // unused atm - } - - override fun viewAttached() { - // unused atm - } - - override fun viewRecycled() { - // unused atm - } - - companion object { - val TAG: String? = CallStartedViewHolder::class.simpleName - } -} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java index d331f1d61f..6eda1ff98c 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java @@ -68,9 +68,6 @@ public void onBindViewHolder(ViewHolder holder, int position) { } else if (holder instanceof SystemMessageViewHolder holderInstance) { holderInstance.assignSystemMessageInterface(chatActivity); - } else if (holder instanceof CallStartedViewHolder holderInstance) { - holderInstance.assignCallStartedMessageInterface(chatActivity); - } else if (holder instanceof TemporaryMessageViewHolder holderInstance) { holderInstance.assignTemporaryMessageInterface(chatActivity); diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 8becfc6c82..0263c92a4b 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -81,7 +81,6 @@ import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.activities.CallActivity import com.nextcloud.talk.activities.TakePhotoActivity import com.nextcloud.talk.adapters.messages.CallStartedMessageInterface -import com.nextcloud.talk.adapters.messages.CallStartedViewHolder import com.nextcloud.talk.adapters.messages.CommonMessageInterface import com.nextcloud.talk.adapters.messages.IncomingDeckCardViewHolder import com.nextcloud.talk.adapters.messages.IncomingLinkPreviewMessageViewHolder @@ -883,7 +882,7 @@ class ChatActivity : } processExpiredMessages() - processCallStartedMessages(chatMessageList) + processCallStartedMessages() adapter?.notifyDataSetChanged() } @@ -1203,17 +1202,6 @@ class ChatActivity : R.layout.item_custom_outcoming_preview_message ) - messageHolders.registerContentType( - CONTENT_TYPE_CALL_STARTED, - CallStartedViewHolder::class.java, - payload, - R.layout.call_started_message, - CallStartedViewHolder::class.java, - payload, - R.layout.call_started_message, - this - ) - messageHolders.registerContentType( CONTENT_TYPE_TEMP, TemporaryMessageViewHolder::class.java, @@ -2577,7 +2565,7 @@ class ChatActivity : } } - private fun processCallStartedMessages(chatMessageList: List) { + private fun processCallStartedMessages() { try { val mostRecentCallSystemMessage = adapter?.items?.first { it.item is ChatMessage && @@ -2595,8 +2583,7 @@ class ChatActivity : if (mostRecentCallSystemMessage != null) { processMostRecentMessage( - mostRecentCallSystemMessage as ChatMessage, - chatMessageList + mostRecentCallSystemMessage as ChatMessage ) } } catch (e: NoSuchElementException) { @@ -3563,29 +3550,21 @@ class ChatActivity : else -> false } - private fun processMostRecentMessage(recent: ChatMessage, chatMessageList: List) { + private fun processMostRecentMessage(recent: ChatMessage) { when (recent.systemMessageType) { - ChatMessage.SystemMessageType.CALL_STARTED -> { // add CallStartedMessage with id -2 + ChatMessage.SystemMessageType.CALL_STARTED -> { if (!callStarted) { - val callStartedChatMessage = ChatMessage() - callStartedChatMessage.jsonMessageId = CALL_STARTED_ID - callStartedChatMessage.actorId = "-2" - val name = if (recent.actorDisplayName.isNullOrEmpty()) "Guest" else recent.actorDisplayName - callStartedChatMessage.actorDisplayName = name - callStartedChatMessage.actorType = recent.actorType - callStartedChatMessage.timestamp = chatMessageList[0].timestamp - callStartedChatMessage.message = null - adapter?.addToStart(callStartedChatMessage, false) + messageInputViewModel.showCallStartedIndicator(recent, true) callStarted = true } - } // remove CallStartedMessage with id -2 + } ChatMessage.SystemMessageType.CALL_ENDED, ChatMessage.SystemMessageType.CALL_MISSED, ChatMessage.SystemMessageType.CALL_TRIED, ChatMessage.SystemMessageType.CALL_ENDED_EVERYONE -> { - adapter?.deleteById("-2") callStarted = false - } // remove message of id -2 + messageInputViewModel.showCallStartedIndicator(recent, false) + } else -> {} } } diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt index f803c4fc00..37d85122a2 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -9,6 +9,7 @@ package com.nextcloud.talk.chat import android.content.res.Resources import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle import android.os.CountDownTimer @@ -36,6 +37,7 @@ import android.widget.PopupMenu import android.widget.RelativeLayout import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat import androidx.core.widget.doAfterTextChanged @@ -43,7 +45,11 @@ import androidx.emoji2.widget.EmojiTextView import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import autodagger.AutoInjector +import coil.Coil.imageLoader import coil.load +import coil.request.ImageRequest +import coil.target.Target +import coil.transform.CircleCropTransformation import com.google.android.flexbox.FlexboxLayout import com.google.android.material.button.MaterialButton import com.google.android.material.snackbar.Snackbar @@ -119,6 +125,7 @@ class MessageInputFragment : Fragment() { private var mentionAutocomplete: Autocomplete<*>? = null private var xcounter = 0f private var ycounter = 0f + private var isCollapsed = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -201,6 +208,49 @@ class MessageInputFragment : Fragment() { binding.fragmentConnectionLost.text = getString(R.string.connection_lost_sent_messages_are_queued) } } + + chatActivity.messageInputViewModel.callStartedFlow.observe(viewLifecycleOwner) { + val (message, show) = it + if (show) { + binding.fragmentCallStarted.callAuthorChip.text = message.actorDisplayName + binding.fragmentCallStarted.callAuthorChipSecondary.text = message.actorDisplayName + val user = userManager.currentUser.blockingGet() + val url: String = if (message.actorType == "guests" || message.actorType == "guest") { + ApiUtils.getUrlForGuestAvatar( + user!!.baseUrl!!, + message.actorDisplayName, + true + ) + } else { + ApiUtils.getUrlForAvatar(user!!.baseUrl!!, message.actorId, false) + } + + val imageRequest: ImageRequest = ImageRequest.Builder(requireContext()) + .data(url) + .crossfade(true) + .transformations(CircleCropTransformation()) + .target(object : Target { + override fun onStart(placeholder: Drawable?) { + // unused atm + } + + override fun onError(error: Drawable?) { + // unused atm + } + + override fun onSuccess(result: Drawable) { + binding.fragmentCallStarted.callAuthorChip.chipIcon = result + binding.fragmentCallStarted.callAuthorChipSecondary.chipIcon = result + } + }) + .build() + + imageLoader(requireContext()).enqueue(imageRequest) + binding.fragmentCallStarted.root.visibility = View.VISIBLE + } else { + binding.fragmentCallStarted.root.visibility = View.GONE + } + } } private fun handleUI(isOnline: Boolean, connectionGained: Boolean) { @@ -395,6 +445,41 @@ class MessageInputFragment : Fragment() { binding.fragmentMessageInputView.button?.contentDescription = resources.getString(R.string.nc_description_send_message_button) + + binding.fragmentCallStarted.joinAudioCall.setOnClickListener { + chatActivity.joinAudioCall() + } + + binding.fragmentCallStarted.joinVideoCall.setOnClickListener { + chatActivity.joinVideoCall() + } + + binding.fragmentCallStarted.callStartedCloseBtn.setOnClickListener { + isCollapsed = !isCollapsed + if (isCollapsed) { + binding.fragmentCallStarted.callAuthorLayout.visibility = View.GONE + binding.fragmentCallStarted.callBtnLayout.visibility = View.GONE + binding.fragmentCallStarted.callAuthorChipSecondary.visibility = View.VISIBLE + binding.fragmentCallStarted.callStartedSecondaryText.visibility = View.VISIBLE + } else { + binding.fragmentCallStarted.callAuthorLayout.visibility = View.VISIBLE + binding.fragmentCallStarted.callBtnLayout.visibility = View.VISIBLE + binding.fragmentCallStarted.callAuthorChipSecondary.visibility = View.GONE + binding.fragmentCallStarted.callStartedSecondaryText.visibility = View.GONE + } + + setDropDown(isCollapsed) + } + } + + private fun setDropDown(collapsed: Boolean) { + val drawable = if (collapsed) { + AppCompatResources.getDrawable(requireContext(), R.drawable.ic_keyboard_arrow_up) + } else { + AppCompatResources.getDrawable(requireContext(), R.drawable.ic_keyboard_arrow_down) + } + + binding.fragmentCallStarted.callStartedCloseBtn.setImageDrawable(drawable) } @Suppress("ClickableViewAccessibility", "CyclomaticComplexMethod", "LongMethod") @@ -911,6 +996,22 @@ class MessageInputFragment : Fragment() { binding.fragmentEditView.clearEdit.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) } + + binding.fragmentCallStarted.callStartedBackground.apply { + viewThemeUtils.talk.themeOutgoingMessageBubble(this, grouped = true, false) + } + + binding.fragmentCallStarted.callAuthorChip.apply { + viewThemeUtils.material.colorChipBackground(this) + } + + binding.fragmentCallStarted.callAuthorChipSecondary.apply { + viewThemeUtils.material.colorChipBackground(this) + } + + binding.fragmentCallStarted.callStartedCloseBtn.apply { + viewThemeUtils.platform.colorImageView(this, ColorRole.PRIMARY) + } } private fun cancelReply() { diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 6e17ac0b61..4292a02817 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -18,6 +18,7 @@ import androidx.lifecycle.asLiveData import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager import com.nextcloud.talk.chat.data.io.AudioRecorderManager import com.nextcloud.talk.chat.data.io.MediaPlayerManager +import com.nextcloud.talk.chat.data.model.ChatMessage import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.models.json.generic.GenericOverall @@ -129,6 +130,10 @@ class MessageInputViewModel @Inject constructor( val messageQueueFlow: LiveData> get() = _messageQueueFlow + private val _callStartedFlow: MutableLiveData> = MutableLiveData() + val callStartedFlow: LiveData> + get() = _callStartedFlow + @Suppress("LongParameterList") fun sendChatMessage( internalId: String, @@ -314,6 +319,10 @@ class MessageInputViewModel @Inject constructor( dataStore.saveMessageQueue(internalId, queue) } + fun showCallStartedIndicator(recent: ChatMessage, show: Boolean) { + _callStartedFlow.postValue(Pair(recent, show)) + } + companion object { private val TAG = MessageInputViewModel::class.java.simpleName private const val DELAY_BETWEEN_QUEUED_MESSAGES: Long = 1000 diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_up.xml b/app/src/main/res/drawable/ic_keyboard_arrow_up.xml new file mode 100644 index 0000000000..93ed0650f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_up.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/app/src/main/res/layout/call_started_message.xml b/app/src/main/res/layout/call_started_message.xml index 33360a4c02..7dd2f1a04c 100644 --- a/app/src/main/res/layout/call_started_message.xml +++ b/app/src/main/res/layout/call_started_message.xml @@ -20,7 +20,46 @@ + + + + + + + + + + @@ -42,6 +81,7 @@ + - - + + + + - + \ No newline at end of file