From 05b57dc4c7908a3cc9dfca42d9e3aa12e8b27d97 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 4 May 2023 18:21:59 +0200 Subject: [PATCH 01/24] rename controller_chat.xml to activity_chat.xml Signed-off-by: Marcel Hibbe --- app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt | 6 +++--- .../res/layout/{controller_chat.xml => activity_chat.xml} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename app/src/main/res/layout/{controller_chat.xml => activity_chat.xml} (100%) 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 0a23661bf..02827bc14 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -124,7 +124,7 @@ import com.nextcloud.talk.callbacks.MentionAutocompleteCallback import com.nextcloud.talk.conversationinfo.ConversationInfoActivity import com.nextcloud.talk.conversationlist.ConversationsListActivity import com.nextcloud.talk.data.user.model.User -import com.nextcloud.talk.databinding.ControllerChatBinding +import com.nextcloud.talk.databinding.ActivityChatBinding import com.nextcloud.talk.events.UserMentionClickEvent import com.nextcloud.talk.events.WebSocketCommunicationEvent import com.nextcloud.talk.extensions.loadAvatarOrImagePreview @@ -231,7 +231,7 @@ class ChatActivity : var active = false - private lateinit var binding: ControllerChatBinding + private lateinit var binding: ActivityChatBinding @Inject lateinit var ncApi: NcApi @@ -315,7 +315,7 @@ class ChatActivity : super.onCreate(savedInstanceState) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) - binding = ControllerChatBinding.inflate(layoutInflater) + binding = ActivityChatBinding.inflate(layoutInflater) setupActionBar() setContentView(binding.root) setupSystemColors() diff --git a/app/src/main/res/layout/controller_chat.xml b/app/src/main/res/layout/activity_chat.xml similarity index 100% rename from app/src/main/res/layout/controller_chat.xml rename to app/src/main/res/layout/activity_chat.xml From 32bd18ff323f5283ed2e1373f3d72490a4a5b036 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 4 May 2023 21:34:03 +0200 Subject: [PATCH 02/24] add textview for typing indicator Signed-off-by: Marcel Hibbe --- app/src/main/res/layout/activity_chat.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index 26adaf994..e80c2a060 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -161,6 +161,15 @@ android:layout_height="1dp" android:background="@color/controller_chat_separator" /> + + + Date: Tue, 16 May 2023 13:55:07 +0200 Subject: [PATCH 03/24] add receiving of "typing" signaling messages Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 20 +++++++ .../ConversationMessageNotifier.java | 54 +++++++++++++++++++ .../signaling/SignalingMessageReceiver.java | 26 +++++++++ 3 files changed, 100 insertions(+) create mode 100644 app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.java 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 02827bc14..8215cd874 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -311,6 +311,24 @@ class ChatActivity : } } + private val conversationMessageListener = object : SignalingMessageReceiver.ConversationMessageListener { + override fun onStartTyping(session: String?) { + val name = webSocketInstance?.getDisplayNameForSession(session) + + runOnUiThread { + Toast.makeText(this@ChatActivity, name + " started typing", Toast.LENGTH_SHORT).show() + } + } + + override fun onStopTyping(session: String?) { + val name = webSocketInstance?.getDisplayNameForSession(session) + + runOnUiThread { + Toast.makeText(this@ChatActivity, name + " stopped typing", Toast.LENGTH_SHORT).show() + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) @@ -398,6 +416,7 @@ class ChatActivity : setupWebsocket() webSocketInstance?.getSignalingMessageReceiver()?.addListener(localParticipantMessageListener) + webSocketInstance?.getSignalingMessageReceiver()?.addListener(conversationMessageListener) if (conversationUser?.userId != "?" && CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "mention-flag") @@ -1980,6 +1999,7 @@ class ChatActivity : eventBus.unregister(this) webSocketInstance?.getSignalingMessageReceiver()?.removeListener(localParticipantMessageListener) + webSocketInstance?.getSignalingMessageReceiver()?.removeListener(conversationMessageListener) findViewById(R.id.toolbar)?.setOnClickListener(null) diff --git a/app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.java b/app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.java new file mode 100644 index 000000000..5f19e29c2 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.java @@ -0,0 +1,54 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2023 Marcel Hibbe + * + * 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 com.nextcloud.talk.signaling; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.Set; + + +class ConversationMessageNotifier { + + private final Set conversationMessageListeners = new LinkedHashSet<>(); + + public synchronized void addListener(SignalingMessageReceiver.ConversationMessageListener listener) { + if (listener == null) { + throw new IllegalArgumentException("conversationMessageListener can not be null"); + } + + conversationMessageListeners.add(listener); + } + + public synchronized void removeListener(SignalingMessageReceiver.ConversationMessageListener listener) { + conversationMessageListeners.remove(listener); + } + + public synchronized void notifyStartTyping(String sessionId) { + for (SignalingMessageReceiver.ConversationMessageListener listener : new ArrayList<>(conversationMessageListeners)) { + listener.onStartTyping(sessionId); + } + } + + public void notifyStopTyping(String sessionId) { + for (SignalingMessageReceiver.ConversationMessageListener listener : new ArrayList<>(conversationMessageListeners)) { + listener.onStopTyping(sessionId); + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java b/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java index 8853af425..8d0c2318a 100644 --- a/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java +++ b/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java @@ -153,6 +153,14 @@ public abstract class SignalingMessageReceiver { void onUnshareScreen(); } + /** + * Listener for conversation messages. + */ + public interface ConversationMessageListener { + void onStartTyping(String session); + void onStopTyping(String session); + } + /** * Listener for WebRTC offers. * @@ -185,6 +193,8 @@ public abstract class SignalingMessageReceiver { private final CallParticipantMessageNotifier callParticipantMessageNotifier = new CallParticipantMessageNotifier(); + private final ConversationMessageNotifier conversationMessageNotifier = new ConversationMessageNotifier(); + private final OfferMessageNotifier offerMessageNotifier = new OfferMessageNotifier(); private final WebRtcMessageNotifier webRtcMessageNotifier = new WebRtcMessageNotifier(); @@ -236,6 +246,14 @@ public abstract class SignalingMessageReceiver { callParticipantMessageNotifier.removeListener(listener); } + public void addListener(ConversationMessageListener listener) { + conversationMessageNotifier.addListener(listener); + } + + public void removeListener(ConversationMessageListener listener) { + conversationMessageNotifier.removeListener(listener); + } + /** * Adds a listener for all offer messages. * @@ -563,6 +581,14 @@ public abstract class SignalingMessageReceiver { return; } + if ("startedTyping".equals(type)) { + conversationMessageNotifier.notifyStartTyping(sessionId); + } + + if ("stoppedTyping".equals(type)) { + conversationMessageNotifier.notifyStopTyping(sessionId); + } + if ("reaction".equals(type)) { // Message schema (external signaling server): // { From 6228ec8d7538ce7e4356729bd1b53cc1f9eb44bb Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 16 May 2023 16:32:44 +0200 Subject: [PATCH 04/24] add sending of "typing" signaling messages Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 18 +++++++++++++++++- .../nextcloud/talk/webrtc/WebSocketInstance.kt | 13 +++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) 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 8215cd874..00b4c08ea 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -144,12 +144,14 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomsOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.mention.Mention +import com.nextcloud.talk.models.json.signaling.NCSignalingMessage import com.nextcloud.talk.polls.ui.PollCreateDialogFragment import com.nextcloud.talk.presenters.MentionAutocompletePresenter import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity import com.nextcloud.talk.repositories.reactions.ReactionsRepository import com.nextcloud.talk.shareditems.activities.SharedItemsActivity import com.nextcloud.talk.signaling.SignalingMessageReceiver +import com.nextcloud.talk.signaling.SignalingMessageSender import com.nextcloud.talk.translate.TranslateActivity import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet import com.nextcloud.talk.ui.dialog.AttachmentDialog @@ -278,7 +280,8 @@ class ChatActivity : private var conversationVideoMenuItem: MenuItem? = null private var conversationSharedItemsItem: MenuItem? = null - var webSocketInstance: WebSocketInstance? = null + private var webSocketInstance: WebSocketInstance? = null + private var signalingMessageSender: SignalingMessageSender? = null var getRoomInfoTimerHandler: Handler? = null var pastPreconditionFailed = false @@ -515,6 +518,8 @@ class ChatActivity : @Suppress("Detekt.TooGenericExceptionCaught") override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + sendTypingMessage() + if (s.length >= lengthFilter) { binding?.messageInputView?.inputEditText?.error = String.format( Objects.requireNonNull(resources).getString(R.string.nc_limit_hit), @@ -647,6 +652,15 @@ class ChatActivity : } } + fun sendTypingMessage() { + for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { + val ncSignalingMessage = NCSignalingMessage() + ncSignalingMessage.to = sessionId + ncSignalingMessage.type = "startedTyping" + signalingMessageSender!!.send(ncSignalingMessage) + } + } + private fun initMessageHolders(): MessageHolders { val messageHolders = MessageHolders() val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!) @@ -2323,6 +2337,8 @@ class ChatActivity : if (webSocketInstance == null) { Log.d(TAG, "webSocketInstance not set up. This should only happen when not using the HPB") } + + signalingMessageSender = webSocketInstance?.signalingMessageSender } fun pullChatMessages( diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt b/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt index f66e2431a..6b249f18a 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt +++ b/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt @@ -206,6 +206,8 @@ class WebSocketInstance internal constructor( processRoomMessageMessage(eventOverallWebSocketMessage) } else if ("join" == eventOverallWebSocketMessage.eventMap!!["type"]) { processRoomJoinMessage(eventOverallWebSocketMessage) + } else if ("leave" == eventOverallWebSocketMessage.eventMap!!["type"]) { + processRoomLeaveMessage(eventOverallWebSocketMessage) } signalingMessageReceiver.process(eventOverallWebSocketMessage.eventMap) } @@ -271,6 +273,17 @@ class WebSocketInstance internal constructor( } } + private fun processRoomLeaveMessage(eventOverallWebSocketMessage: EventOverallWebSocketMessage) { + val leaveEventList = eventOverallWebSocketMessage.eventMap?.get("leave") as List? + for (i in leaveEventList!!.indices) { + usersHashMap.remove(leaveEventList[i]) + } + } + + fun getUserMap(): HashMap { + return usersHashMap + } + @Throws(IOException::class) private fun processJoinedRoomMessage(text: String) { val (_, roomWebSocketMessage) = LoganSquare.parse(text, JoinedRoomOverallWebSocketMessage::class.java) From 16d2d3d693663f8b7bb1744e2c9ccfdbf62ecf87 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 16 May 2023 17:02:18 +0200 Subject: [PATCH 05/24] add timer for typing logic Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) 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 00b4c08ea..d7f010b6e 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -44,6 +44,7 @@ import android.media.MediaRecorder import android.net.Uri import android.os.Build import android.os.Bundle +import android.os.CountDownTimer import android.os.Handler import android.os.Parcelable import android.os.SystemClock @@ -302,6 +303,8 @@ class ChatActivity : private var videoURI: Uri? = null + var typingTimer: CountDownTimer? = null + private val localParticipantMessageListener = object : SignalingMessageReceiver.LocalParticipantMessageListener { override fun onSwitchTo(token: String?) { if (token != null) { @@ -518,7 +521,7 @@ class ChatActivity : @Suppress("Detekt.TooGenericExceptionCaught") override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - sendTypingMessage() + sendStartTypingMessage() if (s.length >= lengthFilter) { binding?.messageInputView?.inputEditText?.error = String.format( @@ -652,11 +655,35 @@ class ChatActivity : } } - fun sendTypingMessage() { + fun sendStartTypingMessage() { + if (typingTimer == null) { + for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { + val ncSignalingMessage = NCSignalingMessage() + ncSignalingMessage.to = sessionId + ncSignalingMessage.type = "startedTyping" + signalingMessageSender!!.send(ncSignalingMessage) + } + + typingTimer = object : CountDownTimer(4000, 1000) { + override fun onTick(millisUntilFinished: Long) { + } + + override fun onFinish() { + sendStopTypingMessage() + typingTimer = null + } + }.start() + } else { + typingTimer?.cancel() + typingTimer?.start() + } + } + + fun sendStopTypingMessage() { for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { val ncSignalingMessage = NCSignalingMessage() ncSignalingMessage.to = sessionId - ncSignalingMessage.type = "startedTyping" + ncSignalingMessage.type = "stoppedTyping" signalingMessageSender!!.send(ncSignalingMessage) } } From 50e7af4d04cda51e50b0c44bc0880f3298a5217b Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 16 May 2023 17:05:47 +0200 Subject: [PATCH 06/24] add timer for typing logic Signed-off-by: Marcel Hibbe --- app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt | 4 ++++ 1 file changed, 4 insertions(+) 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 d7f010b6e..18eed29a9 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -656,6 +656,10 @@ class ChatActivity : } fun sendStartTypingMessage() { + if (webSocketInstance == null) { + return + } + if (typingTimer == null) { for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { val ncSignalingMessage = NCSignalingMessage() From 85f637ca8a55e36fe0503ba9d36f719afdf8ed01 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 16 May 2023 17:26:50 +0200 Subject: [PATCH 07/24] WIP. update UI when typing message received Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/chat/ChatActivity.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) 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 18eed29a9..5375cff48 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -321,17 +321,13 @@ class ChatActivity : override fun onStartTyping(session: String?) { val name = webSocketInstance?.getDisplayNameForSession(session) - runOnUiThread { - Toast.makeText(this@ChatActivity, name + " started typing", Toast.LENGTH_SHORT).show() - } + // binding.typingIndicator.visibility = View.VISIBLE + binding.typingIndicator.text = name + " started typing" } override fun onStopTyping(session: String?) { - val name = webSocketInstance?.getDisplayNameForSession(session) - - runOnUiThread { - Toast.makeText(this@ChatActivity, name + " stopped typing", Toast.LENGTH_SHORT).show() - } + // binding.typingIndicator.visibility = View.INVISIBLE + binding.typingIndicator.text = "x" } } From 6adca2395d162e21e2d3869d2fdf88af49e4dad9 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Wed, 17 May 2023 12:46:15 +0200 Subject: [PATCH 08/24] add handling for multiple typing participants Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 89 +++++++++++++++++-- app/src/main/res/layout/activity_chat.xml | 3 +- app/src/main/res/values/strings.xml | 5 ++ 3 files changed, 89 insertions(+), 8 deletions(-) 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 5375cff48..58f111c05 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -52,6 +52,7 @@ import android.provider.ContactsContract import android.provider.MediaStore import android.text.Editable import android.text.InputFilter +import android.text.SpannableStringBuilder import android.text.TextUtils import android.text.TextWatcher import android.util.Log @@ -76,6 +77,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.content.PermissionChecker import androidx.core.graphics.drawable.toBitmap +import androidx.core.text.bold import androidx.core.widget.doAfterTextChanged import androidx.emoji2.text.EmojiCompat import androidx.emoji2.widget.EmojiTextView @@ -304,6 +306,7 @@ class ChatActivity : private var videoURI: Uri? = null var typingTimer: CountDownTimer? = null + val typingParticipants = HashMap() private val localParticipantMessageListener = object : SignalingMessageReceiver.LocalParticipantMessageListener { override fun onSwitchTo(token: String?) { @@ -318,16 +321,85 @@ class ChatActivity : } private val conversationMessageListener = object : SignalingMessageReceiver.ConversationMessageListener { - override fun onStartTyping(session: String?) { - val name = webSocketInstance?.getDisplayNameForSession(session) + override fun onStartTyping(session: String) { + var name = webSocketInstance?.getDisplayNameForSession(session) - // binding.typingIndicator.visibility = View.VISIBLE - binding.typingIndicator.text = name + " started typing" + if (name != null && !typingParticipants.contains(session)) { + if (name == "") { + name = context.resources?.getString(R.string.nc_guest)!! + } + typingParticipants[session] = name + updateTypingIndicator() + } } - override fun onStopTyping(session: String?) { - // binding.typingIndicator.visibility = View.INVISIBLE - binding.typingIndicator.text = "x" + override fun onStopTyping(session: String) { + typingParticipants.remove(session) + updateTypingIndicator() + } + } + + private fun updateTypingIndicator() { + val participantNames = ArrayList(typingParticipants.values) + + val typingString: SpannableStringBuilder + when (typingParticipants.size) { + 0 -> { + typingString = SpannableStringBuilder().append("") + } + + 1 -> { + typingString = SpannableStringBuilder() + .bold { append(participantNames[0]) } + .append(WHITESPACE + context.resources?.getString(R.string.typing_is_typing)) + } + + 2 -> typingString = SpannableStringBuilder() + .bold { append(participantNames[0]) } + .append(WHITESPACE + context.resources?.getString(R.string.nc_common_and) + WHITESPACE) + .bold { append(participantNames[1]) } + .append(WHITESPACE + context.resources?.getString(R.string.typing_are_typing)) + + 3 -> typingString = SpannableStringBuilder() + .bold { append(participantNames[0]) } + .append(COMMA) + .bold { append(participantNames[1]) } + .append(WHITESPACE + context.resources?.getString(R.string.nc_common_and) + WHITESPACE) + .bold { append(participantNames[2]) } + .append(WHITESPACE + context.resources?.getString(R.string.typing_are_typing)) + + 4 -> typingString = SpannableStringBuilder() + .bold { append(participantNames[0]) } + .append(COMMA) + .bold { append(participantNames[1]) } + .append(COMMA) + .bold { append(participantNames[2]) } + .append(WHITESPACE + context.resources?.getString(R.string.typing_1_other)) + + else -> { + val moreTypersAmount = typingParticipants.size - 3 + + val othersTyping = context.resources?.getString(R.string.typing_x_others)?.let { + String.format(it, moreTypersAmount) + } + + typingString = SpannableStringBuilder() + .bold { append(participantNames[0]) } + .append(COMMA) + .bold { append(participantNames[1]) } + .append(COMMA) + .bold { append(participantNames[2]) } + .append(othersTyping) + } + } + + runOnUiThread { + if (participantNames.size > 0) { + binding.typingIndicator.visibility = View.VISIBLE + } else { + binding.typingIndicator.visibility = View.GONE + } + binding.typingIndicator.text = typingString } } @@ -3690,5 +3762,8 @@ class ChatActivity : private const val LOOKING_INTO_FUTURE_TIMEOUT = 30 private const val CHUNK_SIZE: Int = 10 private const val ONE_SECOND_IN_MILLIS = 1000 + + private const val WHITESPACE = " " + private const val COMMA = ", " } } diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index e80c2a060..a9fc29bd9 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -164,9 +164,10 @@ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bd7218b52..86df63f31 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,6 +44,7 @@ How to translate with transifex: Yes No + and Skip Set Dismiss @@ -447,6 +448,10 @@ How to translate with transifex: Open in Files app You are not allowed to share content to this chat + is typing + are typing + and 1 other is typing… + and %1$s others are typing… Add to conversation From ecec2667656e15036e638b7055e64fa6054cf1a7 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Wed, 17 May 2023 17:15:26 +0200 Subject: [PATCH 09/24] improve typing indicator design Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 26 ++++++---- app/src/main/res/layout/activity_chat.xml | 49 ++++++++++++------- app/src/main/res/values/strings.xml | 8 +-- 3 files changed, 51 insertions(+), 32 deletions(-) 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 58f111c05..37c105900 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -62,6 +62,7 @@ import android.view.Menu import android.view.MenuItem import android.view.MotionEvent import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AlphaAnimation import android.view.animation.Animation import android.view.animation.LinearInterpolator @@ -344,15 +345,11 @@ class ChatActivity : val typingString: SpannableStringBuilder when (typingParticipants.size) { - 0 -> { - typingString = SpannableStringBuilder().append("") - } + 0 -> typingString = SpannableStringBuilder().append(binding.typingIndicator.text) - 1 -> { - typingString = SpannableStringBuilder() + 1 -> typingString = SpannableStringBuilder() .bold { append(participantNames[0]) } .append(WHITESPACE + context.resources?.getString(R.string.typing_is_typing)) - } 2 -> typingString = SpannableStringBuilder() .bold { append(participantNames[0]) } @@ -378,11 +375,9 @@ class ChatActivity : else -> { val moreTypersAmount = typingParticipants.size - 3 - val othersTyping = context.resources?.getString(R.string.typing_x_others)?.let { String.format(it, moreTypersAmount) } - typingString = SpannableStringBuilder() .bold { append(participantNames[0]) } .append(COMMA) @@ -393,12 +388,22 @@ class ChatActivity : } } + val typingIndicatorHeight = DisplayUtils.convertDpToPixel(20f,context) + runOnUiThread { if (participantNames.size > 0) { - binding.typingIndicator.visibility = View.VISIBLE + binding.typingIndicatorWrapper.animate() + .translationY(binding.messageInputView.y - typingIndicatorHeight) + .setInterpolator(AccelerateDecelerateInterpolator()) + .duration = TYPING_INDICATOR_ANIMATION_DURATION + } else { - binding.typingIndicator.visibility = View.GONE + binding.typingIndicatorWrapper.animate() + .translationY(binding.messageInputView.y) + .setInterpolator(AccelerateDecelerateInterpolator()) + .duration = TYPING_INDICATOR_ANIMATION_DURATION } + binding.typingIndicator.text = typingString } } @@ -3765,5 +3770,6 @@ class ChatActivity : private const val WHITESPACE = " " private const val COMMA = ", " + private const val TYPING_INDICATOR_ANIMATION_DURATION = 200L } } diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index a9fc29bd9..b90eb204d 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -80,7 +80,8 @@ android:id="@+id/messagesListView" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingBottom="0dp" + android:paddingBottom="20dp" + android:clipToPadding="false" android:visibility="gone" app:dateHeaderTextSize="13sp" app:incomingBubblePaddingBottom="@dimen/message_bubble_corners_vertical_padding" @@ -108,7 +109,8 @@ app:outcomingTextLinkColor="@color/high_emphasis_text" app:outcomingTextSize="@dimen/chat_text_size" app:outcomingTimeTextSize="12sp" - app:textAutoLink="all" /> + app:textAutoLink="all" + tools:visibility="visible"/> + + + + + + + + + - - - - - Open in Files app You are not allowed to share content to this chat - is typing - are typing - and 1 other is typing… - and %1$s others are typing… + is typing … + are typing … + and 1 other is typing … + and %1$s others are typing … Add to conversation From 0d6e971c38b3653628e0eae4aea66308b9bd55e7 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 22 May 2023 12:55:30 +0200 Subject: [PATCH 10/24] fix to use newest capabilities to update settings. Without this refreshing of capabilites, depending settings (for now read privacy) were never updated until app was started again because the user still contained old capabilities. This fix will also be necessary for the typing indicator setting. Signed-off-by: Marcel Hibbe --- .../talk/settings/SettingsActivity.kt | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt b/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt index 2b2b1d720..ab29d7942 100644 --- a/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt @@ -56,6 +56,7 @@ import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.work.OneTimeWorkRequest +import androidx.work.WorkInfo import androidx.work.WorkManager import autodagger.AutoInjector import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -69,6 +70,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppT import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ActivitySettingsBinding import com.nextcloud.talk.jobs.AccountRemovalWorker +import com.nextcloud.talk.jobs.CapabilitiesWorker import com.nextcloud.talk.jobs.ContactAddressBookWorker import com.nextcloud.talk.jobs.ContactAddressBookWorker.Companion.checkPermission import com.nextcloud.talk.jobs.ContactAddressBookWorker.Companion.deleteAll @@ -172,6 +174,8 @@ class SettingsActivity : BaseActivity() { supportActionBar?.show() dispose(null) + loadCapabilitiesAndUpdateSettings() + binding.settingsVersion.setOnClickListener { sendLogs() } @@ -224,6 +228,19 @@ class SettingsActivity : BaseActivity() { themeSwitchPreferences() } + private fun loadCapabilitiesAndUpdateSettings() { + val capabilitiesWork = OneTimeWorkRequest.Builder(CapabilitiesWorker::class.java).build() + WorkManager.getInstance(context).enqueue(capabilitiesWork) + + WorkManager.getInstance(context).getWorkInfoByIdLiveData(capabilitiesWork.id) + .observe(this) { workInfo -> + if (workInfo?.state == WorkInfo.State.SUCCEEDED) { + getCurrentUser() + setupCheckables() + } + } + } + private fun setupActionBar() { setSupportActionBar(binding.settingsToolbar) binding.settingsToolbar.setNavigationOnClickListener { @@ -636,7 +653,7 @@ class SettingsActivity : BaseActivity() { (binding.settingsIncognitoKeyboard.findViewById(R.id.mp_checkable) as Checkable).isChecked = appPreferences.isKeyboardIncognito - if (CapabilitiesUtilNew.isReadStatusAvailable(userManager.currentUser.blockingGet())) { + if (CapabilitiesUtilNew.isReadStatusAvailable(currentUser!!)) { (binding.settingsReadPrivacy.findViewById(R.id.mp_checkable) as Checkable).isChecked = !CapabilitiesUtilNew.isReadStatusPrivate(currentUser!!) } else { @@ -993,7 +1010,7 @@ class SettingsActivity : BaseActivity() { } override fun onNext(genericOverall: GenericOverall) { - // unused atm + Log.d(TAG, "onNext") } override fun onError(e: Throwable) { From d3d8e2abef0200cf775da46eaaa68b3c0c717505 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 22 May 2023 13:45:50 +0200 Subject: [PATCH 11/24] add setting to toggle typing status privacy Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/api/NcApi.java | 5 ++ .../talk/settings/SettingsActivity.kt | 50 ++++++++++++++++++- .../database/user/CapabilitiesUtilNew.kt | 17 +++++++ .../utils/preferences/AppPreferences.java | 11 ++++ app/src/main/res/layout/activity_settings.xml | 8 +++ app/src/main/res/values/strings.xml | 2 + 6 files changed, 92 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index 6bf7f984c..498a73678 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -453,6 +453,11 @@ public interface NcApi { @Url String url, @Body RequestBody body); + @POST + Observable setTypingStatusPrivacy(@Header("Authorization") String authorization, + @Url String url, + @Body RequestBody body); + @POST Observable searchContactsByPhoneNumber(@Header("Authorization") String authorization, @Url String url, diff --git a/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt b/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt index ab29d7942..55a0f9385 100644 --- a/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/settings/SettingsActivity.kt @@ -124,6 +124,7 @@ class SettingsActivity : BaseActivity() { private var screenLockTimeoutChangeListener: OnPreferenceValueChangedListener? = null private var themeChangeListener: OnPreferenceValueChangedListener? = null private var readPrivacyChangeListener: OnPreferenceValueChangedListener? = null + private var typingStatusChangeListener: OnPreferenceValueChangedListener? = null private var phoneBookIntegrationChangeListener: OnPreferenceValueChangedListener? = null private var profileQueryDisposable: Disposable? = null private var dbQueryDisposable: Disposable? = null @@ -419,6 +420,11 @@ class SettingsActivity : BaseActivity() { readPrivacyChangeListener = it } ) + appPreferences.registerTypingStatusChangeListener( + TypingStatusChangeListener().also { + typingStatusChangeListener = it + } + ) } fun sendLogs() { @@ -487,6 +493,7 @@ class SettingsActivity : BaseActivity() { settingsIncognitoKeyboard, settingsPhoneBookIntegration, settingsReadPrivacy, + settingsTypingStatus, settingsProxyUseCredentials ).forEach(viewThemeUtils.talk::colorSwitchPreference) } @@ -660,6 +667,13 @@ class SettingsActivity : BaseActivity() { binding.settingsReadPrivacy.visibility = View.GONE } + if (CapabilitiesUtilNew.isTypingStatusAvailable(currentUser!!)) { + (binding.settingsTypingStatus.findViewById(R.id.mp_checkable) as Checkable).isChecked = + !CapabilitiesUtilNew.isTypingStatusPrivate(currentUser!!) + } else { + binding.settingsTypingStatus.visibility = View.GONE + } + (binding.settingsPhoneBookIntegration.findViewById(R.id.mp_checkable) as Checkable).isChecked = appPreferences.isPhoneBookIntegrationEnabled } @@ -697,6 +711,7 @@ class SettingsActivity : BaseActivity() { appPreferences.unregisterScreenLockTimeoutListener(screenLockTimeoutChangeListener) appPreferences.unregisterThemeChangeListener(themeChangeListener) appPreferences.unregisterReadPrivacyChangeListener(readPrivacyChangeListener) + appPreferences.unregisterTypingStatusChangeListener(typingStatusChangeListener) appPreferences.unregisterPhoneBookIntegrationChangeListener(phoneBookIntegrationChangeListener) super.onDestroy() @@ -1010,7 +1025,7 @@ class SettingsActivity : BaseActivity() { } override fun onNext(genericOverall: GenericOverall) { - Log.d(TAG, "onNext") + // unused atm } override fun onError(e: Throwable) { @@ -1026,6 +1041,39 @@ class SettingsActivity : BaseActivity() { } } + private inner class TypingStatusChangeListener : OnPreferenceValueChangedListener { + override fun onChanged(newValue: Boolean) { + val booleanValue = if (newValue) "0" else "1" + val json = "{\"key\": \"typing_privacy\", \"value\" : $booleanValue}" + ncApi.setTypingStatusPrivacy( + ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token), + ApiUtils.getUrlForUserSettings(currentUser!!.baseUrl), + RequestBody.create("application/json".toMediaTypeOrNull(), json) + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onNext(genericOverall: GenericOverall) { + // unused atm + } + + override fun onError(e: Throwable) { + appPreferences.setTypingStatus(!newValue) + (binding.settingsTypingStatus.findViewById(R.id.mp_checkable) as Checkable).isChecked = + !newValue + } + + override fun onComplete() { + // unused atm + } + }) + } + } + companion object { private const val TAG = "SettingsController" private const val DURATION: Long = 2500 diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt index d91a4178f..88a2676b9 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt @@ -98,7 +98,24 @@ object CapabilitiesUtilNew { return (map["read-privacy"]!!.toString()).toInt() == 1 } } + return false + } + fun isTypingStatusAvailable(user: User): Boolean { + if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) { + val map: Map? = user.capabilities!!.spreedCapability!!.config!!["chat"] + return map != null && map.containsKey("typing-privacy") + } + return false + } + + fun isTypingStatusPrivate(user: User): Boolean { + if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) { + val map = user.capabilities!!.spreedCapability!!.config!!["chat"] + if (map?.containsKey("typing-privacy") == true) { + return (map["typing-privacy"]!!.toString()).toInt() == 1 + } + } return false } diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java index 96b53631d..898819a97 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java @@ -312,6 +312,9 @@ public interface AppPreferences { @KeyByResource(R.string.nc_settings_read_privacy_key) void setReadPrivacy(boolean value); + + @KeyByResource(R.string.nc_settings_read_privacy_key) + void setTypingStatus(boolean value); @KeyByResource(R.string.nc_settings_read_privacy_key) @RegisterChangeListenerMethod @@ -321,6 +324,14 @@ public interface AppPreferences { @UnregisterChangeListenerMethod void unregisterReadPrivacyChangeListener(OnPreferenceValueChangedListener listener); + @KeyByResource(R.string.nc_settings_read_privacy_key) + @RegisterChangeListenerMethod + void registerTypingStatusChangeListener(OnPreferenceValueChangedListener listener); + + @KeyByResource(R.string.nc_settings_read_privacy_key) + @UnregisterChangeListenerMethod + void unregisterTypingStatusChangeListener(OnPreferenceValueChangedListener listener); + @KeyByResource(R.string.nc_file_browser_sort_by_key) void setSorting(String value); diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index c292f30e2..7c5c4890a 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -264,6 +264,14 @@ apc:mp_key="@string/nc_settings_read_privacy_key" apc:mp_summary="@string/nc_settings_read_privacy_desc" apc:mp_title="@string/nc_settings_read_privacy_title" /> + + Locked Share my read-status and show the read-status of others Read status + Share my typing-status and show the typing-status of others + Typing status 30 seconds 1 minute From 231cfef8c3e2a45bb406b13ee0326ad48a3fd4f8 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 22 May 2023 14:09:20 +0200 Subject: [PATCH 12/24] check capabilities to use/ignore typing indicators Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 67 ++++++++++--------- .../database/user/CapabilitiesUtilNew.kt | 2 +- 2 files changed, 37 insertions(+), 32 deletions(-) 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 37c105900..8b3949a41 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -323,20 +323,24 @@ class ChatActivity : private val conversationMessageListener = object : SignalingMessageReceiver.ConversationMessageListener { override fun onStartTyping(session: String) { - var name = webSocketInstance?.getDisplayNameForSession(session) + if (!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)) { + var name = webSocketInstance?.getDisplayNameForSession(session) - if (name != null && !typingParticipants.contains(session)) { - if (name == "") { - name = context.resources?.getString(R.string.nc_guest)!! + if (name != null && !typingParticipants.contains(session)) { + if (name == "") { + name = context.resources?.getString(R.string.nc_guest)!! + } + typingParticipants[session] = name + updateTypingIndicator() } - typingParticipants[session] = name - updateTypingIndicator() } } override fun onStopTyping(session: String) { - typingParticipants.remove(session) - updateTypingIndicator() + if (!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)) { + typingParticipants.remove(session) + updateTypingIndicator() + } } } @@ -348,8 +352,8 @@ class ChatActivity : 0 -> typingString = SpannableStringBuilder().append(binding.typingIndicator.text) 1 -> typingString = SpannableStringBuilder() - .bold { append(participantNames[0]) } - .append(WHITESPACE + context.resources?.getString(R.string.typing_is_typing)) + .bold { append(participantNames[0]) } + .append(WHITESPACE + context.resources?.getString(R.string.typing_is_typing)) 2 -> typingString = SpannableStringBuilder() .bold { append(participantNames[0]) } @@ -388,7 +392,7 @@ class ChatActivity : } } - val typingIndicatorHeight = DisplayUtils.convertDpToPixel(20f,context) + val typingIndicatorHeight = DisplayUtils.convertDpToPixel(20f, context) runOnUiThread { if (participantNames.size > 0) { @@ -396,7 +400,6 @@ class ChatActivity : .translationY(binding.messageInputView.y - typingIndicatorHeight) .setInterpolator(AccelerateDecelerateInterpolator()) .duration = TYPING_INDICATOR_ANIMATION_DURATION - } else { binding.typingIndicatorWrapper.animate() .translationY(binding.messageInputView.y) @@ -733,26 +736,28 @@ class ChatActivity : return } - if (typingTimer == null) { - for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { - val ncSignalingMessage = NCSignalingMessage() - ncSignalingMessage.to = sessionId - ncSignalingMessage.type = "startedTyping" - signalingMessageSender!!.send(ncSignalingMessage) + if (!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)) { + if (typingTimer == null) { + for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { + val ncSignalingMessage = NCSignalingMessage() + ncSignalingMessage.to = sessionId + ncSignalingMessage.type = "startedTyping" + signalingMessageSender!!.send(ncSignalingMessage) + } + + typingTimer = object : CountDownTimer(4000, 1000) { + override fun onTick(millisUntilFinished: Long) { + } + + override fun onFinish() { + sendStopTypingMessage() + typingTimer = null + } + }.start() + } else { + typingTimer?.cancel() + typingTimer?.start() } - - typingTimer = object : CountDownTimer(4000, 1000) { - override fun onTick(millisUntilFinished: Long) { - } - - override fun onFinish() { - sendStopTypingMessage() - typingTimer = null - } - }.start() - } else { - typingTimer?.cancel() - typingTimer?.start() } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt index 88a2676b9..d8ef399f9 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt @@ -103,7 +103,7 @@ object CapabilitiesUtilNew { fun isTypingStatusAvailable(user: User): Boolean { if (user.capabilities?.spreedCapability?.config?.containsKey("chat") == true) { - val map: Map? = user.capabilities!!.spreedCapability!!.config!!["chat"] + val map = user.capabilities!!.spreedCapability!!.config!!["chat"] return map != null && map.containsKey("typing-privacy") } return false From add5e518e1815ee2c4bf0a74c5952db070915e42 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 22 May 2023 14:32:37 +0200 Subject: [PATCH 13/24] use height from typingIndicator layout no need to define it redundant... Signed-off-by: Marcel Hibbe --- app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 8b3949a41..3306404b1 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -392,12 +392,10 @@ class ChatActivity : } } - val typingIndicatorHeight = DisplayUtils.convertDpToPixel(20f, context) - runOnUiThread { if (participantNames.size > 0) { binding.typingIndicatorWrapper.animate() - .translationY(binding.messageInputView.y - typingIndicatorHeight) + .translationY(binding.messageInputView.y - binding.typingIndicator.height) .setInterpolator(AccelerateDecelerateInterpolator()) .duration = TYPING_INDICATOR_ANIMATION_DURATION } else { From fa4d02e2c65b6de911c2c271fcd4e85f404c0598 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 22 May 2023 15:01:32 +0200 Subject: [PATCH 14/24] send stoppedTyping signaling message when sending chat message Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/chat/ChatActivity.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 3306404b1..ca998dcba 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -749,7 +749,6 @@ class ChatActivity : override fun onFinish() { sendStopTypingMessage() - typingTimer = null } }.start() } else { @@ -760,11 +759,15 @@ class ChatActivity : } fun sendStopTypingMessage() { - for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { - val ncSignalingMessage = NCSignalingMessage() - ncSignalingMessage.to = sessionId - ncSignalingMessage.type = "stoppedTyping" - signalingMessageSender!!.send(ncSignalingMessage) + if (!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)) { + typingTimer = null + + for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { + val ncSignalingMessage = NCSignalingMessage() + ncSignalingMessage.to = sessionId + ncSignalingMessage.type = "stoppedTyping" + signalingMessageSender!!.send(ncSignalingMessage) + } } } @@ -2369,6 +2372,7 @@ class ChatActivity : } binding?.messageInputView?.inputEditText?.setText("") + sendStopTypingMessage() val replyMessageId: Int? = findViewById(R.id.quotedChatMessageView)?.tag as Int? sendMessage( editable, From 7b12f27a1aeec07c49e0d1e23a301260cf598bbe Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 22 May 2023 15:07:40 +0200 Subject: [PATCH 15/24] move typing indicator methods Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 214 +++++++++--------- 1 file changed, 107 insertions(+), 107 deletions(-) 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 ca998dcba..d1bc3da78 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -344,71 +344,6 @@ class ChatActivity : } } - private fun updateTypingIndicator() { - val participantNames = ArrayList(typingParticipants.values) - - val typingString: SpannableStringBuilder - when (typingParticipants.size) { - 0 -> typingString = SpannableStringBuilder().append(binding.typingIndicator.text) - - 1 -> typingString = SpannableStringBuilder() - .bold { append(participantNames[0]) } - .append(WHITESPACE + context.resources?.getString(R.string.typing_is_typing)) - - 2 -> typingString = SpannableStringBuilder() - .bold { append(participantNames[0]) } - .append(WHITESPACE + context.resources?.getString(R.string.nc_common_and) + WHITESPACE) - .bold { append(participantNames[1]) } - .append(WHITESPACE + context.resources?.getString(R.string.typing_are_typing)) - - 3 -> typingString = SpannableStringBuilder() - .bold { append(participantNames[0]) } - .append(COMMA) - .bold { append(participantNames[1]) } - .append(WHITESPACE + context.resources?.getString(R.string.nc_common_and) + WHITESPACE) - .bold { append(participantNames[2]) } - .append(WHITESPACE + context.resources?.getString(R.string.typing_are_typing)) - - 4 -> typingString = SpannableStringBuilder() - .bold { append(participantNames[0]) } - .append(COMMA) - .bold { append(participantNames[1]) } - .append(COMMA) - .bold { append(participantNames[2]) } - .append(WHITESPACE + context.resources?.getString(R.string.typing_1_other)) - - else -> { - val moreTypersAmount = typingParticipants.size - 3 - val othersTyping = context.resources?.getString(R.string.typing_x_others)?.let { - String.format(it, moreTypersAmount) - } - typingString = SpannableStringBuilder() - .bold { append(participantNames[0]) } - .append(COMMA) - .bold { append(participantNames[1]) } - .append(COMMA) - .bold { append(participantNames[2]) } - .append(othersTyping) - } - } - - runOnUiThread { - if (participantNames.size > 0) { - binding.typingIndicatorWrapper.animate() - .translationY(binding.messageInputView.y - binding.typingIndicator.height) - .setInterpolator(AccelerateDecelerateInterpolator()) - .duration = TYPING_INDICATOR_ANIMATION_DURATION - } else { - binding.typingIndicatorWrapper.animate() - .translationY(binding.messageInputView.y) - .setInterpolator(AccelerateDecelerateInterpolator()) - .duration = TYPING_INDICATOR_ANIMATION_DURATION - } - - binding.typingIndicator.text = typingString - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) @@ -729,48 +664,6 @@ class ChatActivity : } } - fun sendStartTypingMessage() { - if (webSocketInstance == null) { - return - } - - if (!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)) { - if (typingTimer == null) { - for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { - val ncSignalingMessage = NCSignalingMessage() - ncSignalingMessage.to = sessionId - ncSignalingMessage.type = "startedTyping" - signalingMessageSender!!.send(ncSignalingMessage) - } - - typingTimer = object : CountDownTimer(4000, 1000) { - override fun onTick(millisUntilFinished: Long) { - } - - override fun onFinish() { - sendStopTypingMessage() - } - }.start() - } else { - typingTimer?.cancel() - typingTimer?.start() - } - } - } - - fun sendStopTypingMessage() { - if (!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)) { - typingTimer = null - - for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { - val ncSignalingMessage = NCSignalingMessage() - ncSignalingMessage.to = sessionId - ncSignalingMessage.type = "stoppedTyping" - signalingMessageSender!!.send(ncSignalingMessage) - } - } - } - private fun initMessageHolders(): MessageHolders { val messageHolders = MessageHolders() val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!) @@ -1015,6 +908,113 @@ class ChatActivity : } } + private fun updateTypingIndicator() { + val participantNames = ArrayList(typingParticipants.values) + + val typingString: SpannableStringBuilder + when (typingParticipants.size) { + 0 -> typingString = SpannableStringBuilder().append(binding.typingIndicator.text) + + 1 -> typingString = SpannableStringBuilder() + .bold { append(participantNames[0]) } + .append(WHITESPACE + context.resources?.getString(R.string.typing_is_typing)) + + 2 -> typingString = SpannableStringBuilder() + .bold { append(participantNames[0]) } + .append(WHITESPACE + context.resources?.getString(R.string.nc_common_and) + WHITESPACE) + .bold { append(participantNames[1]) } + .append(WHITESPACE + context.resources?.getString(R.string.typing_are_typing)) + + 3 -> typingString = SpannableStringBuilder() + .bold { append(participantNames[0]) } + .append(COMMA) + .bold { append(participantNames[1]) } + .append(WHITESPACE + context.resources?.getString(R.string.nc_common_and) + WHITESPACE) + .bold { append(participantNames[2]) } + .append(WHITESPACE + context.resources?.getString(R.string.typing_are_typing)) + + 4 -> typingString = SpannableStringBuilder() + .bold { append(participantNames[0]) } + .append(COMMA) + .bold { append(participantNames[1]) } + .append(COMMA) + .bold { append(participantNames[2]) } + .append(WHITESPACE + context.resources?.getString(R.string.typing_1_other)) + + else -> { + val moreTypersAmount = typingParticipants.size - 3 + val othersTyping = context.resources?.getString(R.string.typing_x_others)?.let { + String.format(it, moreTypersAmount) + } + typingString = SpannableStringBuilder() + .bold { append(participantNames[0]) } + .append(COMMA) + .bold { append(participantNames[1]) } + .append(COMMA) + .bold { append(participantNames[2]) } + .append(othersTyping) + } + } + + runOnUiThread { + if (participantNames.size > 0) { + binding.typingIndicatorWrapper.animate() + .translationY(binding.messageInputView.y - binding.typingIndicator.height) + .setInterpolator(AccelerateDecelerateInterpolator()) + .duration = TYPING_INDICATOR_ANIMATION_DURATION + } else { + binding.typingIndicatorWrapper.animate() + .translationY(binding.messageInputView.y) + .setInterpolator(AccelerateDecelerateInterpolator()) + .duration = TYPING_INDICATOR_ANIMATION_DURATION + } + + binding.typingIndicator.text = typingString + } + } + + fun sendStartTypingMessage() { + if (webSocketInstance == null) { + return + } + + if (!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)) { + if (typingTimer == null) { + for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { + val ncSignalingMessage = NCSignalingMessage() + ncSignalingMessage.to = sessionId + ncSignalingMessage.type = "startedTyping" + signalingMessageSender!!.send(ncSignalingMessage) + } + + typingTimer = object : CountDownTimer(4000, 1000) { + override fun onTick(millisUntilFinished: Long) { + } + + override fun onFinish() { + sendStopTypingMessage() + } + }.start() + } else { + typingTimer?.cancel() + typingTimer?.start() + } + } + } + + fun sendStopTypingMessage() { + if (!CapabilitiesUtilNew.isTypingStatusPrivate(conversationUser!!)) { + typingTimer = null + + for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { + val ncSignalingMessage = NCSignalingMessage() + ncSignalingMessage.to = sessionId + ncSignalingMessage.type = "stoppedTyping" + signalingMessageSender!!.send(ncSignalingMessage) + } + } + } + private fun getRoomInfo() { logConversationInfos("getRoomInfo") From f0a9a302bb0fa4c6ffc73253a8e25ed211a90412 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 22 May 2023 17:27:49 +0200 Subject: [PATCH 16/24] make typing indicator two lines if necessary Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 43 +++++++++++++------ .../nextcloud/talk/utils/DisplayUtils.java | 7 +++ app/src/main/res/layout/activity_chat.xml | 6 ++- 3 files changed, 41 insertions(+), 15 deletions(-) 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 d1bc3da78..d0f62eff7 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -909,30 +909,38 @@ class ChatActivity : } private fun updateTypingIndicator() { + fun ellipsize(text: String): String { + return DisplayUtils.ellipsize(text, TYPING_INDICATOR_MAX_NAME_LENGTH) + } + val participantNames = ArrayList(typingParticipants.values) val typingString: SpannableStringBuilder when (typingParticipants.size) { 0 -> typingString = SpannableStringBuilder().append(binding.typingIndicator.text) + // person1 is typing 1 -> typingString = SpannableStringBuilder() - .bold { append(participantNames[0]) } + .bold { append(ellipsize(participantNames[0])) } .append(WHITESPACE + context.resources?.getString(R.string.typing_is_typing)) + // person1 and person2 are typing 2 -> typingString = SpannableStringBuilder() - .bold { append(participantNames[0]) } + .bold { append(ellipsize(participantNames[0])) } .append(WHITESPACE + context.resources?.getString(R.string.nc_common_and) + WHITESPACE) - .bold { append(participantNames[1]) } + .bold { append(ellipsize(participantNames[1])) } .append(WHITESPACE + context.resources?.getString(R.string.typing_are_typing)) + // person1, person2 and person3 are typing 3 -> typingString = SpannableStringBuilder() - .bold { append(participantNames[0]) } + .bold { append(ellipsize(participantNames[0])) } .append(COMMA) - .bold { append(participantNames[1]) } + .bold { append(ellipsize(participantNames[1])) } .append(WHITESPACE + context.resources?.getString(R.string.nc_common_and) + WHITESPACE) - .bold { append(participantNames[2]) } + .bold { append(ellipsize(participantNames[2])) } .append(WHITESPACE + context.resources?.getString(R.string.typing_are_typing)) + // person1, person2, person3 and 1 other is typing 4 -> typingString = SpannableStringBuilder() .bold { append(participantNames[0]) } .append(COMMA) @@ -941,6 +949,7 @@ class ChatActivity : .bold { append(participantNames[2]) } .append(WHITESPACE + context.resources?.getString(R.string.typing_1_other)) + // person1, person2, person3 and x others are typing else -> { val moreTypersAmount = typingParticipants.size - 3 val othersTyping = context.resources?.getString(R.string.typing_x_others)?.let { @@ -957,19 +966,26 @@ class ChatActivity : } runOnUiThread { + binding.typingIndicator.text = typingString + if (participantNames.size > 0) { binding.typingIndicatorWrapper.animate() - .translationY(binding.messageInputView.y - binding.typingIndicator.height) + .translationY(binding.messageInputView.y - DisplayUtils.convertDpToPixel(20f, context)) .setInterpolator(AccelerateDecelerateInterpolator()) .duration = TYPING_INDICATOR_ANIMATION_DURATION } else { - binding.typingIndicatorWrapper.animate() - .translationY(binding.messageInputView.y) - .setInterpolator(AccelerateDecelerateInterpolator()) - .duration = TYPING_INDICATOR_ANIMATION_DURATION + if (binding.typingIndicator.lineCount == 1) { + binding.typingIndicatorWrapper.animate() + .translationY(binding.messageInputView.y) + .setInterpolator(AccelerateDecelerateInterpolator()) + .duration = TYPING_INDICATOR_ANIMATION_DURATION + } else if (binding.typingIndicator.lineCount == 2) { + binding.typingIndicatorWrapper.animate() + .translationY(binding.messageInputView.y + DisplayUtils.convertDpToPixel(15f, context)) + .setInterpolator(AccelerateDecelerateInterpolator()) + .duration = TYPING_INDICATOR_ANIMATION_DURATION + } } - - binding.typingIndicator.text = typingString } } @@ -3778,5 +3794,6 @@ class ChatActivity : private const val WHITESPACE = " " private const val COMMA = ", " private const val TYPING_INDICATOR_ANIMATION_DURATION = 200L + private const val TYPING_INDICATOR_MAX_NAME_LENGTH = 14 } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java index ceeea1aa5..aac979cba 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java @@ -553,4 +553,11 @@ public class DisplayUtils { DateFormat df = DateFormat.getDateTimeInstance(); return df.format(date); } + + public static String ellipsize(String text, int maxLength) { + if (text.length() > maxLength) { + return text.substring(0, maxLength - 1) + "…"; + } + return text; + } } diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index b90eb204d..6b31e45a8 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -156,7 +156,7 @@ android:layout_height="wrap_content" android:orientation="vertical" android:layout_alignParentBottom="true" - android:layout_marginBottom="-20dp"> + android:layout_marginBottom="-19dp"> Date: Tue, 23 May 2023 10:32:31 +0200 Subject: [PATCH 17/24] avoid lint warning ..."Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them as annotation attributes" by using @KeyByString keys were also wrong by copy&paste and are now fixed Signed-off-by: Marcel Hibbe --- .../nextcloud/talk/utils/preferences/AppPreferences.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java index 898819a97..1170a5863 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java @@ -37,6 +37,7 @@ import net.orange_box.storebox.annotations.option.SaveOption; import net.orange_box.storebox.enums.SaveMode; import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener; + @SaveOption(SaveMode.APPLY) public interface AppPreferences { @@ -313,7 +314,7 @@ public interface AppPreferences { @KeyByResource(R.string.nc_settings_read_privacy_key) void setReadPrivacy(boolean value); - @KeyByResource(R.string.nc_settings_read_privacy_key) + @KeyByString("typing_status") void setTypingStatus(boolean value); @KeyByResource(R.string.nc_settings_read_privacy_key) @@ -324,11 +325,11 @@ public interface AppPreferences { @UnregisterChangeListenerMethod void unregisterReadPrivacyChangeListener(OnPreferenceValueChangedListener listener); - @KeyByResource(R.string.nc_settings_read_privacy_key) + @KeyByString("typing_status") @RegisterChangeListenerMethod void registerTypingStatusChangeListener(OnPreferenceValueChangedListener listener); - @KeyByResource(R.string.nc_settings_read_privacy_key) + @KeyByString("typing_status") @UnregisterChangeListenerMethod void unregisterTypingStatusChangeListener(OnPreferenceValueChangedListener listener); From e9fc9fce28e770c2a14c283360ee53daab74675b Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 23 May 2023 10:46:04 +0200 Subject: [PATCH 18/24] avoid Overlapping items in RelativeLayout for typing indicator -> align Unread messages above typing_indicator_wrapper Signed-off-by: Marcel Hibbe --- app/src/main/res/layout/activity_chat.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index 6b31e45a8..9d1f7535e 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -117,12 +117,12 @@ android:theme="@style/Button.Primary" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentBottom="true" + android:layout_alignBottom="@id/typing_indicator_wrapper" android:layout_centerHorizontal="true" android:layout_marginStart="64dp" android:layout_marginTop="16dp" android:layout_marginEnd="64dp" - android:layout_marginBottom="16dp" + android:layout_marginBottom="26dp" android:minHeight="@dimen/min_size_clickable_area" android:layout_toStartOf="@+id/scrollDownButton" android:text="@string/nc_new_messages" From 0f58deacef141225d3ff4abe8d58cb492f979e1d Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 23 May 2023 10:49:09 +0200 Subject: [PATCH 19/24] ignore lint Overdraw warning Overdraw is expected here to avoid transparency for the typing indicator view. Instead chat message content should be hidden behind the typing indicator view. Signed-off-by: Marcel Hibbe --- app/src/main/res/layout/activity_chat.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index 9d1f7535e..c49e4b063 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -174,7 +174,8 @@ android:layout_marginEnd="@dimen/side_margin" android:background="@color/bg_default" android:textColor="@color/low_emphasis_text" - tools:text="Marcel is typing"> + tools:text="Marcel is typing" + tools:ignore="Overdraw"> From 61859f361a16bae3e59f8cd920e051935b50bd5e Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 23 May 2023 11:16:47 +0200 Subject: [PATCH 20/24] convert ConversationMessageNotifier to kt Signed-off-by: Marcel Hibbe --- .../ConversationMessageNotifier.java | 54 ------------------- .../signaling/ConversationMessageNotifier.kt | 50 +++++++++++++++++ 2 files changed, 50 insertions(+), 54 deletions(-) delete mode 100644 app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.java create mode 100644 app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.kt diff --git a/app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.java b/app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.java deleted file mode 100644 index 5f19e29c2..000000000 --- a/app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Marcel Hibbe - * Copyright (C) 2023 Marcel Hibbe - * - * 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 com.nextcloud.talk.signaling; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.Set; - - -class ConversationMessageNotifier { - - private final Set conversationMessageListeners = new LinkedHashSet<>(); - - public synchronized void addListener(SignalingMessageReceiver.ConversationMessageListener listener) { - if (listener == null) { - throw new IllegalArgumentException("conversationMessageListener can not be null"); - } - - conversationMessageListeners.add(listener); - } - - public synchronized void removeListener(SignalingMessageReceiver.ConversationMessageListener listener) { - conversationMessageListeners.remove(listener); - } - - public synchronized void notifyStartTyping(String sessionId) { - for (SignalingMessageReceiver.ConversationMessageListener listener : new ArrayList<>(conversationMessageListeners)) { - listener.onStartTyping(sessionId); - } - } - - public void notifyStopTyping(String sessionId) { - for (SignalingMessageReceiver.ConversationMessageListener listener : new ArrayList<>(conversationMessageListeners)) { - listener.onStopTyping(sessionId); - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.kt b/app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.kt new file mode 100644 index 000000000..6b8fac543 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.kt @@ -0,0 +1,50 @@ +/* + * Nextcloud Talk application + * + * @author Marcel Hibbe + * Copyright (C) 2023 Marcel Hibbe + * + * 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 com.nextcloud.talk.signaling + +import com.nextcloud.talk.signaling.SignalingMessageReceiver.ConversationMessageListener + +internal class ConversationMessageNotifier { + private val conversationMessageListeners: MutableSet = LinkedHashSet() + + @Synchronized + fun addListener(listener: ConversationMessageListener?) { + requireNotNull(listener) { "conversationMessageListener can not be null" } + conversationMessageListeners.add(listener) + } + + @Synchronized + fun removeListener(listener: ConversationMessageListener) { + conversationMessageListeners.remove(listener) + } + + @Synchronized + fun notifyStartTyping(sessionId: String?) { + for (listener in ArrayList(conversationMessageListeners)) { + listener.onStartTyping(sessionId) + } + } + + fun notifyStopTyping(sessionId: String?) { + for (listener in ArrayList(conversationMessageListeners)) { + listener.onStopTyping(sessionId) + } + } +} From 154ea08c376ead260078390eebda492ee3c36633 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 23 May 2023 11:18:01 +0200 Subject: [PATCH 21/24] ignore lint Overdraw warning followup to 0f58deac Signed-off-by: Marcel Hibbe --- app/src/main/res/layout/activity_chat.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index c49e4b063..97f2d91ad 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -30,7 +30,8 @@ android:layout_height="match_parent" android:animateLayoutChanges="true" android:background="@color/bg_default" - android:orientation="vertical"> + android:orientation="vertical" + tools:ignore="Overdraw"> Date: Tue, 23 May 2023 11:31:55 +0200 Subject: [PATCH 22/24] improve detekt score: avoid magicNumbers... Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/chat/ChatActivity.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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 d0f62eff7..a3a4ce232 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -908,6 +908,7 @@ class ChatActivity : } } + @Suppress("MagicNumber") private fun updateTypingIndicator() { fun ellipsize(text: String): String { return DisplayUtils.ellipsize(text, TYPING_INDICATOR_MAX_NAME_LENGTH) @@ -999,12 +1000,16 @@ class ChatActivity : for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { val ncSignalingMessage = NCSignalingMessage() ncSignalingMessage.to = sessionId - ncSignalingMessage.type = "startedTyping" + ncSignalingMessage.type = TYPING_STARTED_SIGNALING_MESSAGE_TYPE signalingMessageSender!!.send(ncSignalingMessage) } - typingTimer = object : CountDownTimer(4000, 1000) { + typingTimer = object : CountDownTimer( + TYPING_DURATION_BEFORE_SENDING_STOP, + TYPING_DURATION_BEFORE_SENDING_STOP + ) { override fun onTick(millisUntilFinished: Long) { + // unused atm } override fun onFinish() { @@ -1025,7 +1030,7 @@ class ChatActivity : for ((sessionId, participant) in webSocketInstance?.getUserMap()!!) { val ncSignalingMessage = NCSignalingMessage() ncSignalingMessage.to = sessionId - ncSignalingMessage.type = "stoppedTyping" + ncSignalingMessage.type = TYPING_STOPPED_SIGNALING_MESSAGE_TYPE signalingMessageSender!!.send(ncSignalingMessage) } } @@ -3795,5 +3800,8 @@ class ChatActivity : private const val COMMA = ", " private const val TYPING_INDICATOR_ANIMATION_DURATION = 200L private const val TYPING_INDICATOR_MAX_NAME_LENGTH = 14 + private const val TYPING_DURATION_BEFORE_SENDING_STOP = 4000L + private const val TYPING_STARTED_SIGNALING_MESSAGE_TYPE = "startedTyping" + private const val TYPING_STOPPED_SIGNALING_MESSAGE_TYPE = "stoppedTyping" } } From 9a76ce7ccdf6b05360f68d5dd08f93ec75d6264b Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 23 May 2023 11:50:33 +0200 Subject: [PATCH 23/24] fix to close gap between typing indicator and message input otherwise chat content was visible in between Signed-off-by: Marcel Hibbe --- app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a3a4ce232..a8a686be7 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -971,7 +971,7 @@ class ChatActivity : if (participantNames.size > 0) { binding.typingIndicatorWrapper.animate() - .translationY(binding.messageInputView.y - DisplayUtils.convertDpToPixel(20f, context)) + .translationY(binding.messageInputView.y - DisplayUtils.convertDpToPixel(18f, context)) .setInterpolator(AccelerateDecelerateInterpolator()) .duration = TYPING_INDICATOR_ANIMATION_DURATION } else { From 0eac3a2d1119ad9c94ba1fc61c08e916e1334358 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 23 May 2023 12:10:18 +0200 Subject: [PATCH 24/24] fix codacy warning Fields should be declared at the top of the class, before any method declarations, constructors, initializers or inner classes. Signed-off-by: Marcel Hibbe --- .../signaling/SignalingMessageReceiver.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java b/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java index 8d0c2318a..1455b1ec5 100644 --- a/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java +++ b/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java @@ -50,6 +50,18 @@ import java.util.Map; */ public abstract class SignalingMessageReceiver { + private final ParticipantListMessageNotifier participantListMessageNotifier = new ParticipantListMessageNotifier(); + + private final LocalParticipantMessageNotifier localParticipantMessageNotifier = new LocalParticipantMessageNotifier(); + + private final CallParticipantMessageNotifier callParticipantMessageNotifier = new CallParticipantMessageNotifier(); + + private final ConversationMessageNotifier conversationMessageNotifier = new ConversationMessageNotifier(); + + private final OfferMessageNotifier offerMessageNotifier = new OfferMessageNotifier(); + + private final WebRtcMessageNotifier webRtcMessageNotifier = new WebRtcMessageNotifier(); + /** * Listener for participant list messages. * @@ -187,18 +199,6 @@ public abstract class SignalingMessageReceiver { void onEndOfCandidates(); } - private final ParticipantListMessageNotifier participantListMessageNotifier = new ParticipantListMessageNotifier(); - - private final LocalParticipantMessageNotifier localParticipantMessageNotifier = new LocalParticipantMessageNotifier(); - - private final CallParticipantMessageNotifier callParticipantMessageNotifier = new CallParticipantMessageNotifier(); - - private final ConversationMessageNotifier conversationMessageNotifier = new ConversationMessageNotifier(); - - private final OfferMessageNotifier offerMessageNotifier = new OfferMessageNotifier(); - - private final WebRtcMessageNotifier webRtcMessageNotifier = new WebRtcMessageNotifier(); - /** * Adds a listener for participant list messages. *