diff --git a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt index 324b6c9095..565e5db800 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt @@ -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() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt index 0b58d26a14..e7be1411f6 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt @@ -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, ) : ChatOpener { override fun open(feedbackDataBuilder: (Context) -> String) { - store.dispatch(SprinklrAction.SetConfig(config)) + store.dispatch(SprinklrAction.Init(userId, config)) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Sprinklr)) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt index db828b9619..1c07657555 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt @@ -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() diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt index 3c5571db9a..1505a42f3b 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt @@ -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) : SprinklrAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt index 8fd0798a25..b9b2b41cde 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt @@ -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)) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt index 44e2e33785..8ec0fc117c 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt @@ -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) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt index 8323b35fd0..12ba5b8672 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt @@ -2,4 +2,5 @@ package com.tangem.tap.features.sprinklr.redux data class SprinklrState( val url: String = "", + val sprinklrDomains: List = emptyList(), ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt new file mode 100644 index 0000000000..2a76172029 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt @@ -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" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt index 3cc36f08f7..f80a4b43f3 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt @@ -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) }, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt index c1b5540b10..8757f6e167 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt @@ -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 = emptyList(), val onNavigateBack: () -> Unit = {}, + val onNewUrl: WebContent.(String) -> WebContent = { this }, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt index 2da7f8c31d..1c23eef584 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt @@ -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 { 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 { 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) } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/webview/SprinklrWebViewClient.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/webview/SprinklrWebViewClient.kt new file mode 100644 index 0000000000..2f2487c88a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/webview/SprinklrWebViewClient.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index d419aafffc..7999268acb 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -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"