Updated on 2026-08-14
This commit is contained in:
commit
2c9ea54ebc
32 changed files with 127 additions and 352 deletions
|
|
@ -9,17 +9,28 @@ plugins {
|
|||
id("configuration")
|
||||
}
|
||||
|
||||
//conflict of dependencies when adding WalletConnectV2.0 library
|
||||
configurations {
|
||||
all {
|
||||
exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18")
|
||||
resolutionStrategy {
|
||||
force("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||
configurations.all {
|
||||
exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18")
|
||||
exclude(group = "com.github.komputing.kethereum")
|
||||
|
||||
resolutionStrategy {
|
||||
dependencySubstitution {
|
||||
substitute(module("com.facebook.react:react-native"))
|
||||
.using(module("com.facebook.react:react-android:0.72.4"))
|
||||
|
||||
substitute(module("com.facebook.react:hermes-engine"))
|
||||
.using(module("com.facebook.react:hermes-android:0.72.4"))
|
||||
}
|
||||
exclude(group = "com.github.komputing.kethereum")
|
||||
|
||||
force(
|
||||
"org.bouncycastle:bcpkix-jdk15on:1.70",
|
||||
"com.facebook.react:react-android:0.72.4",
|
||||
"com.facebook.react:hermes-android:0.72.4",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(files("libs/walletconnect-1.5.6.aar"))
|
||||
implementation(projects.domain.legacy)
|
||||
|
|
@ -164,6 +175,10 @@ dependencies {
|
|||
implementation(deps.walletConnectCore)
|
||||
implementation(deps.walletConnectWeb3)
|
||||
implementation(deps.prettyLogger)
|
||||
implementation("com.facebook.react:react-android:0.72.4")
|
||||
implementation(deps.sprClient) {
|
||||
exclude(group = "com.github.stephenc.jcip")
|
||||
}
|
||||
|
||||
/** Testing libraries */
|
||||
testImplementation(deps.test.junit)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,16 @@
|
|||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
|
@ -39,7 +49,7 @@
|
|||
android:extractNativeLibs="true"
|
||||
android:supportsRtl="true"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:allowBackup, android:fullBackupContent">
|
||||
tools:replace="android:allowBackup, android:fullBackupContent, android:label">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.wallet.api.enabled"
|
||||
|
|
@ -135,10 +145,6 @@
|
|||
android:name="zendesk.messaging.MessagingActivity"
|
||||
android:theme="@style/ZendeskTheme" />
|
||||
|
||||
<activity
|
||||
android:name="com.tangem.tap.features.sprinklr.ui.SprinklrActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name="com.tangem.feature.learn2earn.presentation.webView.Learn2earnWebViewActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit b84b1e1ab11b0eb0f6ca6e28f551f9e2ca6809fe
|
||||
Subproject commit 2b49c08b924c03322f35542a7482e4e9fb1928d3
|
||||
|
|
@ -399,7 +399,7 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
val feedbackManager = FeedbackManager(
|
||||
infoHolder = additionalFeedbackInfo,
|
||||
logCollector = tangemLogCollector,
|
||||
chatManager = ChatManager(preferencesStorage, foregroundActivityObserver, store),
|
||||
chatManager = ChatManager(preferencesStorage, foregroundActivityObserver),
|
||||
)
|
||||
store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,37 +2,31 @@ package com.tangem.tap.common.chat
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.datasource.config.models.ChatConfig
|
||||
import com.tangem.datasource.config.models.SprinklrConfig
|
||||
import com.tangem.datasource.config.models.ZendeskConfig
|
||||
import com.tangem.data.source.preferences.PreferencesDataSource
|
||||
import com.tangem.tap.ForegroundActivityObserver
|
||||
import com.tangem.tap.common.chat.opener.ChatOpener
|
||||
import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener
|
||||
import com.tangem.tap.common.chat.opener.implementation.ZendeskChatOpener
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Store
|
||||
import java.io.File
|
||||
|
||||
class ChatManager(
|
||||
private val preferencesStorage: PreferencesDataSource,
|
||||
private val foregroundActivityObserver: ForegroundActivityObserver,
|
||||
private val store: Store<AppState>,
|
||||
) {
|
||||
private val openers = mutableMapOf<ChatConfig, ChatOpener>()
|
||||
|
||||
fun open(config: ChatConfig, createLogsFile: (Context) -> File?, createFeedbackFile: (Context) -> File?) {
|
||||
val opener = openers.getOrPut(config) {
|
||||
when (config) {
|
||||
is SprinklrConfig -> SprinklrChatOpener(getSprinklrUserId(), config, store, foregroundActivityObserver)
|
||||
is SprinklrConfig -> SprinklrChatOpener(config, foregroundActivityObserver)
|
||||
is ZendeskConfig -> ZendeskChatOpener(getZendeskUserId(), config, foregroundActivityObserver)
|
||||
}
|
||||
}
|
||||
|
||||
opener.open(
|
||||
createFeedbackFile = createFeedbackFile,
|
||||
createLogsFile = createLogsFile,
|
||||
)
|
||||
opener.open(createFeedbackFile, createLogsFile)
|
||||
}
|
||||
|
||||
private fun getZendeskUserId(): String {
|
||||
|
|
@ -40,18 +34,6 @@ class ChatManager(
|
|||
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()
|
||||
return "${preferencesStorage.zendeskFirstLaunchTime}${Build.MODEL}".hashCode().toString()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,57 @@
|
|||
package com.tangem.tap.common.chat.opener.implementation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import com.spr.messengerclient.config.SPRMessenger
|
||||
import com.spr.messengerclient.config.bean.SPRMessengerConfig
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.datasource.config.models.SprinklrConfig
|
||||
import com.tangem.tap.ForegroundActivityObserver
|
||||
import com.tangem.tap.common.chat.opener.ChatOpener
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.sprinklr.redux.SprinklrAction
|
||||
import com.tangem.tap.features.sprinklr.ui.SprinklrActivity
|
||||
import com.tangem.tap.withForegroundActivity
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
internal class SprinklrChatOpener(
|
||||
private val userId: String,
|
||||
private val config: SprinklrConfig,
|
||||
private val store: Store<AppState>,
|
||||
private val foregroundActivityObserver: ForegroundActivityObserver,
|
||||
) : ChatOpener {
|
||||
|
||||
override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) {
|
||||
store.dispatch(SprinklrAction.Init(userId, config))
|
||||
foregroundActivityObserver.withForegroundActivity { activity ->
|
||||
val intent = Intent(activity, SprinklrActivity::class.java)
|
||||
activity.startActivity(intent)
|
||||
val messenger = SPRMessenger.shared()
|
||||
|
||||
if (messenger.config == null) {
|
||||
initSprConfig(messenger)
|
||||
}
|
||||
|
||||
messenger.startApplication()
|
||||
}
|
||||
|
||||
private fun initSprConfig(messenger: SPRMessenger) {
|
||||
val application = foregroundActivityObserver.foregroundActivity?.application.guard {
|
||||
Timber.e("The SPR chat cannot be opened because there are no activities in foreground")
|
||||
return
|
||||
}
|
||||
|
||||
messenger.takeOff(application, createSprConfig(application, config))
|
||||
}
|
||||
|
||||
@SuppressLint("HardwareIds")
|
||||
private fun createSprConfig(application: Application, config: SprinklrConfig): SPRMessengerConfig {
|
||||
return SPRMessengerConfig().apply {
|
||||
appId = config.appId
|
||||
appKey = CHAT_APP_KEY
|
||||
deviceId = Settings.Secure.getString(application.contentResolver, Settings.Secure.ANDROID_ID)
|
||||
environment = config.environment
|
||||
skin = CHAT_SKIN
|
||||
locale = Locale.getDefault().language
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CHAT_APP_KEY = "com.sprinklr.messenger.release"
|
||||
const val CHAT_SKIN = "MODERN"
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@ import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer
|
|||
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
|
||||
import com.tangem.tap.features.shop.redux.ShopReducer
|
||||
import com.tangem.tap.features.signin.redux.SignInReducer
|
||||
import com.tangem.tap.features.sprinklr.redux.SprinklrReducer
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensReducer
|
||||
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
|
||||
|
|
@ -45,7 +44,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
|
|||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
saveWalletState = SaveWalletReducer.reduce(action, state),
|
||||
walletSelectorState = WalletSelectorReducer.reduce(action, state),
|
||||
sprinklrState = SprinklrReducer.reduce(action, state),
|
||||
signInState = SignInReducer.reduce(action, state),
|
||||
daggerGraphState = DaggerGraphReducer.reduce(action, state),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ import com.tangem.tap.features.shop.redux.ShopMiddleware
|
|||
import com.tangem.tap.features.shop.redux.ShopState
|
||||
import com.tangem.tap.features.signin.redux.SignInMiddleware
|
||||
import com.tangem.tap.features.signin.redux.SignInState
|
||||
import com.tangem.tap.features.sprinklr.redux.SprinklrMiddleware
|
||||
import com.tangem.tap.features.sprinklr.redux.SprinklrState
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
|
||||
import com.tangem.tap.features.tokens.legacy.redux.TokensState
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
|
@ -67,7 +65,6 @@ data class AppState(
|
|||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
val saveWalletState: SaveWalletState = SaveWalletState(),
|
||||
val walletSelectorState: WalletSelectorState = WalletSelectorState(),
|
||||
val sprinklrState: SprinklrState = SprinklrState(),
|
||||
val signInState: SignInState = SignInState(),
|
||||
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
|
||||
) : StateType {
|
||||
|
|
@ -109,7 +106,6 @@ data class AppState(
|
|||
WalletSelectorMiddleware().middleware,
|
||||
LockUserWalletsTimerMiddleware().middleware,
|
||||
AccessCodeRequestPolicyMiddleware().middleware,
|
||||
SprinklrMiddleware().middleware,
|
||||
SignInMiddleware.middleware,
|
||||
DaggerGraphMiddleware.daggerGraphMiddleware,
|
||||
LegacyMiddleware.legacyMiddleware,
|
||||
|
|
|
|||
|
|
@ -110,10 +110,10 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
}
|
||||
|
||||
// if config not set -> try to get it based on a scanResponse.productType
|
||||
val unsafeChatConfig = action.chatConfig ?: config.zendesk
|
||||
val unsafeChatConfig = action.chatConfig ?: config.sprinklr
|
||||
|
||||
val chatConfig = unsafeChatConfig.guard {
|
||||
store.dispatchDebugErrorNotification("ZendeskConfig not initialized")
|
||||
store.dispatchDebugErrorNotification("The chat config is not initialized")
|
||||
return
|
||||
}
|
||||
feedbackManager.openChat(chatConfig, action.feedbackData)
|
||||
|
|
|
|||
|
|
@ -209,9 +209,7 @@ private class ScanWalletProcessor(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||
) {
|
||||
val isWallet2 = card.settings.isKeysImportAllowed || card.firmwareVersion >= FirmwareVersion.KeysImportAvailable
|
||||
|
||||
val productType = if (isWallet2) ProductType.Wallet2 else ProductType.Wallet
|
||||
val productType = getWalletProductType(card)
|
||||
val config = CardConfig.createConfig(card)
|
||||
scope.launch {
|
||||
val scanResponse = ScanResponse(
|
||||
|
|
@ -239,6 +237,16 @@ private class ScanWalletProcessor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getWalletProductType(card: CardDTO): ProductType {
|
||||
return if (card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available &&
|
||||
card.settings.isKeysImportAllowed
|
||||
) {
|
||||
ProductType.Wallet2
|
||||
} else {
|
||||
ProductType.Wallet
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun collectDerivations(
|
||||
card: CardDTO,
|
||||
config: CardConfig,
|
||||
|
|
|
|||
|
|
@ -53,7 +53,9 @@ object OnboardingHelper {
|
|||
fun whereToNavigate(scanResponse: ScanResponse): AppScreen {
|
||||
return when (scanResponse.productType) {
|
||||
ProductType.Note -> AppScreen.OnboardingNote
|
||||
ProductType.Wallet, ProductType.Wallet2 -> if (scanResponse.card.settings.isBackupAllowed) {
|
||||
ProductType.Wallet,
|
||||
ProductType.Wallet2,
|
||||
-> if (scanResponse.card.settings.isBackupAllowed) {
|
||||
AppScreen.OnboardingWallet
|
||||
} else {
|
||||
AppScreen.OnboardingOther
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.features.sprinklr.redux
|
||||
|
||||
import com.tangem.datasource.config.models.SprinklrConfig
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed interface SprinklrAction : Action {
|
||||
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,42 +0,0 @@
|
|||
package com.tangem.tap.features.sprinklr.redux
|
||||
|
||||
import com.tangem.datasource.config.models.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
|
||||
|
||||
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.Init -> updateUrl(action.userId, action.config)
|
||||
is SprinklrAction.UpdateUrl,
|
||||
is SprinklrAction.UpdateSprinklrDomains,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
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.Init -> state
|
||||
is SprinklrAction.UpdateUrl -> state.copy(url = action.url)
|
||||
is SprinklrAction.UpdateSprinklrDomains -> state.copy(sprinklrDomains = action.domains)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.features.sprinklr.redux
|
||||
|
||||
data class SprinklrState(
|
||||
val url: String = "",
|
||||
val sprinklrDomains: List<String> = emptyList(),
|
||||
)
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
package com.tangem.tap.features.sprinklr.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeActivity
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.tap.common.analytics.events.Chat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class SprinklrActivity : ComposeActivity() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
private val viewModel by viewModels<SprinklrViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Analytics.send(Chat.ScreenOpened())
|
||||
viewModel.setNavigateBackCallback {
|
||||
this.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
val systemBarsColor = TangemTheme.colors.background.primary
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(systemBarsColor)
|
||||
}
|
||||
|
||||
BackHandler(onBack = state.onNavigateBack)
|
||||
SprinklrScreenContent(
|
||||
modifier = modifier
|
||||
.systemBarsPadding()
|
||||
.imePadding(),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
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
|
||||
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
|
||||
}
|
||||
},
|
||||
client = remember { SprinklrWebViewClient(state.onNewUrl) },
|
||||
)
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
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,66 +0,0 @@
|
|||
package com.tangem.tap.features.sprinklr.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.google.accompanist.web.WebContent
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
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,
|
||||
sprinklrDomains = state.sprinklrDomains,
|
||||
onNewUrl = { updateWebContentOrOpenExternalUrl(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
fun setNavigateBackCallback(callback: () -> Unit) {
|
||||
stateInternal.update { prevState ->
|
||||
prevState.copy(
|
||||
onNavigateBack = callback,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToStoreChanges() {
|
||||
store.subscribe(this) { appState ->
|
||||
appState.skip { old, new -> old.sprinklrState == new.sprinklrState }
|
||||
.select { it.sprinklrState }
|
||||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ class WalletMiddleware {
|
|||
}
|
||||
|
||||
if (walletState.isMultiwalletAllowed) {
|
||||
val amountToSend = sendableAmounts.find { it.currencySymbol == currency.currencySymbol }
|
||||
val amountToSend = findAmountToSend(currency = currency, amounts = sendableAmounts)
|
||||
if (amountToSend == null) {
|
||||
val error = TapError.UnsupportedState("WalletAction.Send: Amount to send is null")
|
||||
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
|
||||
|
|
@ -277,6 +277,18 @@ class WalletMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun findAmountToSend(currency: Currency, amounts: List<Amount>): Amount? {
|
||||
return amounts.find { amount ->
|
||||
val amountType = amount.type
|
||||
if (amountType is AmountType.Token && currency is Currency.Token) {
|
||||
val token = amountType.token
|
||||
token.symbol == currency.currencySymbol && token.contractAddress == currency.token.contractAddress
|
||||
} else {
|
||||
amount.currencySymbol == currency.currencySymbol
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeInitSendStateActionByAmount(
|
||||
amount: Amount,
|
||||
walletStore: WalletStoreModel,
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
amplitudeApiKey = configValues.amplitudeApiKey,
|
||||
shopify = configValues.shopifyShop,
|
||||
zendesk = configValues.zendesk,
|
||||
sprinklr = configValues.sprinklr,
|
||||
swapReferrerAccount = configValues.swapReferrerAccount,
|
||||
walletConnectProjectId = configValues.walletConnectProjectId,
|
||||
tangemComAuthorization = configValues.tangemComAuthorization,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ data class ZendeskConfig(
|
|||
data class SprinklrConfig(
|
||||
@Json(name = "appID")
|
||||
val appId: String,
|
||||
@Json(name = "baseURL")
|
||||
val baseUrl: String,
|
||||
@Json(name = "apiKey")
|
||||
val apiKey: String,
|
||||
@Json(name = "environment")
|
||||
val environment: String,
|
||||
) : ChatConfig
|
||||
|
|
@ -16,6 +16,7 @@ data class Config(
|
|||
val isCreatingTwinCardsAllowed: Boolean = false,
|
||||
val shopify: ShopifyShop? = null,
|
||||
val zendesk: ZendeskConfig? = null,
|
||||
val sprinklr: SprinklrConfig? = null,
|
||||
val swapReferrerAccount: SwapReferrerAccount? = null,
|
||||
val walletConnectProjectId: String = "",
|
||||
val tangemComAuthorization: String? = null,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class ConfigValueModel(
|
|||
val appsFlyer: AppsFlyer,
|
||||
val shopifyShop: ShopifyShop?,
|
||||
val zendesk: ZendeskConfig?,
|
||||
val sprinklr: SprinklrConfig?,
|
||||
val tronGridApiKey: String,
|
||||
val amplitudeApiKey: String,
|
||||
val swapReferrerAccount: SwapReferrerAccount?,
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ internal class DefaultCardSdkConfigRepository(
|
|||
ProductType.Twins -> CardIdDisplayFormat.LastLuhn(numbers = 4)
|
||||
ProductType.Note,
|
||||
ProductType.Wallet,
|
||||
ProductType.Wallet2,
|
||||
ProductType.Start2Coin,
|
||||
ProductType.Wallet2,
|
||||
-> CardIdDisplayFormat.Full
|
||||
|
|
|
|||
|
|
@ -188,7 +188,8 @@ fun Blockchain.toCoinId(): String {
|
|||
Blockchain.Telos, Blockchain.TelosTestnet -> "telos"
|
||||
Blockchain.AlephZero, Blockchain.AlephZeroTestnet -> "aleph-zero"
|
||||
Blockchain.OctaSpace, Blockchain.OctaSpaceTestnet -> "octaspace"
|
||||
Blockchain.Chia, Blockchain.ChiaTestnet -> "chia"
|
||||
Blockchain.Chia -> "chia"
|
||||
Blockchain.ChiaTestnet -> "chia/test"
|
||||
Blockchain.Near -> "near"
|
||||
Blockchain.NearTestnet -> "near/test"
|
||||
Blockchain.Unknown -> "unknown"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ class UserWalletBuilder(
|
|||
ProductType.Note -> "Note"
|
||||
ProductType.Twins -> "Twin"
|
||||
ProductType.Start2Coin -> "Start2Coin"
|
||||
ProductType.Wallet -> when {
|
||||
ProductType.Wallet,
|
||||
ProductType.Wallet2,
|
||||
-> when {
|
||||
card.isBackupNotAllowed -> "Tangem card"
|
||||
cardTypesResolver.isStart2Coin() -> "Start2Coin"
|
||||
else -> "Wallet"
|
||||
|
|
|
|||
|
|
@ -79,10 +79,11 @@ walletConnectCore = "1.18.0"
|
|||
walletConnectWeb3 = "1.11.0"
|
||||
prettyLogger = "2.2.0"
|
||||
okHttp-prettyLogging = "3.1.0"
|
||||
spr-client = "3.6.2"
|
||||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "release-app_5.0-362"
|
||||
tangemBlockchainSdk = "release-app_5.0-363"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "release-app_5.0-303"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds
|
||||
|
|
@ -230,4 +231,5 @@ reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", versio
|
|||
walletConnectCore = { module = "com.walletconnect:android-core", version.ref = "walletConnectCore" }
|
||||
walletConnectWeb3 = { module = "com.walletconnect:web3wallet", version.ref = "walletConnectWeb3" }
|
||||
prettyLogger = { module = "com.orhanobut:logger", version.ref = "prettyLogger" }
|
||||
sprClient = { module = "com.spr:messengerclient", version.ref = "spr-client" }
|
||||
# endregion Other
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ private fun AndroidBuildType.configureBuildVariant(extension: AppExtension, buil
|
|||
BuildType.External,
|
||||
-> {
|
||||
initWith(extension.buildTypes.getByName(BuildType.Release.id))
|
||||
matchingFallbacks.add(BuildType.Release.id)
|
||||
signingConfig = extension.signingConfigs.getByName(BuildType.Debug.id)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ dependencyResolutionManagement {
|
|||
}
|
||||
maven("https://jitpack.io")
|
||||
maven("https://zendesk.jfrog.io/zendesk/repo")
|
||||
maven("https://clients-nexus.sprinklr.com/")
|
||||
}
|
||||
|
||||
versionCatalogs {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue