Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-18 17:13:28 +03:00
parent ff282a1d7d
commit 65a55cb5e3
23 changed files with 222 additions and 144 deletions

View file

@ -69,8 +69,8 @@ repositories {
dependencies { dependencies {
implementation fileTree(include: ['*.aar'], dir: 'libs') implementation fileTree(include: ['*.aar'], dir: 'libs')
implementation implementation(project(path: ':domain')) implementation implementation(project(path: ':domain'))
// TODO: refactoring: only for backwards compatibility with non-relocated services to the network module
implementation implementation(project(path: ':network')) implementation implementation(project(path: ':network'))
implementation implementation(project(path: ':common'))
implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'androidx.appcompat:appcompat:1.4.1'

View file

@ -20,7 +20,7 @@ import com.tangem.domain.DomainDialog
import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.domain.redux.global.DomainGlobalState import com.tangem.domain.redux.global.DomainGlobalState
import com.tangem.tap.features.tokens.addCustomToken.DomainErrorConverter import com.tangem.tap.common.moduleMessage.ModuleMessageConverter
import com.tangem.tap.features.tokens.addCustomToken.compose.SelectTokenNetworkDialog import com.tangem.tap.features.tokens.addCustomToken.compose.SelectTokenNetworkDialog
import com.tangem.wallet.R import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber import org.rekotlin.StoreSubscriber
@ -55,13 +55,13 @@ private fun ShowTheDialog(dialogState: MutableState<DomainDialog?>) {
if (dialogState.value == null) return if (dialogState.value == null) return
val context = LocalContext.current val context = LocalContext.current
val errorConverter = remember { DomainErrorConverter(context) } val errorConverter = remember { ModuleMessageConverter(context) }
val onDismissRequest = { domainStore.dispatch(DomainGlobalAction.ShowDialog(null)) } val onDismissRequest = { domainStore.dispatch(DomainGlobalAction.ShowDialog(null)) }
when (val dialog = dialogState.value) { when (val dialog = dialogState.value) {
is DomainDialog.DialogError -> ErrorDialog( is DomainDialog.DialogError -> ErrorDialog(
title = stringResource(id = R.string.common_error), title = stringResource(id = R.string.common_error),
body = errorConverter.convertError(dialog.error), body = errorConverter.convert(dialog.error),
onDismissRequest onDismissRequest
) )
is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest) is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest)

View file

@ -15,16 +15,17 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.tangem.domain.DomainError import com.tangem.common.module.ModuleError
import com.tangem.domain.ErrorConverter
import com.tangem.domain.common.form.Field import com.tangem.domain.common.form.Field
import com.tangem.tap.common.compose.extensions.stringResourceDefault import com.tangem.tap.common.compose.extensions.stringResourceDefault
import com.tangem.tap.common.moduleMessage.ModuleMessageConverter
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
@ -41,8 +42,8 @@ fun OutlinedTextFieldWidget(
isEnabled: Boolean = true, isEnabled: Boolean = true,
isVisible: Boolean = true, isVisible: Boolean = true,
isLoading: Boolean = false, isLoading: Boolean = false,
error: DomainError? = null, error: ModuleError? = null,
errorConverter: ErrorConverter<String>? = null, errorConverter: ModuleMessageConverter? = null,
debounceTextChanges: Long = 400, debounceTextChanges: Long = 400,
visualTransformation: VisualTransformation = VisualTransformation.None, visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
@ -79,7 +80,7 @@ private fun OutlinedProgressTextField(
placeholder: String = "", placeholder: String = "",
isEnabled: Boolean = true, isEnabled: Boolean = true,
isLoading: Boolean = false, isLoading: Boolean = false,
error: DomainError? = null, error: ModuleError? = null,
debounce: Long = 400, debounce: Long = 400,
visualTransformation: VisualTransformation = VisualTransformation.None, visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
@ -132,8 +133,8 @@ private fun OutlinedProgressTextField(
@Composable @Composable
private fun AnimatedErrorView( private fun AnimatedErrorView(
error: DomainError? = null, error: ModuleError? = null,
errorConverter: ErrorConverter<String>, errorConverter: ModuleMessageConverter,
) { ) {
AnimatedVisibility( AnimatedVisibility(
visible = error != null, visible = error != null,
@ -141,7 +142,7 @@ private fun AnimatedErrorView(
exit = slideOutVertically() + fadeOut(), exit = slideOutVertically() + fadeOut(),
) { ) {
error?.let { error?.let {
ErrorView(errorConverter.convertError(it), style = TextStyle(fontSize = 14.sp)) ErrorView(errorConverter.convert(it), style = TextStyle(fontSize = 14.sp))
} }
} }
} }
@ -149,20 +150,14 @@ private fun AnimatedErrorView(
@Preview @Preview
@Composable @Composable
fun OutlinedTextFieldWithErrorTest() { fun OutlinedTextFieldWithErrorTest() {
val converter = remember { val context = LocalContext.current
object : ErrorConverter<String> { val converter = remember { ModuleMessageConverter(context) }
override fun convertError(error: DomainError): String {
return "Hello, i'am the error: ${error::class.java.simpleName}"
}
}
}
class SimpleError( class SimpleError(
override val code: Int = 1, override val code: Int = 1,
override val message: String = "Error message", override val message: String = "Error message",
override val data: Any? = null, override val data: Any? = null,
) : DomainError ) : ModuleError
val modifier = Modifier val modifier = Modifier
.fillMaxWidth() .fillMaxWidth()

View file

@ -0,0 +1,24 @@
package com.tangem.tap.common.moduleMessage
import android.content.Context
import com.tangem.common.module.ModuleMessage
import com.tangem.common.module.ModuleMessageConverter
import com.tangem.domain.DomainModuleMessage
import com.tangem.tap.common.moduleMessage.domain.DomainMessageConverter
class ModuleMessageConverter(
private val context: Context
) : ModuleMessageConverter<ModuleMessage, String> {
override fun convert(message: ModuleMessage): String {
val convertedMessage = when (message) {
is DomainModuleMessage -> DomainMessageConverter(context).convert(message)
else -> null
}
return convertedMessage ?: convertUnknownMessage(message)
}
private fun convertUnknownMessage(message: ModuleMessage): String {
return "Unknown message: ${message::class.java.simpleName}"
}
}

View file

@ -1,10 +1,9 @@
package com.tangem.tap.features.tokens.addCustomToken package com.tangem.tap.common.moduleMessage.domain
import android.content.Context import android.content.Context
import com.tangem.common.module.ModuleMessageConverter
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.DomainError import com.tangem.domain.DomainError
import com.tangem.domain.ErrorConverter
import com.tangem.domain.features.addCustomToken.AddCustomTokenError
import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning
import com.tangem.wallet.R import com.tangem.wallet.R
/** /**
@ -12,27 +11,23 @@ import com.tangem.wallet.R
*/ */
class DomainErrorConverter( class DomainErrorConverter(
private val context: Context private val context: Context
) : ErrorConverter<String> { ) : ModuleMessageConverter<DomainError, String?> {
override fun convert(message: DomainError): String? = when (message) {
override fun convertError(error: DomainError): String { is AddCustomTokenError -> AddCustomTokenConverter(context).convert(message)
val errorMessage = when (error) { else -> null
is AddCustomTokenError -> AddCustomTokenConverter(context).convertError(error)
else -> null
}
return errorMessage?.let { it } ?: "Unknown error: ${error::class.java.simpleName}"
} }
} }
private class AddCustomTokenConverter( private class AddCustomTokenConverter(
private val context: Context private val context: Context
) : ErrorConverter<String> { ) : ModuleMessageConverter<DomainError, String?> {
override fun convertError(error: DomainError): String { override fun convert(message: DomainError): String? {
val customTokenError = (error as? AddCustomTokenError) ?: throw UnsupportedOperationException() val customTokenError = (message as? AddCustomTokenError) ?: throw UnsupportedOperationException()
val rawMessage = when (customTokenError) { val rawMessage = when (customTokenError) {
AddCustomTokenWarning.PotentialScamToken -> R.string.custom_token_validation_error_not_found AddCustomTokenError.Warning.PotentialScamToken -> R.string.custom_token_validation_error_not_found
AddCustomTokenWarning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added AddCustomTokenError.Warning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added
AddCustomTokenError.InvalidContractAddress -> R.string.custom_token_creation_error_invalid_contract_address AddCustomTokenError.InvalidContractAddress -> R.string.custom_token_creation_error_invalid_contract_address
AddCustomTokenError.NetworkIsNotSelected -> R.string.custom_token_creation_error_network_not_selected AddCustomTokenError.NetworkIsNotSelected -> R.string.custom_token_creation_error_network_not_selected
AddCustomTokenError.InvalidDerivationPath -> R.string.custom_token_creation_error_invalid_derivation_path AddCustomTokenError.InvalidDerivationPath -> R.string.custom_token_creation_error_invalid_derivation_path
@ -42,10 +37,11 @@ private class AddCustomTokenConverter(
AddCustomTokenError.FieldIsEmpty -> R.string.custom_token_creation_error_required_field AddCustomTokenError.FieldIsEmpty -> R.string.custom_token_creation_error_required_field
else -> null else -> null
} }
return when (rawMessage) { return when (rawMessage) {
is Int -> context.getString(rawMessage) is Int -> context.getString(rawMessage)
is String -> rawMessage is String -> rawMessage
else -> "Unknown error: ${customTokenError::class.java.simpleName}" else -> null
} }
} }
} }

View file

@ -0,0 +1,20 @@
package com.tangem.tap.common.moduleMessage.domain
import android.content.Context
import com.tangem.common.module.ModuleMessageConverter
import com.tangem.domain.DomainError
import com.tangem.domain.DomainModuleMessage
/**
[REDACTED_AUTHOR]
*/
class DomainMessageConverter(
private val context: Context
) : ModuleMessageConverter<DomainModuleMessage, String?> {
override fun convert(message: DomainModuleMessage): String? {
return when (message) {
is DomainError -> DomainErrorConverter(context).convert(message)
else -> null
}
}
}

View file

@ -13,11 +13,9 @@ import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.tangem.domain.ErrorConverter import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.common.form.DataField import com.tangem.domain.common.form.DataField
import com.tangem.domain.common.form.FieldId import com.tangem.domain.common.form.FieldId
import com.tangem.domain.features.addCustomToken.AddCustomTokenError
import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.* import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
@ -27,7 +25,7 @@ import com.tangem.domain.redux.domainStore
import com.tangem.tap.common.compose.ComposeDialogManager import com.tangem.tap.common.compose.ComposeDialogManager
import com.tangem.tap.common.compose.ToggledRippleTheme import com.tangem.tap.common.compose.ToggledRippleTheme
import com.tangem.tap.common.compose.keyboardAsState import com.tangem.tap.common.compose.keyboardAsState
import com.tangem.tap.features.tokens.addCustomToken.DomainErrorConverter import com.tangem.tap.common.moduleMessage.ModuleMessageConverter
import com.tangem.wallet.R import com.tangem.wallet.R
/** /**
@ -82,7 +80,7 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
@Composable @Composable
private fun FormFields(state: MutableState<AddCustomTokenState>) { private fun FormFields(state: MutableState<AddCustomTokenState>) {
val context = LocalContext.current val context = LocalContext.current
val errorConverter = remember { DomainErrorConverter(context) } val errorConverter = remember { ModuleMessageConverter(context) }
val stateValue = state.value val stateValue = state.value
stateValue.form.fieldList.forEach { field -> stateValue.form.fieldList.forEach { field ->
@ -99,11 +97,11 @@ private fun FormFields(state: MutableState<AddCustomTokenState>) {
} }
@Composable @Composable
fun Warnings(warnings: List<AddCustomTokenWarning>) { fun Warnings(warnings: List<AddCustomTokenError.Warning>) {
if (warnings.isEmpty()) return if (warnings.isEmpty()) return
val context = LocalContext.current val context = LocalContext.current
val warningConverter = remember { DomainErrorConverter(context) } val warningConverter = remember { ModuleMessageConverter(context) }
Column { Column {
warnings.forEachIndexed { index, item -> warnings.forEachIndexed { index, item ->
@ -120,7 +118,7 @@ fun Warnings(warnings: List<AddCustomTokenWarning>) {
) { ) {
Text( Text(
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),
text = warningConverter.convertError(item), text = warningConverter.convert(item),
color = colorResource(id = R.color.white), color = colorResource(id = R.color.white),
fontSize = 14.sp fontSize = 14.sp
) )
@ -172,14 +170,14 @@ private fun AddCustomTokenFab(
data class ScreenFieldData( data class ScreenFieldData(
val field: DataField<*>, val field: DataField<*>,
val error: AddCustomTokenError?, val error: AddCustomTokenError?,
val errorConverter: ErrorConverter<String>, val errorConverter: ModuleMessageConverter,
val viewState: ViewStates.TokenField val viewState: ViewStates.TokenField
) { ) {
companion object { companion object {
fun fromState( fun fromState(
field: DataField<*>, field: DataField<*>,
state: AddCustomTokenState, state: AddCustomTokenState,
errorConverter: DomainErrorConverter errorConverter: ModuleMessageConverter
): ScreenFieldData { ): ScreenFieldData {
return ScreenFieldData( return ScreenFieldData(
field = field, field = field,

1
common/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

9
common/build.gradle Normal file
View file

@ -0,0 +1,9 @@
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View file

@ -0,0 +1,8 @@
package com.tangem.common
/**
[REDACTED_AUTHOR]
*/
interface Validator<Data, Error> {
fun validate(data: Data? = null): Error?
}

View file

@ -0,0 +1,14 @@
package com.tangem.common.module
/**
[REDACTED_AUTHOR]
* A module exception
*/
interface ModuleException {
val message: String
}
/**
* An exception marked as FbConsumeException should be submitted to Firebase.Crashlytics as a non-fatal issue.
*/
interface FbConsumeException

View file

@ -0,0 +1,22 @@
package com.tangem.common.module
/**
[REDACTED_AUTHOR]
* The base object for communication between modules
*/
interface ModuleMessage
interface ModuleMessageConverter<ModuleMessage, R> {
fun convert(message: ModuleMessage): R
}
/**
* @property code describes what feature is the error coming from
* @property message the error description
* @property data any data that can help in the part where this error is being handled
*/
interface ModuleError : ModuleMessage {
val code: Int
val message: String
val data: Any?
}

View file

@ -47,13 +47,14 @@ android {
dependencies { dependencies {
implementation implementation(project(path: ':network')) implementation implementation(project(path: ':network'))
implementation implementation(project(path: ':common'))
// Tangem sdk's // Tangem sdk's
implementation 'com.tangem:blockchain:develop-71' implementation 'com.tangem:blockchain:develop-71'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142' implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-142' implementation 'com.tangem.tangem-sdk-kotlin:android:develop-142'
// Kotlin // Kotlin coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
// State management // State management

View file

@ -1,29 +1,42 @@
package com.tangem.domain package com.tangem.domain
import com.tangem.common.module.ModuleError
import com.tangem.common.module.ModuleMessage
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
* @property code describes what feature is the error coming from * All DomainError descendants must use their own range of codes, but no more than 999 error codes for each.
* @property message the error description
* @property data any data that can help in the part where this error is being handled
*/ */
interface DomainError : DomainMessage { sealed interface DomainModuleMessage : ModuleMessage
val code: Int
val message: String
val data: Any?
}
open class AnError( sealed class DomainError(
override val code: Int, override val code: Int,
override val message: String, override val message: String,
override val data: Any? = null, override val data: Any?,
) : DomainError ) : DomainModuleMessage, ModuleError {
interface ErrorConverter<T> { companion object {
fun convertError(error: DomainError): T const val CODE_ADD_CUSTOM_TOKEN = 1000
}
} }
interface Validator<Data, Error> { sealed class AddCustomTokenError(
fun validate(data: Data? = null): Error? subCode: Int = 0
} ) : DomainError(CODE_ADD_CUSTOM_TOKEN + subCode, this::class.java.simpleName, null) {
const val ERROR_CODE_ADD_CUSTOM_TOKEN = 100 object FieldIsEmpty : AddCustomTokenError()
object FieldIsNotEmpty : AddCustomTokenError()
object InvalidContractAddress : AddCustomTokenError()
object NetworkIsNotSelected : AddCustomTokenError()
object InvalidDecimalsCount : AddCustomTokenError()
object InvalidDerivationPath : AddCustomTokenError()
sealed class Network : AddCustomTokenError() {
object CheckAddressRequestError : Network()
}
sealed class Warning : AddCustomTokenError() {
object PotentialScamToken : Warning()
object TokenAlreadyAdded : Warning()
}
}

View file

@ -1,17 +1,19 @@
package com.tangem.domain package com.tangem.domain
import com.tangem.common.module.FbConsumeException
import com.tangem.common.module.ModuleException
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
* Must be handled by the module or sent to Crashlytics
*/ */
interface DomainInternalException sealed class AddCustomTokenException(override val message: String) : Throwable(message), ModuleException {
sealed class DomainException(message: String?) : Throwable(message), DomainInternalException { data class SelectTokeNetworkException(val networkId: String) : AddCustomTokenException(
data class SelectTokeNetworkException(val networkId: String) : DomainException(
"Unknown network [$networkId] should not be included in the network selection dialog." "Unknown network [$networkId] should not be included in the network selection dialog."
) ), FbConsumeException
data class UnAppropriateInitializationException(val of: String, val info: String? = null) : DomainException( data class UnAppropriateInitializationException(
"The [$of], must be properly initialized. Info []" val of: String,
) val info: String? = null
) : AddCustomTokenException("The [$of], must be properly initialized. Info [$info]")
} }

View file

@ -1,15 +0,0 @@
package com.tangem.domain
/**
[REDACTED_AUTHOR]
*/
sealed interface DomainMessage
sealed interface DomainNotification : DomainMessage {
interface Toast : DomainNotification {}
interface Snackbar : DomainNotification {}
interface Dialog : DomainNotification {}
}

View file

@ -2,8 +2,8 @@ package com.tangem.domain.common.form
import com.tangem.blockchain.blockchains.ethereum.EthereumAddressService import com.tangem.blockchain.blockchains.ethereum.EthereumAddressService
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.Validator import com.tangem.common.Validator
import com.tangem.domain.features.addCustomToken.AddCustomTokenError import com.tangem.domain.AddCustomTokenError
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]

View file

@ -1,25 +0,0 @@
package com.tangem.domain.features.addCustomToken
import com.tangem.domain.AnError
import com.tangem.domain.ERROR_CODE_ADD_CUSTOM_TOKEN
/**
[REDACTED_AUTHOR]
*/
sealed class AddCustomTokenError : AnError(ERROR_CODE_ADD_CUSTOM_TOKEN, "Add custom token - error") {
object FieldIsEmpty : AddCustomTokenError()
object FieldIsNotEmpty : AddCustomTokenError()
object InvalidContractAddress : AddCustomTokenError()
object NetworkIsNotSelected : AddCustomTokenError()
object InvalidDecimalsCount : AddCustomTokenError()
object InvalidDerivationPath : AddCustomTokenError()
sealed class Network : AddCustomTokenWarning() {
object CheckAddressRequestError : Network()
}
}
sealed class AddCustomTokenWarning : AddCustomTokenError() {
object PotentialScamToken : AddCustomTokenWarning()
object TokenAlreadyAdded : AddCustomTokenWarning()
}

View file

@ -2,11 +2,10 @@ package com.tangem.domain.features.addCustomToken.redux
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.DerivationStyle
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.DomainWrapped import com.tangem.domain.DomainWrapped
import com.tangem.domain.common.form.Field import com.tangem.domain.common.form.Field
import com.tangem.domain.common.form.FieldId import com.tangem.domain.common.form.FieldId
import com.tangem.domain.features.addCustomToken.AddCustomTokenError
import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning
import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId import com.tangem.domain.features.addCustomToken.CustomTokenFieldId
import org.rekotlin.Action import org.rekotlin.Action
@ -47,9 +46,12 @@ sealed class AddCustomTokenAction : Action {
// warnings // warnings
sealed class Warning : AddCustomTokenAction() { sealed class Warning : AddCustomTokenAction() {
data class Add(val warnings: Set<AddCustomTokenWarning>) : Warning() data class Add(val warnings: Set<AddCustomTokenError.Warning>) : Warning()
data class Remove(val warnings: Set<AddCustomTokenWarning>) : Warning() data class Remove(val warnings: Set<AddCustomTokenError.Warning>) : Warning()
data class Replace(val remove: Set<AddCustomTokenWarning>, val add: Set<AddCustomTokenWarning>) : Warning() data class Replace(
val remove: Set<AddCustomTokenError.Warning>,
val add: Set<AddCustomTokenError.Warning>
) : Warning()
} }
// To change the screenState // To change the screenState

View file

@ -5,8 +5,11 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.extensions.guard import com.tangem.common.extensions.guard
import com.tangem.common.services.Result import com.tangem.common.services.Result
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.AddCustomTokenError.Warning.PotentialScamToken
import com.tangem.domain.AddCustomTokenError.Warning.TokenAlreadyAdded
import com.tangem.domain.AddCustomTokenException
import com.tangem.domain.DomainDialog import com.tangem.domain.DomainDialog
import com.tangem.domain.DomainException
import com.tangem.domain.DomainWrapped import com.tangem.domain.DomainWrapped
import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.fromNetworkId
@ -145,9 +148,9 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
private suspend fun updateWarningAlreadyAdded(isInAppSavedList: Boolean) { private suspend fun updateWarningAlreadyAdded(isInAppSavedList: Boolean) {
if (isInAppSavedList) { if (isInAppSavedList) {
AddCustomTokenWarning.TokenAlreadyAdded.add() TokenAlreadyAdded.add()
} else { } else {
AddCustomTokenWarning.TokenAlreadyAdded.remove() TokenAlreadyAdded.remove()
} }
} }
@ -170,7 +173,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
val result = when (foundTokensResult) { val result = when (foundTokensResult) {
is Result.Success -> foundTokensResult.data is Result.Success -> foundTokensResult.data
is Result.Failure -> { is Result.Failure -> {
// val warning = AddCustomTokenWarning.Network.CheckAddressRequestError // val warning = Warning.Network.CheckAddressRequestError
// dispatchOnMain(Warning.Add(setOf(warning))) // dispatchOnMain(Warning.Add(setOf(warning)))
emptyList() emptyList()
} }
@ -182,8 +185,8 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
private suspend fun manageFoundTokenChanges(foundTokens: List<Coins.CheckAddressResponse.Token>) { private suspend fun manageFoundTokenChanges(foundTokens: List<Coins.CheckAddressResponse.Token>) {
if (foundTokens.isEmpty()) { if (foundTokens.isEmpty()) {
// token not found - it's completely custom // token not found - it's completely custom
AddCustomTokenWarning.TokenAlreadyAdded.remove() TokenAlreadyAdded.remove()
AddCustomTokenWarning.PotentialScamToken.add() PotentialScamToken.add()
dispatchOnMain(SetFoundTokenId(null)) dispatchOnMain(SetFoundTokenId(null))
clearTokenFields() clearTokenFields()
@ -208,33 +211,33 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
if (isInAppSavedTokens) { if (isInAppSavedTokens) {
lockTokenFields() lockTokenFields()
lockAddButton() lockAddButton()
AddCustomTokenWarning.PotentialScamToken.replace(AddCustomTokenWarning.TokenAlreadyAdded) PotentialScamToken.replace(TokenAlreadyAdded)
} else { } else {
// not in the saved tokens list // not in the saved tokens list
if (singleTokenContract.active) { if (singleTokenContract.active) {
lockTokenFields() lockTokenFields()
unlockAddButton() unlockAddButton()
if (hubState.derivationPathIsSelected()) { if (hubState.derivationPathIsSelected()) {
AddCustomTokenWarning.PotentialScamToken.add() PotentialScamToken.add()
} else { } else {
AddCustomTokenWarning.TokenAlreadyAdded.remove() TokenAlreadyAdded.remove()
AddCustomTokenWarning.PotentialScamToken.remove() PotentialScamToken.remove()
} }
} else { } else {
unlockAddButton() unlockAddButton()
AddCustomTokenWarning.PotentialScamToken.add() PotentialScamToken.add()
} }
} }
} }
else -> { else -> {
AddCustomTokenWarning.PotentialScamToken.replace(AddCustomTokenWarning.TokenAlreadyAdded) PotentialScamToken.replace(TokenAlreadyAdded)
val dialog = DomainDialog.SelectTokenDialog( val dialog = DomainDialog.SelectTokenDialog(
items = foundToken.contracts, items = foundToken.contracts,
networkIdConverter = { networkId -> networkIdConverter = { networkId ->
val blockchain = Blockchain.fromNetworkId(networkId) val blockchain = Blockchain.fromNetworkId(networkId)
if (blockchain == null || blockchain == Blockchain.Unknown) { if (blockchain == null || blockchain == Blockchain.Unknown) {
throw DomainException.SelectTokeNetworkException(networkId) throw AddCustomTokenException.SelectTokeNetworkException(networkId)
} }
hubState.blockchainToName(blockchain) ?: "" hubState.blockchainToName(blockchain) ?: ""
}, },
@ -253,8 +256,8 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
} }
private suspend fun replaceWarnings( private suspend fun replaceWarnings(
warningsAdd: MutableSet<AddCustomTokenWarning> = mutableSetOf(), warningsAdd: MutableSet<AddCustomTokenError.Warning> = mutableSetOf(),
warningsRemove: MutableSet<AddCustomTokenWarning> = mutableSetOf(), warningsRemove: MutableSet<AddCustomTokenError.Warning> = mutableSetOf(),
) { ) {
if (warningsAdd.isNotEmpty() || warningsRemove.isNotEmpty()) { if (warningsAdd.isNotEmpty() || warningsRemove.isNotEmpty()) {
dispatchOnMain(Warning.Replace(warningsRemove.toSet(), warningsAdd.toSet())) dispatchOnMain(Warning.Replace(warningsRemove.toSet(), warningsAdd.toSet()))
@ -263,7 +266,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
private suspend fun updateAddButton() { private suspend fun updateAddButton() {
val state = hubState val state = hubState
if (state.warnings.contains(AddCustomTokenWarning.TokenAlreadyAdded)) { if (state.warnings.contains(TokenAlreadyAdded)) {
lockAddButton() lockAddButton()
return return
} }
@ -483,19 +486,19 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
dispatchOnMain(action) dispatchOnMain(action)
} }
private suspend fun AddCustomTokenWarning.add() { private suspend fun AddCustomTokenError.Warning.add() {
dispatchOnMain(Warning.Add(setOf(this))) dispatchOnMain(Warning.Add(setOf(this)))
} }
private suspend fun AddCustomTokenWarning.remove() { private suspend fun AddCustomTokenError.Warning.remove() {
dispatchOnMain(Warning.Remove(setOf(this))) dispatchOnMain(Warning.Remove(setOf(this)))
} }
private suspend fun AddCustomTokenWarning.replace(to: AddCustomTokenWarning) { private suspend fun AddCustomTokenError.Warning.replace(to: AddCustomTokenError.Warning) {
dispatchOnMain(Warning.Replace(setOf(this), setOf(to))) dispatchOnMain(Warning.Replace(setOf(this), setOf(to)))
} }
// private suspend fun AddCustomTokenWarning.replace(replace: Boolean, to: AddCustomTokenWarning) { // private suspend fun Warning.replace(replace: Boolean, to: Warning) {
// if (replace) dispatchOnMain(Warning.Replace(setOf(this), setOf(to))) // if (replace) dispatchOnMain(Warning.Replace(setOf(this), setOf(to)))
// } // }
@ -653,7 +656,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
@Throws @Throws
private fun throwUnAppropriateInitialization(objName: String) { private fun throwUnAppropriateInitialization(objName: String) {
throw DomainException.UnAppropriateInitializationException( throw AddCustomTokenException.UnAppropriateInitializationException(
"AddCustomTokenHub", "$objName must be not NULL" "AddCustomTokenHub", "$objName must be not NULL"
) )
} }

View file

@ -2,6 +2,7 @@ package com.tangem.domain.features.addCustomToken.redux
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.DerivationStyle
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.DomainWrapped import com.tangem.domain.DomainWrapped
import com.tangem.domain.common.form.* import com.tangem.domain.common.form.*
import com.tangem.domain.features.addCustomToken.* import com.tangem.domain.features.addCustomToken.*
@ -16,7 +17,7 @@ data class AddCustomTokenState(
val formValidators: Map<CustomTokenFieldId, CustomTokenValidator<out Any>> = createFormValidators(), val formValidators: Map<CustomTokenFieldId, CustomTokenValidator<out Any>> = createFormValidators(),
val formErrors: Map<CustomTokenFieldId, AddCustomTokenError> = emptyMap(), val formErrors: Map<CustomTokenFieldId, AddCustomTokenError> = emptyMap(),
val tokenId: String? = null, val tokenId: String? = null,
val warnings: Set<AddCustomTokenWarning> = emptySet(), val warnings: Set<AddCustomTokenError.Warning> = emptySet(),
val screenState: ScreenState = createInitialScreenState(), val screenState: ScreenState = createInitialScreenState(),
val tangemTechServiceManager: AddCustomTokenService? = null val tangemTechServiceManager: AddCustomTokenService? = null
) : StateType { ) : StateType {

View file

@ -9,9 +9,17 @@ java {
} }
dependencies { dependencies {
implementation implementation(project(path: ':common'))
// Tangem sdk's // Tangem sdk's
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142' implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142'
// Kotlin coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
// Logs
implementation 'com.jakewharton.timber:timber:4.7.1'
// Network // Network
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3")) implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3"))
implementation("com.squareup.okhttp3:okhttp") implementation("com.squareup.okhttp3:okhttp")

View file

@ -1,3 +1,4 @@
include ':app' include ':app'
include ':domain' include ':domain'
include ':network' include ':network'
include ':common'