Updated on 2026-08-14

This commit is contained in:
Tangem 2023-02-17 15:25:12 +03:00
commit 30210ca422
26 changed files with 243 additions and 71 deletions

View file

@ -1,12 +1,13 @@
package com.tangem.tap.features.onboarding.products.wallet.saltPay
import com.tangem.tap.common.chat.SprinklrConfig
import org.spongycastle.util.encoders.Base64.toBase64String
/**
[REDACTED_AUTHOR]
*/
data class SaltPayConfig(
val sprinklrAppID: String,
val sprinklr: SprinklrConfig,
val kycProvider: KYCProvider,
val credentials: Credentials,
val blockscoutCredentials: Credentials,
@ -14,7 +15,7 @@ data class SaltPayConfig(
companion object {
fun stub(): SaltPayConfig {
return SaltPayConfig(
sprinklrAppID = "",
sprinklr = SprinklrConfig("", ""),
kycProvider = KYCProvider("", "", "", ""),
credentials = Credentials("", ""),
blockscoutCredentials = Credentials("", ""),

View file

@ -4,7 +4,6 @@ import android.app.Dialog
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.common.extensions.guard
import com.tangem.tap.common.chat.SprinklrConfig
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.feedback.SupportInfo
import com.tangem.tap.common.redux.global.GlobalAction
@ -21,14 +20,14 @@ class NoFundsForActivationDialog {
setTitle(R.string.saltpay_error_no_gas_title)
setMessage(R.string.saltpay_error_no_gas_message)
setPositiveButton(R.string.chat_button_title) { _, _ ->
val sprinklrAppId = store.state.globalState.configManager?.config
val sprinklrConfig = store.state.globalState.configManager?.config
?.saltPayConfig
?.sprinklrAppID
?.sprinklr
.guard {
store.dispatchDebugErrorNotification("SaltPayConfig not initialized")
return@setPositiveButton
}
store.dispatch(GlobalAction.OpenChat(SupportInfo(), SprinklrConfig(sprinklrAppId)))
store.dispatch(GlobalAction.OpenChat(SupportInfo(), sprinklrConfig))
}
setNegativeButton(R.string.common_cancel) { _, _ ->
}

View file

@ -0,0 +1,9 @@
package com.tangem.tap.features.sprinklr.redux
import com.tangem.tap.common.chat.SprinklrConfig
import org.rekotlin.Action
sealed interface SprinklrAction : Action {
data class SetConfig(val config: SprinklrConfig) : SprinklrAction
data class UpdateUrl(val url: String) : SprinklrAction
}

View file

@ -0,0 +1,34 @@
package com.tangem.tap.features.sprinklr.redux
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.store
import org.rekotlin.Middleware
internal class SprinklrMiddleware {
val middleware: Middleware<AppState> = { _, appStateProvider ->
{ next ->
{ action ->
val appState = appStateProvider()
if (action is SprinklrAction && appState != null) {
handleAction(action)
}
next(action)
}
}
}
private fun handleAction(action: SprinklrAction) {
when (action) {
is SprinklrAction.SetConfig -> createUrl()
is SprinklrAction.UpdateUrl -> 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"
store.dispatchOnMain(SprinklrAction.UpdateUrl(url))
}
}

View file

@ -0,0 +1,19 @@
package com.tangem.tap.features.sprinklr.redux
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action
internal object SprinklrReducer {
fun reduce(action: Action, state: AppState): SprinklrState {
return if (action is SprinklrAction) {
internalReduce(action, state.sprinklrState)
} else state.sprinklrState
}
private fun internalReduce(action: SprinklrAction, state: SprinklrState): SprinklrState {
return when (action) {
is SprinklrAction.SetConfig -> state
is SprinklrAction.UpdateUrl -> state.copy(url = action.url)
}
}
}

View file

@ -0,0 +1,5 @@
package com.tangem.tap.features.sprinklr.redux
data class SprinklrState(
val url: String = "",
)

View file

@ -0,0 +1,31 @@
package com.tangem.tap.features.sprinklr.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import com.tangem.core.ui.fragments.ComposeFragment
internal class SprinklrFragment : ComposeFragment<SprinklrScreenState>() {
private val viewModel by viewModels<SprinklrViewModel>()
@Composable
override fun provideState(): State<SprinklrScreenState> {
return viewModel.state.collectAsState()
}
@Composable
override fun ScreenContent(modifier: Modifier, state: SprinklrScreenState) {
BackHandler(onBack = state.onNavigateBack)
SprinklrScreenContent(
modifier = modifier
.systemBarsPadding()
.imePadding(),
state = state,
)
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.tap.features.sprinklr.ui
import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.google.accompanist.web.WebView
import com.google.accompanist.web.rememberWebViewState
@SuppressLint("SetJavaScriptEnabled")
@Composable
internal fun SprinklrScreenContent(
state: SprinklrScreenState,
modifier: Modifier = Modifier,
) {
WebView(
modifier = modifier,
state = rememberWebViewState(url = state.initialUrl),
onCreated = { webView ->
with(webView.settings) {
javaScriptEnabled = true
domStorageEnabled = true
}
},
)
}

View file

@ -0,0 +1,6 @@
package com.tangem.tap.features.sprinklr.ui
internal data class SprinklrScreenState(
val initialUrl: String = "",
val onNavigateBack: () -> Unit = {},
)

View file

@ -0,0 +1,44 @@
package com.tangem.tap.features.sprinklr.ui
import androidx.lifecycle.ViewModel
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.sprinklr.redux.SprinklrState
import com.tangem.tap.store
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import org.rekotlin.StoreSubscriber
internal class SprinklrViewModel : ViewModel(), StoreSubscriber<SprinklrState> {
private val stateInternal = MutableStateFlow(SprinklrScreenState())
val state = stateInternal.asStateFlow()
init {
subscribeToStoreChanges()
}
override fun newState(state: SprinklrState) {
stateInternal.update { prevState ->
prevState.copy(
initialUrl = state.url,
onNavigateBack = this::navigateBack,
)
}
}
override fun onCleared() {
store.unsubscribe(this)
}
private fun subscribeToStoreChanges() {
store.subscribe(this) { appState ->
appState.skip { old, new -> old.sprinklrState == new.sprinklrState }
.select { it.sprinklrState }
}
}
private fun navigateBack() {
store.dispatchOnMain(NavigationAction.PopBackTo())
}
}

View file

@ -55,7 +55,6 @@ import com.tangem.domain.redux.domainStore
import com.tangem.tap.common.compose.AddCustomTokenWarning
import com.tangem.tap.common.compose.ClosePopupTrigger
import com.tangem.tap.common.compose.ComposeDialogManager
import com.tangem.tap.common.compose.ToggledRippleTheme
import com.tangem.tap.common.compose.keyboardAsState
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCase
@ -66,7 +65,7 @@ import kotlinx.coroutines.launch
/**
[REDACTED_AUTHOR]
*/
private class AddCustomTokenScreen {} // for simple search
private class AddCustomTokenScreen // for simple search
@OptIn(ExperimentalMaterialApi::class)
@Composable
@ -91,11 +90,13 @@ fun AddCustomTokenScreen(
},
sheetPeekHeight = 0.dp,
) {
Column() {
TestCasesList(onItemClick = {
selectedTestCase.value = it
toggleBottomSheet()
})
Column {
TestCasesList(
onItemClick = {
selectedTestCase.value = it
toggleBottomSheet()
},
)
ScreenContent(state, closePopupTrigger)
}
}
@ -228,23 +229,21 @@ private fun AddCustomTokenFab(
FloatingActionButtonDefaults.elevation()
}
ToggledRippleTheme(isEnabled) {
ExtendedFloatingActionButton(
modifier = modifier,
icon = {
Icon(
imageVector = Icons.Filled.Add,
tint = contentColor,
contentDescription = "Add",
)
},
text = { Text(text = stringResource(id = R.string.common_add)) },
onClick = { if (isEnabled) onClick() },
backgroundColor = backgroundColor,
contentColor = contentColor,
elevation = elevation,
)
}
ExtendedFloatingActionButton(
modifier = modifier,
icon = {
Icon(
imageVector = Icons.Filled.Add,
tint = contentColor,
contentDescription = "Add",
)
},
text = { Text(text = stringResource(id = R.string.common_add)) },
onClick = { if (isEnabled) onClick() },
backgroundColor = backgroundColor,
contentColor = contentColor,
elevation = elevation,
)
}
data class ScreenFieldData(