Browse Source

Merge pull request #3307 from nextcloud/show-accept-button

Show accept call button in chat
Andy Scherzinger 1 year ago
parent
commit
50c51c1a94

+ 25 - 0
app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedMessageInterface.kt

@@ -0,0 +1,25 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Julius Linus
+ * Copyright (C) 2023 Julius Linus <julius.linus@nextcloud.com>
+ *
+ * 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 com.nextcloud.talk.adapters.messages
+
+interface CallStartedMessageInterface {
+    fun joinAudioCall()
+    fun joinVideoCall()
+}

+ 129 - 0
app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt

@@ -0,0 +1,129 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Julius Linus
+ * Copyright (C) 2023 Julius Linus <julius.linus@nextcloud.com>
+ *
+ * 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 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.models.json.chat.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<ChatMessage>(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, true)
+        }
+
+        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 {
+        var TAG: String? = CallStartedViewHolder::class.simpleName
+    }
+}

+ 2 - 0
app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java

@@ -77,6 +77,8 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
 
         } else if (holder instanceof SystemMessageViewHolder) {
             ((SystemMessageViewHolder) holder).assignSystemMessageInterface(chatActivity);
+        } else if (holder instanceof CallStartedViewHolder) {
+            ((CallStartedViewHolder) holder).assignCallStartedMessageInterface(chatActivity);
         }
     }
 }

+ 89 - 9
app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt

@@ -121,6 +121,8 @@ import com.nextcloud.talk.R
 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.IncomingLinkPreviewMessageViewHolder
 import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
@@ -268,7 +270,8 @@ class ChatActivity :
     VoiceMessageInterface,
     CommonMessageInterface,
     PreviewMessageInterface,
-    SystemMessageInterface {
+    SystemMessageInterface,
+    CallStartedMessageInterface {
 
     var active = false
 
@@ -377,6 +380,8 @@ class ChatActivity :
     var typedWhileTypingTimerIsRunning: Boolean = false
     val typingParticipants = HashMap<String, TypingParticipant>()
 
+    var callStarted = false
+
     private val localParticipantMessageListener = object : SignalingMessageReceiver.LocalParticipantMessageListener {
         override fun onSwitchTo(token: String?) {
             if (token != null) {
@@ -946,6 +951,17 @@ 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_SYSTEM_MESSAGE,
             SystemMessageViewHolder::class.java,
@@ -1808,7 +1824,7 @@ class ChatActivity :
                             }
                         }
                     }
-                    mediaPlayerHandler.postDelayed(this, 15)
+                    mediaPlayerHandler.postDelayed(this, MILISEC_15)
                 }
             })
 
@@ -3189,6 +3205,31 @@ class ChatActivity :
                                 Integer.parseInt(it)
                             }
 
+                            try {
+                                val mostRecentCallSystemMessage = adapter?.items?.first {
+                                    it.item is ChatMessage &&
+                                        (it.item as ChatMessage).systemMessageType in
+                                        listOf(
+                                            ChatMessage.SystemMessageType.CALL_STARTED,
+                                            ChatMessage.SystemMessageType.CALL_JOINED,
+                                            ChatMessage.SystemMessageType.CALL_LEFT,
+                                            ChatMessage.SystemMessageType.CALL_ENDED,
+                                            ChatMessage.SystemMessageType.CALL_TRIED,
+                                            ChatMessage.SystemMessageType.CALL_ENDED_EVERYONE,
+                                            ChatMessage.SystemMessageType.CALL_MISSED
+                                        )
+                                }?.item
+
+                                if (mostRecentCallSystemMessage != null) {
+                                    processMostRecentMessage(
+                                        mostRecentCallSystemMessage as ChatMessage,
+                                        chatMessageList
+                                    )
+                                }
+                            } catch (e: java.util.NoSuchElementException) {
+                                Log.d(TAG, "No System messages found $e")
+                            }
+
                             updateReadStatusOfAllMessages(newXChatLastCommonRead)
                             adapter?.notifyDataSetChanged()
 
@@ -4230,10 +4271,39 @@ class ChatActivity :
             CONTENT_TYPE_LINK_PREVIEW -> message.isLinkPreview()
             CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage)
             CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == "-1"
+            CONTENT_TYPE_CALL_STARTED -> message.id == "-2"
+
             else -> false
         }
     }
 
+    private fun processMostRecentMessage(recent: ChatMessage, chatMessageList: List<ChatMessage>) {
+        when (recent.systemMessageType) {
+            ChatMessage.SystemMessageType.CALL_STARTED -> { // add CallStartedMessage with id -2
+                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)
+                    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
+            else -> {}
+        }
+    }
+
     @Subscribe(threadMode = ThreadMode.BACKGROUND)
     fun onMessageEvent(webSocketCommunicationEvent: WebSocketCommunicationEvent) {
         /*
@@ -4367,6 +4437,14 @@ class ChatActivity :
         }
     }
 
+    override fun joinAudioCall() {
+        startACall(true, false)
+    }
+
+    override fun joinVideoCall() {
+        startACall(false, false)
+    }
+
     private fun logConversationInfos(methodName: String) {
         Log.d(TAG, " |-----------------------------------------------")
         Log.d(TAG, " | method: $methodName")
@@ -4379,12 +4457,13 @@ class ChatActivity :
 
     companion object {
         private val TAG = ChatActivity::class.simpleName
-        private const val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1
-        private const val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2
-        private const val CONTENT_TYPE_LOCATION: Byte = 3
-        private const val CONTENT_TYPE_VOICE_MESSAGE: Byte = 4
-        private const val CONTENT_TYPE_POLL: Byte = 5
-        private const val CONTENT_TYPE_LINK_PREVIEW: Byte = 6
+        private const val CONTENT_TYPE_CALL_STARTED: Byte = 1
+        private const val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 2
+        private const val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 3
+        private const val CONTENT_TYPE_LOCATION: Byte = 4
+        private const val CONTENT_TYPE_VOICE_MESSAGE: Byte = 5
+        private const val CONTENT_TYPE_POLL: Byte = 6
+        private const val CONTENT_TYPE_LINK_PREVIEW: Byte = 7
         private const val NEW_MESSAGES_POPUP_BUBBLE_DELAY: Long = 200
         private const val GET_ROOM_INFO_DELAY_NORMAL: Long = 30000
         private const val GET_ROOM_INFO_DELAY_LOBBY: Long = 5000
@@ -4417,7 +4496,6 @@ class ChatActivity :
         private const val FULLY_OPAQUE_INT: Int = 255
         private const val SEMI_TRANSPARENT_INT: Int = 99
         private const val VOICE_MESSAGE_SEEKBAR_BASE = 1000
-        private const val SECOND: Long = 1000
         private const val NO_PREVIOUS_MESSAGE_ID: Int = -1
         private const val GROUPED_MESSAGES_THRESHOLD = 4
         private const val GROUPED_MESSAGES_SAME_AUTHOR_THRESHOLD = 5
@@ -4447,5 +4525,7 @@ class ChatActivity :
         private const val TYPING_INTERVAL_TO_SEND_NEXT_TYPING_MESSAGE = 1000L
         private const val TYPING_STARTED_SIGNALING_MESSAGE_TYPE = "startedTyping"
         private const val TYPING_STOPPED_SIGNALING_MESSAGE_TYPE = "stoppedTyping"
+        private const val CALL_STARTED_ID = -2
+        private const val MILISEC_15: Long = 15
     }
 }

+ 90 - 0
app/src/main/res/layout/call_started_message.xml

@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Julius Linus
+  ~ Copyright (C) 2023 Julius Linus <julius.linus@nextcloud.com>
+  ~
+  ~ 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/>.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/call_started_background"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="@dimen/standard_double_margin"
+    android:padding="@dimen/standard_padding"
+    tools:background="@drawable/shape_grouped_outcoming_message"
+    tools:backgroundTint="@color/colorPrimaryDark">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/standard_margin"
+        android:orientation="horizontal"
+        android:gravity="center">
+
+        <com.google.android.material.chip.Chip
+            android:id="@+id/call_author_chip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/standard_quarter_margin"
+            app:chipIcon="@drawable/accent_circle"
+            app:chipCornerRadius="@dimen/dialogBorderRadius"
+            tools:text="Jessica Fox" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/started_a_call" />
+
+    </LinearLayout>
+
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:gravity="center">
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/join_video_call"
+            style="@style/Widget.Material3.Button.Icon"
+            android:backgroundTint="@color/nc_darkGreen"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/standard_half_margin"
+            app:icon="@drawable/ic_videocam_grey_600_24dp"
+            app:iconTint="@color/white"
+            android:alpha="0.8"
+            android:textColor="@color/white"
+            android:text="@string/video_call" />
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/join_audio_call"
+            style="@style/Widget.Material3.Button.Icon"
+            android:backgroundTint="@color/nc_darkGreen"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/standard_half_margin"
+            android:alpha="0.8"
+            app:icon="@drawable/ic_phone"
+            app:iconTint="@color/white"
+            android:text="@string/audio_call"
+            android:textColor="@color/white" />
+    </LinearLayout>
+
+
+</LinearLayout>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -706,6 +706,9 @@ How to translate with transifex:
     <string name="custom">Custom</string>
     <string name="set">Set</string>
     <string name="calendar">Calendar</string>
+    <string name="video_call">Video Call</string>
+    <string name="audio_call">Audio Call</string>
+    <string name="started_a_call">started a call</string>
     <string name="nc_settings_phone_book_integration_phone_number_dialog_429">Error 429 Too Many Requests</string>
 
 </resources>