Chat screen refactor for latency reduction from 5 sec to 1 sec (#8752)
This commit is contained in:
@@ -257,7 +257,7 @@ object Constants {
|
||||
const val EXPIRY_TIME_IN_MIN = "expiryTimeInMinutes"
|
||||
const val NOTIFICATION_SENT_TIME = "notificationSentTimeInUTC"
|
||||
const val SOURCE = "source"
|
||||
const val SHOW_CSAT = "showCsatFlag"
|
||||
const val SHOW_CSAT = "shouldShowCsat"
|
||||
const val SHOW_RESOLUTION = "showResolutionStatusWidgetFlag"
|
||||
const val CONVERSATION_ID = "conversationId"
|
||||
const val SKIP_TUTORIAL_SCREEN = "SKIP_TUTORIAL_SCREEN"
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.navi.chat.ui.activities.NaviTicketViewActivity
|
||||
import com.navi.chat.ui.activities.SupportScreenActivity
|
||||
import com.navi.chat.ui.fragments.ChatAttachmentBottomSheet
|
||||
import com.navi.chat.ui.fragments.NaviChatFragment
|
||||
import com.navi.chat.ui.fragments.NaviChatFragmentV2
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
|
||||
@@ -34,6 +35,7 @@ interface NaviChatComponent {
|
||||
}
|
||||
|
||||
fun inject(naviChatFragment: NaviChatFragment)
|
||||
fun inject(naviChatFragmentV2: NaviChatFragmentV2)
|
||||
|
||||
fun inject(naviChatViewImageActivity: NaviChatViewImageActivity)
|
||||
fun inject(chatAttachmentBottomSheet: ChatAttachmentBottomSheet)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.chat.models.response
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ChatConversationIdResponse(
|
||||
@SerializedName("conversation_id") val conversationId: String? = null
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.chat.models.response
|
||||
|
||||
import com.google.firebase.Timestamp
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.navi.naviwidgets.models.NaviChatWidget
|
||||
|
||||
data class NaviChatConversationResponse(
|
||||
@SerializedName("channelInfo") val channelInfo: ChatChannelInfo? = null,
|
||||
@SerializedName("lastMessageTimestamp") val lastMessageTimeStamp: Timestamp? = null,
|
||||
@SerializedName("content") val listOfMessages: List<NaviChatWidget>? = null,
|
||||
@SerializedName("showTextbox") val showFreeText: Boolean? = false,
|
||||
@SerializedName("last") val isLast: Boolean? = true
|
||||
)
|
||||
|
||||
data class ChatChannelInfo(
|
||||
@SerializedName("readPath") val readPath: String? = null,
|
||||
@SerializedName("writePath") val writePath: String? = null,
|
||||
@SerializedName("eventPath") val eventsPath: String? = null,
|
||||
@SerializedName("chatControllerPath") val controllerWidgetPath: String? = null
|
||||
)
|
||||
@@ -11,8 +11,10 @@ import com.navi.chat.di.scopes.NaviChatScope
|
||||
import com.navi.chat.models.ChatConfigResponse
|
||||
import com.navi.chat.models.request.NaviChatInitiateRequest
|
||||
import com.navi.chat.models.request.NaviMarkConversationsReadRequest
|
||||
import com.navi.chat.models.response.ChatConversationIdResponse
|
||||
import com.navi.chat.models.response.HelpCenterResponse
|
||||
import com.navi.chat.models.response.NaviChatActivityRedirectionValidationResponse
|
||||
import com.navi.chat.models.response.NaviChatConversationResponse
|
||||
import com.navi.chat.models.response.NaviChatHistoryResponse
|
||||
import com.navi.chat.models.response.NaviChatHistoryResponseByConvId
|
||||
import com.navi.chat.models.response.NaviChatInitiateResponse
|
||||
@@ -29,6 +31,17 @@ import retrofit2.http.*
|
||||
@NaviChatScope
|
||||
interface NaviChatApiInterface {
|
||||
|
||||
@POST("/crm/api/v2/customersupport/conversations/initiate")
|
||||
suspend fun fetchConversationId(
|
||||
@Body naviChatInitiateRequest: NaviChatInitiateRequest
|
||||
): Response<GenericResponse<ChatConversationIdResponse>>
|
||||
|
||||
@GET("/crm/api/v2/chat/history/{conversationId}")
|
||||
suspend fun fetchChatConversations(
|
||||
@Path("conversationId") conversationId: String,
|
||||
@Query("page") page: Int
|
||||
): Response<GenericResponse<NaviChatConversationResponse>>
|
||||
|
||||
@POST("/crm/api/v1/customersupport/conversations/initiate")
|
||||
suspend fun initiateChat(
|
||||
@Body naviChatInitiateRequest: NaviChatInitiateRequest
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.chat.paging
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.navi.chat.utils.PAGE_SIZE
|
||||
|
||||
abstract class PaginationScrollV2Listener(private val layoutManager: LinearLayoutManager) :
|
||||
RecyclerView.OnScrollListener() {
|
||||
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
val totalItemCount = layoutManager.itemCount
|
||||
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
|
||||
|
||||
if (!isLoading() && canLoadMore()) {
|
||||
if (totalItemCount - 1 == lastVisibleItem) {
|
||||
loadMoreItems()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun loadMoreItems()
|
||||
|
||||
abstract fun canLoadMore(): Boolean
|
||||
|
||||
abstract fun isLoading(): Boolean
|
||||
|
||||
}
|
||||
@@ -26,6 +26,35 @@ import java.util.UUID
|
||||
class NaviChatRepository @Inject constructor(private val chatApiInterface: NaviChatApiInterface) :
|
||||
ResponseCallback(), AwsRepositoryInterface {
|
||||
|
||||
suspend fun fetchConversationID(
|
||||
source: String,
|
||||
sourceId: String,
|
||||
userName: String,
|
||||
metaData: HashMap<String?, String?>? = null
|
||||
) =
|
||||
apiResponseCallback(
|
||||
chatApiInterface.fetchConversationId(
|
||||
naviChatInitiateRequest =
|
||||
NaviChatInitiateRequest(
|
||||
source = source,
|
||||
sourceId = sourceId,
|
||||
userName = userName,
|
||||
metaData = metaData
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun fetchChatConversations(
|
||||
conversationId: String,
|
||||
pageNo: Int
|
||||
) =
|
||||
apiResponseCallback(
|
||||
chatApiInterface.fetchChatConversations(
|
||||
conversationId = conversationId,
|
||||
page = pageNo
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun initiateChat(
|
||||
source: String,
|
||||
sourceId: String,
|
||||
@@ -41,7 +70,7 @@ class NaviChatRepository @Inject constructor(private val chatApiInterface: NaviC
|
||||
userName = userName,
|
||||
metaData = metaData
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun getHelpCenterDetails() =
|
||||
@@ -121,6 +150,6 @@ class NaviChatRepository @Inject constructor(private val chatApiInterface: NaviC
|
||||
readTimestamp = readTimestamp,
|
||||
deliveredTimestamp = deliveredTimestamp
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.navi.chat.db.utils.getFileName
|
||||
import com.navi.chat.interfaces.AttachmentOptionClickListener
|
||||
import com.navi.chat.models.NaviChatSystemLocalData
|
||||
import com.navi.chat.ui.fragments.NaviChatFragment
|
||||
import com.navi.chat.ui.fragments.NaviChatFragmentV2
|
||||
import com.navi.chat.utils.CONVERSATION_ID_PARAM
|
||||
import com.navi.chat.utils.DOCUMENT_ITEM
|
||||
import com.navi.chat.utils.FILE_SIZES
|
||||
@@ -31,12 +32,16 @@ import com.navi.chat.utils.NaviChatAnalytics
|
||||
import com.navi.chat.utils.NaviChatAnalytics.Companion.CHAT_SCREEN_CLOSES
|
||||
import com.navi.chat.utils.URI_LIST
|
||||
import com.navi.chat.viewmodels.AttachmentPickerViewModel
|
||||
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
|
||||
|
||||
class NaviChatActivity : ChatBaseActivity() , AttachmentOptionClickListener{
|
||||
|
||||
private lateinit var binding: ActivityNaviChatBinding
|
||||
private val currentScreenTag: String = NaviChatFragment.TAG
|
||||
private val chatScreenTag: String = NaviChatFragment.TAG
|
||||
private val chatV2ScreenTag: String = NaviChatFragmentV2.TAG
|
||||
private var chatFragment: NaviChatFragment? = null
|
||||
private var chatV2Fragment: NaviChatFragmentV2? = null
|
||||
|
||||
private val attachmentPickerViewModel by lazy {
|
||||
ViewModelProvider(this)[AttachmentPickerViewModel::class.java]
|
||||
}
|
||||
@@ -58,7 +63,7 @@ class NaviChatActivity : ChatBaseActivity() , AttachmentOptionClickListener{
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DataBindingUtil.setContentView(this, R.layout.activity_navi_chat)
|
||||
setContentView(binding.root)
|
||||
addFragment()
|
||||
checkFirebaseAndAddFragment()
|
||||
isChatActivityVisible = true
|
||||
}
|
||||
|
||||
@@ -72,13 +77,21 @@ class NaviChatActivity : ChatBaseActivity() , AttachmentOptionClickListener{
|
||||
chatFragment?.scrollChatToLatestMessageReceived()
|
||||
}
|
||||
|
||||
private fun addFragment() {
|
||||
private fun checkFirebaseAndAddFragment() {
|
||||
if (FirebaseRemoteConfigHelper.getBoolean(FirebaseRemoteConfigHelper.ENABLE_CHAT_V2)) {
|
||||
addChatV2Fragment()
|
||||
} else {
|
||||
addChatFragment()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addChatFragment() {
|
||||
val fragment =
|
||||
supportFragmentManager.findFragmentByTag(currentScreenTag) ?: getChatFragment()
|
||||
supportFragmentManager.findFragmentByTag(chatScreenTag) ?: getChatFragment()
|
||||
val fragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
chatFragment = fragment as? NaviChatFragment
|
||||
if (!supportFragmentManager.isStateSaved && !supportFragmentManager.isDestroyed) {
|
||||
fragmentTransaction.replace(R.id.fcvLayout, fragment, currentScreenTag)
|
||||
fragmentTransaction.replace(R.id.fcvLayout, fragment, chatScreenTag)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
}
|
||||
@@ -96,6 +109,30 @@ class NaviChatActivity : ChatBaseActivity() , AttachmentOptionClickListener{
|
||||
arguments = bundle
|
||||
}
|
||||
|
||||
private fun addChatV2Fragment() {
|
||||
val fragment =
|
||||
supportFragmentManager.findFragmentByTag(chatV2ScreenTag) ?: getChatV2Fragment()
|
||||
val fragmentTransaction = supportFragmentManager.beginTransaction()
|
||||
chatV2Fragment = fragment as? NaviChatFragmentV2
|
||||
if (!supportFragmentManager.isStateSaved && !supportFragmentManager.isDestroyed) {
|
||||
fragmentTransaction.replace(R.id.fcvLayout, fragment, chatScreenTag)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getChatV2Fragment() =
|
||||
NaviChatFragmentV2().apply {
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(
|
||||
NAVI_CHAT_SYSTEM_LOCAL_DATA,
|
||||
intent.extras?.getParcelable<NaviChatSystemLocalData>(NAVI_CHAT_SYSTEM_LOCAL_DATA)
|
||||
)
|
||||
intent.extras?.getString(CONVERSATION_ID_PARAM)?.let {
|
||||
bundle.putString(CONVERSATION_ID_PARAM, it)
|
||||
}
|
||||
arguments = bundle
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
crmEventTracker.sendEvent(CHAT_SCREEN_CLOSES)
|
||||
super.onBackPressed()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright © 2023 by Navi Technologies Limited
|
||||
* * All rights reserved. Strictly confidential
|
||||
*
|
||||
*/
|
||||
|
||||
package com.navi.chat.viewmodels
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.firebase.Timestamp
|
||||
import com.navi.base.awsupload.AWSFileType
|
||||
import com.navi.base.model.LineItem
|
||||
import com.navi.base.utils.isNotNull
|
||||
import com.navi.base.utils.isNotNullAndNotEmpty
|
||||
import com.navi.base.utils.orFalse
|
||||
import com.navi.chat.base.ChatBaseVM
|
||||
import com.navi.chat.db.utils.getDisplayableMessages
|
||||
import com.navi.chat.di.scopes.NaviChatScope
|
||||
import com.navi.chat.models.ChatActivityRedirectionConstructedResponse
|
||||
import com.navi.chat.models.ChatConfigConstructedResponse
|
||||
import com.navi.chat.models.ChatConfigResponse
|
||||
import com.navi.chat.models.response.ChatChannelInfo
|
||||
import com.navi.chat.models.response.ChatConversationIdResponse
|
||||
import com.navi.chat.repositories.NaviChatRepository
|
||||
import com.navi.chat.utils.CONVERSATION_ID_PARAM
|
||||
import com.navi.chat.utils.NODE_UUID_PARAM
|
||||
import com.navi.common.awsupload.FileDownloadManager
|
||||
import com.navi.common.awsupload.model.DownloadTask
|
||||
import com.navi.naviwidgets.models.NaviChatWidget
|
||||
import com.navi.naviwidgets.models.response.NaviChatControllerWidget
|
||||
import com.navi.naviwidgets.models.response.NaviChatCsatRatingWidget
|
||||
import com.navi.naviwidgets.models.response.NaviChatResolutionStatusWidget
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@NaviChatScope
|
||||
class NaviChatV2ViewModel
|
||||
@Inject
|
||||
constructor(private val naviChatRepository: NaviChatRepository, private val context: Context) :
|
||||
ChatBaseVM(), FileDownloadManager.StatusCallBack {
|
||||
|
||||
private val _naviChatInitResponse = MutableLiveData<ChatConversationIdResponse>()
|
||||
val naviChatInitResponse: LiveData<ChatConversationIdResponse>
|
||||
get() = _naviChatInitResponse
|
||||
|
||||
private val _chatHistoryMessages = MutableLiveData<Pair<Int, List<NaviChatWidget>>>()
|
||||
val chatHistoryMessages: LiveData<Pair<Int, List<NaviChatWidget>>>
|
||||
get() = _chatHistoryMessages
|
||||
|
||||
private val _chatChannelInfo = MutableLiveData<ChatChannelInfo>()
|
||||
val chatChannelInfo: LiveData<ChatChannelInfo>
|
||||
get() = _chatChannelInfo
|
||||
|
||||
private val _showFreeText = MutableLiveData<Boolean>()
|
||||
val showFreeText: LiveData<Boolean>
|
||||
get() = _showFreeText
|
||||
|
||||
private val _naviChatConfigResponse = MutableLiveData<ChatConfigConstructedResponse?>()
|
||||
val naviChatConfigResponse: LiveData<ChatConfigConstructedResponse?>
|
||||
get() = _naviChatConfigResponse
|
||||
|
||||
private var _downloadSuccessSharedFlow = MutableSharedFlow<DownloadTask>(0)
|
||||
val downloadSuccessSharedFlow = _downloadSuccessSharedFlow.asSharedFlow()
|
||||
private var _downloadFailureSharedFlow = MutableSharedFlow<DownloadTask>(0)
|
||||
val downloadFailureSharedFlow = _downloadFailureSharedFlow.asSharedFlow()
|
||||
|
||||
private val _csatRatingWidget = MutableLiveData<NaviChatCsatRatingWidget?>()
|
||||
val csatRatingWidget: LiveData<NaviChatCsatRatingWidget?>
|
||||
get() = _csatRatingWidget
|
||||
|
||||
private val _chatResolutionWidget = MutableLiveData<NaviChatResolutionStatusWidget?>()
|
||||
val chatResolutionWidget: LiveData<NaviChatResolutionStatusWidget?>
|
||||
get() = _chatResolutionWidget
|
||||
|
||||
private val _activityRedirectionValidationResponse = MutableSharedFlow<ChatActivityRedirectionConstructedResponse>(0)
|
||||
val activityRedirectionValidationResponse = _activityRedirectionValidationResponse.asSharedFlow()
|
||||
|
||||
private val _chatControllerWidget = MutableLiveData<NaviChatControllerWidget?>()
|
||||
val chatControllerWidget: LiveData<NaviChatControllerWidget?>
|
||||
get() = _chatControllerWidget
|
||||
|
||||
private var isCsatFeedbackShown: Boolean = false
|
||||
private var isChatResolutionShown: Boolean = false
|
||||
private var isTimerEnabledForMessage: Boolean = false
|
||||
private var isTimerSet: Boolean = false
|
||||
private var timerDuration: Long = 10000
|
||||
private var showRemainingCharactersView: Boolean = false
|
||||
private var setCharLimit: Int = 1000
|
||||
private var pageCount = 0
|
||||
|
||||
var latestMessageTimeStamp: Timestamp? = null
|
||||
var isPaginationLoading: Boolean = false
|
||||
var canLoadMore: Boolean = true
|
||||
|
||||
private val downloadManager: FileDownloadManager by lazy {
|
||||
FileDownloadManager(naviChatRepository, this, context, "/chat/", true)
|
||||
}
|
||||
|
||||
fun fetchConversationID(
|
||||
source: String,
|
||||
sourceId: String,
|
||||
userName: String,
|
||||
metaData: HashMap<String?, String?>? = null
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val response =
|
||||
naviChatRepository.fetchConversationID(
|
||||
source = source,
|
||||
sourceId = sourceId,
|
||||
userName = userName,
|
||||
metaData = metaData
|
||||
)
|
||||
if (response.error == null && response.errors.isNullOrEmpty() && response.data != null) {
|
||||
_naviChatInitResponse.value = response.data!!
|
||||
} else {
|
||||
setError(
|
||||
errors = response.errors,
|
||||
errorTag = response.errors?.firstOrNull()?.code,
|
||||
cancelable = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchChatConversations(
|
||||
conversationId: String,
|
||||
isFromNotification: Boolean,
|
||||
shouldShowCsat: Boolean,
|
||||
shouldShowChatResolution: Boolean,
|
||||
initialLoadComplete: () -> Unit,
|
||||
hidePaginationLoader: () -> Unit,
|
||||
hideLoaderFromNotification: (channelInfo: ChatChannelInfo?, showFreeText: Boolean?) -> Unit,
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val response = naviChatRepository.fetchChatConversations(
|
||||
conversationId = conversationId,
|
||||
pageNo = pageCount
|
||||
)
|
||||
|
||||
if (response.error == null && response.errors.isNullOrEmpty()) {
|
||||
canLoadMore = response.data?.isLast?.not() ?: false
|
||||
|
||||
if (pageCount == 0) {
|
||||
latestMessageTimeStamp = response.data?.lastMessageTimeStamp
|
||||
_chatHistoryMessages.value =
|
||||
Pair(
|
||||
first = pageCount,
|
||||
second =
|
||||
response.data?.listOfMessages?.let {
|
||||
val msgs = getDisplayableMessages(it)
|
||||
msgs
|
||||
} ?: emptyList()
|
||||
)
|
||||
if (isFromNotification) {
|
||||
if ((response.data?.listOfMessages?.size ?: 0) > 0) {
|
||||
response.data?.listOfMessages?.let {
|
||||
checkMsgForCsat(it, shouldShowCsat, shouldShowChatResolution)
|
||||
run loop@{
|
||||
it.forEach { naviChatWidget ->
|
||||
if (naviChatWidget is NaviChatControllerWidget) {
|
||||
_chatControllerWidget.value = naviChatWidget
|
||||
return@loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hideLoaderFromNotification.invoke(
|
||||
response.data?.channelInfo,
|
||||
response.data?.showFreeText
|
||||
)
|
||||
} else {
|
||||
response.data?.channelInfo?.let {
|
||||
_chatChannelInfo.value = it
|
||||
}
|
||||
response.data?.showFreeText?.let {
|
||||
_showFreeText.value = it
|
||||
}
|
||||
initialLoadComplete.invoke()
|
||||
}
|
||||
isPaginationLoading = false
|
||||
pageCount++
|
||||
} else {
|
||||
_chatHistoryMessages.value =
|
||||
Pair(
|
||||
first = pageCount,
|
||||
second =
|
||||
response.data?.listOfMessages?.let {
|
||||
val msgs = getDisplayableMessages(it)
|
||||
msgs
|
||||
} ?: emptyList()
|
||||
)
|
||||
isPaginationLoading = false
|
||||
pageCount++
|
||||
}
|
||||
} else {
|
||||
if (pageCount == 0) {
|
||||
setError(
|
||||
errors = response.errors,
|
||||
errorTag = response.errors?.firstOrNull()?.code,
|
||||
cancelable = false
|
||||
)
|
||||
} else {
|
||||
hidePaginationLoader.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun markConversationRead(
|
||||
conversationId: String,
|
||||
user: String,
|
||||
readTimestamp: String,
|
||||
deliveredTimestamp: String
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
if (conversationId.isNotNullAndNotEmpty()) {
|
||||
naviChatRepository.updateReceipts(
|
||||
conversationId = conversationId,
|
||||
user = user,
|
||||
readTimestamp = readTimestamp,
|
||||
deliveredTimestamp = deliveredTimestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkMsgForCsat(msgList: List<NaviChatWidget>, shouldShowCsat: Boolean, shouldShowChatResolution: Boolean) {
|
||||
msgList.forEach { msg ->
|
||||
if (msg is NaviChatCsatRatingWidget && shouldShowCsat) {
|
||||
_csatRatingWidget.value = msg
|
||||
} else if (msg is NaviChatResolutionStatusWidget && shouldShowChatResolution) {
|
||||
_chatResolutionWidget.value = msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getFileTypeSizeConfig() {
|
||||
viewModelScope.launch {
|
||||
val response = naviChatRepository.getConfig()
|
||||
if (
|
||||
response.error == null &&
|
||||
response.errors.isNullOrEmpty() &&
|
||||
response.isNotNull() &&
|
||||
response.data?.fileUpload.isNotNull() &&
|
||||
response.data?.fileUpload?.sizeInKiloBytes.isNotNull()
|
||||
) {
|
||||
_naviChatConfigResponse.value = response.data?.let {
|
||||
constructConfigResponse(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun constructConfigResponse(response: ChatConfigResponse): ChatConfigConstructedResponse {
|
||||
return ChatConfigConstructedResponse(
|
||||
fileSizeInKB = getHashMap(response),
|
||||
botTypingCuesDelayInMs = response.botTypingCuesDelayInMs?.toLong() ?: 1000L,
|
||||
csatAppearanceDelayInMs = response.chatAppearanceDelayInMs?.toLong() ?: 2000L
|
||||
)
|
||||
}
|
||||
|
||||
private fun getHashMap(response: ChatConfigResponse): HashMap<String, Int> {
|
||||
val hashmap = HashMap<String, Int>()
|
||||
response.fileUpload?.sizeInKiloBytes?.let { sizeInKb ->
|
||||
sizeInKb.JPEG?.let { hashmap[AWSFileType.JPEG.value] = it }
|
||||
sizeInKb.JPG?.let { hashmap[AWSFileType.JPG.value] = it }
|
||||
sizeInKb.PNG?.let { hashmap[AWSFileType.PNG.value] = it }
|
||||
sizeInKb.PDF?.let { hashmap[AWSFileType.PDF.value] = it }
|
||||
sizeInKb.MP4?.let { hashmap[AWSFileType.MP4.value] = it }
|
||||
sizeInKb.DOC?.let { hashmap[AWSFileType.DOC.value] = it }
|
||||
sizeInKb.DOCX?.let { hashmap[AWSFileType.DOCX.value] = it }
|
||||
}
|
||||
return hashmap
|
||||
}
|
||||
|
||||
fun setLatestMessageLatestTimeStamp(timestamp: Timestamp?) {
|
||||
timestamp?.let {
|
||||
latestMessageTimeStamp = it
|
||||
Timber.d("Setting latest timestamp: $timestamp")
|
||||
}
|
||||
}
|
||||
|
||||
fun setCsatFeedbackState(value: Boolean) {
|
||||
isCsatFeedbackShown = value
|
||||
}
|
||||
|
||||
fun getCsatFeedbackState() = isCsatFeedbackShown
|
||||
|
||||
fun setChatResolutionState(value: Boolean) {
|
||||
isChatResolutionShown = value
|
||||
}
|
||||
|
||||
fun getChatResolutionState () = isChatResolutionShown
|
||||
|
||||
fun setTimerEnabledForMessage(value: Boolean) {
|
||||
isTimerEnabledForMessage = value
|
||||
}
|
||||
|
||||
fun getTimerEnabledForMessage() = isTimerEnabledForMessage
|
||||
|
||||
fun setTimerFlag(value: Boolean) {
|
||||
isTimerSet = value
|
||||
}
|
||||
|
||||
fun getTimerFlag() = isTimerSet
|
||||
|
||||
fun setTimerDuration(duration: Long) {
|
||||
timerDuration = duration
|
||||
}
|
||||
|
||||
fun getTimerDuration() = timerDuration
|
||||
|
||||
fun setRemainingCharactersView(value: Boolean) {
|
||||
showRemainingCharactersView = value
|
||||
}
|
||||
|
||||
fun showRemainingCharactersView() = showRemainingCharactersView
|
||||
|
||||
fun setCharLimit(value: Int) {
|
||||
setCharLimit = value
|
||||
}
|
||||
|
||||
fun getCharLimit() = setCharLimit
|
||||
|
||||
fun enqueueDownload(task: DownloadTask) {
|
||||
downloadManager.enqueueDownloadTask(task)
|
||||
}
|
||||
|
||||
fun checkIfActivityRedirectionIsValid(conversationId: String, nodeUuid: String,
|
||||
url: String?, parameters: List<LineItem>) {
|
||||
viewModelScope.launch {
|
||||
val queryMap = java.util.HashMap<String, String>()
|
||||
queryMap[CONVERSATION_ID_PARAM] = conversationId
|
||||
queryMap[NODE_UUID_PARAM] = nodeUuid
|
||||
val response = naviChatRepository.checkIfActivityRedirectionIsValid(
|
||||
queryMap = queryMap
|
||||
)
|
||||
if (response.error == null && response.errors.isNullOrEmpty()) {
|
||||
_activityRedirectionValidationResponse.emit(
|
||||
ChatActivityRedirectionConstructedResponse(
|
||||
isValid = response.data?.isValid, url = url,
|
||||
parameters = parameters
|
||||
)
|
||||
)
|
||||
} else {
|
||||
_activityRedirectionValidationResponse.emit(ChatActivityRedirectionConstructedResponse(
|
||||
isValid = false
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onDownloadSuccess(downloadTask: DownloadTask) {
|
||||
Timber.d("NaviChatViewModel:: onSuccess")
|
||||
_downloadSuccessSharedFlow.emit(downloadTask)
|
||||
}
|
||||
|
||||
override suspend fun onError(error: Throwable, downloadTask: DownloadTask) {
|
||||
Timber.d("NaviChatViewModel:: onError")
|
||||
_downloadFailureSharedFlow.emit(downloadTask)
|
||||
}
|
||||
}
|
||||
168
navi-chat/src/main/res/layout/fragment_common_navi_chat_v2.xml
Normal file
168
navi-chat/src/main/res/layout/fragment_common_navi_chat_v2.xml
Normal file
@@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
~
|
||||
~ * Copyright © 2023 by Navi Technologies Limited
|
||||
~ * All rights reserved. Strictly confidential
|
||||
~
|
||||
-->
|
||||
|
||||
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.fragments.NaviChatFragment">
|
||||
|
||||
<com.navi.chat.ui.components.NaviChatToolbar
|
||||
android:id="@+id/nctLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:id="@+id/chatLoader"
|
||||
android:layout_width="@dimen/layout_dp_56"
|
||||
android:layout_height="@dimen/layout_dp_56"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/nctLayout"
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_fileName="purple_loader.json"
|
||||
app:lottie_loop="true"
|
||||
app:lottie_speed="1.0" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/chatScreen"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="rvChat,viewSeparator,clFreeText" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvChat"
|
||||
android:background="@color/white"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/layout_dp_0"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
app:layout_constraintBottom_toTopOf="@id/viewSeparator"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/nctLayout" />
|
||||
|
||||
<View
|
||||
android:id="@+id/viewSeparator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/layout_dp_1"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:background="@color/divider_color_light_grey"
|
||||
app:layout_constraintBottom_toTopOf="@id/clFreeText"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/rvChat" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/clFreeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
app:layout_constraintBottom_toTopOf="@id/viewSeparator1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/viewSeparator">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/clInnerLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="@dimen/dp_12"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/verticalGuideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.77" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/etFreeText"
|
||||
android:layout_width="@dimen/layout_dp_0"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@null"
|
||||
android:cursorVisible="true"
|
||||
android:fontFamily="@font/tt_regular"
|
||||
android:gravity="center_vertical"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLines="@integer/integer_3"
|
||||
android:hint="@string/type_here"
|
||||
android:imeOptions="actionDone"
|
||||
android:paddingStart="@dimen/layout_dp_16"
|
||||
android:paddingEnd="@dimen/layout_dp_16"
|
||||
android:textColor="@color/title_color_two"
|
||||
android:textCursorDrawable="@null"
|
||||
android:textSize="@dimen/font_small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/verticalGuideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/ivSendAttachment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/dp_9"
|
||||
android:padding="@dimen/dp_7"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_add_attachment_purple" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/ivSendFreeText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_send_msg_purple" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.navi.design.textview.NaviTextView
|
||||
android:id="@+id/tvCharLimitCounter"
|
||||
style="@style/SmallTextSemiBoldDescriptionColorSeven"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:background="@color/white"
|
||||
android:paddingHorizontal="@dimen/_16dp"
|
||||
android:paddingVertical="@dimen/dp_4"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/clInnerLayout"
|
||||
tools:text="1000 characters left" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<View
|
||||
android:id="@+id/viewSeparator1"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dp_230"
|
||||
android:background="@color/divider_color_light_grey"
|
||||
app:layout_constraintTop_toBottomOf="@id/clFreeText"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
@@ -70,6 +70,7 @@ object FirebaseRemoteConfigHelper {
|
||||
const val DISABLE_ALFRED = "DISABLE_ALFRED"
|
||||
const val INTENT_DATA_INSERT_DISABLED = "INTENT_DATA_INSERT_DISABLED"
|
||||
const val UPI_NUMBER_DISABLED = "UPI_NUMBER_DISABLED"
|
||||
const val ENABLE_CHAT_V2 = "ENABLE_CHAT_V2"
|
||||
const val UW_INGESTION_POLLING = "UW_INGESTION_POLLING"
|
||||
|
||||
fun init() {
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
<value>false</value>
|
||||
</entry>
|
||||
|
||||
<entry>
|
||||
<key>ENABLE_CHAT_V2</key>
|
||||
<value>false</value>
|
||||
</entry>
|
||||
|
||||
<entry>
|
||||
<key>hundred_ms_setting_for_auto_resize</key>
|
||||
<value>false</value>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/red"
|
||||
android:indeterminateTint="@color/dark_purple"
|
||||
android:indeterminateTintMode="src_atop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
||||
Reference in New Issue
Block a user