Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-27 13:22:43 +04:00
parent 1a0eaa779a
commit 654b2ff948
17 changed files with 103 additions and 36 deletions

View file

@ -15,7 +15,6 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -30,10 +29,7 @@ import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.wallets.models.UserWalletId
@ -218,7 +214,7 @@ fun getBalanceValueAndFlickerState(balance: UserWalletItemUM.Balance): Pair<Stri
is UserWalletItemUM.Balance.Failed -> DASH_SIGN to false
is UserWalletItemUM.Balance.Hidden -> THREE_STARS to false
is UserWalletItemUM.Balance.Loading -> null to false
is UserWalletItemUM.Balance.Locked -> stringResource(R.string.common_locked) to false
is UserWalletItemUM.Balance.Locked -> stringResourceSafe(R.string.common_locked) to false
is UserWalletItemUM.Balance.Loaded -> balance.value to balance.isFlickering
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.datasource.local.nft.converter
import android.content.res.Resources
import com.tangem.core.res.getStringSafe
import com.tangem.datasource.R
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.network.Network
@ -48,12 +49,12 @@ class NFTSdkCollectionConverter(
is NFTCollection.Identifier.EVM ->
name.toCollectionName()
is NFTCollection.Identifier.TON -> if (collectionId.contractAddress == null) {
resources.getString(R.string.nft_no_collection)
resources.getStringSafe(R.string.nft_no_collection)
} else {
name.toCollectionName()
}
is NFTCollection.Identifier.Solana -> if (collectionId.collectionAddress == null) {
resources.getString(R.string.nft_no_collection)
resources.getStringSafe(R.string.nft_no_collection)
} else {
name.toCollectionName()
}
@ -61,7 +62,7 @@ class NFTSdkCollectionConverter(
}
private fun String?.toCollectionName() = if (this.isNullOrEmpty()) {
resources.getString(R.string.nft_untitled_collection)
resources.getStringSafe(R.string.nft_untitled_collection)
} else {
this
}

View file

@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.PrimaryButton
@ -40,14 +39,14 @@ internal fun NFTCollectionsEmpty(state: NFTCollectionsUM.Empty, modifier: Modifi
)
Text(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing24),
text = stringResource(R.string.nft_collections_empty_title),
text = stringResourceSafe(R.string.nft_collections_empty_title),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
Text(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
text = stringResource(R.string.nft_collections_empty_description),
text = stringResourceSafe(R.string.nft_collections_empty_description),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,

View file

@ -7,7 +7,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -43,7 +42,8 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod
state = WalletArtworksState.Folded,
)
Column(
modifier = Modifier.weight(1 - ALL_STEPS_TOP_CONTAINER_WEIGHT)
modifier = Modifier
.weight(1 - ALL_STEPS_TOP_CONTAINER_WEIGHT)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(horizontal = 32.dp),
@ -51,7 +51,7 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod
) {
SpacerHMax()
Text(
text = stringResource(R.string.onboarding_create_wallet_header),
text = stringResourceSafe(R.string.onboarding_create_wallet_header),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
@ -59,7 +59,7 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod
)
SpacerH16()
Text(
text = stringResource(R.string.onboarding_create_wallet_body),
text = stringResourceSafe(R.string.onboarding_create_wallet_body),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,

View file

@ -10,7 +10,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
@ -134,7 +133,7 @@ private fun Content(
} else {
state.onAccessCodeFirstChange
},
label = stringResource(id = R.string.onboarding_wallet_info_title_third),
label = stringResourceSafe(id = R.string.onboarding_wallet_info_title_third),
isError = state.codesNotMatchError || state.atLeastMinCharsError,
visualTransformation = if (state.accessCodeHidden) {
PasswordVisualTransformation()
@ -143,9 +142,9 @@ private fun Content(
},
caption = when {
state.codesNotMatchError && reEnterAccessCodeState ->
stringResource(R.string.onboarding_access_codes_doesnt_match)
stringResourceSafe(R.string.onboarding_access_codes_doesnt_match)
state.atLeastMinCharsError && !reEnterAccessCodeState ->
stringResource(R.string.onboarding_access_code_too_short)
stringResourceSafe(R.string.onboarding_access_code_too_short)
else -> null
},
)

View file

@ -8,12 +8,12 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.ClickableText
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.extensions.appendColored
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.main.entity.OnrampMainComponentUM
@ -35,7 +35,7 @@ internal fun OnrampButtonComponent(state: OnrampMainComponentUM) {
OnrampTosText(providerState)
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.common_buy),
text = stringResourceSafe(id = R.string.common_buy),
onClick = state.buyButtonConfig.onClick,
enabled = state.buyButtonConfig.enabled,
)
@ -44,9 +44,9 @@ internal fun OnrampButtonComponent(state: OnrampMainComponentUM) {
@Composable
private fun OnrampTosText(provider: OnrampProviderBlockUM.Content?) {
val termsOfUse = stringResource(R.string.common_terms_of_use)
val privacyPolicy = stringResource(R.string.common_privacy_policy)
val tosText = stringResource(R.string.onramp_legal, termsOfUse, privacyPolicy)
val termsOfUse = stringResourceSafe(R.string.common_terms_of_use)
val privacyPolicy = stringResourceSafe(R.string.common_privacy_policy)
val tosText = stringResourceSafe(R.string.onramp_legal, termsOfUse, privacyPolicy)
val clickableAnnotation = buildAnnotatedString {
append(tosText.substringBefore(termsOfUse))

View file

@ -10,19 +10,19 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.extensions.resourceReference
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.divider.DividerWithPadding
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.walletconnect.impl.R
@ -75,7 +75,7 @@ internal fun WcEstimatedWalletChangesItem(item: WcEstimatedWalletChangesUM, modi
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(if (isExpanded) R.string.common_show_less else R.string.common_show_more),
text = stringResourceSafe(if (isExpanded) R.string.common_show_less else R.string.common_show_more),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.tertiary,
)

View file

@ -191,6 +191,7 @@ firebase-perf = { module = "com.google.firebase:firebase-perf" }
# region Detekt
detekt-compose = { module = "ru.kode:detekt-rules-compose", version.ref = "detektComposeRules" }
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
detekt-api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "detekt" }
# endregion Detekt
# region Test

View file

@ -22,13 +22,19 @@ private fun DetektExtension.configure(project: Project) {
ignoreFailures = false
autoCorrect = true
buildUponDefaultConfig = true
config.setFrom(project.rootProject.files("tangem-android-tools/detekt-config.yml"))
config.setFrom(
project.rootProject.files(
"tangem-android-tools/detekt-config.yml",
"plugins/detekt-rules/app-detekt-config.yml"
)
)
}
private fun Project.configureDetektPlugins() {
listOf(
listOfNotNull(
findLibrary(alias = "detekt-formatting"),
findLibrary(alias = "detekt-compose"),
findProject(":plugins:detekt-rules"),
).forEach {
dependencies.add(CONFIGURATION_DETEKT_PLUGINS, it)
}

1
plugins/detekt-rules/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,3 @@
tangem-rules:
UnsafeStringResourceUsage:
active: true

View file

@ -0,0 +1,7 @@
plugins {
alias(deps.plugins.kotlin.jvm)
}
dependencies {
implementation(deps.detekt.api)
}

View file

@ -0,0 +1,20 @@
package com.tangem.detekt
import com.tangem.detekt.rules.UnsafeStringResourceUsage
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
class TangemRuleSetProvider : RuleSetProvider {
override val ruleSetId: String = "tangem-rules"
override fun instance(config: Config): RuleSet {
return RuleSet(
id = ruleSetId,
rules = listOf(
UnsafeStringResourceUsage(config),
)
)
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.detekt.rules
import io.gitlab.arturbosch.detekt.api.*
import org.jetbrains.kotlin.psi.*
class UnsafeStringResourceUsage(config: Config) : Rule(config) {
override val issue: Issue = Issue(
id = "UnsafeStringResourceUsage",
severity = Severity.Security,
description = "Avoid using stringResource directly in the code.",
debt = Debt.FIVE_MINS,
)
val unsafeFunctionNames = listOf("stringResource", "pluralStringResource")
override fun visitCallExpression(expression: KtCallExpression) {
super.visitCallExpression(expression)
val functionName = expression.calleeExpression?.text
if (functionName in unsafeFunctionNames) {
report(
CodeSmell(
issue = issue,
entity = Entity.from(expression),
message = "Usage of `$functionName` is unsafe. Use the version of the function with the `Safe` " +
"suffix. For example, `${functionName}Safe`."
)
)
}
}
}

View file

@ -0,0 +1 @@
com.tangem.detekt.TangemRuleSetProvider

View file

@ -134,6 +134,7 @@ dependencyResolutionManagement {
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
include(":app")
include(":plugins:detekt-rules")
// region Core modules
include(":core:analytics")

@ -1 +1 @@
Subproject commit 4eb84b4150ab0b03400707a6f121d4fd313e3cef
Subproject commit 53e359bc2ead37feeb9e70d7004c91cde07ccf9b