Updated on 2026-08-14

This commit is contained in:
Tangem 2022-09-15 09:31:18 +03:00
parent 7124a57c4e
commit 30d2fab208
10 changed files with 120 additions and 149 deletions

View file

@ -1,17 +1,17 @@
package com.tangem.tap
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.appsflyer.AppsFlyerLib
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.tangem.Log
import com.tangem.LogFormat
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.domain.DomainLayer
import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.AndroidAssetReader
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
import com.tangem.tap.common.feedback.FeedbackManager
@ -21,9 +21,9 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.shop.TangemShopService
import com.tangem.tap.domain.configurable.config.Config
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
import com.tangem.tap.domain.configurable.config.FeaturesRemoteLoader
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
@ -36,7 +36,7 @@ import timber.log.Timber
val store = Store(
reducer = ::appReducer,
middleware = AppState.getMiddleware(),
state = AppState()
state = AppState(),
)
val logConfig = LogConfig()
@ -48,98 +48,98 @@ lateinit var walletConnectRepository: WalletConnectRepository
lateinit var shopService: TangemShopService
class TapApplication : Application(), ImageLoaderFactory {
override fun onCreate() {
super.onCreate()
DomainLayer.init()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
this.minimumFetchIntervalInSeconds = 60
})
} else {
Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
this.minimumFetchIntervalInSeconds = 3600
})
}
foregroundActivityObserver = ForegroundActivityObserver()
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
DomainLayer.init()
NetworkConnectivity.createInstance(store, this)
preferencesStorage = PreferencesStorage(this)
currenciesRepository = CurrenciesRepository(this, store.state.domainNetworks.tangemTechService)
walletConnectRepository = WalletConnectRepository(this)
foregroundActivityObserver = ForegroundActivityObserver()
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
initFeedbackManager()
loadConfigs()
val configLoader = FeaturesLocalLoader(AndroidAssetReader(this), MoshiConverter.defaultMoshi())
initConfigManager(configLoader) { config ->
shopService = TangemShopService(this, config.shopify!!)
initAppsFlyer(this, config)
initFeedbackManager(this, preferencesStorage, config)
}
initWarningMessagesManager()
BlockchainSdkRetrofitBuilder.enableNetworkLogging = BuildConfig.DEBUG
initAppsFlyer()
}
override fun newImageLoader(): ImageLoader {
return createCoilImageLoader(context = this)
}
private fun loadConfigs() {
val moshi = MoshiConverter.defaultMoshi()
val localLoader = FeaturesLocalLoader(this, moshi)
val remoteLoader = FeaturesRemoteLoader(moshi)
val configManager = ConfigManager(localLoader, remoteLoader)
configManager.load { config ->
private fun initConfigManager(loader: FeaturesLocalLoader, onComplete: (Config) -> Unit) {
val configManager = ConfigManager()
configManager.load(loader) { config ->
store.dispatch(GlobalAction.SetConfigManager(configManager))
shopService = TangemShopService(
application = this,
shopifyShop = config.shopify!!
)
store.state.globalState.feedbackManager?.initChat(
context = this,
zendeskConfig = config.zendesk!!
)
onComplete(config)
}
store.dispatch(GlobalAction.SetWarningManager(WarningMessagesManager())) }
}
private fun initFeedbackManager() {
val infoHolder = AdditionalFeedbackInfo()
infoHolder.setAppVersion(this)
val logLevels = listOf(
Log.Level.ApduCommand,
Log.Level.Apdu,
Log.Level.Tlv,
Log.Level.Nfc,
Log.Level.Command,
Log.Level.Session,
Log.Level.View,
Log.Level.Network,
Log.Level.Error,
)
val logWriter = TangemLogCollector(
levels = logLevels,
messageFormatter = LogFormat.StairsFormatter(),
)
Log.addLogger(logWriter)
store.dispatch(
GlobalAction.SetFeedbackManager(
FeedbackManager(
infoHolder = infoHolder,
logCollector = logWriter,
preferencesStorage = preferencesStorage,
),
),
)
}
private fun initAppsFlyer() {
val devKey = store.state.globalState.configManager?.config?.appsFlyerDevKey ?: return
AppsFlyerLib.getInstance().init(devKey, null, this)
AppsFlyerLib.getInstance().start(this)
val analyticsHandler = GlobalAnalyticsHandler.createDefaultAnalyticHandlers(this)
private fun initAppsFlyer(context: Context, config: Config) {
AppsFlyerLib.getInstance().init(config.appsFlyerDevKey, null, context)
AppsFlyerLib.getInstance().start(context)
val analyticsHandler = GlobalAnalyticsHandler.createDefaultAnalyticHandlers(context)
store.dispatch(GlobalAction.SetAnanlyticHandlers(analyticsHandler))
}
private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) {
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply {
appVersion = try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
pInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
"x.y.z"
}
}
fun initTangemLogCollector(): TangemLogCollector {
val logLevels = listOf(
Log.Level.ApduCommand,
Log.Level.Apdu,
Log.Level.Tlv,
Log.Level.Nfc,
Log.Level.Command,
Log.Level.Session,
Log.Level.View,
Log.Level.Network,
Log.Level.Error,
)
return TangemLogCollector(logLevels, LogFormat.StairsFormatter())
}
val additionalFeedbackInfo = initAdditionalFeedbackInfo(context)
val tangemLogCollector = initTangemLogCollector()
Log.addLogger(tangemLogCollector)
val feedbackManager = FeedbackManager(
infoHolder = additionalFeedbackInfo,
logCollector = tangemLogCollector,
preferencesStorage = preferencesStorage,
)
feedbackManager.initChat(
context = context,
zendeskConfig = config.zendesk!!,
)
store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager))
}
private fun initWarningMessagesManager() {
store.dispatch(GlobalAction.SetWarningManager(WarningMessagesManager()))
}
}
data class LogConfig(

View file

@ -0,0 +1,32 @@
package com.tangem.tap.common
import android.content.Context
import com.tangem.tap.common.extensions.readAssetAsString
import com.tangem.tap.common.extensions.readFile
import com.tangem.tap.common.extensions.rewriteFile
/**
[REDACTED_AUTHOR]
*/
interface AssetReader {
fun readAssetAsString(name: String): String
fun readFile(fileName: String): String
fun rewriteFile(content: String, fileName: String)
}
class AndroidAssetReader(
private val context: Context,
) : AssetReader {
override fun readAssetAsString(name: String): String {
return context.readAssetAsString(name)
}
override fun readFile(fileName: String): String {
return context.readFile(fileName)
}
override fun rewriteFile(content: String, fileName: String) {
context.rewriteFile(content, fileName)
}
}

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics
import android.app.Application
import android.content.Context
import com.appsflyer.AFInAppEventParameterName
import com.appsflyer.AFInAppEventType
import com.appsflyer.AppsFlyerLib
@ -8,7 +8,7 @@ import com.shopify.buy3.Storefront
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
class AppsFlyerAnalyticsHandler(val context: Application): AnalyticsHandler() {
class AppsFlyerAnalyticsHandler(val context: Context): AnalyticsHandler() {
override fun triggerEvent(
event: AnalyticsEvent,
@ -16,8 +16,7 @@ class AppsFlyerAnalyticsHandler(val context: Application): AnalyticsHandler() {
blockchain: String?,
params: Map<String, String>
) {
AppsFlyerLib.getInstance().logEvent(context,
event.event, prepareParams(card, blockchain))
AppsFlyerLib.getInstance().logEvent(context, event.event, prepareParams(card, blockchain))
}
override fun logCardSdkError(

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics
import android.app.Application
import android.content.Context
import com.shopify.buy3.Storefront
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
@ -42,7 +42,7 @@ class GlobalAnalyticsHandler(val analyticsHandlers: List<AnalyticsHandler>) :
}
companion object {
fun createDefaultAnalyticHandlers(context: Application): GlobalAnalyticsHandler {
fun createDefaultAnalyticHandlers(context: Context): GlobalAnalyticsHandler {
return GlobalAnalyticsHandler(
listOf(
FirebaseAnalyticsHandler,

View file

@ -1,7 +1,5 @@
package com.tangem.tap.common.feedback
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
@ -45,15 +43,6 @@ class AdditionalFeedbackInfo {
var fee: String = ""
var token: String = ""
fun setAppVersion(context: Context) {
try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
appVersion = pInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
}
fun setCardInfo(data: ScanResponse) {
cardId = data.card.cardId
cardBlockchain = data.walletData?.blockchain ?: ""

View file

@ -1,12 +1,8 @@
package com.tangem.tap.domain.configurable.config
import android.content.Context
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.readAssetAsString
import com.tangem.tap.common.AssetReader
import com.tangem.tap.domain.configurable.Loader
import timber.log.Timber
@ -14,8 +10,8 @@ import timber.log.Timber
[REDACTED_AUTHOR]
*/
class FeaturesLocalLoader(
private val context: Context,
private val moshi: Moshi,
private val assetReader: AssetReader,
private val moshi: Moshi,
) : Loader<ConfigModel> {
override fun load(onComplete: (ConfigModel) -> Unit) {
@ -23,8 +19,8 @@ class FeaturesLocalLoader(
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
val valuesAdapter: JsonAdapter<ConfigValueModel> = moshi.adapter(ConfigValueModel::class.java)
val jsonFeatures = context.readAssetAsString(Loader.featuresName)
val jsonConfigValues = context.readAssetAsString(Loader.configValuesName)
val jsonFeatures = assetReader.readAssetAsString(Loader.featuresName)
val jsonConfigValues = assetReader.readAssetAsString(Loader.configValuesName)
ConfigModel(featureAdapter.fromJson(jsonFeatures), valuesAdapter.fromJson(jsonConfigValues))
} catch (ex: Exception) {
@ -33,31 +29,4 @@ class FeaturesLocalLoader(
}
onComplete(config)
}
}
class FeaturesRemoteLoader(
private val moshi: Moshi,
) : Loader<ConfigModel> {
override fun load(onComplete: (ConfigModel) -> Unit) {
val emptyConfig = ConfigModel.empty()
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener {
if (it.isSuccessful) {
val config = remoteConfig.getValue(Loader.featuresName)
val jsonConfig = config.asString()
if (jsonConfig.isEmpty()) {
onComplete(emptyConfig)
return@addOnCompleteListener
}
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
onComplete(ConfigModel(featureAdapter.fromJson(jsonConfig), null))
} else {
onComplete(emptyConfig)
}
}.addOnFailureListener {
FirebaseAnalyticsHandler.logException("remote_config_error.features", it)
onComplete(emptyConfig)
}
}
}

View file

@ -24,26 +24,19 @@ data class Config(
val zendesk: ZendeskConfig? = null,
)
class ConfigManager(
private val localLoader: Loader<ConfigModel>,
private val remoteLoader: Loader<ConfigModel>
) {
class ConfigManager {
var config: Config = Config()
private set
private var defaultConfig = Config()
fun load(onComplete: ((config: Config) -> Unit)? = null) {
localLoader.load { configModel ->
fun load(configLoader: Loader<ConfigModel>, onComplete: ((config: Config) -> Unit)? = null) {
configLoader.load { configModel ->
setupFeature(configModel.features)
setupKey(configModel.configValues)
onComplete?.invoke(config)
}
// Uncomment to enable remote config
// remoteLoader.load { config ->
// setupFeature(config.features)
// }
}
fun turnOff(name: String) {

View file

@ -13,6 +13,7 @@ import com.tangem.common.extensions.toHexString
import com.tangem.domain.common.ScanResponse
import com.tangem.network.common.MoshiConverter
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.tap.common.AssetReader
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.tangemSdkManager
@ -119,10 +120,6 @@ class TwinCardsManager(
}
}
interface AssetReader {
fun readAssetAsString(name: String): String
}
private class Issuer(
val id: String,
val privateKey: String,

View file

@ -4,9 +4,9 @@ import com.tangem.Message
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.AssetReader
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.twins.AssetReader
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
import org.rekotlin.Action

View file

@ -15,10 +15,10 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.VoidCallback
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.tap.common.AndroidAssetReader
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.readAssetAsString
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.navigation.AppScreen
@ -26,7 +26,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.common.redux.navigation.ShareElement
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
import com.tangem.tap.domain.twins.AssetReader
import com.tangem.tap.domain.twins.TwinsCardWidget
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
@ -46,13 +45,6 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
private lateinit var twinsWidget: TwinsCardWidget
private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
private val assetReader: AssetReader by lazy {
object : AssetReader {
override fun readAssetAsString(name: String): String =
requireContext().readAssetAsString(name)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
@ -250,7 +242,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
store.dispatch(
TwinCardsAction.Wallet.LaunchFirstStep(
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
assetReader
AndroidAssetReader(requireContext()),
)
)
}