Updated on 2026-08-14
This commit is contained in:
commit
aa9f9ed884
13 changed files with 130 additions and 23 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.common.chat
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.tangem.tap.ForegroundActivityObserver
|
||||
import com.tangem.tap.common.chat.opener.ChatOpener
|
||||
import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener
|
||||
|
|
@ -19,11 +20,31 @@ class ChatManager(
|
|||
fun open(config: ChatConfig, feedbackDataBuilder: (Context) -> String) {
|
||||
val opener = openers.getOrPut(config) {
|
||||
when (config) {
|
||||
is SprinklrConfig -> SprinklrChatOpener(config, store)
|
||||
is ZendeskConfig -> ZendeskChatOpener(config, preferencesStorage, foregroundActivityObserver)
|
||||
is SprinklrConfig -> SprinklrChatOpener(getSprinklrUserId(), config, store)
|
||||
is ZendeskConfig -> ZendeskChatOpener(getZendeskUserId(), config, foregroundActivityObserver)
|
||||
}
|
||||
}
|
||||
|
||||
opener.open(feedbackDataBuilder)
|
||||
}
|
||||
|
||||
private fun getZendeskUserId(): String {
|
||||
if (preferencesStorage.zendeskFirstLaunchTime == null) {
|
||||
preferencesStorage.zendeskFirstLaunchTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
return getChatUserId(preferencesStorage.zendeskFirstLaunchTime!!)
|
||||
}
|
||||
|
||||
private fun getSprinklrUserId(): String {
|
||||
if (preferencesStorage.sprinklrFirstLaunchTime == null) {
|
||||
preferencesStorage.sprinklrFirstLaunchTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
return getChatUserId(preferencesStorage.sprinklrFirstLaunchTime!!)
|
||||
}
|
||||
|
||||
private fun getChatUserId(firstLaunchTimeMillis: Long): String {
|
||||
return "${firstLaunchTimeMillis}${Build.MODEL}".hashCode().toString()
|
||||
}
|
||||
}
|
||||
|
|
@ -11,11 +11,12 @@ import com.tangem.tap.features.sprinklr.redux.SprinklrAction
|
|||
import org.rekotlin.Store
|
||||
|
||||
internal class SprinklrChatOpener(
|
||||
private val userId: String,
|
||||
private val config: SprinklrConfig,
|
||||
private val store: Store<AppState>,
|
||||
) : ChatOpener {
|
||||
override fun open(feedbackDataBuilder: (Context) -> String) {
|
||||
store.dispatch(SprinklrAction.SetConfig(config))
|
||||
store.dispatch(SprinklrAction.Init(userId, config))
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Sprinklr))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
package com.tangem.tap.common.chat.opener.implementation
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.tap.ForegroundActivityObserver
|
||||
import com.tangem.tap.common.chat.ZendeskConfig
|
||||
import com.tangem.tap.common.chat.opener.ChatOpener
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
import com.tangem.tap.withForegroundActivity
|
||||
import com.tangem.wallet.R
|
||||
import com.zendesk.logger.Logger
|
||||
|
|
@ -20,8 +18,8 @@ import zendesk.configurations.Configuration
|
|||
import zendesk.messaging.MessagingActivity
|
||||
|
||||
internal class ZendeskChatOpener(
|
||||
private val userId: String,
|
||||
private val config: ZendeskConfig,
|
||||
private val preferencesStorage: PreferencesStorage,
|
||||
private val foregroundActivityObserver: ForegroundActivityObserver,
|
||||
) : ChatOpener {
|
||||
private var isInitialized = false
|
||||
|
|
@ -44,12 +42,8 @@ internal class ZendeskChatOpener(
|
|||
}
|
||||
|
||||
private fun setChatVisitorInfo() {
|
||||
if (preferencesStorage.chatFirstLaunchTime == null) {
|
||||
preferencesStorage.chatFirstLaunchTime = System.currentTimeMillis()
|
||||
}
|
||||
val chatUserId = (preferencesStorage.chatFirstLaunchTime.toString() + Build.MODEL).hashCode()
|
||||
val visitorInfo = VisitorInfo.builder()
|
||||
.withName("User $chatUserId")
|
||||
.withName("User $userId")
|
||||
.build()
|
||||
|
||||
Chat.INSTANCE.chatProvidersConfiguration = ChatProvidersConfiguration.builder()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.tap.common.chat.SprinklrConfig
|
|||
import org.rekotlin.Action
|
||||
|
||||
sealed interface SprinklrAction : Action {
|
||||
data class SetConfig(val config: SprinklrConfig) : SprinklrAction
|
||||
data class Init(val userId: String, val config: SprinklrConfig) : SprinklrAction
|
||||
data class UpdateUrl(val url: String) : SprinklrAction
|
||||
data class UpdateSprinklrDomains(val domains: List<String>) : SprinklrAction
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tap.features.sprinklr.redux
|
||||
|
||||
import com.tangem.tap.common.chat.SprinklrConfig
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.sprinklr.redux.model.SprinklrUrl
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
|
|
@ -20,15 +22,21 @@ internal class SprinklrMiddleware {
|
|||
|
||||
private fun handleAction(action: SprinklrAction) {
|
||||
when (action) {
|
||||
is SprinklrAction.SetConfig -> createUrl()
|
||||
is SprinklrAction.UpdateUrl -> Unit
|
||||
is SprinklrAction.Init -> updateUrl(action.userId, action.config)
|
||||
is SprinklrAction.UpdateUrl,
|
||||
is SprinklrAction.UpdateSprinklrDomains,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun createUrl() {
|
||||
// TODO: Create Sprinklr URL, will be in further MRs
|
||||
val url = "https://prod-live-chat.sprinklr.com/" +
|
||||
"page?appId=60c1d169c96beb5bf5a326f3_app_950954&device=MOBILE&enableClose=true&zoom=false"
|
||||
private fun updateUrl(userId: String, config: SprinklrConfig) {
|
||||
updateSprinklrDomains(config.baseUrl)
|
||||
val url = SprinklrUrl.Prod(userId, config.baseUrl, config.appId).url
|
||||
store.dispatchOnMain(SprinklrAction.UpdateUrl(url))
|
||||
}
|
||||
|
||||
private fun updateSprinklrDomains(baseUrl: String) {
|
||||
val domains = listOf(baseUrl, SprinklrUrl.Static.url)
|
||||
store.dispatchOnMain(SprinklrAction.UpdateSprinklrDomains(domains))
|
||||
}
|
||||
}
|
||||
|
|
@ -12,8 +12,9 @@ internal object SprinklrReducer {
|
|||
|
||||
private fun internalReduce(action: SprinklrAction, state: SprinklrState): SprinklrState {
|
||||
return when (action) {
|
||||
is SprinklrAction.SetConfig -> state
|
||||
is SprinklrAction.Init -> state
|
||||
is SprinklrAction.UpdateUrl -> state.copy(url = action.url)
|
||||
is SprinklrAction.UpdateSprinklrDomains -> state.copy(sprinklrDomains = action.domains)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,4 +2,5 @@ package com.tangem.tap.features.sprinklr.redux
|
|||
|
||||
data class SprinklrState(
|
||||
val url: String = "",
|
||||
val sprinklrDomains: List<String> = emptyList(),
|
||||
)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.features.sprinklr.redux.model
|
||||
|
||||
internal sealed class SprinklrUrl {
|
||||
abstract val url: String
|
||||
|
||||
class Prod(userId: String, baseUrl: String, appId: String) : SprinklrUrl() {
|
||||
private val locale = java.util.Locale.getDefault().language
|
||||
|
||||
override val url: String = "$baseUrl/page?" +
|
||||
"appId=$appId" +
|
||||
"&device=$Device" +
|
||||
"&enableClose=$CloseEnabled" +
|
||||
"&zoom=$ZoomEnabled" +
|
||||
"&locale=$locale" +
|
||||
"&user_id=$userId"
|
||||
|
||||
companion object {
|
||||
private const val Device = "MOBILE"
|
||||
private const val CloseEnabled = true
|
||||
private const val ZoomEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
object Static : SprinklrUrl() {
|
||||
override val url: String = "live-chat-static.sprinklr.com"
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,11 @@ package com.tangem.tap.features.sprinklr.ui
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.google.accompanist.web.WebView
|
||||
import com.google.accompanist.web.rememberWebViewState
|
||||
import com.tangem.tap.features.sprinklr.ui.webview.SprinklrWebViewClient
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Composable
|
||||
|
|
@ -21,5 +23,6 @@ internal fun SprinklrScreenContent(
|
|||
domStorageEnabled = true
|
||||
}
|
||||
},
|
||||
client = remember { SprinklrWebViewClient(state.onNewUrl) },
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.tap.features.sprinklr.ui
|
||||
|
||||
import com.google.accompanist.web.WebContent
|
||||
|
||||
internal data class SprinklrScreenState(
|
||||
val initialUrl: String = "",
|
||||
val sprinklrDomains: List<String> = emptyList(),
|
||||
val onNavigateBack: () -> Unit = {},
|
||||
val onNewUrl: WebContent.(String) -> WebContent = { this },
|
||||
)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.sprinklr.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.google.accompanist.web.WebContent
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.sprinklr.redux.SprinklrState
|
||||
|
|
@ -22,7 +23,9 @@ internal class SprinklrViewModel : ViewModel(), StoreSubscriber<SprinklrState> {
|
|||
stateInternal.update { prevState ->
|
||||
prevState.copy(
|
||||
initialUrl = state.url,
|
||||
sprinklrDomains = state.sprinklrDomains,
|
||||
onNavigateBack = this::navigateBack,
|
||||
onNewUrl = { updateWebContentOrOpenExternalUrl(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -41,4 +44,18 @@ internal class SprinklrViewModel : ViewModel(), StoreSubscriber<SprinklrState> {
|
|||
private fun navigateBack() {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
}
|
||||
|
||||
private fun WebContent.updateWebContentOrOpenExternalUrl(url: String): WebContent {
|
||||
return if (isExternalUrl(url)) {
|
||||
store.dispatchOnMain(NavigationAction.OpenUrl(url))
|
||||
this
|
||||
} else when (this) {
|
||||
is WebContent.Url -> copy(url = url)
|
||||
else -> WebContent.Url(url)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isExternalUrl(url: String): Boolean {
|
||||
return state.value.sprinklrDomains.none { url.contains(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.tap.features.sprinklr.ui.webview
|
||||
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import com.google.accompanist.web.AccompanistWebViewClient
|
||||
import com.google.accompanist.web.WebContent
|
||||
|
||||
internal class SprinklrWebViewClient(
|
||||
private val onNewUrl: WebContent.(String) -> WebContent,
|
||||
) : AccompanistWebViewClient() {
|
||||
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
||||
// If the url hasn't changed, this is probably an internal event like
|
||||
// a javascript reload. We should let it happen.
|
||||
if (view?.url == request?.url.toString()) {
|
||||
return false
|
||||
}
|
||||
|
||||
request?.let {
|
||||
state.content = onNewUrl(state.content, it.url.toString())
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -29,9 +29,13 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
toppedUpWalletStorage = ToppedUpWalletStorage(preferences, MoshiConverter.INSTANCE)
|
||||
}
|
||||
|
||||
var chatFirstLaunchTime: Long?
|
||||
get() = preferences.getLong(CHAT_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L }
|
||||
set(value) = preferences.edit { putLong(CHAT_FIRST_LAUNCH_KEY, value ?: 0) }
|
||||
var zendeskFirstLaunchTime: Long?
|
||||
get() = preferences.getLong(ZENDESK_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L }
|
||||
set(value) = preferences.edit { putLong(ZENDESK_FIRST_LAUNCH_KEY, value ?: 0) }
|
||||
|
||||
var sprinklrFirstLaunchTime: Long?
|
||||
get() = preferences.getLong(SPRINKLR_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L }
|
||||
set(value) = preferences.edit { putLong(SPRINKLR_FIRST_LAUNCH_KEY, value ?: 0) }
|
||||
|
||||
var shouldShowSaveUserWalletScreen: Boolean
|
||||
get() = preferences.getBoolean(SAVE_WALLET_DIALOG_SHOWN_KEY, true)
|
||||
|
|
@ -82,7 +86,8 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
private const val PREFERENCES_NAME = "tapPrefs"
|
||||
private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown"
|
||||
private const val APP_LAUNCH_COUNT_KEY = "launchCount"
|
||||
private const val CHAT_FIRST_LAUNCH_KEY = "chatFirstLaunchKey"
|
||||
private const val ZENDESK_FIRST_LAUNCH_KEY = "chatFirstLaunchKey"
|
||||
private const val SPRINKLR_FIRST_LAUNCH_KEY = "sprinklrFirstLaunch"
|
||||
private const val SAVE_WALLET_DIALOG_SHOWN_KEY = "saveUserWalletShown"
|
||||
private const val SAVE_USER_WALLETS_KEY = "saveUserWallets"
|
||||
private const val SAVE_ACCESS_CODES_KEY = "saveAccessCodes"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue