Updated on 2026-08-14

This commit is contained in:
Tangem 2022-05-03 16:39:56 +03:00
parent a085c2998c
commit 8539729032
8 changed files with 166 additions and 111 deletions

View file

@ -7,6 +7,7 @@ import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.tangem.Log
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.domain.DomainLayer
import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.images.PicassoHelper
@ -46,6 +47,7 @@ class TapApplication : Application() {
override fun onCreate() {
super.onCreate()
DomainLayer.init()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {

View file

@ -0,0 +1,26 @@
package com.tangem.domain
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
import com.tangem.domain.redux.state.ActionStateLoggerImpl
/**
[REDACTED_AUTHOR]
*/
object DomainLayer {
internal val actionStateLogger = ActionStateLoggerImpl()
var onInitComplete: ((DomainException?) -> Unit)? = null
fun init(data: Map<String, Any?>? = null) {
initActionStateLogger()
onInitComplete?.invoke(null)
}
private fun initActionStateLogger() {
val factory = actionStateLogger.actionStateConvertersFactory
factory.addConverter(AddCustomTokenAction::class.java, AddCustomTokenState.Converter())
}
}

View file

@ -1,61 +0,0 @@
package com.tangem.domain.features.addCustomToken
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.domain.common.form.FieldToJsonConverter
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.state.StringActionConverter
import org.rekotlin.Action
class AddCustomTokenActionStateConverter : StringActionConverter<DomainState> {
private val jsonConverter: MoshiJsonConverter = MoshiJsonConverter.INSTANCE
private var builder: StringBuilder = StringBuilder()
override fun convert(action: Action, stateHolder: DomainState): String? {
val action = (action as? AddCustomTokenAction) ?: return null
val state = stateHolder.addCustomTokensState
val fieldConverter = FieldToJsonConverter(listOf(
CustomTokenFieldId.ContractAddress,
CustomTokenFieldId.Network,
CustomTokenFieldId.Name,
CustomTokenFieldId.Symbol,
CustomTokenFieldId.Decimals,
CustomTokenFieldId.DerivationPath,
), jsonConverter)
state.visitDataConverter(fieldConverter)
val errors = state.formErrors.map {
"${it.key}: ${it.value::class.java.simpleName}"
}
val warnings = state.warnings.map { it::class.java.simpleName }
printAction(action, state)
printStateValue("fields", fieldConverter.getConvertedData())
printStateValue("fieldErrors", toJson(errors))
printStateValue("warnings", toJson(warnings))
printStateValue("screenState", toJson(state.screenState))
printMessage("------------------------------------------------------")
val printed = builder.toString()
builder = StringBuilder()
return printed
}
private fun printStateValue(name: String, value: String) {
printMessage("$name: $value")
}
private fun printAction(action: AddCustomTokenAction, state: AddCustomTokenState) {
printMessage("action: $action, state: ${state::class.java.simpleName}")
}
private fun toJson(value: Any): String {
return jsonConverter.prettyPrint(value)
}
private fun printMessage(message: String) {
builder.append("$message\n")
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.domain.features.addCustomToken.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.DomainWrapped
import com.tangem.domain.common.extensions.SolanaAvailable
@ -10,7 +11,10 @@ import com.tangem.domain.common.extensions.SolanaTokensAvailable
import com.tangem.domain.common.form.*
import com.tangem.domain.features.addCustomToken.*
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.state.StringActionStateConverter
import org.rekotlin.Action
import org.rekotlin.StateType
data class AddCustomTokenState(
@ -239,6 +243,58 @@ data class AddCustomTokenState(
)
}
}
class Converter : StringActionStateConverter<DomainState> {
private val jsonConverter: MoshiJsonConverter = MoshiJsonConverter.INSTANCE
private var builder: StringBuilder = StringBuilder()
override fun convert(action: Action, stateHolder: DomainState): String? {
val action = (action as? AddCustomTokenAction) ?: return null
val state = stateHolder.addCustomTokensState
val fieldConverter = FieldToJsonConverter(listOf(
ContractAddress,
Network,
Name,
Symbol,
Decimals,
DerivationPath,
), jsonConverter)
state.visitDataConverter(fieldConverter)
val errors = state.formErrors.map {
"${it.key}: ${it.value::class.java.simpleName}"
}
val warnings = state.warnings.map { it::class.java.simpleName }
printAction(action, state)
printStateValue("fields", fieldConverter.getConvertedData())
printStateValue("fieldErrors", toJson(errors))
printStateValue("warnings", toJson(warnings))
printStateValue("screenState", toJson(state.screenState))
printMessage("------------------------------------------------------")
val printed = builder.toString()
builder = StringBuilder()
return printed
}
private fun printStateValue(name: String, value: String) {
printMessage("$name: $value")
}
private fun printAction(action: AddCustomTokenAction, state: AddCustomTokenState) {
printMessage("action: $action, state: ${state::class.java.simpleName}")
}
private fun toJson(value: Any): String {
return jsonConverter.prettyPrint(value)
}
private fun printMessage(message: String) {
builder.append("$message\n")
}
}
}
enum class CustomTokenType {

View file

@ -1,8 +1,9 @@
package com.tangem.domain.redux
import com.tangem.domain.DomainLayer
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenHub
import com.tangem.domain.redux.global.DomainGlobalHub
import com.tangem.domain.redux.state.observeReducedStates
import org.rekotlin.Action
import org.rekotlin.Store
/**
@ -18,16 +19,27 @@ private val RE_STORE_HUBS: List<ReStoreHub<DomainState, *>> = listOf(
val domainStore = Store(
state = DomainState(),
middleware = RE_STORE_HUBS.map { it.getMiddleware() },
reducer = { action, state ->
requireNotNull(state)
// we can examine the store state after each change by reducer
val reducedSates = RE_STORE_HUBS.mapNotNull {
val reducedState = it.reduce(action, state)
if (reducedState == state) null else Pair(action, reducedState)
}
observeReducedStates(reducedSates)
if (reducedSates.isEmpty()) state else reducedSates.last().second
}
reducer = { action, state -> reduce(action, state) }
)
private fun reduce(action: Action, domainState: DomainState?): DomainState {
requireNotNull(domainState)
// we can examine the store state after each change by reducer
var assembleReducedDomainState: DomainState = domainState
val reducedStatesByAction = mutableListOf<Pair<Action, DomainState>>()
RE_STORE_HUBS.forEach {
val reducedState = it.reduce(action, assembleReducedDomainState)
assembleReducedDomainState = if (reducedState != assembleReducedDomainState) {
reducedStatesByAction.add(action to assembleReducedDomainState)
reducedState
} else {
assembleReducedDomainState
}
}
DomainLayer.actionStateLogger.log(reducedStatesByAction)
return assembleReducedDomainState
}

View file

@ -1,5 +1,6 @@
package com.tangem.domain.redux.state
import com.tangem.domain.redux.DomainState
import org.rekotlin.Action
/**
@ -9,6 +10,26 @@ interface StringStateConverter<StateHolder> {
fun convert(stateHolder: StateHolder): String
}
interface StringActionConverter<StateHolder> {
interface StringActionStateConverter<StateHolder> {
fun convert(action: Action, stateHolder: StateHolder): String?
}
class ActionStateConvertersFactory {
private val stateConverters = mutableMapOf<Class<out Action>, StringActionStateConverter<DomainState>>()
fun addConverter(classOfAction: Class<out Action>, converter: StringActionStateConverter<DomainState>) {
stateConverters[classOfAction] = converter
}
fun getConverter(action: Action): StringActionStateConverter<DomainState>? {
val converter = stateConverters.firstNotNullOfOrNull { (classOfAction, converter) ->
if (classOfAction.isAssignableFrom(action::class.java)) {
converter
} else {
null
}
} ?: return null
return converter
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.domain.redux.state
import com.tangem.domain.features.BuildConfig
import com.tangem.domain.redux.DomainState
import org.rekotlin.Action
import timber.log.Timber
/**
[REDACTED_AUTHOR]
* Use it only in debug mode!
*/
internal interface ActionStateLogger {
fun log(reducedSates: List<Pair<Action, DomainState>>)
}
internal class ActionStateLoggerImpl : ActionStateLogger {
val actionStateConvertersFactory = ActionStateConvertersFactory()
override fun log(reducedSates: List<Pair<Action, DomainState>>) {
if (!BuildConfig.LOG_ENABLED) return
logStates(reducedSates)
}
private fun logStates(reducedSates: List<Pair<Action, DomainState>>) {
reducedSates.forEach { (action, domainState) ->
val messageToPrint = actionStateConvertersFactory.getConverter(action)
?.convert(action, domainState)
?: return@forEach
Timber.d(messageToPrint)
}
}
}

View file

@ -1,36 +0,0 @@
package com.tangem.domain.redux.state
import com.tangem.domain.features.addCustomToken.AddCustomTokenActionStateConverter
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.redux.DomainState
import org.rekotlin.Action
import timber.log.Timber
/**
[REDACTED_AUTHOR]
* Use it only in debug mode!
*/
internal fun observeReducedStates(reducedSates: List<Pair<Action, DomainState>>) {
// we can add any logic to watch for changes of actions, states, etc.
val isDebugMode = true
if (!isDebugMode) return
logStates(reducedSates)
}
private fun logStates(reducedSates: List<Pair<Action, DomainState>>) {
reducedSates.forEach {
val converter = stateConverters.firstNotNullOfOrNull { entry ->
if (entry.key.isAssignableFrom(it.first::class.java)) entry.value else null
} ?: return@forEach
val messageToPrint = converter.convert(it.first, it.second) ?: return@forEach
Timber.d(messageToPrint)
}
}
// TODO: refactoring: mutate to factory
private val stateConverters = mutableMapOf(
AddCustomTokenAction::class.java to AddCustomTokenActionStateConverter()
)