diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0740725de0..5979dc8c9a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,13 +13,7 @@
-
-
-
diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt
index 8e0a528fca..f9888910fa 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -242,12 +242,17 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
appRouterConfig.routerScope = lifecycleScope
appRouterConfig.componentRouter = routingComponent.router
+ appRouterConfig.snackbarHandler = this
routingComponent.stack.observe(lifecycle.asEssentyLifecycle()) { childStack ->
- appRouterConfig.stack = childStack.backStack
+ val stack = childStack.backStack
.plus(childStack.active)
.map { it.configuration }
+ if (stack == appRouterConfig.stack) return@observe
+
+ appRouterConfig.stack = stack
+
when (val child = childStack.active.instance) {
is RoutingComponent.Child.Initial -> Unit
is RoutingComponent.Child.LegacyFragment -> {
diff --git a/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt b/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt
index 7a7e2c72b0..1b3f599f3f 100644
--- a/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt
+++ b/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt
@@ -3,19 +3,19 @@ package com.tangem.tap.common.log
import android.util.Log
import com.orhanobut.logger.AndroidLogAdapter
import com.orhanobut.logger.Logger
-import com.tangem.domain.settings.repositories.SettingsRepository
+import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.wallet.BuildConfig
import timber.log.Timber
/**
* Tangem app logger
*
- * @property settingsRepository repository for saving logs
+ * @property appLogsStore app logs store
*
[REDACTED_AUTHOR]
*/
class TangemAppLoggerInitializer(
- private val settingsRepository: SettingsRepository,
+ private val appLogsStore: AppLogsStore,
) {
/** Initialize */
@@ -35,7 +35,7 @@ class TangemAppLoggerInitializer(
}
if (PERMITTED_PRIORITY.contains(priority)) {
- settingsRepository.saveLogMessage(message)
+ appLogsStore.saveLogMessage(message)
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/log/TangemCardSDKLogger.kt b/app/src/main/java/com/tangem/tap/common/log/TangemCardSDKLogger.kt
index fad6b562f7..ef740906db 100644
--- a/app/src/main/java/com/tangem/tap/common/log/TangemCardSDKLogger.kt
+++ b/app/src/main/java/com/tangem/tap/common/log/TangemCardSDKLogger.kt
@@ -3,26 +3,26 @@ package com.tangem.tap.common.log
import com.tangem.Log
import com.tangem.LogFormat
import com.tangem.TangemSdkLogger
-import com.tangem.domain.settings.repositories.SettingsRepository
+import com.tangem.datasource.local.logs.AppLogsStore
/**
* CardSDK logger implementation
*
- * @property levels logging levels
- * @property messageFormatter message formatter
- * @property settingsRepository settings repository
+ * @property levels logging levels
+ * @property messageFormatter message formatter
+ * @property appLogsStore app logs store
*
[REDACTED_AUTHOR]
*/
internal class TangemCardSDKLogger(
private val levels: List,
private val messageFormatter: LogFormat,
- private val settingsRepository: SettingsRepository,
+ private val appLogsStore: AppLogsStore,
) : TangemSdkLogger {
override fun log(message: () -> String, level: Log.Level) {
if (!levels.contains(level)) return
- settingsRepository.saveLogMessage(message = messageFormatter.format(message, level))
+ appLogsStore.saveLogMessage(message = messageFormatter.format(message, level))
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt
index 21cca072f7..a544190398 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/LogMiddleware.kt
@@ -9,7 +9,7 @@ import timber.log.Timber
val logMiddleware: Middleware = { _, _ ->
{ nextDispatch ->
{ action ->
- Timber.i("Dispatch action: $action")
+ Timber.i("Dispatch action: ${action::class.java.simpleName}")
nextDispatch(action)
}
}
diff --git a/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt b/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt
index c275de1626..a85c3e4667 100644
--- a/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt
+++ b/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt
@@ -1,20 +1,20 @@
package com.tangem.tap.data
import com.tangem.blockchain.common.logging.BlockchainSDKLogger
-import com.tangem.domain.settings.repositories.SettingsRepository
+import com.tangem.datasource.local.logs.AppLogsStore
/**
* BlockchainSDK logger implementation
*
- * @property settingsRepository settings repository
+ * @property appLogsStore app logs store
*
[REDACTED_AUTHOR]
*/
internal class TangemBlockchainSDKLogger(
- private val settingsRepository: SettingsRepository,
+ private val appLogsStore: AppLogsStore,
) : BlockchainSDKLogger {
override fun log(level: BlockchainSDKLogger.Level, message: String) {
- settingsRepository.saveLogMessage(message)
+ appLogsStore.saveLogMessage(message)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/data/TangemLoggingModule.kt b/app/src/main/java/com/tangem/tap/di/data/TangemLoggingModule.kt
index e361ca70a6..400f58d8b2 100644
--- a/app/src/main/java/com/tangem/tap/di/data/TangemLoggingModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/data/TangemLoggingModule.kt
@@ -4,7 +4,7 @@ import com.tangem.Log
import com.tangem.LogFormat
import com.tangem.TangemSdkLogger
import com.tangem.blockchain.common.logging.BlockchainSDKLogger
-import com.tangem.domain.settings.repositories.SettingsRepository
+import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.tap.common.log.TangemAppLoggerInitializer
import com.tangem.tap.common.log.TangemCardSDKLogger
import com.tangem.tap.data.TangemBlockchainSDKLogger
@@ -20,13 +20,13 @@ internal object TangemLoggingModule {
@Provides
@Singleton
- fun provideAppLoggerInitializer(settingsRepository: SettingsRepository): TangemAppLoggerInitializer {
- return TangemAppLoggerInitializer(settingsRepository)
+ fun provideAppLoggerInitializer(appLogsStore: AppLogsStore): TangemAppLoggerInitializer {
+ return TangemAppLoggerInitializer(appLogsStore)
}
@Provides
@Singleton
- fun provideCardSDKLogger(settingsRepository: SettingsRepository): TangemSdkLogger {
+ fun provideCardSDKLogger(appLogsStore: AppLogsStore): TangemSdkLogger {
val logLevels = listOf(
Log.Level.ApduCommand,
Log.Level.Apdu,
@@ -44,13 +44,13 @@ internal object TangemLoggingModule {
return TangemCardSDKLogger(
levels = logLevels,
messageFormatter = LogFormat.StairsFormatter(),
- settingsRepository = settingsRepository,
+ appLogsStore = appLogsStore,
)
}
@Provides
@Singleton
- fun provideBlockchainSDKLogger(settingsRepository: SettingsRepository): BlockchainSDKLogger {
- return TangemBlockchainSDKLogger(settingsRepository)
+ fun provideBlockchainSDKLogger(appLogsStore: AppLogsStore): BlockchainSDKLogger {
+ return TangemBlockchainSDKLogger(appLogsStore)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
index 3e8529f150..3914707cc3 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
@@ -50,9 +50,7 @@ class WalletConnectSdkHelper {
@Suppress("MagicNumber")
suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData {
val transaction = data.transaction
- val blockchain = requireNotNull(Blockchain.fromNetworkId(data.networkId)) {
- "Blockchain not found"
- }
+ val blockchain = requireNotNull(Blockchain.fromNetworkId(data.networkId)) { "Blockchain not found" }
val walletManager = requireNotNull(getWalletManager(blockchain, data.rawDerivationPath)) {
"WalletManager not found"
}
@@ -73,27 +71,31 @@ class WalletConnectSdkHelper {
"Transaction amount is null"
}
+ // TODO move fee calculation to SDK getFee() [REDACTED_JIRA]
val gasLimit = getGasLimitFromTx(value, walletManager, transaction)
+ val gasPrice = getGasPrice(walletManager, transaction)
- val gasPrice = transaction.gasPrice?.hexToBigDecimal()
- ?: when (val result = (walletManager as? EthereumGasLoader)?.getGasPrice()) {
- is Result.Success -> result.data.toBigDecimal()
- is Result.Failure -> {
- (result.error as? Throwable)?.let { Timber.e(it, "getGasPrice failed") }
-
- error("Unable to get gas price: ${result.error}")
- }
- null -> error("Gas price is null")
- }
-
- val fee = (gasLimit * gasPrice).movePointLeft(decimals)
- val total = value + fee
+ val feeDecimal = (gasLimit * gasPrice).movePointLeft(decimals)
+ val total = value + feeDecimal
+ val feeAmount = Amount(feeDecimal, wallet.blockchain)
val destinationAddress = requireNotNull(transaction.to) { "Destination address is null" }
+ val fee = if (blockchain.isEvm()) {
+ // TODO [REDACTED_JIRA]
+ // workaround for Mantle, remove after [REDACTED_JIRA]
+ val patchedAmount = if (blockchain == Blockchain.Mantle) {
+ feeAmount.copy(value = feeAmount.value?.multiply(MANTLE_FEE_ESTIMATE_MULTIPLIER))
+ } else {
+ feeAmount
+ }
+ Fee.Ethereum(patchedAmount, gasLimit.toBigInteger(), gasPrice.toBigInteger())
+ } else {
+ Fee.Common(feeAmount)
+ }
val transactionData = TransactionData.Uncompiled(
amount = Amount(value, wallet.blockchain),
- fee = Fee.Common(Amount(fee, wallet.blockchain)),
+ fee = fee,
sourceAddress = transaction.from,
destinationAddress = destinationAddress,
extras = EthereumTransactionExtras(
@@ -107,7 +109,7 @@ class WalletConnectSdkHelper {
dAppName = data.metaName,
dAppUrl = data.metaUrl,
amount = value.toFormattedString(decimals),
- gasAmount = fee.toFormattedString(decimals),
+ feeAmount = feeDecimal.toFormattedString(decimals),
totalAmount = total.toFormattedString(decimals),
balance = balance.toFormattedString(decimals),
isEnoughFundsToSend = balance - total >= BigDecimal.ZERO,
@@ -148,6 +150,22 @@ class WalletConnectSdkHelper {
}
}
+ private suspend fun getGasPrice(walletManager: WalletManager, transaction: WcEthereumTransaction): BigDecimal {
+ val txGasPrice = transaction.gasPrice?.hexToBigDecimal()
+ if (txGasPrice != null) {
+ return txGasPrice
+ }
+ return when (val result = (walletManager as? EthereumGasLoader)?.getGasPrice()) {
+ is Result.Success -> result.data.toBigDecimal()
+ is Result.Failure -> {
+ (result.error as? Throwable)?.let { Timber.e(it, "getGasPrice failed") }
+
+ error("Unable to get gas price: ${result.error}")
+ }
+ null -> error("Gas price is null")
+ }
+ }
+
private suspend fun getGasLimitFromTx(
value: BigDecimal,
walletManager: WalletManager,
@@ -155,14 +173,14 @@ class WalletConnectSdkHelper {
): BigDecimal {
return transaction.gas?.hexToBigDecimal()
?: transaction.gasLimit?.hexToBigDecimal()
- ?: getGaLimitFromBlockchain(
+ ?: getGasLimitFromBlockchain(
value = value,
walletManager = walletManager,
transaction = transaction,
)
}
- private suspend fun getGaLimitFromBlockchain(
+ private suspend fun getGasLimitFromBlockchain(
value: BigDecimal,
walletManager: WalletManager,
transaction: WcEthereumTransaction,
@@ -418,5 +436,7 @@ class WalletConnectSdkHelper {
const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n"
const val HEX_PREFIX = "0x"
const val DEFAULT_MAX_GASLIMIT = 350000
+ // TODO remove after [REDACTED_JIRA]
+ private val MANTLE_FEE_ESTIMATE_MULTIPLIER = BigDecimal("1.6")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt
index 3ec2d8bfda..4fd49641fb 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt
@@ -18,7 +18,7 @@ object TransactionDialog {
data.dAppName,
data.dAppUrl,
data.amount,
- data.gasAmount,
+ data.feeAmount,
data.totalAmount,
data.balance,
)
@@ -51,7 +51,7 @@ data class TransactionRequestDialogData(
val dAppName: String,
val dAppUrl: String,
val amount: String,
- val gasAmount: String,
+ val feeAmount: String,
val totalAmount: String,
val balance: String,
val isEnoughFundsToSend: Boolean,
diff --git a/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt b/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt
index 592656008e..d744032000 100644
--- a/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt
+++ b/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt
@@ -1,10 +1,12 @@
package com.tangem.tap.routing
+import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.navigation.Router
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import com.tangem.wallet.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
@@ -31,37 +33,59 @@ internal class ProxyAppRouter(
}
override fun push(route: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) {
- routerScope.launch(dispatchers.mainImmediate) {
- Timber.i("Push route: $route")
+ safeNavigate(onComplete, message = "Push $route") {
innerRouter.push(route, onComplete)
}
}
override fun replaceAll(vararg routes: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) {
- routerScope.launch(dispatchers.mainImmediate) {
- Timber.i("Replace all routes with $routes")
+ safeNavigate(onComplete, message = "Replace all routes with $routes") {
innerRouter.replaceAll(*routes, onComplete = onComplete)
}
}
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
- routerScope.launch(dispatchers.mainImmediate) {
- Timber.i("Pop route")
+ safeNavigate(onComplete, message = "Pop route") {
innerRouter.pop(onComplete)
}
}
override fun popTo(route: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) {
- routerScope.launch(dispatchers.mainImmediate) {
- Timber.i("Pop to route: $route")
+ safeNavigate(onComplete, message = "Pop to $route") {
innerRouter.popTo(route, onComplete)
}
}
override fun popTo(routeClass: KClass, onComplete: (isSuccess: Boolean) -> Unit) {
- routerScope.launch(dispatchers.mainImmediate) {
- Timber.i("Pop to route class: $routeClass")
+ safeNavigate(onComplete, message = "Pop to $routeClass") {
innerRouter.popTo(routeClass, onComplete)
}
}
+
+ private fun safeNavigate(onComplete: (isSuccess: Boolean) -> Unit, message: String, block: () -> Unit) {
+ routerScope.launch(dispatchers.mainImmediate) {
+ Timber.i(message)
+
+ try {
+ block()
+ } catch (e: Throwable) {
+ onComplete(false)
+ }
+ }
+ }
+
+ override fun defaultCompletionHandler(isSuccess: Boolean, errorMessage: String) {
+ if (!isSuccess) {
+ FirebaseCrashlytics.getInstance().recordException(RuntimeException(errorMessage))
+ Timber.w(errorMessage)
+
+ with(receiver = config.snackbarHandler ?: return) {
+ showSnackbar(
+ text = R.string.common_unknown_error,
+ buttonTitle = R.string.common_ok,
+ action = { dismissSnackbar() },
+ )
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt b/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt
index 470c85f674..5c2c9b3843 100644
--- a/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt
+++ b/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.routing.configurator
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.navigation.Router
+import com.tangem.tap.common.SnackbarHandler
import kotlinx.coroutines.CoroutineScope
internal interface AppRouterConfig {
@@ -9,4 +10,7 @@ internal interface AppRouterConfig {
var routerScope: CoroutineScope?
var componentRouter: Router?
var stack: List?
+
+ // TODO: Replace with UI message handler: [REDACTED_JIRA]
+ var snackbarHandler: SnackbarHandler?
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt b/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt
index ae724ac04e..d1b1e93f7c 100644
--- a/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt
+++ b/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.routing.configurator
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.navigation.Router
+import com.tangem.tap.common.SnackbarHandler
import kotlinx.coroutines.CoroutineScope
internal class MutableAppRouterConfig : AppRouterConfig {
@@ -9,4 +10,5 @@ internal class MutableAppRouterConfig : AppRouterConfig {
override var routerScope: CoroutineScope? = null
override var componentRouter: Router? = null
override var stack: List? = null
+ override var snackbarHandler: SnackbarHandler? = null
}
\ No newline at end of file
diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRouter.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRouter.kt
index cb0f21a793..9f8d5f4111 100644
--- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRouter.kt
+++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRouter.kt
@@ -23,7 +23,12 @@ interface AppRouter {
* @param route The route to push.
* @param onComplete The callback to be invoked when the operation is complete.
*/
- fun push(route: AppRoute, onComplete: (isSuccess: Boolean) -> Unit = {})
+ fun push(
+ route: AppRoute,
+ onComplete: (isSuccess: Boolean) -> Unit = { isSuccess ->
+ defaultCompletionHandler(isSuccess, errorMessage = "Unable to push $route")
+ },
+ )
/**
* Replaces ***all*** routes in the navigation stack with the specified [routes].
@@ -31,14 +36,23 @@ interface AppRouter {
* @param routes The routes to replace the current stack with.
* @param onComplete The callback to be invoked when the operation is complete.
*/
- fun replaceAll(vararg routes: AppRoute, onComplete: (isSuccess: Boolean) -> Unit = {})
+ fun replaceAll(
+ vararg routes: AppRoute,
+ onComplete: (isSuccess: Boolean) -> Unit = { isSuccess ->
+ defaultCompletionHandler(isSuccess, errorMessage = "Unable to replace routes with $routes")
+ },
+ )
/**
* Pops the top route from the navigation stack.
*
* @param onComplete The callback to be invoked when the operation is complete.
*/
- fun pop(onComplete: (isSuccess: Boolean) -> Unit = {})
+ fun pop(
+ onComplete: (isSuccess: Boolean) -> Unit = { isSuccess ->
+ defaultCompletionHandler(isSuccess, errorMessage = "Unable to pop route")
+ },
+ )
/**
* Pops routes from the navigation stack until the specified [route] is found.
@@ -46,7 +60,12 @@ interface AppRouter {
* @param route The route to pop to.
* @param onComplete The callback to be invoked when the operation is complete.
*/
- fun popTo(route: AppRoute, onComplete: (isSuccess: Boolean) -> Unit = {})
+ fun popTo(
+ route: AppRoute,
+ onComplete: (isSuccess: Boolean) -> Unit = { isSuccess ->
+ defaultCompletionHandler(isSuccess, errorMessage = "Unable to pop to $route")
+ },
+ )
/**
* Pops routes from the navigation stack until the ***first*** specified [routeClass] is found.
@@ -54,5 +73,12 @@ interface AppRouter {
* @param routeClass The route class to pop to.
* @param onComplete The callback to be invoked when the operation is complete.
*/
- fun popTo(routeClass: KClass, onComplete: (isSuccess: Boolean) -> Unit = {})
+ fun popTo(
+ routeClass: KClass,
+ onComplete: (isSuccess: Boolean) -> Unit = { isSuccess ->
+ defaultCompletionHandler(isSuccess, errorMessage = "Unable to pop to $routeClass")
+ },
+ )
+
+ fun defaultCompletionHandler(isSuccess: Boolean, errorMessage: String)
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/crypto/Sha256SignatureVerifier.kt b/core/datasource/src/main/java/com/tangem/datasource/crypto/Sha256SignatureVerifier.kt
index dc2c197c50..5353f15353 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/crypto/Sha256SignatureVerifier.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/crypto/Sha256SignatureVerifier.kt
@@ -2,16 +2,30 @@ package com.tangem.datasource.crypto
import com.tangem.common.extensions.hexToBytes
import com.tangem.crypto.CryptoUtils
+import com.tangem.datasource.api.common.config.ApiConfig
+import com.tangem.datasource.api.common.config.ApiEnvironment
+import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.config.ConfigManager
-internal class Sha256SignatureVerifier(private val configManager: ConfigManager) : DataSignatureVerifier {
+internal class Sha256SignatureVerifier(
+ private val configManager: ConfigManager,
+ private val apiConfigsManager: ApiConfigsManager,
+) : DataSignatureVerifier {
override fun verifySignature(signature: String, data: String): Boolean {
- val pubKey = configManager.config.express?.signVerifierPublicKey ?: return false
+ val pubKey = getPubKey() ?: return false
return CryptoUtils.verify(
publicKey = pubKey.hexToBytes().takeLast(n = 65).toByteArray(),
message = data.toByteArray(),
signature = signature.hexToBytes(),
)
}
+
+ private fun getPubKey(): String? {
+ val expressConfig = apiConfigsManager.getEnvironmentConfig(ApiConfig.ID.Express)
+ return when (expressConfig.environment) {
+ ApiEnvironment.PROD -> configManager.config.express?.signVerifierPublicKey
+ else -> configManager.config.devExpress?.signVerifierPublicKey
+ }
+ }
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/SecurityModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/SecurityModule.kt
index b5c2f07c49..b4550b25b2 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/di/SecurityModule.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/di/SecurityModule.kt
@@ -1,5 +1,6 @@
package com.tangem.datasource.di
+import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.crypto.Sha256SignatureVerifier
@@ -15,7 +16,10 @@ internal object SecurityModule {
@Provides
@Singleton
- fun provideDataSignatureVerifier(configManager: ConfigManager): DataSignatureVerifier {
- return Sha256SignatureVerifier(configManager)
+ fun provideDataSignatureVerifier(
+ configManager: ConfigManager,
+ apiConfigsManager: ApiConfigsManager,
+ ): DataSignatureVerifier {
+ return Sha256SignatureVerifier(configManager, apiConfigsManager)
}
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt
index 38a05e8d79..d3fa302f26 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt
@@ -1,69 +1,109 @@
package com.tangem.datasource.local.logs
-import androidx.datastore.preferences.core.MutablePreferences
-import com.tangem.datasource.local.preferences.AppPreferencesStore
-import com.tangem.datasource.local.preferences.PreferencesKeys
+import android.content.Context
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.joda.time.DateTime
+import org.joda.time.format.DateTimeFormatterBuilder
+import timber.log.Timber
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileWriter
import javax.inject.Inject
+import javax.inject.Singleton
/**
* Store for saving app logs
*
- * @property appPreferencesStore app preferences store
- * @param dispatchers coroutine dispatcher provider
+ * @property applicationContext application context
+ * @param dispatchers coroutine dispatcher provider
*
[REDACTED_AUTHOR]
*/
+@Singleton
class AppLogsStore @Inject constructor(
- private val appPreferencesStore: AppPreferencesStore,
+ @ApplicationContext private val applicationContext: Context,
dispatchers: CoroutineDispatcherProvider,
) {
private val scope = CoroutineScope(dispatchers.io)
private val mutex = Mutex()
+ private val file = File(applicationContext.filesDir, LOG_FILE_NAME)
+
+ private val formatter = DateTimeFormatterBuilder()
+ .appendDayOfMonth(2)
+ .appendLiteral('.')
+ .appendMonthOfYear(2)
+ .appendLiteral(' ')
+ .appendHourOfDay(1)
+ .appendLiteral(':')
+ .appendMinuteOfHour(2)
+ .appendLiteral(':')
+ .appendSecondOfMinute(2)
+ .appendLiteral('.')
+ .appendMillisOfSecond(3)
+ .toFormatter()
+
+ /** Get log file */
+ fun getFile(): File? = if (file.exists()) file else null
+
/** Save log [message] */
fun saveLogMessage(message: String) {
- val newLogs = DateTime.now().millis.toString() to message
+ launchWithLock {
+ createFileIfNotExist()
- appPreferencesStore.editDataWithLock { preferences ->
- val savedLogs = preferences.getObjectMap(PreferencesKeys.APP_LOGS_KEY)
+ writeMessage(message)
+ }
+ }
- preferences.setObjectMap(key = PreferencesKeys.APP_LOGS_KEY, value = savedLogs + newLogs)
+ /** Save log that consists from [messages] */
+ fun saveLogMessage(vararg messages: String) {
+ launchWithLock {
+ createFileIfNotExist()
+
+ writeMessage(*messages)
}
}
/** Delete deprecated logs if file size exceeds [maxSize] */
fun deleteDeprecatedLogs(maxSize: Int) {
- appPreferencesStore.editDataWithLock { preferences ->
- val savedLogs = preferences.getObjectMap(PreferencesKeys.APP_LOGS_KEY)
-
- var sum = 0
- preferences.setObjectMap(
- key = PreferencesKeys.APP_LOGS_KEY,
- value = savedLogs.entries
- .sortedBy(Map.Entry::key)
- .takeLastWhile {
- sum += it.value.length
- sum < maxSize
- }
- .associate { it.key to it.value },
- )
- }
- }
-
- private fun AppPreferencesStore.editDataWithLock(
- transform: suspend AppPreferencesStore.(MutablePreferences) -> Unit,
- ) {
- scope.launch {
- mutex.withLock {
- editData(transform)
+ launchWithLock {
+ if (file.exists() && file.length() > maxSize) {
+ file.delete()
}
}
}
+
+ private fun writeMessage(vararg messages: String) {
+ BufferedWriter(FileWriter(file, true)).use { writer ->
+ writer.append(formatter.print(DateTime.now()))
+ writer.append(": ")
+ messages.forEach(writer::append)
+ writer.newLine()
+ }
+ }
+
+ private fun createFileIfNotExist() {
+ if (!file.exists()) {
+ runCatching { file.createNewFile() }
+ .onFailure(Timber::e)
+ }
+ }
+
+ private fun launchWithLock(callback: () -> Unit) {
+ scope.launch {
+ mutex.withLock {
+ callback()
+ }
+ }
+ }
+
+ private companion object {
+ const val LOG_FILE_NAME = "logs.txt"
+ }
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt
index ee74b94ab5..b593688bd9 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt
@@ -54,8 +54,8 @@ internal class NetworkLogsSaveInterceptor(
val connectionProtocol = if (connection != null) " ${connection.protocol()}" else ""
appLogsStore.saveLogMessage(
- "--> ${request.method} ${request.url}$connectionProtocol\n" +
- createRequestEndMessage(request),
+ "--> ${request.method} ${request.url}$connectionProtocol\n",
+ createRequestEndMessage(request),
)
}
@@ -88,12 +88,6 @@ internal class NetworkLogsSaveInterceptor(
}
private fun logResponseMessage(response: Response, startNs: Long) {
- val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
-
- val responseMessage = if (response.message.isEmpty()) "" else ' ' + response.message
- val startMessage = "<-- ${response.code}$responseMessage ${response.request.url} " +
- "(${tookMs}ms)"
-
val responseHeaders = response.headers
val responseBody = response.body!!
val contentLength = responseBody.contentLength()
@@ -138,7 +132,17 @@ internal class NetworkLogsSaveInterceptor(
}
}
- appLogsStore.saveLogMessage(startMessage + "\n" + message)
+ val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
+
+ val spaceBeforeResponseMessage = if (response.message.isEmpty()) "" else ' ' + response.message
+
+ appLogsStore.saveLogMessage(
+ "<-- ${response.code}",
+ spaceBeforeResponseMessage,
+ response.message,
+ " ${response.request.url} (${tookMs}ms)\n",
+ message,
+ )
}
private fun bodyHasUnknownEncoding(headers: Headers): Boolean {
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt
index 86c43706a6..d2d4fb95e7 100644
--- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt
@@ -3,7 +3,6 @@ package com.tangem.core.decompose.navigation
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.pop
-import com.arkivanov.decompose.router.stack.popWhile
import com.arkivanov.decompose.router.stack.pushNew
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import kotlin.reflect.KClass
@@ -34,16 +33,27 @@ internal class DefaultRouter(
}
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
- navigation.popWhile(
+ popTo(
predicate = { it != route },
onComplete = onComplete,
)
}
override fun popTo(routeClass: KClass, onComplete: (isSuccess: Boolean) -> Unit) {
- navigation.popWhile(
+ popTo(
predicate = { it::class != routeClass },
onComplete = onComplete,
)
}
+
+ private fun popTo(predicate: (Route) -> Boolean, onComplete: (isSuccess: Boolean) -> Unit) {
+ navigation.navigate(
+ transformer = { stack ->
+ stack
+ .dropLastWhile(predicate)
+ .ifEmpty { stack }
+ },
+ onComplete = { newStack, oldStack -> onComplete(newStack.size < oldStack.size) },
+ )
+ }
}
\ No newline at end of file
diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
index f13343fc36..f0c510874b 100644
--- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
+++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
@@ -5,7 +5,7 @@
},
{
"name": "LOCAL_USER_LOGS_ENABLED",
- "version": "undefined"
+ "version": "5.14.0"
},
{
"name": "GENERATE_XPUB_ENABLED",
diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml
index d7bd63adac..81ebcec61e 100644
--- a/core/res/src/main/res/values-de/strings.xml
+++ b/core/res/src/main/res/values-de/strings.xml
@@ -222,7 +222,6 @@
Tangem bietet Token-Swaps über Drittanbieter gemäß den jeweiligen Bedingungen des jeweiligen Anbieters an.
Anbieter wählen
Es ist ein Fehler aufgetreten. Code: %s
- Huch! Der Tausch des ausgewählten Paares über den gewählten Anbieter ist vorübergehend nicht möglich. Bitte versuche es später erneut. (Code: %s)
Der gewählte Anbieter ist im Moment nicht verfügbar. Bitte versuche es später noch einmal. (Code: %s)
Swaps sind im Moment nicht verfügbar. Bitte versuche es später noch einmal. (Code: %s)
Geschätzter Betrag
diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml
index d39ab86d92..581c5d1af7 100644
--- a/core/res/src/main/res/values-fr/strings.xml
+++ b/core/res/src/main/res/values-fr/strings.xml
@@ -217,7 +217,6 @@
Tangem propose des échanges de jetons via des fournisseurs tiers selon les conditions de chaque fournisseur
Choisissez un fournisseur
Une erreur s\'est produite. Code : %s
- Oups! L\'échange de la paire sélectionnée via le fournisseur choisi est temporairement indisponible. Veuillez réessayer plus tard. (Code : %s)
Le fournisseur sélectionné n\'est pas disponible pour le moment. Veuillez réessayer plus tard. (Code : %s)
Les échanges ne sont pas disponibles pour le moment. Veuillez réessayer plus tard. (Code : %s)
Montant estimé
diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml
index 70892f297f..f27b090b8a 100644
--- a/core/res/src/main/res/values-ja/strings.xml
+++ b/core/res/src/main/res/values-ja/strings.xml
@@ -220,7 +220,6 @@
Tangemは、各プロバイダーの条件に従って、サードパーティプロバイダーを介してトークンスワップを提供します。
プロバイダーを選択
エラーが発生しました。コード: %s
- おっと!このプロバイダーで選択したペアを交換することは一時的にできません。後でもう一度お試しください。(Code:%s)
選択したプロバイダーは現在利用できません。しばらくしてからもう一度お試しください。(コード: %s )
現在、交換はご利用いただけません。しばらくしてからもう一度お試しください。(コード: %s )
推定金額
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 723c6c4ffb..3d8a825d38 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -225,7 +225,6 @@
Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами
Выберите провайдера
Произошла ошибка. Код: %s
- К сожалению, обмен указанной пары через выбранного провайдера на данный момент невозможен. Попробуйте совершить обмен позже. (Код: %s)
Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s)
В данный момент обмен невозможен. Попробуйте позже. (Код: %s)
Курс обмена
diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml
index 10a8cd574b..d0ff9d566d 100644
--- a/core/res/src/main/res/values-uk-rUA/strings.xml
+++ b/core/res/src/main/res/values-uk-rUA/strings.xml
@@ -225,7 +225,6 @@
Tangem пропонує обмін токенів через сторонніх провайдерів відповідно до умов кожного провайдера
Оберіть провайдера
Виникла помилка. Код: %s
- На жаль, обмін вказаної пари через обраного провайдера тимчасово неможливий. Спробуйте здійснити обмін пізніше. (Код: %s)
Наразі обраний провайдер недоступний для обміну. Спробуй пізніше. (Код: %s)
Наразі обмін неможливий. Спробуй пізніше. (Код: %s)
Курс обміну
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 86185e2839..329dcfb63e 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -226,7 +226,6 @@
Tangem offers token swaps via 3rd-party providers according to each provider\'s terms
Choose provider
An error occurred. Code: %s
- Oops! Swapping the selected pair through the chosen provider is temporarily unavailable. Please try again later. (Code: %s)
Selected provider is unavailable at the moment. Please try again later. (Code: %s)
Swaps are unavailable at the moment. Please try again later. (Code: %s)
Estimated amount
diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt
index 26278b5877..4b018346c2 100644
--- a/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt
+++ b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt
@@ -6,48 +6,43 @@ import android.os.Build
import com.tangem.blockchain.common.Blockchain
import com.tangem.data.feedback.converters.BlockchainInfoConverter
import com.tangem.data.feedback.converters.CardInfoConverter
-import com.tangem.datasource.local.preferences.AppPreferencesStore
-import com.tangem.datasource.local.preferences.PreferencesKeys
-import com.tangem.datasource.local.preferences.utils.getObjectMapSync
+import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
-import com.tangem.domain.feedback.models.*
+import com.tangem.domain.feedback.models.BlockchainErrorInfo
+import com.tangem.domain.feedback.models.BlockchainInfo
+import com.tangem.domain.feedback.models.PhoneInfo
+import com.tangem.domain.feedback.models.UserWalletsInfo
import com.tangem.domain.feedback.repository.FeedbackRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWalletId
-import com.tangem.utils.coroutines.CoroutineDispatcherProvider
-import com.tangem.utils.coroutines.runCatching
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import timber.log.Timber
import java.io.File
-import java.io.FileWriter
-import java.io.StringWriter
/**
* Implementation of [FeedbackRepository]
*
- * @property appPreferencesStore application preferences store
+ * @property appLogsStore app logs store
* @property userWalletsListManager user wallets list manager
* @property walletManagersStore wallet managers store
* @property context context for getting app version
- * @property dispatchers coroutine dispatchers provider
*
[REDACTED_AUTHOR]
*/
internal class DefaultFeedbackRepository(
- private val appPreferencesStore: AppPreferencesStore,
+ private val appLogsStore: AppLogsStore,
private val userWalletsListManager: UserWalletsListManager,
private val walletManagersStore: WalletManagersStore,
private val context: Context,
- private val dispatchers: CoroutineDispatcherProvider,
) : FeedbackRepository {
private val blockchainsErrors = MutableStateFlow