Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-22 11:55:20 +04:00
parent a96fb4b2f4
commit f43fb6a8ac
23 changed files with 122 additions and 177 deletions

View file

@ -1,26 +1,27 @@
package com.tangem.datasource.info
package com.tangem.tap.data
import android.os.Build
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
import java.util.*
import com.tangem.wallet.BuildConfig
import java.util.Locale
import java.util.TimeZone
import javax.inject.Inject
internal class AndroidAppInfoProvider @Inject constructor(
private val appVersionProvider: AppVersionProvider,
) : AppInfoProvider {
internal class DefaultAppInfoProvider @Inject constructor() : AppInfoProvider {
override val platform: String
get() = "Android"
override val device: String
get() = "${Build.MANUFACTURER} ${Build.MODEL}"
override val osVersion: String
get() = Build.VERSION.RELEASE
override val sdkVersion: Int
get() = Build.VERSION.SDK_INT
override val language: String
get() = Locale.getDefault().language
get() = Locale.getDefault().toLanguageTag()
override val timezone: String
get() = TimeZone.getDefault().id
override val appVersion: String
get() = appVersionProvider.versionName
override val appVersion: String = BuildConfig.VERSION_NAME
override val appVersionCode: Int = BuildConfig.VERSION_CODE
override val isHuaweiDevice: Boolean
get() = Build.MANUFACTURER.equals("HUAWEI", ignoreCase = true) ||
Build.BRAND.equals("HUAWEI", ignoreCase = true)

View file

@ -32,7 +32,6 @@ import com.tangem.tap.foregroundActivityObserver
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
import javax.inject.Singleton
@ -48,7 +47,6 @@ internal class DefaultCardSdkProvider @Inject constructor(
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
private val dispatchers: CoroutineDispatcherProvider,
private val apiConfigsManager: ApiConfigsManager,
appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider,
authProvider: AuthProvider,
) : CardSdkProvider, CardSdkOwner {
@ -74,10 +72,7 @@ internal class DefaultCardSdkProvider @Inject constructor(
val apiEnvironment = Provider {
apiConfigsManager.getEnvironmentConfig(ApiConfig.ID.TangemTech).environment
}
val platformHeaders = RequestHeader.AppVersionPlatformHeaders(
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
)
val platformHeaders = RequestHeader.AppVersionPlatformHeaders(appInfoProvider)
val apiKeyHeader = RequestHeader.TangemApiKeyHeader(authProvider, apiEnvironment)
TangemApiServiceSettings.addInterceptors(
AddHeadersInterceptor(platformHeaders.values + apiKeyHeader.values),

View file

@ -13,6 +13,8 @@ import com.tangem.tap.common.settings.IntentSettingsManager
import com.tangem.tap.common.share.IntentShareManager
import com.tangem.tap.common.url.CustomTabsUrlOpener
import com.tangem.tap.core.DefaultAppCoroutineScope
import com.tangem.tap.data.DefaultAppInfoProvider
import com.tangem.utils.info.AppInfoProvider
import dagger.Binds
import dagger.Module
import dagger.Provides
@ -28,6 +30,10 @@ internal interface UtilsModule {
@Binds
fun provideAppScope(defaultAppScope: DefaultAppCoroutineScope): AppCoroutineScope
@Binds
@Singleton
fun bindAppInfoProvider(impl: DefaultAppInfoProvider): AppInfoProvider
companion object {
@Provides

View file

@ -1,11 +0,0 @@
package com.tangem.tap.network.auth
import com.tangem.utils.version.AppVersionProvider
import com.tangem.wallet.BuildConfig
internal class DefaultAppVersionProvider : AppVersionProvider {
override val versionName: String = BuildConfig.VERSION_NAME
override val versionCode: Int = BuildConfig.VERSION_CODE
}

View file

@ -7,7 +7,6 @@ import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.P2PEthPoolAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.tap.network.auth.*
import com.tangem.utils.version.AppVersionProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -47,10 +46,4 @@ internal class AuthModule {
fun provideP2PEthPoolAuthProvider(environmentConfig: EnvironmentConfig): P2PEthPoolAuthProvider {
return DefaultP2PEthPoolAuthProvider(environmentConfig)
}
@Provides
@Singleton
fun provideAppVersionProvider(): AppVersionProvider {
return DefaultAppVersionProvider()
}
}

View file

@ -6,20 +6,17 @@ import com.tangem.datasource.utils.RequestHeader
import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
/**
* Express [ApiConfig]
*
* @property environmentConfig environment config
* @property expressAuthProvider express auth provider
* @property appVersionProvider app version provider
* @property appInfoProvider app info provider
*/
internal class Express(
private val environmentConfig: EnvironmentConfig,
private val expressAuthProvider: ExpressAuthProvider,
private val appVersionProvider: AppVersionProvider,
private val appInfoProvider: AppInfoProvider,
) : ApiConfig() {
@ -102,7 +99,7 @@ internal class Express(
private fun createHeaders(isProd: Boolean) = buildMap {
put(key = "api-key", value = ProviderSuspend { getApiKey(isProd) })
put(key = "session-id", value = ProviderSuspend(expressAuthProvider::getSessionId))
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
putAll(from = RequestHeader.AppVersionPlatformHeaders(appInfoProvider).values)
}
private fun getApiKey(isProd: Boolean): String {

View file

@ -6,14 +6,12 @@ import com.tangem.datasource.utils.RequestHeader
import com.tangem.utils.Provider
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
/**
* Gasless transactions [ApiConfig]
*/
internal class GaslessTxService(
private val authProvider: AuthProvider,
private val appVersionProvider: AppVersionProvider,
private val appInfoProvider: AppInfoProvider,
) : ApiConfig() {
@ -50,7 +48,7 @@ internal class GaslessTxService(
)
private fun createHeaders(environment: ApiEnvironment) = buildMap {
putAll(RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
putAll(RequestHeader.AppVersionPlatformHeaders(appInfoProvider).values)
put(
key = "Authorization",
value = ProviderSuspend {

View file

@ -5,14 +5,12 @@ import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.utils.RequestHeader
import com.tangem.utils.Provider
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
/**
* News [ApiConfig]
[REDACTED_AUTHOR]
*/
internal class News(
private val appVersionProvider: AppVersionProvider,
private val appInfoProvider: AppInfoProvider,
private val authProvider: AuthProvider,
) : ApiConfig() {
@ -64,7 +62,7 @@ internal class News(
apiEnvironment = Provider { environment },
).values,
)
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
putAll(from = RequestHeader.AppVersionPlatformHeaders(appInfoProvider).values)
}
private companion object {

View file

@ -3,11 +3,11 @@ package com.tangem.datasource.api.common.config
import com.tangem.datasource.BuildConfig
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.version.AppVersionProvider
import com.tangem.utils.info.AppInfoProvider
internal sealed class TangemPay(
private val environmentConfig: EnvironmentConfig,
private val appVersionProvider: AppVersionProvider,
private val appInfoProvider: AppInfoProvider,
) : ApiConfig() {
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
@ -52,7 +52,7 @@ internal sealed class TangemPay(
)
private fun createHeaders(apiEnvironment: ApiEnvironment) = mapOf(
"version" to ProviderSuspend { appVersionProvider.versionName },
"version" to ProviderSuspend { appInfoProvider.appVersion },
"platform" to ProviderSuspend { "Android" },
"X-API-KEY" to ProviderSuspend { getBffStaticToken(apiEnvironment) },
)
@ -74,8 +74,8 @@ internal sealed class TangemPay(
class Bff(
environmentConfig: EnvironmentConfig,
appVersionProvider: AppVersionProvider,
) : TangemPay(environmentConfig, appVersionProvider) {
appInfoProvider: AppInfoProvider,
) : TangemPay(environmentConfig, appInfoProvider) {
override fun getBaseUrl(apiEnvironment: ApiEnvironment): String {
return when (apiEnvironment) {
ApiEnvironment.DEV -> "https://api.dev.us.paera.com/bff-v2/"
@ -93,8 +93,8 @@ internal sealed class TangemPay(
class Auth(
environmentConfig: EnvironmentConfig,
appVersionProvider: AppVersionProvider,
) : TangemPay(environmentConfig, appVersionProvider) {
appInfoProvider: AppInfoProvider,
) : TangemPay(environmentConfig, appInfoProvider) {
override fun getBaseUrl(apiEnvironment: ApiEnvironment): String {
return when (apiEnvironment) {
ApiEnvironment.DEV -> "https://api.dev.us.paera.com/"

View file

@ -5,11 +5,9 @@ import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.utils.RequestHeader
import com.tangem.utils.Provider
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
/** TangemTech [ApiConfig] */
internal class TangemTech(
private val appVersionProvider: AppVersionProvider,
private val authProvider: AuthProvider,
private val appInfoProvider: AppInfoProvider,
) : ApiConfig() {
@ -62,7 +60,7 @@ internal class TangemTech(
private fun createHeaders(apiEnvironment: ApiEnvironment) = buildMap {
putAll(from = RequestHeader.TangemApiKeyHeader(authProvider, Provider { apiEnvironment }).values)
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
putAll(from = RequestHeader.AppVersionPlatformHeaders(appInfoProvider).values)
putAll(from = RequestHeader.AuthenticationHeader(authProvider).values)
}
}

View file

@ -6,12 +6,10 @@ import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.datasource.utils.RequestHeader
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
/** YieldSupply [ApiConfig] */
internal class YieldSupply(
private val environmentConfig: EnvironmentConfig,
private val appVersionProvider: AppVersionProvider,
private val authProvider: AuthProvider,
private val appInfoProvider: AppInfoProvider,
) : ApiConfig() {
@ -66,7 +64,7 @@ internal class YieldSupply(
put(key = "api-key", value = ProviderSuspend {
getApiKey(apiEnvironment)
})
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
putAll(from = RequestHeader.AppVersionPlatformHeaders(appInfoProvider).values)
putAll(from = RequestHeader.AuthenticationHeader(authProvider).values)
}

View file

@ -7,7 +7,6 @@ import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.P2PEthPoolAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -23,13 +22,11 @@ internal object ApiConfigsModule {
fun provideExpressConfig(
environmentConfig: EnvironmentConfig,
expressAuthProvider: ExpressAuthProvider,
appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider,
): ApiConfig {
return Express(
environmentConfig = environmentConfig,
expressAuthProvider = expressAuthProvider,
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
)
}
@ -48,55 +45,47 @@ internal object ApiConfigsModule {
@Provides
@IntoSet
fun provideTangemTechConfig(
appVersionProvider: AppVersionProvider,
authProvider: AuthProvider,
appInfoProvider: AppInfoProvider,
): ApiConfig = TangemTech(
appVersionProvider = appVersionProvider,
authProvider = authProvider,
appInfoProvider = appInfoProvider,
)
fun provideTangemTechConfig(authProvider: AuthProvider, appInfoProvider: AppInfoProvider): ApiConfig {
return TangemTech(
authProvider = authProvider,
appInfoProvider = appInfoProvider,
)
}
@Provides
@IntoSet
fun provideNewsConfig(
appVersionProvider: AppVersionProvider,
authProvider: AuthProvider,
appInfoProvider: AppInfoProvider,
): ApiConfig = News(
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
authProvider = authProvider,
)
fun provideNewsConfig(authProvider: AuthProvider, appInfoProvider: AppInfoProvider): ApiConfig {
return News(
appInfoProvider = appInfoProvider,
authProvider = authProvider,
)
}
@Provides
@IntoSet
fun provideYieldSupplyConfig(
environmentConfig: EnvironmentConfig,
appVersionProvider: AppVersionProvider,
authProvider: AuthProvider,
appInfoProvider: AppInfoProvider,
): ApiConfig = YieldSupply(
environmentConfig = environmentConfig,
appVersionProvider = appVersionProvider,
authProvider = authProvider,
appInfoProvider = appInfoProvider,
)
): ApiConfig {
return YieldSupply(
environmentConfig = environmentConfig,
authProvider = authProvider,
appInfoProvider = appInfoProvider,
)
}
@Provides
@IntoSet
fun provideTangemPayBffConfig(
environmentConfig: EnvironmentConfig,
appVersionProvider: AppVersionProvider,
): ApiConfig = TangemPay.Bff(environmentConfig, appVersionProvider)
fun provideTangemPayBffConfig(environmentConfig: EnvironmentConfig, appInfoProvider: AppInfoProvider): ApiConfig {
return TangemPay.Bff(environmentConfig, appInfoProvider)
}
@Provides
@IntoSet
fun provideTangemPayAuthConfig(
environmentConfig: EnvironmentConfig,
appVersionProvider: AppVersionProvider,
): ApiConfig = TangemPay.Auth(environmentConfig, appVersionProvider)
fun provideTangemPayAuthConfig(environmentConfig: EnvironmentConfig, appInfoProvider: AppInfoProvider): ApiConfig {
return TangemPay.Auth(environmentConfig, appInfoProvider)
}
@Provides
@IntoSet
@ -112,14 +101,9 @@ internal object ApiConfigsModule {
@Provides
@IntoSet
fun provideGaslessServiceConfig(
appVersionProvider: AppVersionProvider,
authProvider: AuthProvider,
appInfoProvider: AppInfoProvider,
): ApiConfig {
fun provideGaslessServiceConfig(authProvider: AuthProvider, appInfoProvider: AppInfoProvider): ApiConfig {
return GaslessTxService(
authProvider = authProvider,
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
)
}

View file

@ -1,21 +0,0 @@
package com.tangem.datasource.di
import com.tangem.datasource.info.AndroidAppInfoProvider
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object AppInfoModule {
@Singleton
@Provides
fun provideAppInfoProvider(appVersionProvider: AppVersionProvider): AppInfoProvider {
return AndroidAppInfoProvider(appVersionProvider)
}
}

View file

@ -1,14 +1,11 @@
package com.tangem.datasource.utils
import android.os.Build
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.utils.RequestHeader.CacheControlHeader.checkHeaderValueOrEmpty
import com.tangem.utils.Provider
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
import java.util.Locale
import java.util.TimeZone
/**
@ -29,17 +26,16 @@ sealed class RequestHeader(vararg pairs: Pair<String, ProviderSuspend<String>>)
)
class AppVersionPlatformHeaders(
appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider,
) : RequestHeader(
"system_version" to ProviderSuspend { appInfoProvider.osVersion },
"version" to ProviderSuspend { appVersionProvider.versionName },
"version" to ProviderSuspend { appInfoProvider.appVersion },
"platform" to ProviderSuspend { "android" },
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
"language" to ProviderSuspend { appInfoProvider.language.checkHeaderValueOrEmpty() },
"timezone" to ProviderSuspend {
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
},
"device" to ProviderSuspend { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
"device" to ProviderSuspend { appInfoProvider.device.checkHeaderValueOrEmpty() },
)
/**

View file

@ -51,21 +51,18 @@ class ApiConfigTest {
Express(
environmentConfig = environmentConfig,
expressAuthProvider = mockk(),
appVersionProvider = mockk(),
appInfoProvider = mockk(),
)
}
ApiConfig.ID.YieldSupply -> {
YieldSupply(
environmentConfig = environmentConfig,
appVersionProvider = mockk(),
authProvider = appAuthProvider,
appInfoProvider = mockk(),
)
}
ApiConfig.ID.TangemTech -> {
TangemTech(
appVersionProvider = mockk(),
authProvider = appAuthProvider,
appInfoProvider = mockk(),
)
@ -73,23 +70,21 @@ class ApiConfigTest {
ApiConfig.ID.StakeKit -> StakeKit(stakeKitAuthProvider = mockk())
ApiConfig.ID.TangemPay -> TangemPay.Bff(
environmentConfig = environmentConfig,
appVersionProvider = mockk(),
appInfoProvider = mockk(),
)
ApiConfig.ID.TangemPayAuth -> TangemPay.Auth(
environmentConfig = environmentConfig,
appVersionProvider = mockk(),
appInfoProvider = mockk(),
)
ApiConfig.ID.BlockAid -> BlockAid(environmentConfig = environmentConfig)
ApiConfig.ID.MoonPay -> MoonPay()
ApiConfig.ID.P2PEthPool -> P2PEthPool(p2pAuthProvider = mockk())
ApiConfig.ID.News -> News(
appVersionProvider = mockk(),
authProvider = appAuthProvider,
appInfoProvider = mockk(),
)
ApiConfig.ID.GaslessTxService -> GaslessTxService(
authProvider = appAuthProvider,
appVersionProvider = mockk(),
appInfoProvider = mockk(),
)
}

View file

@ -19,7 +19,6 @@ import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.test.core.ProvideTestModels
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.every
@ -38,7 +37,6 @@ import java.util.TimeZone
internal class ProdApiConfigsManagerTest {
private val environmentConfig = createMockEnvironmentConfig()
private val appVersionProvider = mockk<AppVersionProvider>()
private val expressAuthProvider = mockk<ExpressAuthProvider>()
private val stakeKitAuthProvider = mockk<StakeKitAuthProvider>()
private val p2pEthPoolAuthProvider = mockk<P2PEthPoolAuthProvider>()
@ -52,14 +50,13 @@ internal class ProdApiConfigsManagerTest {
@BeforeEach
fun setup() {
clearMocks(
appVersionProvider,
expressAuthProvider,
stakeKitAuthProvider,
appAuthProvider,
appInfoProvider,
)
every { appVersionProvider.versionName } returns VERSION_NAME
every { appInfoProvider.appVersion } returns VERSION_NAME
every { expressAuthProvider.getSessionId() } returns EXPRESS_SESSION_ID
every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY
every { p2pEthPoolAuthProvider.getApiKey() } returns P2P_API_KEY
@ -71,6 +68,8 @@ internal class ProdApiConfigsManagerTest {
coEvery { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY
every { appInfoProvider.osVersion } returns "Android 16"
every { appInfoProvider.language } returns Locale.getDefault().toLanguageTag()
every { appInfoProvider.device } returns "${Build.MANUFACTURER} ${Build.MODEL}"
manager = ProdApiConfigsManager(apiConfigs = createApiConfigs())
}
@ -94,21 +93,18 @@ internal class ProdApiConfigsManagerTest {
Express(
environmentConfig = environmentConfig,
expressAuthProvider = expressAuthProvider,
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
)
}
ApiConfig.ID.YieldSupply -> {
YieldSupply(
environmentConfig = environmentConfig,
appVersionProvider = appVersionProvider,
authProvider = appAuthProvider,
appInfoProvider = appInfoProvider,
)
}
ApiConfig.ID.TangemTech -> {
TangemTech(
appVersionProvider = appVersionProvider,
authProvider = appAuthProvider,
appInfoProvider = appInfoProvider,
)
@ -116,23 +112,21 @@ internal class ProdApiConfigsManagerTest {
ApiConfig.ID.StakeKit -> StakeKit(stakeKitAuthProvider = stakeKitAuthProvider)
ApiConfig.ID.TangemPay -> TangemPay.Bff(
environmentConfig = environmentConfig,
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
)
ApiConfig.ID.TangemPayAuth -> TangemPay.Auth(
environmentConfig = environmentConfig,
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
)
ApiConfig.ID.BlockAid -> BlockAid(environmentConfig = environmentConfig)
ApiConfig.ID.MoonPay -> MoonPay()
ApiConfig.ID.P2PEthPool -> P2PEthPool(p2pAuthProvider = p2pEthPoolAuthProvider)
ApiConfig.ID.News -> News(
appVersionProvider = appVersionProvider,
authProvider = appAuthProvider,
appInfoProvider = appInfoProvider,
)
ApiConfig.ID.GaslessTxService -> GaslessTxService(
authProvider = appAuthProvider,
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
)
}
@ -195,7 +189,7 @@ internal class ProdApiConfigsManagerTest {
"version" to ProviderSuspend { VERSION_NAME },
"system_version" to ProviderSuspend { "Android 16" },
"platform" to ProviderSuspend { "android" },
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
"language" to ProviderSuspend { Locale.getDefault().toLanguageTag().checkHeaderValueOrEmpty() },
"timezone" to ProviderSuspend {
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
},
@ -218,7 +212,7 @@ internal class ProdApiConfigsManagerTest {
"version" to ProviderSuspend { VERSION_NAME },
"platform" to ProviderSuspend { "android" },
"system_version" to ProviderSuspend { "Android 16" },
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
"language" to ProviderSuspend { Locale.getDefault().toLanguageTag().checkHeaderValueOrEmpty() },
"timezone" to ProviderSuspend {
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
},
@ -241,7 +235,7 @@ internal class ProdApiConfigsManagerTest {
"version" to ProviderSuspend { VERSION_NAME },
"platform" to ProviderSuspend { "android" },
"system_version" to ProviderSuspend { "Android 16" },
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
"language" to ProviderSuspend { Locale.getDefault().toLanguageTag().checkHeaderValueOrEmpty() },
"timezone" to ProviderSuspend {
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
},
@ -316,7 +310,7 @@ internal class ProdApiConfigsManagerTest {
"version" to ProviderSuspend { VERSION_NAME },
"platform" to ProviderSuspend { "android" },
"system_version" to ProviderSuspend { "Android 16" },
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
"language" to ProviderSuspend { Locale.getDefault().toLanguageTag().checkHeaderValueOrEmpty() },
"timezone" to ProviderSuspend {
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
},
@ -394,7 +388,7 @@ internal class ProdApiConfigsManagerTest {
"version" to ProviderSuspend { VERSION_NAME },
"platform" to ProviderSuspend { "android" },
"system_version" to ProviderSuspend { "Android 16" },
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
"language" to ProviderSuspend { Locale.getDefault().toLanguageTag().checkHeaderValueOrEmpty() },
"timezone" to ProviderSuspend {
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
},

View file

@ -1,11 +1,44 @@
package com.tangem.utils.info
/**
* Runtime information about the host application and device.
*
* Consumed by API layers to populate request headers and bodies (see
* `RequestHeader.AppVersionPlatformHeaders` and push-notification registration) and by feature
* code that needs to branch on platform / vendor.
*/
interface AppInfoProvider {
/** Platform identifier, e.g. `"Android"`. */
val platform: String
/** Human-readable device name in the form `"$MANUFACTURER $MODEL"`, e.g. `"Google Pixel 8"`. */
val device: String
/** OS release string, e.g. `"14"` on Android 14. Corresponds to `Build.VERSION.RELEASE`. */
val osVersion: String
/**
* Android API level of the running system, e.g. `34` on Android 14. Corresponds to
* `Build.VERSION.SDK_INT`. Use this (not [osVersion]) when branching by framework capability.
*/
val sdkVersion: Int
/** Current locale as a BCP 47 language tag (e.g. `"en-US"`, `"zh-CN"`). */
val language: String
/** IANA time-zone id of the device's current time zone, e.g. `"Europe/Moscow"`, `"UTC"`. */
val timezone: String
/** User-visible app version string (e.g. `"5.36.4"`), matching `BuildConfig.VERSION_NAME`. */
val appVersion: String
/** Monotonically-increasing internal build number, matching `BuildConfig.VERSION_CODE`. */
val appVersionCode: Int
/**
* `true` if the device manufacturer or brand is Huawei. Used to gate features that depend on
* Google Play Services (HMS-only devices cannot rely on FCM, Play Billing, etc.).
*/
val isHuaweiDevice: Boolean
}

View file

@ -1,8 +0,0 @@
package com.tangem.utils.version
interface AppVersionProvider {
val versionName: String
val versionCode: Int
}

View file

@ -1,6 +1,5 @@
package com.tangem.data.feedback
import android.os.Build
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.core.navigation.email.EmailSender
import com.tangem.data.feedback.converters.BlockchainInfoConverter
@ -14,8 +13,8 @@ import com.tangem.domain.models.network.Network
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.logging.TangemLogger
import com.tangem.utils.version.AppVersionProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import java.io.File
@ -27,7 +26,7 @@ import java.io.File
* @property userWalletsListRepository repository for getting user wallets
* @property walletManagersStore wallet managers store
* @property emailSender email sender
* @property appVersionProvider app version provider
* @property appInfoProvider app info provider
*
[REDACTED_AUTHOR]
*/
@ -36,7 +35,7 @@ internal class DefaultFeedbackRepository(
private val userWalletsListRepository: UserWalletsListRepository,
private val walletManagersStore: WalletManagersStore,
private val emailSender: EmailSender,
private val appVersionProvider: AppVersionProvider,
private val appInfoProvider: AppInfoProvider,
) : FeedbackRepository {
private val blockchainsErrors = MutableStateFlow<Map<UserWalletId, BlockchainErrorInfo>>(emptyMap())
@ -77,9 +76,9 @@ internal class DefaultFeedbackRepository(
override fun getPhoneInfo(): PhoneInfo {
return PhoneInfo(
phoneModel = Build.MODEL,
osVersion = Build.VERSION.SDK_INT.toString(),
appVersion = appVersionProvider.versionName,
phoneModel = appInfoProvider.device,
osVersion = appInfoProvider.sdkVersion.toString(),
appVersion = appInfoProvider.appVersion,
)
}

View file

@ -9,7 +9,7 @@ import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.feedback.repository.FeedbackFeatureToggles
import com.tangem.domain.feedback.repository.FeedbackRepository
import com.tangem.utils.version.AppVersionProvider
import com.tangem.utils.info.AppInfoProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -27,13 +27,13 @@ internal object FeedbackModule {
userWalletsListRepository: UserWalletsListRepository,
walletManagersStore: WalletManagersStore,
emailSender: EmailSender,
appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider,
): FeedbackRepository {
return DefaultFeedbackRepository(
appLogsStore = appLogsStore,
walletManagersStore = walletManagersStore,
emailSender = emailSender,
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
userWalletsListRepository = userWalletsListRepository,
)
}

View file

@ -36,7 +36,7 @@ import com.tangem.features.details.entity.SelectEmailFeedbackTypeBS
import com.tangem.features.details.utils.ItemsBuilder
import com.tangem.features.details.utils.SocialsBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.version.AppVersionProvider
import com.tangem.utils.info.AppInfoProvider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
@ -54,7 +54,7 @@ internal class DetailsModel @Inject constructor(
paramsContainer: ParamsContainer,
feedbackFeatureToggles: FeedbackFeatureToggles,
private val itemsBuilder: ItemsBuilder,
private val appVersionProvider: AppVersionProvider,
private val appInfoProvider: AppInfoProvider,
private val checkIsWalletConnectAvailableUseCase: CheckIsWalletConnectAvailableUseCase,
private val router: Router,
private val urlOpener: UrlOpener,
@ -269,7 +269,7 @@ internal class DetailsModel @Inject constructor(
}
}
private fun getAppVersion(): String = "${appVersionProvider.versionName} (${appVersionProvider.versionCode})"
private fun getAppVersion(): String = "${appInfoProvider.appVersion} (${appInfoProvider.appVersionCode})"
private companion object {
val SYSTEM_LANGUAGE = runCatching { Resources.getSystem().configuration.locales[0].language }.getOrElse { "" }

View file

@ -13,7 +13,7 @@ import com.tangem.feature.tester.presentation.excludedblockchains.state.Blockcha
import com.tangem.feature.tester.presentation.excludedblockchains.state.ExcludedBlockchainsScreenUM
import com.tangem.feature.tester.presentation.excludedblockchains.state.mapper.toUiModels
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
import com.tangem.utils.version.AppVersionProvider
import com.tangem.utils.info.AppInfoProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate
@ -25,7 +25,7 @@ import javax.inject.Inject
@HiltViewModel
internal class ExcludedBlockchainsViewModel @Inject constructor(
private val appVersionProvider: AppVersionProvider,
private val appInfoProvider: AppInfoProvider,
excludedBlockchainsManager: ExcludedBlockchainsManager,
) : ViewModel() {
@ -56,7 +56,7 @@ internal class ExcludedBlockchainsViewModel @Inject constructor(
search = getInitialSearchBar(),
blockchains = getBlockchains(),
showRecoverWarning = !excludedBlockchainsManager.isMatchLocalConfig(),
appVersion = appVersionProvider.versionName,
appVersion = appInfoProvider.appVersion,
onRestartClick = {},
onRecoverClick = ::recoverLocalConfig,
)

View file

@ -13,7 +13,7 @@ import com.tangem.feature.tester.presentation.common.components.appbar.TopBarWit
import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle
import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
import com.tangem.utils.version.AppVersionProvider
import com.tangem.utils.info.AppInfoProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@ -25,14 +25,14 @@ import javax.inject.Inject
* ViewModel for screen with list of feature toggles
*
* @property featureTogglesManager manager for getting information about the availability of feature toggles
* @property appVersionProvider app version provider
* @property appInfoProvider app info provider
*
[REDACTED_AUTHOR]
*/
@HiltViewModel
internal class FeatureTogglesViewModel @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
private val appVersionProvider: AppVersionProvider,
private val appInfoProvider: AppInfoProvider,
) : ViewModel() {
/** Current ui state */
@ -55,7 +55,7 @@ internal class FeatureTogglesViewModel @Inject constructor(
private fun initState(): FeatureTogglesContentState {
return FeatureTogglesContentState(
topBar = getConfigSetupState(isPrimarySetup = true),
appVersion = appVersionProvider.versionName,
appVersion = appInfoProvider.appVersion,
featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles(),
onToggleValueChange = ::onToggleValueChange,
onRestartAppClick = {},