NTP-61 | Update user typing status in firebase (#11531)

This commit is contained in:
Varun Jain
2024-07-01 16:34:44 +05:30
committed by GitHub
parent 48ec3d4c10
commit 8bb02b3469
6 changed files with 143 additions and 3 deletions

View File

@@ -21,6 +21,7 @@ import com.navi.chat.utils.NaviChatAnalytics.Companion.DOCUMENT
import com.navi.chat.utils.NaviChatAnalytics.Companion.DOCUMENT_ADDED_FROM_FIRESTORE
import com.navi.chat.utils.NaviChatAnalytics.Companion.DOCUMENT_TYPE
import com.navi.chat.utils.NaviChatAnalytics.Companion.ERROR_LISTENING_TO_FIRESTORE_CHANGES
import com.navi.chat.utils.NaviChatAnalytics.Companion.ERROR_UPDATING_USER_TYPING_STATUS
import com.navi.chat.utils.NaviChatAnalytics.Companion.LISTENER_ATTACHED_TO_FIRESTORE_SUCCESSFULLY
import com.navi.chat.utils.NaviChatAnalytics.Companion.LISTEN_TO_CONTROLLER_WIDGET
import com.navi.chat.utils.NaviChatAnalytics.Companion.LISTEN_TO_FIRESTORE_CLIENT_CHANGES
@@ -35,12 +36,15 @@ import com.navi.chat.utils.NaviChatAnalytics.Companion.NOT_ABLE_TO_LISTEN_TO_FIR
import com.navi.chat.utils.NaviChatAnalytics.Companion.PATH
import com.navi.chat.utils.NaviChatAnalytics.Companion.PROCESSING_CONTROLLER_WIDGET
import com.navi.chat.utils.NaviChatAnalytics.Companion.SUCCESS_LISTENING_TO_FIRESTORE_CHANGES
import com.navi.chat.utils.NaviChatAnalytics.Companion.SUCCESS_UPDATING_USER_TYPING_STATUS
import com.navi.chat.utils.NaviChatAnalytics.Companion.UPDATE_FIRESTORE_USER_TYPING_STATUS
import com.navi.chat.utils.NaviChatAnalytics.Companion.UPDATING_CLIENT_MESSAGE_RECEIPTS
import com.navi.chat.utils.NaviChatAnalytics.Companion.UPDATING_TYPING_STATUS
import com.navi.chat.utils.NaviChatAnalytics.Companion.WRITE_TO_FIRESTORE
import com.navi.chat.utils.SOURCE
import com.navi.chat.utils.TYPING_CUES_PATH_AGENT
import com.navi.common.utils.ERROR
import com.navi.common.utils.log
import com.navi.naviwidgets.models.NaviChatWidget
import com.navi.naviwidgets.models.response.*
import com.navi.naviwidgets.utils.NAVI_CHAT_RT_CREATED_AT
@@ -584,6 +588,38 @@ class ChatFireStoreDatabase {
}
}
fun updateFireStoreForUserTypingStatus(
path: String,
documentId: String,
typingStatusDocument: Map<String, Any>
) {
ChatFireStore.getFireStoreReference(path)
.document(documentId)
.set(typingStatusDocument)
.addOnSuccessListener {
crmEventTracker.sendEvent(
SUCCESS_UPDATING_USER_TYPING_STATUS,
hashMapOf(
DOCUMENT to typingStatusDocument.toString(),
PATH to path,
SOURCE to UPDATE_FIRESTORE_USER_TYPING_STATUS
)
)
}
.addOnFailureListener { e ->
e.log()
crmEventTracker.sendEvent(
ERROR_UPDATING_USER_TYPING_STATUS,
hashMapOf(
DOCUMENT to typingStatusDocument.toString(),
PATH to path,
ERROR to e.toString(),
SOURCE to UPDATE_FIRESTORE_USER_TYPING_STATUS
)
)
}
}
fun removeFireStoreListener() {
if (listenerToFirestoreChangesList.size > 0) {
for (listener in listenerToFirestoreChangesList) {

View File

@@ -1,6 +1,6 @@
/*
*
* * Copyright © 2022 by Navi Technologies Limited
* * Copyright © 2022-2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
@@ -20,4 +20,6 @@ interface ChatDataProvider {
fun writeMessage(naviChatWidget: NaviChatWidget)
fun listenToControllerWidget()
fun updateUserTypingStatus(typingStatusDocument: Map<String, Any>)
}

View File

@@ -16,6 +16,7 @@ import com.navi.chat.data.firestore.ChatFireStoreDatabase
import com.navi.chat.models.FireStoreDataProviderModel
import com.navi.chat.provider.ChatDataProvider
import com.navi.chat.provider.MessageOperation
import com.navi.chat.utils.TYPING_CUES_PATH_USER
import com.navi.naviwidgets.models.NaviChatWidget
import dagger.hilt.android.scopes.ActivityRetainedScoped
@@ -71,6 +72,16 @@ class FireStoreDataProvider : ChatDataProvider, LifecycleObserver {
}
}
override fun updateUserTypingStatus(typingStatusDocument: Map<String, Any>) {
if (::chatFireStoreDatabase.isInitialized) {
chatFireStoreDatabase.updateFireStoreForUserTypingStatus(
path = fireStoreDataProviderModel.eventsPath,
documentId = TYPING_CUES_PATH_USER,
typingStatusDocument = typingStatusDocument
)
}
}
override fun listenToTypingStatus() {
if (::chatFireStoreDatabase.isInitialized) {
if (fireStoreDataProviderModel.eventsPath.isNotNullAndNotEmpty()) {

View File

@@ -148,6 +148,7 @@ class NaviChatFragment : BaseFragment(), WidgetCallback, MessageOperation, Toolb
@Inject lateinit var fireStoreDataProvider: FireStoreDataProvider
@Inject lateinit var fileUploadManager: FileUploadManager
@Inject lateinit var fileHelper: ChatFileHelper
@Inject lateinit var firebaseAuthHelper: FirebaseAuthHelper
private lateinit var binding: FragmentCommonNaviChatBinding
private lateinit var linearLayoutManager: LinearLayoutManager
private lateinit var chatRVAdapter: ChatRVAdapter<NaviBaseAdapterModel>
@@ -170,9 +171,11 @@ class NaviChatFragment : BaseFragment(), WidgetCallback, MessageOperation, Toolb
private var showCsatHandler = Handler(Looper.getMainLooper())
private var timerHandler = Handler(Looper.getMainLooper())
private var callStatusHandler = Handler(Looper.getMainLooper())
private val userTypingStatusHandler = Handler(Looper.getMainLooper())
private var textWatcherForTimer: TextWatcher? = null
private var textWatcherForCharLimit: TextWatcher? = null
private var textWatcherForSendAnimation: TextWatcher? = null
private var textWatcherForUserTyping: TextWatcher? = null
private var naviChatSystemLocalData: NaviChatSystemLocalData? = null
private var chatResolutionStatusWidget: NaviChatResolutionStatusWidget? = null
private var csatWidgetData: NaviChatCsatRatingWidget? = null
@@ -193,8 +196,10 @@ class NaviChatFragment : BaseFragment(), WidgetCallback, MessageOperation, Toolb
private var topBackButtonClickListener: ChatTopBackButtonClickListener? = null
private var latestMessageLocation: Int? = null
private var productConfigId: String? = null
@Inject lateinit var firebaseAuthHelper: FirebaseAuthHelper
private val userTypingTimeoutInterval: Long = 3000L
private val userTypingStatusUpdateInterval: Long = 3000L
private var isUserTyping: Boolean = false
private var currTimeForUserTyping: Long? = null
override fun onCreateView(
inflater: LayoutInflater,
@@ -290,10 +295,16 @@ class NaviChatFragment : BaseFragment(), WidgetCallback, MessageOperation, Toolb
setTextWatcher()
setTextWatcherForCharLimit()
setTextWatcherForSendAnimation()
setTextWatcherForUserTyping()
ivSendFreeText.setOnClickListener {
val freeText = binding.etFreeText.text.toString()
if (freeText != EMPTY) {
if (isUserTyping) {
userTypingStatusHandler.removeCallbacks(userTypingTimeoutRunnable)
isUserTyping = false
onUserTypingStatusChanged(false)
}
writeUserMessageToFirestore(message = freeText, metaData = NaviChatMetaData())
binding.etFreeText.text = null
naviChatSharedViewModel.incrementNumberOfMessagesSent()
@@ -1681,6 +1692,7 @@ class NaviChatFragment : BaseFragment(), WidgetCallback, MessageOperation, Toolb
override fun processControllerWidget(naviChatControllerWidget: NaviChatControllerWidget) {
when (naviChatControllerWidget.widgetData?.message) {
NaviChatControllerEnum.ENABLE_FREE_TEXT.name -> {
attachTextWatcherForUserTyping()
if (binding.clFreeText.visibility == GONE) {
binding.clFreeText.isVisible = true
binding.etFreeText.requestFocus()
@@ -1696,6 +1708,7 @@ class NaviChatFragment : BaseFragment(), WidgetCallback, MessageOperation, Toolb
}
if (naviChatControllerWidget.widgetData?.controllerConfig == null) {
hideWaitTimeNudges()
naviChatSharedViewModel.setChatAttachmentOptionList(emptyList())
}
naviChatControllerWidget.widgetData?.controllerConfig?.let { config ->
config.showAttachment?.let { showAttachment ->
@@ -1821,6 +1834,16 @@ class NaviChatFragment : BaseFragment(), WidgetCallback, MessageOperation, Toolb
naviChatSharedViewModel.setChatExitOptionList(options)
}
}
if (
naviChatControllerWidget.widgetData?.controllerConfig?.attachmentConfig == null ||
naviChatControllerWidget.widgetData
?.controllerConfig
?.attachmentConfig
?.chatAttachmentOptions
.isNullOrEmpty()
) {
naviChatSharedViewModel.setChatAttachmentOptionList(emptyList())
}
naviChatControllerWidget.widgetData?.controllerConfig?.attachmentConfig?.let {
attachmentConfig ->
attachmentConfig.chatAttachmentOptions?.let { attachmentOptions ->
@@ -1860,6 +1883,66 @@ class NaviChatFragment : BaseFragment(), WidgetCallback, MessageOperation, Toolb
}
}
private val userTypingTimeoutRunnable = Runnable {
isUserTyping = false
onUserTypingStatusChanged(false)
}
private fun setTextWatcherForUserTyping() {
textWatcherForUserTyping =
object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
userTypingStatusHandler.removeCallbacks(userTypingTimeoutRunnable)
if (s?.isNotEmpty() == true) {
if (
System.currentTimeMillis().minus(currTimeForUserTyping ?: 0) >=
userTypingStatusUpdateInterval || isUserTyping.not()
) {
currTimeForUserTyping = System.currentTimeMillis()
isUserTyping = true
onUserTypingStatusChanged(true)
}
userTypingStatusHandler.postDelayed(
userTypingTimeoutRunnable,
userTypingTimeoutInterval
)
} else {
if (isUserTyping) {
isUserTyping = false
userTypingStatusHandler.postDelayed(
userTypingTimeoutRunnable,
userTypingTimeoutInterval
)
}
}
}
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}
}
private fun onUserTypingStatusChanged(isUserTyping: Boolean) {
val typingStatusDocument = mapOf(TYPING to isUserTyping, UPDATED_AT to Timestamp.now())
if (::chatDataProvider.isInitialized)
chatDataProvider.updateUserTypingStatus(typingStatusDocument)
}
private fun attachTextWatcherForUserTyping() {
removeTextWatcherForUserTyping()
binding.etFreeText.addTextChangedListener(textWatcherForUserTyping)
}
private fun removeTextWatcherForUserTyping() {
binding.etFreeText.removeTextChangedListener(textWatcherForUserTyping)
}
private fun attachTextWatcher() {
removeTextWatcher()
binding.etFreeText.addTextChangedListener(textWatcherForTimer)
@@ -2115,12 +2198,14 @@ class NaviChatFragment : BaseFragment(), WidgetCallback, MessageOperation, Toolb
super.onDestroyView()
removeTextWatcher()
removeTextWatcherForCharLimit()
removeTextWatcherForUserTyping()
removeInactivityTimer()
fireStoreListenerHandler.removeCallbacksAndMessages(null)
botTypingStatusHandler.removeCallbacksAndMessages(null)
typingStatusHandler.removeCallbacksAndMessages(null)
showCsatHandler.removeCallbacksAndMessages(null)
callStatusHandler.removeCallbacksAndMessages(null)
userTypingStatusHandler.removeCallbacksAndMessages(null)
fileUploadManager.cancelUploads()
naviChatSharedViewModel.resetNumberOfMessagesSent()
naviChatSharedViewModel.resetNumberOfOptionsSelected()

View File

@@ -84,6 +84,7 @@ const val FILE_TYPE_PARAM = "fileType"
const val CONVERSATION_ID_PARAM = "conversationId"
const val NAVI_CHAT_SYSTEM_REMOTE_DATA = "naviChatSystemRemoteData"
const val TYPING_CUES_PATH_AGENT = "agent"
const val TYPING_CUES_PATH_USER = "user"
const val BOT = "BOT"
const val DIGITAL_GOLD = "DIGITAL_GOLD"
const val FILENAME = "filename"
@@ -125,3 +126,5 @@ const val CONNECTED_NUDGE_DELAY = 2000L
const val FREE_TEXT_DISABLED_ALPHA_VALUE = 0.2f
const val FREE_TEXT_ENABLED_ALPHA_VALUE = 1.0f
const val HELP_CENTER_CACHE_KEY = "help_center"
const val TYPING = "typing"
const val UPDATED_AT = "updated_at"

View File

@@ -161,6 +161,9 @@ class NaviChatAnalytics private constructor() {
const val LISTEN_TO_FIRESTORE_SERVER_UPDATES = "listen_to_firestore_server_updates"
const val FIREBASE_AUTHENTICATION_SUCCESS = "firebase_authentication_success"
const val FIREBASE_AUTHENTICATION_FAILURE = "firebase_authentication_failure"
const val SUCCESS_UPDATING_USER_TYPING_STATUS = "success_updating_user_typing_status"
const val ERROR_UPDATING_USER_TYPING_STATUS = "error_updating_user_typing_status"
const val UPDATE_FIRESTORE_USER_TYPING_STATUS = "update_firestore_user_typing_status"
val naviChatAnalytics: NaviChatAnalytics by lazy { Holder.INSTANCE }
}