Updated on 2026-08-14

This commit is contained in:
Tangem 2024-12-16 18:07:48 +03:00
commit 8232cc6e18
255 changed files with 2657 additions and 2338 deletions

44
Dockerfile Normal file
View file

@ -0,0 +1,44 @@
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
ENV ANDROID_HOME=/opt/android-sdk
ENV BUNDLE_PATH=vendor/bundle
ENV PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/34.0.0:$BUNDLE_PATH/bin
ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
RUN apt-get update && apt-get install -y \
openjdk-17-jdk \
wget \
unzip \
curl \
git \
ruby \
ruby-dev \
build-essential \
&& apt-get clean
RUN mkdir -p $ANDROID_HOME/cmdline-tools/latest && \
wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -O /tmp/cmdline-tools.zip && \
unzip /tmp/cmdline-tools.zip -d $ANDROID_HOME/cmdline-tools/latest && \
mv $ANDROID_HOME/cmdline-tools/latest/cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest/ && \
rm -rf $ANDROID_HOME/cmdline-tools/latest/cmdline-tools && \
rm -f /tmp/cmdline-tools.zip && \
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses && \
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-31" "platforms;android-34" "build-tools;34.0.0"
RUN wget https://github.com/lzhiyong/android-sdk-tools/releases/download/34.0.3/android-sdk-tools-static-aarch64.zip -O /tmp/android-sdk-tools-static-aarch64.zip && \
unzip /tmp/android-sdk-tools-static-aarch64.zip -d /tmp/android-sdk-tools-static-arm && \
cp -r /tmp/android-sdk-tools-static-arm/build-tools/* $ANDROID_HOME/build-tools/34.0.0/ && \
rm -rf /tmp/android-sdk-tools-static-arm /tmp/android-sdk-tools-static-aarch64.zip
RUN gem install bundler:2.5.23
RUN gem install fastlane -v 2.211.0 -N -V
RUN gem install fastlane-plugin-firebase_app_distribution -v 0.9.1 -N -V
COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs=4 --retry=3 --verbose
RUN bundle exec fastlane -v
CMD ["bash"]

View file

@ -166,6 +166,7 @@ dependencies {
implementation(deps.androidx.browser)
implementation(deps.androidx.paging.runtime)
implementation(deps.androidx.swipeRefreshLayout)
implementation(deps.androidx.fragment.compose)
implementation(deps.lifecycle.runtime.ktx)
implementation(deps.lifecycle.common.java8)
implementation(deps.lifecycle.viewModel.ktx)
@ -226,7 +227,6 @@ dependencies {
implementation(deps.xmlShimmer)
implementation(deps.viewBindingDelegate)
implementation(deps.armadillo)
implementation(deps.mviCore.watcher)
implementation(deps.kotlin.serialization)
implementation(deps.reownCore)
implementation(deps.reownWeb3)

View file

@ -35,7 +35,6 @@ import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.tap.common.log.TangemAppLoggerInitializer
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.EntryPoint
@ -112,8 +111,6 @@ interface ApplicationEntryPoint {
fun getTransactionSignerFactory(): TransactionSignerFactory
fun getHomeFeatureToggles(): HomeFeatureToggles
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
fun getOnrampFeatureToggles(): OnrampFeatureToggles

View file

@ -87,6 +87,7 @@ import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphAction
import com.tangem.tap.routing.RoutingComponent
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.tap.routing.toggle.RoutingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import com.tangem.wallet.R
@ -209,6 +210,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
lateinit var dispatchers: CoroutineDispatcherProvider
@Inject
internal lateinit var routingFeatureToggles: RoutingFeatureToggles
internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
@ -255,8 +259,16 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
installActivityDependencies()
observeAppThemeModeUpdates()
setContentView(R.layout.activity_main)
installRouting()
if (routingFeatureToggles.isNavigationRefactoringEnabled) {
TODO(
"Will be implemented in [REDACTED_JIRA] " +
"and in [REDACTED_JIRA]",
)
} else {
setContentView(R.layout.activity_main)
installRouting()
}
initContent()
observeStateUpdates()

View file

@ -57,7 +57,6 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.tasks.product.DerivationsFinder
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -177,9 +176,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val transactionSignerFactory: TransactionSignerFactory
get() = entryPoint.getTransactionSignerFactory()
private val homeFeatureToggles: HomeFeatureToggles
get() = entryPoint.getHomeFeatureToggles()
private val getUserCountryUseCase: GetUserCountryUseCase
get() = entryPoint.getGetUserCountryCodeUseCase()
@ -272,7 +268,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
shareManager = shareManager,
appRouter = appRouter,
transactionSignerFactory = transactionSignerFactory,
homeFeatureToggles = homeFeatureToggles,
getUserCountryUseCase = getUserCountryUseCase,
onrampFeatureToggles = onrampFeatureToggles,
environmentConfigStorage = environmentConfigStorage,

View file

@ -0,0 +1,33 @@
package com.tangem.tap.common.analytics.handlers.firebase
internal class FirebaseAnalyticsEventConverter {
fun convertEventName(event: String): String {
return convertString(event, FIREBASE_EVENT_NAME_MAX_LENGTH)
}
fun convertEventParams(params: Map<String, String>): Map<String, String> {
return params.map { (key, value) ->
convertString(key, FIREBASE_EVENT_NAME_MAX_LENGTH) to convertString(value, FIREBASE_EVENT_VALUE_MAX_LENGTH)
}.toMap()
}
private fun convertString(string: String, maxLength: Int): String {
return string
.replace(REPLACING_PATTERN.toRegex(), WORD_SEPARATOR)
.trim { it in TRIMMING_CHARACTERS }
.trimToLength(maxLength)
}
private fun String.trimToLength(length: Int): String {
return if (this.length > length) this.substring(0, length) else this
}
private companion object {
const val REPLACING_PATTERN = "[^\\w]+"
const val WORD_SEPARATOR = "_"
const val TRIMMING_CHARACTERS = WORD_SEPARATOR
const val FIREBASE_EVENT_NAME_MAX_LENGTH = 40
const val FIREBASE_EVENT_VALUE_MAX_LENGTH = 100
}
}

View file

@ -18,12 +18,19 @@ internal class FirebaseClient : FirebaseAnalyticsClient {
private val fbAnalytics = Firebase.analytics
private val fbCrashlytics = Firebase.crashlytics
private val eventConverter = FirebaseAnalyticsEventConverter()
override fun logEvent(event: String, params: Map<String, String>) {
fbAnalytics.logEvent(event, params.toBundle())
fbAnalytics.logEvent(
eventConverter.convertEventName(event),
eventConverter.convertEventParams(params).toBundle(),
)
}
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
params.forEach { fbCrashlytics.setCustomKey(it.key, it.value) }
eventConverter.convertEventParams(params)
.forEach { fbCrashlytics.setCustomKey(it.key, it.value) }
fbCrashlytics.recordException(error)
}

View file

@ -1,3 +0,0 @@
package com.tangem.tap.common.extensions
fun StringBuilder.breakLine(count: Int = 1): StringBuilder = append("\n".repeat(count))

View file

@ -2,26 +2,21 @@
package com.tangem.tap.common.extensions
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.ContextWrapper
import android.graphics.drawable.Drawable
import android.view.View
import androidx.annotation.*
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? {
return ContextCompat.getDrawable(this, drawableResId)
}
@ColorInt
fun Fragment.getColor(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(requireContext(), colorRes)
}
@ColorInt
fun Context.getColorCompat(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(this, colorRes)
@ -40,10 +35,6 @@ fun View.getString(@StringRes id: Int, vararg formatArgs: String): String {
return context.getString(id, *formatArgs)
}
fun View.getQuantityString(@PluralsRes id: Int, quantity: Int): String {
return context.resources.getQuantityString(id, quantity, quantity)
}
fun View.show(show: Boolean, invokeBeforeStateChanged: (() -> Unit)? = null) {
return if (show) this.show(invokeBeforeStateChanged) else this.hide(invokeBeforeStateChanged)
}
@ -62,9 +53,6 @@ fun View.hide(invokeBeforeStateChanged: (() -> Unit)? = null) {
this.visibility = View.GONE
}
tailrec fun Context?.getActivity(): Activity? = this as? Activity
?: (this as? ContextWrapper)?.baseContext?.getActivity()
fun Context.copyToClipboard(value: Any, label: String = "") {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return

View file

@ -1,8 +1,5 @@
package com.tangem.tap.common.redux
import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.NetworkServices
import com.tangem.tap.common.redux.global.GlobalMiddleware
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.legacy.LegacyMiddleware
@ -28,7 +25,6 @@ import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
import com.tangem.tap.features.welcome.redux.WelcomeState
import com.tangem.tap.proxy.redux.DaggerGraphMiddleware
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import org.rekotlin.Middleware
import org.rekotlin.StateType
@ -46,18 +42,6 @@ data class AppState(
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
) : StateType {
private val domainState: DomainState
get() = domainStore.state
private val domainNetworks: NetworkServices
get() = domainState.globalState.networkServices
val featureRepositoryProvider: FeatureRepositoryProvider
get() = FeatureRepositoryProvider(
tangemTechApi = domainNetworks.tangemTechService.api,
dispatchers = AppCoroutineDispatcherProvider(),
)
companion object {
fun getMiddleware(): List<Middleware<AppState>> {
return listOf(

View file

@ -1,11 +0,0 @@
package com.tangem.tap.common.redux
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.tap.features.home.data.HomeRepositoryImpl
import com.tangem.tap.features.home.domain.HomeRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
class FeatureRepositoryProvider(tangemTechApi: TangemTechApi, dispatchers: CoroutineDispatcherProvider) {
val homeRepository: HomeRepository = HomeRepositoryImpl(tangemTechApi, dispatchers)
}

View file

@ -82,8 +82,4 @@ sealed class GlobalAction : Action {
object Update : GlobalAction()
}
object FetchUserCountry : GlobalAction() {
data class Success(val countryCode: String) : GlobalAction()
}
}

View file

@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
import java.util.Locale
object GlobalMiddleware {
val handler = globalMiddlewareHandler
@ -87,27 +86,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
}
scope.launch { exchangeManager.update() }
}
is GlobalAction.FetchUserCountry -> {
val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles)
if (!homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
scope.launch {
runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() }
.onSuccess {
store.dispatchOnMain(
GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()),
)
}
.onFailure {
store.dispatchOnMain(
GlobalAction.FetchUserCountry.Success(
countryCode = Locale.getDefault().country.lowercase(),
),
)
}
}
}
}
}
}

View file

@ -1,7 +1,5 @@
package com.tangem.tap.common.redux.global
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.proxy.AppStateHolder
@ -38,7 +36,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
}
is GlobalAction.SaveScanResponse -> {
appStateHolder.scanResponse = action.scanResponse
domainStore.dispatch(DomainGlobalAction.SaveScanNoteResponse(action.scanResponse))
globalState.copy(scanResponse = action.scanResponse)
}
is GlobalAction.ChangeAppCurrency -> {
@ -77,9 +74,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
}
is GlobalAction.SetIfCardVerifiedOnline ->
globalState.copy(cardVerifiedOnline = action.verified)
is GlobalAction.FetchUserCountry.Success -> {
globalState.copy(userCountryCode = action.countryCode)
}
else -> globalState
}
}

View file

@ -15,6 +15,7 @@ import com.tangem.common.services.secure.SecureStorage
import com.tangem.common.usersCode.UserCodeRepository
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.core.res.getStringSafe
import com.tangem.crypto.bip39.DefaultMnemonic
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
@ -35,7 +36,10 @@ import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.derivationsFinder
import com.tangem.tap.domain.tasks.product.*
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
import com.tangem.tap.domain.tasks.product.ScanProductTask
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask
import com.tangem.tap.domain.twins.FinalizeTwinTask
@ -122,7 +126,7 @@ class DefaultTangemSdkManager(
messageRes: Int?,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<ScanResponse> {
val message = Message(resources.getString(messageRes ?: R.string.initial_message_scan_header))
val message = Message(resources.getStringSafe(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain(
runnable = ScanProductTask(
card = null,
@ -154,9 +158,9 @@ class DefaultTangemSdkManager(
),
cardId = scanResponse.card.cardId,
initialMessage = if (scanResponse.cardTypesResolver.isRing()) {
Message(resources.getString(R.string.initial_message_create_wallet_body_ring))
Message(resources.getStringSafe(R.string.initial_message_create_wallet_body_ring))
} else {
Message(resources.getString(R.string.initial_message_create_wallet_body))
Message(resources.getStringSafe(R.string.initial_message_create_wallet_body))
},
iconScanRes = if (scanResponse.cardTypesResolver.isRing()) R.drawable.img_hand_scan_ring else null,
preflightReadFilter = null,
@ -194,9 +198,9 @@ class DefaultTangemSdkManager(
),
cardId = scanResponse.card.cardId,
initialMessage = if (scanResponse.cardTypesResolver.isRing()) {
Message(resources.getString(R.string.initial_message_create_wallet_body_ring))
Message(resources.getStringSafe(R.string.initial_message_create_wallet_body_ring))
} else {
Message(resources.getString(R.string.initial_message_create_wallet_body))
Message(resources.getStringSafe(R.string.initial_message_create_wallet_body))
},
preflightReadFilter = null,
)
@ -243,7 +247,7 @@ class DefaultTangemSdkManager(
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
),
cardId = cardId,
initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)),
initialMessage = Message(resources.getStringSafe(R.string.card_settings_reset_card_to_factory)),
)
}
@ -251,7 +255,7 @@ class DefaultTangemSdkManager(
return runTaskAsyncReturnOnMain(
runnable = ResetBackupCardTask(userWalletId),
initialMessage = Message(
resources.getString(
resources.getStringSafe(
R.string.initial_message_reset_backup_card_header,
cardNumber.toString(),
),
@ -281,7 +285,7 @@ class DefaultTangemSdkManager(
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changePasscode(null),
cardId,
initialMessage = Message(resources.getString(R.string.initial_message_change_passcode_body)),
initialMessage = Message(resources.getStringSafe(R.string.initial_message_change_passcode_body)),
)
}
@ -289,7 +293,7 @@ class DefaultTangemSdkManager(
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changeAccessCode(null),
cardId,
initialMessage = Message(resources.getString(R.string.initial_message_change_access_code_body)),
initialMessage = Message(resources.getStringSafe(R.string.initial_message_change_access_code_body)),
)
}
@ -297,7 +301,7 @@ class DefaultTangemSdkManager(
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.resetUserCodes(),
cardId,
initialMessage = Message(resources.getString(R.string.initial_message_tap_header)),
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
)
}
@ -308,7 +312,7 @@ class DefaultTangemSdkManager(
return runTaskAsyncReturnOnMain(
SetUserCodeRecoveryAllowedTask(enabled),
cardId,
initialMessage = Message(resources.getString(R.string.initial_message_tap_header)),
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
)
}
@ -319,7 +323,7 @@ class DefaultTangemSdkManager(
return runTaskAsyncReturnOnMain(
runnable = ScanTask(allowRequestAccessCodeFromRepository),
cardId = cardId,
initialMessage = Message(resources.getString(R.string.initial_message_tap_header)),
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
)
.map { CardDTO(it) }
}
@ -372,7 +376,9 @@ class DefaultTangemSdkManager(
@Deprecated("TangemSdkManager shouldn't returns a string from resources")
override fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
return resources.getString(stringResId, *formatArgs)
val args = formatArgs.toSet().filterNotNull().toTypedArray()
return resources.getStringSafe(stringResId, *args)
}
override fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {

View file

@ -12,6 +12,7 @@ import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.UserCodeRequestPolicy
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.services.InMemoryStorage
import com.tangem.core.res.getStringSafe
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
@ -150,7 +151,9 @@ class MockTangemSdkManager(
@Deprecated("TangemSdkManager shouldn't returns a string from resources")
override fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
return resources.getString(stringResId, *formatArgs)
val args = formatArgs.toSet().filterNotNull().toTypedArray()
return resources.getStringSafe(stringResId, *args)
}
override fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {

View file

@ -18,7 +18,6 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
@ -27,8 +26,9 @@ import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorState.Currency
import com.tangem.wallet.R
import kotlinx.collections.immutable.ImmutableList
@ -114,7 +114,7 @@ private fun TopBar(
is AppCurrencySelectorState.Loading,
is AppCurrencySelectorState.Default,
-> Text(
text = stringResource(id = R.string.details_row_title_currency),
text = stringResourceSafe(id = R.string.details_row_title_currency),
style = TangemTheme.typography.subtitle1,
)
}
@ -163,7 +163,7 @@ private fun SearchBar(onInputChange: (String) -> Unit, modifier: Modifier = Modi
singleLine = true,
textStyle = TangemTheme.typography.subtitle2,
placeholder = {
Text(text = stringResource(id = R.string.common_search))
Text(text = stringResourceSafe(id = R.string.common_search))
},
colors = SearchBarColors,
)

View file

@ -2,13 +2,13 @@ package com.tangem.tap.features.details.ui.appsettings.components
import android.content.res.Configuration
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Dialog
@ -26,7 +26,7 @@ internal fun SettingsAlertDialog(dialog: Dialog.Alert) {
onClick = dialog.onConfirm,
),
dismissButton = DialogButtonUM(
title = stringResource(id = R.string.common_cancel),
title = stringResourceSafe(id = R.string.common_cancel),
onClick = dialog.onDismiss,
),
onDismissDialog = dialog.onDismiss,

View file

@ -2,13 +2,13 @@ package com.tangem.tap.features.details.ui.appsettings.components
import android.content.res.Configuration
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.components.SelectorDialog
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Dialog
@ -22,7 +22,7 @@ internal fun SettingsSelectorDialog(dialog: Dialog.Selector) {
selectedItemIndex = dialog.selectedItemIndex,
items = dialog.items.map { it.resolveReference() }.toImmutableList(),
confirmButton = DialogButtonUM(
title = stringResource(R.string.common_cancel),
title = stringResourceSafe(R.string.common_cancel),
onClick = dialog.onDismiss,
),
onSelect = dialog.onSelect,

View file

@ -14,8 +14,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.details.ui.common.DetailsMainButton
@ -88,13 +88,13 @@ private fun CardSettingsReadCard(onScanCardClick: () -> Unit) {
),
) {
Text(
text = stringResource(id = R.string.scan_card_settings_title),
text = stringResourceSafe(id = R.string.scan_card_settings_title),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h3,
)
Spacer(modifier = Modifier.size(TangemTheme.dimens.size20))
Text(
text = stringResource(id = R.string.scan_card_settings_message),
text = stringResourceSafe(id = R.string.scan_card_settings_message),
color = TangemTheme.colors.text.secondary,
style = TangemTheme.typography.body1,
modifier = Modifier
@ -103,7 +103,7 @@ private fun CardSettingsReadCard(onScanCardClick: () -> Unit) {
)
Spacer(modifier = Modifier.size(TangemTheme.dimens.size32))
DetailsMainButton(
title = stringResource(id = R.string.scan_card_settings_button),
title = stringResourceSafe(id = R.string.scan_card_settings_button),
onClick = onScanCardClick,
)
}

View file

@ -3,7 +3,7 @@ package com.tangem.tap.features.details.ui.cardsettings
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.stringResource
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.details.ui.securitymode.toTitleRes
import com.tangem.wallet.R
@ -77,7 +77,7 @@ internal sealed interface TextReference {
@ReadOnlyComposable
internal fun TextReference.resolveReference(): String {
return when (this) {
is TextReference.Res -> stringResource(this.id, *this.formatArgs.toTypedArray())
is TextReference.Res -> stringResourceSafe(this.id, *this.formatArgs.toTypedArray())
is TextReference.Str -> this.value
}
}

View file

@ -5,7 +5,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.details.ui.common.DetailsMainButton
import com.tangem.tap.features.details.ui.common.DetailsRadioButtonElement
@ -36,14 +36,14 @@ fun AccessCodeRecoveryOptions(state: AccessCodeRecoveryScreenState) {
)
DetailsRadioButtonElement(
title = stringResource(id = R.string.common_enabled),
subtitle = stringResource(id = R.string.card_settings_access_code_recovery_enabled_description),
title = stringResourceSafe(id = R.string.common_enabled),
subtitle = stringResourceSafe(id = R.string.card_settings_access_code_recovery_enabled_description),
selected = state.enabledSelection,
onClick = { state.onOptionClick(true) },
)
DetailsRadioButtonElement(
title = stringResource(id = R.string.common_disabled),
subtitle = stringResource(id = R.string.card_settings_access_code_recovery_disabled_description),
title = stringResourceSafe(id = R.string.common_disabled),
subtitle = stringResourceSafe(id = R.string.card_settings_access_code_recovery_disabled_description),
selected = !state.enabledSelection,
onClick = { state.onOptionClick(false) },
)
@ -51,7 +51,7 @@ fun AccessCodeRecoveryOptions(state: AccessCodeRecoveryScreenState) {
Spacer(modifier = Modifier.weight(1f))
DetailsMainButton(
title = stringResource(id = R.string.common_save_changes),
title = stringResourceSafe(id = R.string.common_save_changes),
enabled = state.isSaveChangesEnabled,
onClick = { state.onSaveChangesClick() },
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing20),

View file

@ -10,9 +10,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.wallet.R
@ -64,7 +64,7 @@ internal fun SettingsScreensScaffold(
) {
if (titleRes != null) {
Text(
text = stringResource(id = titleRes),
text = stringResourceSafe(id = titleRes),
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing20)
.padding(bottom = TangemTheme.dimens.spacing36),
@ -82,7 +82,7 @@ internal fun SettingsScreensScaffold(
@Composable
internal fun ScreenTitle(titleRes: Int, modifier: Modifier = Modifier) {
Text(
text = stringResource(id = titleRes),
text = stringResourceSafe(id = titleRes),
modifier = modifier.padding(start = 20.dp, end = 20.dp),
style = TangemTheme.typography.h1,
color = TangemTheme.colors.text.primary1,

View file

@ -10,9 +10,9 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.*
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.details.ui.cardsettings.TextReference
@ -71,7 +71,7 @@ private fun ResetCardView(state: ResetCardScreenState) {
@Composable
private fun Title() {
Text(
text = stringResource(id = R.string.card_settings_reset_card_to_factory),
text = stringResourceSafe(id = R.string.card_settings_reset_card_to_factory),
style = TangemTheme.typography.h1,
color = TangemTheme.colors.text.primary1,
)
@ -89,7 +89,7 @@ private fun AlertImage() {
@Composable
private fun Subtitle() {
Text(
text = stringResource(id = R.string.common_attention),
text = stringResourceSafe(id = R.string.common_attention),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
@ -179,7 +179,7 @@ private fun ColumnScope.DynamicSpacer(scrollState: ScrollState) {
@Composable
private fun ResetButton(enabled: Boolean, onResetButtonClick: () -> Unit) {
DetailsMainButton(
title = stringResource(id = R.string.reset_card_to_factory_button_title),
title = stringResourceSafe(id = R.string.reset_card_to_factory_button_title),
onClick = onResetButtonClick,
enabled = enabled,
)
@ -188,14 +188,14 @@ private fun ResetButton(enabled: Boolean, onResetButtonClick: () -> Unit) {
@Composable
private fun CommonResetDialog(dialog: ResetCardScreenState.Dialog) {
BasicDialog(
title = stringResource(dialog.titleResId),
message = stringResource(dialog.messageResId),
title = stringResourceSafe(dialog.titleResId),
message = stringResourceSafe(dialog.messageResId),
dismissButton = DialogButtonUM(
title = stringResource(id = R.string.common_cancel),
title = stringResourceSafe(id = R.string.common_cancel),
onClick = dialog.onDismiss,
),
confirmButton = DialogButtonUM(
title = stringResource(id = R.string.card_settings_action_sheet_reset),
title = stringResourceSafe(id = R.string.card_settings_action_sheet_reset),
warning = true,
onClick = dialog.onConfirmClick,
),
@ -206,10 +206,10 @@ private fun CommonResetDialog(dialog: ResetCardScreenState.Dialog) {
@Composable
private fun CompletedResetDialog(dialog: ResetCardDialog) {
BasicDialog(
title = stringResource(id = dialog.titleResId),
message = stringResource(id = dialog.messageResId),
title = stringResourceSafe(id = dialog.titleResId),
message = stringResourceSafe(id = dialog.messageResId),
confirmButton = DialogButtonUM(
title = stringResource(id = R.string.common_ok),
title = stringResourceSafe(id = R.string.common_ok),
onClick = dialog.onConfirmClick,
),
onDismissDialog = {},

View file

@ -5,9 +5,9 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.details.ui.common.DetailsMainButton
import com.tangem.tap.features.details.ui.common.DetailsRadioButtonElement
@ -47,7 +47,7 @@ private fun SecurityModeOptions(state: SecurityModeScreenState) {
Spacer(modifier = Modifier.weight(1f))
DetailsMainButton(
title = stringResource(id = R.string.common_save_changes),
title = stringResourceSafe(id = R.string.common_save_changes),
enabled = state.isSaveChangesEnabled,
onClick = state.onSaveChangesClicked,
modifier = Modifier.padding(start = 20.dp, end = 20.dp),
@ -68,8 +68,8 @@ private fun SecurityOption(option: SecurityOption, state: SecurityModeScreenStat
}
DetailsRadioButtonElement(
title = stringResource(id = title),
subtitle = stringResource(id = subtitle),
title = stringResourceSafe(id = title),
subtitle = stringResourceSafe(id = subtitle),
selected = selected,
onClick = { state.onNewModeSelected(option) },
)
@ -80,7 +80,7 @@ private fun SecurityOption(option: SecurityOption, state: SecurityModeScreenStat
private fun SecurityModeScreenPreview() {
SecurityModeScreen(
state = SecurityModeScreenState(
availableOptions = SecurityOption.values().toList(),
availableOptions = SecurityOption.entries,
selectedSecurityMode = SecurityOption.LongTap,
isSaveChangesEnabled = false,
onNewModeSelected = {},

View file

@ -13,10 +13,10 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.getFromClipboard
@ -96,7 +96,7 @@ private fun EmptyScreen(state: WalletConnectScreenState) {
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = stringResource(id = R.string.wallet_connect_subtitle),
text = stringResourceSafe(id = R.string.wallet_connect_subtitle),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)

View file

@ -2,20 +2,23 @@ package com.tangem.tap.features.home.compose.content
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
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 androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation
@ -27,8 +30,8 @@ fun StoriesRevolutionaryWallet() {
SplitContent(
topContent = {
TopContent(
titleText = stringResource(id = R.string.story_awe_title),
subtitleText = stringResource(id = R.string.story_awe_description),
titleText = stringResourceSafe(id = R.string.story_awe_title),
subtitleText = stringResourceSafe(id = R.string.story_awe_description),
)
},
bottomContent = {
@ -46,8 +49,8 @@ fun StoriesUltraSecureBackup(isPaused: Boolean, stepDuration: Int) {
SplitContent(
topContent = {
TopContent(
titleText = stringResource(id = R.string.story_backup_title),
subtitleText = stringResource(id = R.string.story_backup_description),
titleText = stringResourceSafe(id = R.string.story_backup_title),
subtitleText = stringResourceSafe(id = R.string.story_backup_description),
)
},
bottomContent = {
@ -65,8 +68,8 @@ fun StoriesCurrencies(isPaused: Boolean, stepDuration: Int) {
SplitContent(
topContent = {
TopContent(
titleText = stringResource(id = R.string.story_currencies_title),
subtitleText = stringResource(id = R.string.story_currencies_description),
titleText = stringResourceSafe(id = R.string.story_currencies_title),
subtitleText = stringResourceSafe(id = R.string.story_currencies_description),
)
},
bottomContent = {
@ -81,8 +84,8 @@ fun StoriesWeb3(isPaused: Boolean, stepDuration: Int) {
SplitContent(
topContent = {
TopContent(
titleText = stringResource(id = R.string.story_web3_title),
subtitleText = stringResource(id = R.string.story_web3_description),
titleText = stringResourceSafe(id = R.string.story_web3_title),
subtitleText = stringResourceSafe(id = R.string.story_web3_description),
)
},
bottomContent = {
@ -97,8 +100,8 @@ fun StoriesWalletForEveryone(stepDuration: Int) {
SplitContent(
topContent = {
TopContent(
titleText = stringResource(id = R.string.story_finish_title),
subtitleText = stringResource(id = R.string.story_finish_description),
titleText = stringResourceSafe(id = R.string.story_finish_title),
subtitleText = stringResourceSafe(id = R.string.story_finish_description),
)
},
bottomContent = {

View file

@ -16,13 +16,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.home.compose.StoriesTextAnimation
@ -66,7 +66,7 @@ fun FirstStoriesContent(isPaused: Boolean, duration: Int) {
) { modifier ->
Text(
modifier = modifier,
text = stringResource(R.string.story_meet_title),
text = stringResourceSafe(R.string.story_meet_title),
style = style,
color = TangemColorPalette.White,
textAlign = TextAlign.Center,

View file

@ -9,12 +9,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags
@ -52,7 +52,7 @@ internal fun HomeButtons(
private fun ScanCardButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
StoriesButton(
modifier = modifier,
text = stringResource(id = R.string.home_button_scan),
text = stringResourceSafe(id = R.string.home_button_scan),
useDarkerColors = false,
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
onClick = onClick,
@ -64,7 +64,7 @@ private fun ScanCardButton(showProgress: Boolean, onClick: () -> Unit, modifier:
private fun OrderCardButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
StoriesButton(
modifier = modifier,
text = stringResource(id = R.string.home_button_order),
text = stringResourceSafe(id = R.string.home_button_order),
useDarkerColors = true,
onClick = onClick,
)

View file

@ -7,9 +7,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.wallet.R
@ -18,7 +18,7 @@ import com.tangem.wallet.R
internal fun SearchCurrenciesButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
StoriesButton(
modifier = modifier,
text = stringResource(id = R.string.common_search_tokens),
text = stringResourceSafe(id = R.string.common_search_tokens),
icon = TangemButtonIconPosition.Start(R.drawable.ic_search_24),
showProgress = false,
useDarkerColors = true,

View file

@ -1,23 +0,0 @@
package com.tangem.tap.features.home.data
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.GeoResponse
import com.tangem.tap.features.home.domain.HomeRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
/**
* Implementation of repository for Home feature
*
* @property tangemTechApi API for server requests
* @property dispatchers coroutine dispatcher provider
*/
class HomeRepositoryImpl(
private val tangemTechApi: TangemTechApi,
private val dispatchers: CoroutineDispatcherProvider,
) : HomeRepository {
override suspend fun getUserCountryCode(): GeoResponse = withContext(dispatchers.io) {
tangemTechApi.getUserCountryCode()
}
}

View file

@ -1,10 +0,0 @@
package com.tangem.tap.features.home.domain
import com.tangem.datasource.api.tangemTech.models.GeoResponse
/** Repository for Home feature */
interface HomeRepository {
/** Get user's country code */
suspend fun getUserCountryCode(): GeoResponse
}

View file

@ -1,12 +0,0 @@
package com.tangem.tap.features.home.featuretoggles
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import javax.inject.Inject
class HomeFeatureToggles @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
) {
val isMigrateUserCountryCodeEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "MIGRATE_USER_COUNTRY_CODE_ENABLED")
}

View file

@ -53,7 +53,6 @@ private fun handleHomeAction(action: Action) {
store.dispatch(GlobalAction.RestoreAppCurrency)
store.dispatch(GlobalAction.ExchangeManager.Init)
store.dispatch(GlobalAction.FetchUserCountry)
}
is HomeAction.ReadCard -> {
action.scope.launch {

View file

@ -18,7 +18,6 @@ import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.staking.FetchStakingTokensUseCase
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.features.main.model.MainScreenState
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
@ -41,7 +40,6 @@ internal class MainViewModel @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
private val apiConfigsManager: ApiConfigsManager,
homeFeatureToggles: HomeFeatureToggles,
private val fetchUserCountryUseCase: FetchUserCountryUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel(), MainIntents {
@ -63,11 +61,9 @@ internal class MainViewModel @Inject constructor(
viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() }
if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
viewModelScope.launch {
fetchUserCountryUseCase().onLeft {
Timber.e("Unable to fetch the user country code $it")
}
viewModelScope.launch {
fetchUserCountryUseCase().onLeft {
Timber.e("Unable to fetch the user country code $it")
}
}

View file

@ -22,11 +22,9 @@ import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
import com.tangem.tap.mainScope
import com.tangem.tap.proxy.redux.DaggerGraphState
@ -50,7 +48,7 @@ object OnboardingHelper {
if (!response.twinsIsTwinned()) {
true
} else {
onboardingManager.isActivationInProgress(cardId) ?: false
onboardingManager.isActivationInProgress(cardId)
}
}
@ -62,7 +60,7 @@ object OnboardingHelper {
emptyWallets || activationInProgress || isNoBackup
}
response.card.wallets.isNotEmpty() -> onboardingManager.isActivationInProgress(cardId) ?: false
response.card.wallets.isNotEmpty() -> onboardingManager.isActivationInProgress(cardId)
else -> true
}
}
@ -70,7 +68,7 @@ object OnboardingHelper {
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
val newOnboardingSupportTypes = scanResponse.productType == ProductType.Wallet2 ||
scanResponse.productType == ProductType.Ring ||
scanResponse.productType == ProductType.Wallet
scanResponse.productType == ProductType.Wallet // AppRoute.OnboardingOther is also supported
if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled &&
newOnboardingSupportTypes
) {
@ -222,7 +220,7 @@ object OnboardingHelper {
}
}
fun handleTopUpAction(walletManager: WalletManager, scanResponse: ScanResponse, globalState: GlobalState) {
fun handleTopUpAction(walletManager: WalletManager, scanResponse: ScanResponse) {
val blockchain = walletManager.wallet.blockchain
val excludedBlockchains = store.inject(DaggerGraphState::excludedBlockchains)
@ -238,17 +236,10 @@ object OnboardingHelper {
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
scope.launch {
val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles)
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
val isRussia = getUserCountryCodeUseCase().isRight { it is UserCountry.Russia }
val onrampFeatureToggles = store.inject(DaggerGraphState::onrampFeatureToggles)
val isRussia = if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
getUserCountryCodeUseCase().isRight { it is UserCountry.Russia }
} else {
globalState.userCountryCode == RUSSIA_COUNTRY_CODE
}
if (isRussia && !onrampFeatureToggles.isFeatureEnabled) {
val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData))

View file

@ -176,7 +176,6 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
OnboardingHelper.handleTopUpAction(
walletManager = walletManager,
scanResponse = scanResponse,
globalState = globalState,
)
}
is OnboardingNoteAction.Done -> {

View file

@ -299,7 +299,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
OnboardingHelper.handleTopUpAction(
walletManager = walletManager,
scanResponse = onboardingManager?.scanResponse ?: return,
globalState = globalState,
)
}
TwinCardsAction.Done -> {

View file

@ -5,11 +5,11 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
@ -17,10 +17,10 @@ import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
@Composable
fun EnrollBiometricsDialogContent(dialog: EnrollBiometricsDialog) {
BasicDialog(
title = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_title),
message = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_description),
title = stringResourceSafe(R.string.save_user_wallet_agreement_enroll_biometrics_title),
message = stringResourceSafe(R.string.save_user_wallet_agreement_enroll_biometrics_description),
confirmButton = DialogButtonUM(
title = stringResource(R.string.common_enable),
title = stringResourceSafe(R.string.common_enable),
onClick = dialog.onEnroll,
),
dismissButton = DialogButtonUM(

View file

@ -11,11 +11,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
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.*
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.wallet.R
@ -63,7 +63,7 @@ private fun Header(onCloseClick: () -> Unit) {
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = R.drawable.ic_close_24),
tint = TangemTheme.colors.icon.secondary,
contentDescription = stringResource(id = R.string.common_cancel),
contentDescription = stringResourceSafe(id = R.string.common_cancel),
)
}
SpacerW8()
@ -85,7 +85,7 @@ private fun Title(modifier: Modifier = Modifier) {
contentDescription = null,
)
Text(
text = stringResource(id = R.string.save_user_wallet_agreement_header_biometrics),
text = stringResourceSafe(id = R.string.save_user_wallet_agreement_header_biometrics),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
@ -102,13 +102,13 @@ private fun Description(modifier: Modifier = Modifier) {
) {
DescriptionItem(
iconPainter = painterResource(id = R.drawable.ic_face_recognition_24),
title = stringResource(id = R.string.save_user_wallet_agreement_access_title),
description = stringResource(id = R.string.save_user_wallet_agreement_access_description),
title = stringResourceSafe(id = R.string.save_user_wallet_agreement_access_title),
description = stringResourceSafe(id = R.string.save_user_wallet_agreement_access_description),
)
DescriptionItem(
iconPainter = painterResource(id = R.drawable.ic_lock_24),
title = stringResource(id = R.string.save_user_wallet_agreement_code_title),
description = stringResource(id = R.string.save_user_wallet_agreement_code_description_biometrics),
title = stringResourceSafe(id = R.string.save_user_wallet_agreement_code_title),
description = stringResourceSafe(id = R.string.save_user_wallet_agreement_code_description_biometrics),
)
}
}
@ -125,12 +125,12 @@ private fun Footer(showProgress: Boolean, onSaveWalletClick: () -> Unit) {
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
showProgress = showProgress,
text = stringResource(id = R.string.save_user_wallet_agreement_allow_biometrics),
text = stringResourceSafe(id = R.string.save_user_wallet_agreement_allow_biometrics),
onClick = onSaveWalletClick,
)
Text(
modifier = Modifier.fillMaxWidth(fraction = .7f),
text = stringResource(R.string.save_user_wallet_agreement_notice),
text = stringResourceSafe(R.string.save_user_wallet_agreement_notice),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,

View file

@ -17,7 +17,6 @@ import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
import com.tangem.tap.proxy.redux.DaggerGraphState
@ -26,7 +25,6 @@ import com.tangem.tap.store
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
@Suppress("LargeClass")
@Deprecated("Will be removed soon")
object TradeCryptoMiddleware {
@ -41,25 +39,24 @@ object TradeCryptoMiddleware {
}
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
private fun handle(state: () -> AppState?, action: TradeCryptoAction) {
if (DemoHelper.tryHandle(state, action)) return
when (action) {
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
is TradeCryptoAction.Buy -> proceedBuyAction(state, action)
is TradeCryptoAction.Buy -> proceedBuyAction(action)
is TradeCryptoAction.Sell -> proceedSellAction(action)
is TradeCryptoAction.SendToken -> handleNewSendToken(action = action)
is TradeCryptoAction.SendCoin -> handleNewSendCoin(action = action)
}
}
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
private fun proceedBuyAction(action: TradeCryptoAction.Buy) {
val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled
if (isOnrampEnabled) {
proceedWithOnramp(action.userWallet.walletId, action.cryptoCurrencyStatus.currency, action.source)
} else {
proceedWithLegacyBuyAction(state, action)
proceedWithLegacyBuyAction(action)
}
}
@ -75,7 +72,7 @@ object TradeCryptoMiddleware {
}
}
private fun proceedWithLegacyBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
private fun proceedWithLegacyBuyAction(action: TradeCryptoAction.Buy) {
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
?.defaultAddress
?.let(NetworkAddress.Address::value)
@ -94,15 +91,10 @@ object TradeCryptoMiddleware {
)
scope.launch {
val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles)
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
val onrampFeatureToggles = store.inject(DaggerGraphState::onrampFeatureToggles)
val isRussia = if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
getUserCountryCodeUseCase().isRight { it is UserCountry.Russia }
} else {
state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE
}
val isRussia = getUserCountryCodeUseCase().isRight { it is UserCountry.Russia }
if (action.checkUserLocation && isRussia && !onrampFeatureToggles.isFeatureEnabled) {
val dialogData = topUrl?.let {

View file

@ -130,6 +130,5 @@ internal class WelcomeViewModel @Inject constructor(
private fun initGlobalState() {
store.dispatch(GlobalAction.RestoreAppCurrency)
store.dispatch(GlobalAction.ExchangeManager.Init)
store.dispatch(GlobalAction.FetchUserCountry)
}
}

View file

@ -4,10 +4,10 @@ import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.welcome.ui.model.WarningModel
import com.tangem.wallet.R
@ -18,8 +18,8 @@ internal fun WarningDialog(warning: WarningModel?) {
null -> Unit
is WarningModel.BiometricsLockoutWarning -> {
BasicDialog(
title = stringResource(id = R.string.biometric_lockout_warning_title),
message = stringResource(
title = stringResourceSafe(id = R.string.biometric_lockout_warning_title),
message = stringResourceSafe(
id = if (warning.isPermanent) {
R.string.biometric_lockout_permanent_warning_description
} else {
@ -28,30 +28,30 @@ internal fun WarningDialog(warning: WarningModel?) {
),
onDismissDialog = warning.onDismiss,
confirmButton = DialogButtonUM(
title = stringResource(id = R.string.common_ok),
title = stringResourceSafe(id = R.string.common_ok),
onClick = warning.onDismiss,
),
)
}
is WarningModel.KeyInvalidatedWarning -> {
BasicDialog(
title = stringResource(id = R.string.common_attention),
message = stringResource(id = R.string.key_invalidated_warning_description),
title = stringResourceSafe(id = R.string.common_attention),
message = stringResourceSafe(id = R.string.key_invalidated_warning_description),
onDismissDialog = warning.onDismiss,
confirmButton = DialogButtonUM(
title = stringResource(id = R.string.common_ok),
title = stringResourceSafe(id = R.string.common_ok),
onClick = warning.onDismiss,
),
)
}
is WarningModel.BiometricsDisabledWarning -> {
BasicDialog(
title = stringResource(id = R.string.common_warning),
message = stringResource(id = R.string.biometric_unavailable_warning),
title = stringResourceSafe(id = R.string.common_warning),
message = stringResourceSafe(id = R.string.biometric_unavailable_warning),
onDismissDialog = warning.onDismiss,
isDismissable = false,
confirmButton = DialogButtonUM(
title = stringResource(id = R.string.common_ok),
title = stringResourceSafe(id = R.string.common_ok),
onClick = warning.onDismiss,
),
)

View file

@ -9,10 +9,10 @@ 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.*
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.wallet.R
@ -38,7 +38,7 @@ internal fun WelcomeScreenContent(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
text = stringResource(R.string.welcome_unlock_title),
text = stringResourceSafe(R.string.welcome_unlock_title),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
@ -48,9 +48,9 @@ internal fun WelcomeScreenContent(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing44)
.fillMaxWidth(),
text = stringResource(
text = stringResourceSafe(
id = R.string.welcome_unlock_description,
stringResource(id = R.string.common_biometric_authentication),
stringResourceSafe(id = R.string.common_biometric_authentication),
),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
@ -61,9 +61,9 @@ internal fun WelcomeScreenContent(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
text = stringResource(
text = stringResourceSafe(
id = R.string.welcome_unlock,
stringResource(id = R.string.common_biometrics),
stringResourceSafe(id = R.string.common_biometrics),
),
showProgress = showUnlockProgress,
onClick = onUnlockClick,
@ -73,7 +73,7 @@ internal fun WelcomeScreenContent(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
text = stringResource(R.string.welcome_unlock_card),
text = stringResourceSafe(R.string.welcome_unlock_card),
showProgress = showScanCardProgress,
iconResId = R.drawable.ic_tangem_24,
onClick = onScanCardClick,

View file

@ -35,7 +35,6 @@ import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import org.rekotlin.StateType
@ -69,7 +68,6 @@ data class DaggerGraphState(
val appRouter: AppRouter? = null,
val pushNotificationsRouter: PushNotificationsRouter? = null,
val transactionSignerFactory: TransactionSignerFactory? = null,
val homeFeatureToggles: HomeFeatureToggles? = null,
val getUserCountryUseCase: GetUserCountryUseCase? = null,
val onrampFeatureToggles: OnrampFeatureToggles? = null,
val environmentConfigStorage: EnvironmentConfigStorage? = null,

View file

@ -0,0 +1,14 @@
package com.tangem.tap.routing.toggle
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
internal class RoutingFeatureToggles @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
) {
val isNavigationRefactoringEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "NAVIGATION_REFACTORING")
}

View file

@ -35,6 +35,7 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFra
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.tap.routing.RoutingComponent.Child
import com.tangem.tap.routing.toggle.RoutingFeatureToggles
import com.tangem.utils.Provider
import dagger.hilt.android.scopes.ActivityScoped
import java.util.WeakHashMap
@ -61,10 +62,19 @@ internal class ChildFactory @Inject constructor(
private val stakingRouter: StakingRouter,
private val testerRouter: TesterRouter,
private val pushNotificationRouter: PushNotificationsRouter,
private val routingFeatureToggles: RoutingFeatureToggles,
) {
@Suppress("LongMethod", "CyclomaticComplexMethod")
fun createChild(route: AppRoute, contextFactory: (route: AppRoute) -> AppComponentContext): Child {
return if (routingFeatureToggles.isNavigationRefactoringEnabled) {
TODO("Will be implemented in [REDACTED_JIRA]")
} else {
createChildLegacy(route, contextFactory)
}
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
private fun createChildLegacy(route: AppRoute, contextFactory: (route: AppRoute) -> AppComponentContext): Child {
componentContexts[route] = contextFactory(route)
return when (route) {

View file

@ -0,0 +1,14 @@
org.gradle.jvmargs=-Xmx7g -XX:MaxMetaspaceSize=512m -XX:+UseParallelGC -XX:ParallelGCThreads=3 -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.workers.max=3
org.gradle.daemon=false
org.gradle.unsafe.configuration-cache=false
org.gradle.console=plain
org.gradle.caching=false
android.useAndroidX=true
android.nonTransitiveRClass=false
android.aapt2FromMavenOverride=/opt/android-sdk/build-tools/34.0.0/aapt2
kotlin.incremental=false
kotlin.compiler.execution.strategy=in-process

View file

@ -0,0 +1,13 @@
org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=512m -XX:+UseParallelGC -XX:ParallelGCThreads=3 -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.workers.max=3
org.gradle.daemon=false
org.gradle.unsafe.configuration-cache=false
org.gradle.console=plain
org.gradle.caching=false
android.useAndroidX=true
android.nonTransitiveRClass=false
android.aapt2FromMavenOverride=/opt/android-sdk/build-tools/34.0.0/aapt2
kotlin.incremental=false

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -12,7 +12,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
@ -21,6 +20,7 @@ import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
import com.tangem.core.ui.components.currency.fiaticon.FiatIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import kotlinx.collections.immutable.PersistentList
@ -62,7 +62,7 @@ internal fun LazyListScope.buttons(
SpacerWMax()
}
Text(
text = stringResource(R.string.send_max_amount),
text = stringResourceSafe(R.string.send_max_amount),
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary1,
modifier = Modifier

View file

@ -9,13 +9,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.common.ui.R
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.extensions.shareText
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.common.ui.R
import com.tangem.core.ui.res.TangemThemePreview
@Composable
@ -38,14 +38,14 @@ fun SendDoneButtons(
) {
Row(modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12)) {
SecondaryButtonIconStart(
text = stringResource(id = R.string.common_explore),
text = stringResourceSafe(id = R.string.common_explore),
iconResId = R.drawable.ic_web_24,
onClick = onExploreClick,
modifier = Modifier.weight(1f),
)
SpacerW12()
SecondaryButtonIconStart(
text = stringResource(id = R.string.common_share),
text = stringResourceSafe(id = R.string.common_share),
iconResId = R.drawable.ic_share_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)

View file

@ -16,7 +16,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -54,7 +53,7 @@ fun GiveTxPermissionBottomSheet(config: TangemBottomSheetConfig) {
if (isPermissionAlertShow) {
BasicDialog(
message = content.data.dialogText.resolveReference(),
title = stringResource(id = R.string.common_approve),
title = stringResourceSafe(id = R.string.common_approve),
confirmButton = DialogButtonUM { isPermissionAlertShow = false },
onDismissDialog = {},
)
@ -87,7 +86,7 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh
SpacerH(height = TangemTheme.dimens.spacing20)
PrimaryButtonIconEnd(
text = stringResource(id = R.string.common_approve),
text = stringResourceSafe(id = R.string.common_approve),
iconResId = R.drawable.ic_tangem_24,
showProgress = data.approveButton.loading,
modifier = Modifier
@ -100,7 +99,7 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh
SpacerH12()
SecondaryButton(
text = stringResource(id = R.string.common_cancel),
text = stringResourceSafe(id = R.string.common_cancel),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing16),
@ -180,7 +179,7 @@ private fun AmountItem(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = R.string.give_permission_rows_amount, currency),
text = stringResourceSafe(id = R.string.give_permission_rows_amount, currency),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
maxLines = 1,
@ -259,10 +258,10 @@ private fun DropdownSelector(
Row {
Text(
text = when (item) {
ApproveType.LIMITED -> stringResource(
ApproveType.LIMITED -> stringResourceSafe(
id = R.string.give_permission_current_transaction,
)
ApproveType.UNLIMITED -> stringResource(id = R.string.give_permission_unlimited)
ApproveType.UNLIMITED -> stringResourceSafe(id = R.string.give_permission_unlimited)
},
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.body1,

View file

@ -19,9 +19,9 @@ sealed class NavigationButtonsState {
data class NavigationButton(
val textReference: TextReference,
@DrawableRes val iconRes: Int? = null,
val isSecondary: Boolean,
val isIconVisible: Boolean,
val showProgress: Boolean,
val isEnabled: Boolean,
val isSecondary: Boolean = false,
val isIconVisible: Boolean = false,
val showProgress: Boolean = false,
val isEnabled: Boolean = true,
val onClick: () -> Unit,
)

View file

@ -1,7 +1,6 @@
package com.tangem.common.ui.notifications
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.common.ui.amountScreen.utils.getFiatString
@ -149,10 +148,19 @@ object NotificationsFactory {
}
}
/**
* Adds Existential Warning
*
* @param existentialDeposit existential deposit of blockchain
* @param feeAmount amount of fee spending for transaction
* @param sendingAmount amount sending by user (excluding fee for coins)
* @param cryptoCurrencyStatus blockchain currency status
* @param onReduceClick action to leave existential amount in balance after transaction
*/
fun MutableList<NotificationUM>.addExistentialWarningNotification(
existentialDeposit: BigDecimal?,
feeAmount: BigDecimal,
receivedAmount: BigDecimal,
sendingAmount: BigDecimal,
cryptoCurrencyStatus: CryptoCurrencyStatus,
onReduceClick: (
reduceAmountBy: BigDecimal,
@ -165,7 +173,7 @@ object NotificationsFactory {
val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) {
feeAmount
} else {
receivedAmount
sendingAmount
}
val diff = balance.minus(spendingAmount)
if (existentialDeposit != null && diff >= BigDecimal.ZERO && existentialDeposit > diff) {
@ -283,9 +291,9 @@ object NotificationsFactory {
fun MutableList<NotificationUM>.addValidateTransactionNotifications(
dustValue: BigDecimal,
fee: Fee?,
validationError: Throwable?,
cryptoCurrency: CryptoCurrency,
minAdaValue: BigDecimal?, // TODO revert to Fee, after swap TxFee refactored
onReduceClick: (
reduceAmountTo: BigDecimal,
notification: Class<out NotificationUM>,
@ -301,11 +309,11 @@ object NotificationsFactory {
error = validationError,
onReduceClick = onReduceClick,
)
null -> (fee as? Fee.CardanoToken)?.let {
null -> minAdaValue?.let {
add(
NotificationUM.Cardano.MinAdaValueCharged(
tokenName = cryptoCurrency.name,
minAdaValue = it.minAdaValue.parseBigDecimal(cryptoCurrency.decimals),
minAdaValue = minAdaValue.parseBigDecimal(cryptoCurrency.decimals),
),
)
}

View file

@ -11,10 +11,6 @@
"name": "IS_ETHEREUM_EIP_1559_ENABLED",
"version": "5.17.0"
},
{
"name": "MIGRATE_USER_COUNTRY_CODE_ENABLED",
"version": "5.17.0"
},
{
"name": "ONRAMP_ENABLED",
"version": "5.19.0"
@ -26,5 +22,9 @@
{
"name": "ONBOARDING_CODE_REFACTORING_ENABLED",
"version": "undefined"
},
{
"name": "NAVIGATION_REFACTORING",
"version": "undefined"
}
]

View file

@ -1,44 +0,0 @@
package com.tangem.datasource.api.tangemTech
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
import com.tangem.datasource.utils.RequestHeader
import com.tangem.datasource.utils.RequestHeader.AuthenticationHeader
import com.tangem.datasource.utils.RequestHeader.CacheControlHeader
import com.tangem.datasource.utils.addHeaders
import com.tangem.datasource.utils.addLoggers
import okhttp3.OkHttpClient
import retrofit2.Retrofit
/**
[REDACTED_AUTHOR]
*/
// TODO("Remove after removing Redux")
@Deprecated("Use TangemTechApi")
object TangemTechService {
var api: TangemTechApi = createApi()
private set
private const val TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/"
fun addAuthenticationHeader(header: AuthenticationHeader) {
api = createApi(header)
}
private fun createApi(header: RequestHeader? = null): TangemTechApi {
val headers = mutableListOf<RequestHeader>(CacheControlHeader).apply { header?.let(::add) }
return Retrofit.Builder()
.addConverterFactory(MoshiConverter.networkMoshiConverter)
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
.baseUrl(TANGEM_TECH_BASE_URL)
.client(
OkHttpClient.Builder()
.addHeaders(*headers.toTypedArray())
.addLoggers()
.build(),
)
.build()
.create(TangemTechApi::class.java)
}
}

View file

@ -6,4 +6,17 @@ plugins {
android {
namespace = "com.tangem.core.res"
}
dependencies {
implementation(projects.core.utils)
implementation(deps.timber)
// region Firebase libraries
implementation(platform(deps.firebase.bom))
implementation(deps.firebase.analytics)
implementation(deps.firebase.crashlytics)
// endregion
}

View file

@ -0,0 +1,82 @@
package com.tangem.core.res
import android.content.res.Resources
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.utils.SupportedLanguages
import timber.log.Timber
/**
* Get a string resource safely or the resource name if an exception is thrown
*
* @param id resource id
*/
fun Resources.getStringSafe(@StringRes id: Int): String {
return runCatching { getString(id) }
.getOrResourceName(resources = this, id)
}
/**
* Get a string resource safely or the resource name if an exception is thrown
*
* @param id resource id
* @param formatArgs format args
*/
fun Resources.getStringSafe(@StringRes id: Int, vararg formatArgs: Any): String {
return runCatching { getString(id, *formatArgs) }
.recoverCatching {
// If something goes wrong, returns the resource without arguments
val string = getString(id)
reportIssue(resources = this, id, *formatArgs)
string
}
.getOrResourceName(resources = this, id, *formatArgs)
}
/**
* Get a plural string resource safely or the resource name if an exception is thrown
*
* @param id resource id
* @param count count
*/
fun Resources.getPluralStringSafe(@PluralsRes id: Int, count: Int): String {
return runCatching { getQuantityString(id, count) }
.getOrResourceName(resources = this, id)
}
/**
* Get a plural string resource safely or the resource name if an exception is thrown
*
* @param id resource id
* @param count count
* @param formatArgs format args
*/
fun Resources.getPluralStringSafe(@PluralsRes id: Int, count: Int, vararg formatArgs: Any): String {
return runCatching { getQuantityString(id, count, *formatArgs) }
.getOrResourceName(resources = this, id, *formatArgs)
}
private fun Result<String>.getOrResourceName(resources: Resources, id: Int, vararg formatArgs: Any): String {
return getOrElse {
reportIssue(resources, id, formatArgs)
// If something still goes wrong, returns the resource name
resources.getResourceEntryName(id)
}
}
private fun reportIssue(resources: Resources, id: Int, vararg formatArgs: Any) {
val exception = IllegalStateException(
"An error occurred while parsing the string:\n" +
"\tname: R.string.${resources.getResourceEntryName(id)}\n" +
"\targs: ${formatArgs.joinToString(prefix = "[", postfix = "]") { it.toString() }}\n" +
"\tlocale: ${SupportedLanguages.getCurrentSupportedLanguageCode()}\n",
)
Timber.tag("Resources").e(exception)
FirebaseCrashlytics.getInstance().recordException(exception)
}

View file

@ -16,7 +16,6 @@ import androidx.compose.runtime.Immutable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -27,6 +26,7 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.components.SelctorDialogParamsProvider.SelectorDialogParams
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.components.fields.SimpleDialogTextField
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.ImmutableList
@ -318,14 +318,14 @@ private fun DialogButtons(
) {
if (dismissButton != null) {
DialogButton(
text = dismissButton.title ?: stringResource(id = R.string.common_cancel),
text = dismissButton.title ?: stringResourceSafe(id = R.string.common_cancel),
warning = dismissButton.warning,
enabled = dismissButton.enabled,
onClick = dismissButton.onClick,
)
}
DialogButton(
text = confirmButton.title ?: stringResource(id = R.string.common_ok),
text = confirmButton.title ?: stringResourceSafe(id = R.string.common_ok),
warning = confirmButton.warning,
enabled = confirmButton.enabled,
onClick = confirmButton.onClick,

View file

@ -23,13 +23,13 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
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.res.getStringSafe
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
@ -96,7 +96,7 @@ private fun Info(currency: String, network: String, showMemoDisclaimer: Boolean,
modifier = Modifier
.padding(horizontal = 18.dp)
.fillMaxWidth(),
text = stringResource(R.string.receive_bottom_sheet_no_memo_required_message),
text = stringResourceSafe(R.string.receive_bottom_sheet_no_memo_required_message),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
@ -191,7 +191,7 @@ private fun QrCodePage(content: TokenReceiveBottomSheetConfig, qrCodePainter: Pa
verticalArrangement = Arrangement.spacedBy(24.dp),
) {
Text(
text = stringResource(
text = stringResourceSafe(
R.string.receive_bottom_sheet_warning_message,
getName(content = content, index = currentIndex),
content.symbol,
@ -247,7 +247,7 @@ private fun Buttons(
) {
SecondaryButtonIconStart(
modifier = Modifier.weight(1f),
text = stringResource(id = R.string.common_copy),
text = stringResourceSafe(id = R.string.common_copy),
iconResId = R.drawable.ic_copy_24,
onClick = {
onCopyClick()
@ -257,7 +257,7 @@ private fun Buttons(
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = resources.getString(R.string.wallet_notification_address_copied),
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
)
}
},
@ -265,7 +265,7 @@ private fun Buttons(
SecondaryButtonIconStart(
modifier = Modifier.weight(1f),
text = stringResource(id = R.string.common_share),
text = stringResourceSafe(id = R.string.common_share),
iconResId = R.drawable.ic_share_24,
onClick = {
onShareClick()

View file

@ -4,7 +4,10 @@ import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@ -14,7 +17,6 @@ import androidx.compose.runtime.remember
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.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -24,8 +26,9 @@ import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* [Input Row Best Rate](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-889&mode=dev)
@ -112,7 +115,7 @@ private fun InnerTitle(title: TextReference, titleExtra: TextReference, showTag:
)
if (showTag) {
Text(
text = stringResource(R.string.express_provider_best_rate),
text = stringResourceSafe(R.string.express_provider_best_rate),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.icon.accent,
modifier = Modifier

View file

@ -18,9 +18,9 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.DEFAULT_ANIMATION_DURATION
@ -51,7 +51,7 @@ fun PasteButton(isPasteButtonVisible: Boolean, onClick: (String) -> Unit, modifi
modifier = modifier,
) {
Text(
text = stringResource(R.string.common_paste),
text = stringResourceSafe(R.string.common_paste),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.primary2,
modifier = Modifier
@ -86,7 +86,7 @@ fun CrossIcon(onClick: (String) -> Unit, modifier: Modifier = Modifier) {
Icon(
painter = painterResource(id = R.drawable.ic_close_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = stringResource(R.string.common_close),
contentDescription = stringResourceSafe(R.string.common_close),
modifier = modifier
.size(TangemTheme.dimens.size24)
.clickable(

View file

@ -10,7 +10,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -18,6 +17,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.BigDecimalFormatter
@ -61,7 +61,7 @@ private fun Title(currencyName: String, modifier: Modifier = Modifier) {
contentAlignment = Alignment.CenterStart,
) {
Text(
text = stringResource(id = R.string.wallet_marketplace_block_title, currencyName),
text = stringResourceSafe(id = R.string.wallet_marketplace_block_title, currencyName),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
@ -157,7 +157,7 @@ private fun LoadingContent() {
@Composable
private fun QuoteTimeStatus() {
Text(
text = stringResource(id = R.string.wallet_marketprice_block_update_time),
text = stringResourceSafe(id = R.string.wallet_marketprice_block_update_time),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
)

View file

@ -14,7 +14,6 @@ import androidx.compose.runtime.getValue
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.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -23,8 +22,9 @@ import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.utils.StringsSigns
@Composable
@ -77,7 +77,7 @@ fun SelectorRowItem(
contentDescription = null,
)
Text(
text = stringResource(titleRes),
text = stringResourceSafe(titleRes),
style = textStyle,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.padding(start = TangemTheme.dimens.spacing8),

View file

@ -10,10 +10,10 @@ 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.TextOverflow
import com.tangem.core.ui.R
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import org.burnoutcrew.reorderable.ReorderableLazyListState
import org.burnoutcrew.reorderable.detectReorder
@ -67,7 +67,7 @@ private fun DraggableImage(reorderableTokenListState: ReorderableLazyListState?)
@Composable
private fun NonFiatContentText(@StringRes text: Int) {
Text(
text = stringResource(id = text),
text = stringResourceSafe(id = text),
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,

View file

@ -6,13 +6,13 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.audits.AuditLabel
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.components.token.state.TokenItemState.Subtitle2State as TokenCryptoAmountState
@ -33,7 +33,7 @@ internal fun TokenCryptoAmount(
AuditLabel(state = state.auditLabelUM, modifier = modifier)
}
is TokenCryptoAmountState.Unreachable -> {
CryptoAmountText(amount = stringResource(id = R.string.common_unreachable), modifier = modifier)
CryptoAmountText(amount = stringResourceSafe(id = R.string.common_unreachable), modifier = modifier)
}
is TokenCryptoAmountState.Loading -> {
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)

View file

@ -8,7 +8,6 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.fields.SearchBar
import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.components.tokenlist.internal.GroupTitleItem
import com.tangem.core.ui.components.tokenlist.internal.NetworkTitleItem
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@ -25,9 +24,6 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun TokenListItem(state: TokensListItemUM, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
when (state) {
is TokensListItemUM.NetworkGroupTitle -> {
NetworkTitleItem(networkName = state.name.resolveReference(), modifier = modifier)
}
is TokensListItemUM.GroupTitle -> {
GroupTitleItem(state, modifier)
}

View file

@ -1,12 +1,32 @@
package com.tangem.core.ui.components.tokenlist.internal
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.rows.NetworkTitle
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import org.burnoutcrew.reorderable.ReorderableLazyListState
import org.burnoutcrew.reorderable.detectReorder
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
/**
* Group title item
@ -18,14 +38,90 @@ import com.tangem.core.ui.res.TangemTheme
*/
@Composable
fun GroupTitleItem(state: TokensListItemUM.GroupTitle, modifier: Modifier = Modifier) {
BaseGroupTitleItem(text = state.text, modifier = modifier)
}
/**
* Draggable network group item
*
* @param state state
* @param reorderableTokenListState reorderable token list state
* @param modifier modifier
*/
@Composable
fun DraggableGroupTitleItem(
state: TokensListItemUM.GroupTitle,
reorderableTokenListState: ReorderableLazyListState,
modifier: Modifier = Modifier,
) {
BaseGroupTitleItem(
text = state.text,
modifier = modifier,
action = { DraggableIcon(reorderableTokenListState = reorderableTokenListState) },
)
}
@Composable
private fun BaseGroupTitleItem(
text: TextReference,
modifier: Modifier = Modifier,
action: (@Composable BoxScope.() -> Unit)? = null,
) {
NetworkTitle(
title = {
Text(
text = state.text.resolveReference(),
text = text.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
},
modifier = modifier,
action = action,
)
}
}
@Composable
private fun DraggableIcon(reorderableTokenListState: ReorderableLazyListState) {
Box(
modifier = Modifier.detectReorder(reorderableTokenListState),
contentAlignment = Alignment.Center,
) {
Icon(
painter = rememberVectorPainter(
image = ImageVector.vectorResource(id = R.drawable.ic_group_drop_24),
),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun NetworkTitleItemPreview(@PreviewParameter(GroupTitleItemProvider::class) isDraggable: Boolean) {
TangemThemePreview {
val state = TokensListItemUM.GroupTitle(
id = 1,
text = resourceReference(
id = R.string.wallet_network_group_title,
formatArgs = wrappedList(resourceReference(id = R.string.main_tokens)),
),
)
if (isDraggable) {
DraggableGroupTitleItem(
state = state,
reorderableTokenListState = rememberReorderableLazyListState(onMove = { _, _ -> }),
modifier = Modifier.background(color = TangemTheme.colors.background.primary),
)
} else {
GroupTitleItem(
state = state,
modifier = Modifier.background(color = TangemTheme.colors.background.primary),
)
}
}
}
private object GroupTitleItemProvider : CollectionPreviewParameterProvider<Boolean>(collection = listOf(true, false))

View file

@ -1,113 +0,0 @@
package com.tangem.core.ui.components.tokenlist.internal
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.rows.NetworkTitle
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import org.burnoutcrew.reorderable.ReorderableLazyListState
import org.burnoutcrew.reorderable.detectReorder
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
/**
* Network title item
*
* @param networkName network name
* @param modifier modifier
*/
@Composable
internal fun NetworkTitleItem(networkName: String, modifier: Modifier = Modifier) {
BaseNetworkTitleItem(networkName = networkName, modifier = modifier)
}
/**
* Draggable network title item
*
* @param networkName network name
* @param reorderableTokenListState reorderable token list state
* @param modifier modifier
*/
@Composable
fun DraggableNetworkTitleItem(
networkName: String,
reorderableTokenListState: ReorderableLazyListState,
modifier: Modifier = Modifier,
) {
BaseNetworkTitleItem(
networkName = networkName,
modifier = modifier,
action = { DraggableIcon(reorderableTokenListState = reorderableTokenListState) },
)
}
@Composable
private fun BaseNetworkTitleItem(
networkName: String,
modifier: Modifier = Modifier,
action: (@Composable BoxScope.() -> Unit)? = null,
) {
NetworkTitle(
title = {
Text(
text = stringResource(id = R.string.wallet_network_group_title, networkName),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
},
modifier = modifier,
action = action,
)
}
@Composable
private fun DraggableIcon(reorderableTokenListState: ReorderableLazyListState) {
Box(
modifier = Modifier.detectReorder(reorderableTokenListState),
contentAlignment = Alignment.Center,
) {
Icon(
painter = rememberVectorPainter(
image = ImageVector.vectorResource(id = R.drawable.ic_group_drop_24),
),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun NetworkTitleItemPreview(@PreviewParameter(NetworkTitleItemProvider::class) isDraggable: Boolean) {
TangemThemePreview {
if (isDraggable) {
DraggableNetworkTitleItem(
networkName = "Ethereum",
reorderableTokenListState = rememberReorderableLazyListState(onMove = { _, _ -> }),
modifier = Modifier.background(color = TangemTheme.colors.background.primary),
)
} else {
NetworkTitleItem(
networkName = "Ethereum",
modifier = Modifier.background(color = TangemTheme.colors.background.primary),
)
}
}
}
private object NetworkTitleItemProvider : CollectionPreviewParameterProvider<Boolean>(collection = listOf(true, false))

View file

@ -23,15 +23,6 @@ sealed interface TokensListItemUM {
val searchBarUM: SearchBarUM,
) : TokensListItemUM
/**
* Network group title
*
* @property id id
* @property name network group name
*/
@Deprecated("Use GroupTitle instead") // TODO: [REDACTED_JIRA]
data class NetworkGroupTitle(override val id: Int, val name: TextReference) : TokensListItemUM
/**
* Group title
*

View file

@ -14,7 +14,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.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -234,7 +233,7 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
is TransactionState.Content -> {
Text(
text = when (state.status) {
is Status.Failed -> stringResource(id = R.string.common_transaction_failed)
is Status.Failed -> stringResourceSafe(id = R.string.common_transaction_failed)
else -> state.subtitle.resolveReference()
},
modifier = modifier,

View file

@ -10,11 +10,11 @@ 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.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* Transactions block title
@ -35,7 +35,7 @@ internal fun TxHistoryTitle(onExploreClick: () -> Unit, modifier: Modifier = Mod
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(id = R.string.common_transactions),
text = stringResourceSafe(id = R.string.common_transactions),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
@ -52,7 +52,7 @@ internal fun TxHistoryTitle(onExploreClick: () -> Unit, modifier: Modifier = Mod
contentDescription = null,
)
Text(
text = stringResource(id = R.string.common_explorer),
text = stringResourceSafe(id = R.string.common_explorer),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)

View file

@ -2,12 +2,56 @@ package com.tangem.core.ui.extensions
import android.graphics.Bitmap
import android.graphics.Color
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.platform.LocalContext
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.tangem.core.res.getPluralStringSafe
import com.tangem.core.res.getStringSafe
import java.util.Hashtable
/**
* Get a string resource safely or the resource name if an exception is thrown
*
* @param id resource id
* @param formatArgs format args
*/
@Composable
@ReadOnlyComposable
fun stringResourceSafe(@StringRes id: Int, vararg formatArgs: Any): String {
val resources = LocalContext.current.resources
return if (formatArgs.isEmpty()) {
resources.getStringSafe(id)
} else {
resources.getStringSafe(id, *formatArgs)
}
}
/**
* Get a plural string resource safely or the resource name if an exception is thrown
*
* @param id resource id
* @param count count
* @param formatArgs format args
*/
@Composable
@ReadOnlyComposable
fun pluralStringResourceSafe(@PluralsRes id: Int, count: Int, vararg formatArgs: Any): String {
val resources = LocalContext.current.resources
return if (formatArgs.isEmpty()) {
resources.getPluralStringSafe(id, count)
} else {
resources.getPluralStringSafe(id, count, *formatArgs)
}
}
@Suppress("MagicNumber")
fun String.toQrCode(sizePx: Int = 256, paddingPx: Int = 0): Bitmap {
val hintMap = Hashtable<EncodeHintType, Any>()

View file

@ -6,12 +6,11 @@ import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.AnnotatedString.Builder
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.decapitalize
import com.tangem.core.res.getPluralStringSafe
import com.tangem.core.res.getStringSafe
import org.intellij.markdown.MarkdownElementTypes
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@ -172,7 +171,7 @@ fun TextReference.resolveReference(): String {
.map { if (it is TextReference) it.resolveReference() else it }
.toTypedArray()
val resolvedReference = stringResource(id = id, *args)
val resolvedReference = stringResourceSafe(id = id, *args)
if (decapitalize) {
resolvedReference.replaceFirstChar { char -> char.lowercase() }
@ -180,7 +179,7 @@ fun TextReference.resolveReference(): String {
resolvedReference
}
}
is TextReference.PluralRes -> pluralStringResource(id, count, *formatArgs.toTypedArray())
is TextReference.PluralRes -> pluralStringResourceSafe(id, count, *formatArgs.toTypedArray())
is TextReference.Str -> value
is TextReference.Annotated -> value.text
is TextReference.Combined -> {
@ -201,9 +200,9 @@ fun TextReference.resolveReference(resources: Resources): String {
.map { if (it is TextReference) it.resolveReference(resources) else it }
.toTypedArray()
resources.getString(id, *args)
resources.getStringSafe(id, *args)
}
is TextReference.PluralRes -> resources.getQuantityString(id, count, *formatArgs.toTypedArray())
is TextReference.PluralRes -> resources.getPluralStringSafe(id, count, *formatArgs.toTypedArray())
is TextReference.Str -> value
is TextReference.Annotated -> value.text
is TextReference.Combined -> {
@ -225,10 +224,10 @@ fun TextReference.resolveAnnotatedReference(): AnnotatedString {
.map { if (it is TextReference) it.resolveReference() else it }
.toTypedArray()
formatAnnotated(stringResource(id = id, *args))
formatAnnotated(stringResourceSafe(id = id, *args))
}
is TextReference.PluralRes -> formatAnnotated(
pluralStringResource(id, count, *formatArgs.toTypedArray()),
pluralStringResourceSafe(id, count, *formatArgs.toTypedArray()),
)
is TextReference.Str -> formatAnnotated(value)
is TextReference.Annotated -> value

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View file

@ -25,11 +25,12 @@ class Debouncer {
coroutineScope: CoroutineScope,
waitMs: Long = 300L,
context: CoroutineContext = EmptyCoroutineContext,
forceUpdate: Boolean = false,
destinationFunction: suspend () -> Unit,
) {
debounceJob?.cancel()
debounceJob = coroutineScope.launch(context) {
delay(waitMs)
if (!forceUpdate) delay(waitMs)
destinationFunction.invoke()
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.domain.feedback
import android.content.res.Resources
import com.tangem.core.res.getStringSafe
import com.tangem.domain.feedback.models.CardInfo
import com.tangem.domain.feedback.models.FeedbackEmail
import com.tangem.domain.feedback.models.FeedbackEmailType
@ -65,7 +66,7 @@ class SendFeedbackEmailUseCase(
is FeedbackEmailType.SwapProblem,
is FeedbackEmailType.TransactionSendingProblem,
-> {
append(resources.getString(R.string.feedback_data_collection_message))
append(resources.getStringSafe(R.string.feedback_data_collection_message))
skipLine()
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.domain.feedback.utils
import android.content.res.Resources
import com.tangem.core.res.getStringSafe
import com.tangem.domain.feedback.R
import com.tangem.domain.feedback.models.FeedbackEmailType
@ -27,6 +28,6 @@ internal class EmailMessageTitleResolver(private val resources: Resources) {
-> R.string.feedback_preface_tx_failed
is FeedbackEmailType.PreActivatedWallet -> R.string.feedback_preface_support
}
.let(resources::getString)
.let(resources::getStringSafe)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.domain.feedback.utils
import android.content.res.Resources
import com.tangem.core.res.getStringSafe
import com.tangem.domain.feedback.R
import com.tangem.domain.feedback.models.FeedbackEmailType
@ -32,6 +33,6 @@ internal class EmailSubjectResolver(private val resources: Resources) {
is FeedbackEmailType.PreActivatedWallet -> R.string.feedback_subject_pre_activated_wallet
is FeedbackEmailType.CurrencyDescriptionError -> R.string.feedback_token_description_error
}
.let(resources::getString)
.let(resources::getStringSafe)
}
}

View file

@ -1,9 +0,0 @@
package com.tangem.domain.redux
import com.tangem.domain.redux.global.DomainGlobalState
import org.rekotlin.StateType
/**
[REDACTED_AUTHOR]
*/
data class DomainState(val globalState: DomainGlobalState = DomainGlobalState()) : StateType

View file

@ -1,37 +0,0 @@
package com.tangem.domain.redux
import com.tangem.domain.redux.global.DomainGlobalHub
import org.rekotlin.Action
import org.rekotlin.Store
/**
[REDACTED_AUTHOR]
*/
private val RE_STORE_HUBS: List<ReStoreHub<DomainState, *>> = listOf(DomainGlobalHub())
val domainStore = Store(
state = DomainState(),
middleware = RE_STORE_HUBS.map { it.getMiddleware() },
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
}
}
return assembleReducedDomainState
}

View file

@ -1,112 +0,0 @@
package com.tangem.domain.redux
import android.webkit.ValueCallback
import com.tangem.domain.redux.global.DomainGlobalState
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
import java.util.concurrent.Executors
/**
[REDACTED_AUTHOR]
* ReStoreHub's should not store the <StoreState> or the <State>, because this can lead to destabilization of
* a state behavior.
* All ReStoreHub's must be marked as internal
*/
internal interface ReStoreHub<StoreState, State> {
fun getMiddleware(): Middleware<StoreState>
fun reduce(action: Action, domainState: StoreState): StoreState
}
internal interface ReStoreReducer<State> {
fun reduceAction(action: Action, state: State): State
}
/**
* ReStoreHub is the entry point for actions. It processes it through middleware and reducer.
* Actions handled by ReStoreHub go into coroutine scope, which can be canceled while the action is being processed.
* All action went from the middleware must be dispatched through ReStoreHub.dispatchOnMain(Actions) to prevent
* concurrent modification in the Store
* Only the changed hub State will change its state in the DomainState
* Do not implement other states like as DomainGlobalState. Because it can dilute the responsibility of
* states.
* @param name - name of the Hub
* @param dispatcher - main coroutine dispatcher for actions
* @property globalState - state witch produce accessibility to global variables
*/
internal abstract class BaseStoreHub<State>(
private val name: String,
private val dispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher(),
) : ReStoreHub<DomainState, State> {
val globalState: DomainGlobalState
get() = domainStore.state.globalState
val hubScope = CoroutineScope(
Job() + dispatcher + CoroutineName(name) + FeatureCoroutineExceptionHandler.create(name),
)
private val actionsAndJobs = mutableMapOf<Action, Job>()
override fun getMiddleware(): Middleware<DomainState> {
return { dispatch, state ->
{ next ->
{ action ->
handle(state, action, dispatch)
next(action)
}
}
}
}
/**
* Launches new coroutine and stores the action with it's coroutine job. (Coroutine can be cancelled
* through invoking the cancelActionJob() function inside a middleware).
* Removes the action when job is completed.
*/
protected open fun handle(storeStateHolder: () -> DomainState?, action: Action, dispatch: DispatchFunction) {
val storeState = storeStateHolder()
?: throw UnsupportedOperationException("StoreState for the $name can't be NULL")
hubScope.launch {
actionsAndJobs[action] = this.coroutineContext.job
actionsAndJobs[action]?.invokeOnCompletion { actionsAndJobs.remove(action) }
handleAction(action, storeState) {
actionsAndJobs.remove(it)?.cancel()
}
}
}
/**
* Reduce the action and check it. If the action hasn't updated the hubState, then it doesn't need to update
* storeState
*/
override fun reduce(action: Action, domainState: DomainState): DomainState {
val hubOldState = getHubState(domainState)
val hubNewState = getReducer().reduceAction(action, hubOldState)
return if (hubOldState === hubNewState) {
domainState
} else {
updateStoreState(domainState, hubNewState)
}
}
protected fun cancelAll() {
actionsAndJobs.forEach { (_, job) -> job.cancel() }
}
protected abstract suspend fun handleAction(action: Action, storeState: DomainState, cancel: ValueCallback<Action>)
protected abstract fun getReducer(): ReStoreReducer<State>
protected abstract fun getHubState(storeState: DomainState): State
protected abstract fun updateStoreState(storeState: DomainState, newHubState: State): DomainState
}

View file

@ -1,12 +0,0 @@
package com.tangem.domain.redux.extensions
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.redux.domainStore
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
internal suspend inline fun dispatchOnMain(vararg actions: Action) {
withMainContext { actions.forEach { domainStore.dispatch(it) } }
}

View file

@ -1,12 +0,0 @@
package com.tangem.domain.redux.global
import com.tangem.domain.models.scan.ScanResponse
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
// TODO: refactoring: is alias for the GlobalAction
sealed class DomainGlobalAction : Action {
data class SaveScanNoteResponse(val scanResponse: ScanResponse) : DomainGlobalAction()
}

View file

@ -1,53 +0,0 @@
package com.tangem.domain.redux.global
import android.webkit.ValueCallback
import com.tangem.common.extensions.toHexString
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.utils.RequestHeader
import com.tangem.domain.redux.BaseStoreHub
import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.ReStoreReducer
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
// TODO: refactoring: is alias for the GlobalMiddleware and the GlobalReducer
internal class DomainGlobalHub : BaseStoreHub<DomainGlobalState>("DomainGlobalHub") {
override fun getHubState(storeState: DomainState): DomainGlobalState {
return storeState.globalState
}
override fun updateStoreState(storeState: DomainState, newHubState: DomainGlobalState): DomainState {
return storeState.copy(globalState = newHubState)
}
override suspend fun handleAction(action: Action, storeState: DomainState, cancel: ValueCallback<Action>) {
if (action !is DomainGlobalAction) return
}
override fun getReducer(): ReStoreReducer<DomainGlobalState> = DomainGlobalReducer()
}
private class DomainGlobalReducer : ReStoreReducer<DomainGlobalState> {
override fun reduceAction(action: Action, state: DomainGlobalState): DomainGlobalState {
return when (action) {
is DomainGlobalAction.SaveScanNoteResponse -> {
val card = action.scanResponse.card
// TODO: AuthHeaders: try to remove it, because now we can use headers with dynamic values
state.networkServices.tangemTechService.addAuthenticationHeader(
RequestHeader.AuthenticationHeader(
object : AuthProvider {
override fun getCardPublicKey(): String = card.cardPublicKey.toHexString()
override fun getCardId(): String = card.cardId
},
),
)
state.copy(scanResponse = action.scanResponse)
}
else -> state
}
}
}

View file

@ -1,19 +0,0 @@
package com.tangem.domain.redux.global
import com.tangem.datasource.api.tangemTech.TangemTechService
import com.tangem.domain.models.scan.ScanResponse
/**
[REDACTED_AUTHOR]
*/
data class DomainGlobalState(
// there is a part of mirrors from the GlobalState.
// It updates on GlobalAction.SaveScanNoteResponse -> DomainGlobalAction.SaveScanNoteResponse(scanResponse)
val scanResponse: ScanResponse? = null,
//
val networkServices: NetworkServices = NetworkServices(),
)
data class NetworkServices(
val tangemTechService: TangemTechService = TangemTechService,
)

View file

@ -21,9 +21,17 @@ platform :android do
FileUtils.cp("../app/src/main/assets/tangem-app-config/android/google-services.json", "../app")
end
desc "Runs all the tests"
lane :test do
desc "Run detekt"
lane :detekt do
FileUtils.cp("../ci_resources/tests_8gb_ram_ci_gradle.properties", "../gradle.properties")
puts File.read("../gradle.properties")
gradle(task: "detekt")
end
desc "Run tests"
lane :test do
FileUtils.cp("../ci_resources/tests_8gb_ram_ci_gradle.properties", "../gradle.properties")
puts File.read("../gradle.properties")
gradle(task: "testDebugUnitTest")
end
@ -79,19 +87,22 @@ platform :android do
desc "Publish internal and external builds to Firebase App Distribution"
lane :publishToFirebase do |options|
FileUtils.cp("../ci_resources/build_8gb_ram_ci_gradle.properties", "../gradle.properties")
puts File.read("../gradle.properties")
gradle(
task: "clean assemble",
build_type: "Internal",
properties: {
'versionCode' => ENV['versionCode'],
'versionName' => ENV['versionName'],
'versionCode' => ENV['version_code'],
'versionName' => ENV['version_name'],
}
)
firebase_app_distribution(
firebase_cli_token: ENV['firebase_cli_token'],
app: ENV['app_id_internal'],
apk_path: ENV['apk_path_internal'],
groups: ENV['groups'],
release_notes: ENV['releaseNotes']
release_notes: ENV['release_notes'],
)
end
end

View file

@ -18,13 +18,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.block.BlockItem
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalSnackbarHostState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -86,7 +86,7 @@ private fun Content(
item {
Text(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
text = stringResource(R.string.details_title),
text = stringResourceSafe(R.string.details_title),
style = TangemTheme.typography.h1,
color = TangemTheme.colors.text.primary1,
)
@ -127,7 +127,9 @@ private fun Block(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top,
) {
val itemModifier = Modifier.fillMaxWidth().testTag(TestTags.DETAILS_SCREEN_ITEM)
val itemModifier = Modifier
.fillMaxWidth()
.testTag(TestTags.DETAILS_SCREEN_ITEM)
when (model) {
is DetailsItemUM.Basic -> {

View file

@ -7,8 +7,8 @@ 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 com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.details.impl.R
@ -37,12 +37,12 @@ internal fun WalletConnectBlock(onClick: () -> Unit, modifier: Modifier = Modifi
verticalArrangement = Arrangement.SpaceAround,
) {
Text(
text = stringResource(id = R.string.wallet_connect_title),
text = stringResourceSafe(id = R.string.wallet_connect_title),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
Text(
text = stringResource(id = R.string.wallet_connect_subtitle),
text = stringResourceSafe(id = R.string.wallet_connect_subtitle),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)

View file

@ -1,6 +1,5 @@
package com.tangem.features.details.utils
import androidx.compose.ui.res.stringResource
import arrow.core.Either
import arrow.core.raise.Raise
import arrow.core.raise.ensureNotNull
@ -13,10 +12,7 @@ import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.navigation.popTo
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.components.SimpleOkDialog
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.isNullOrEmpty
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.message.ContentMessage
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.card.ScanCardProcessor
@ -107,7 +103,7 @@ internal class UserWalletSaver @Inject constructor(
messageSender.send(
message = ContentMessage { onDismiss ->
SimpleOkDialog(
message = stringResource(id = R.string.user_wallet_list_error_wallet_already_saved),
message = stringResourceSafe(id = R.string.user_wallet_list_error_wallet_already_saved),
onDismissDialog = onDismiss,
)
},

View file

@ -16,7 +16,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
@ -31,6 +30,7 @@ import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.buttons.common.TangemButtonColors
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -144,7 +144,7 @@ private fun BoxScope.DisclaimerButton(onAccept: (Boolean) -> Unit) {
rememberPermissionState(permission = permission).status.isGranted
} ?: true
PrimaryButton(
text = stringResource(id = R.string.common_accept),
text = stringResourceSafe(id = R.string.common_accept),
onClick = { onAccept(!isPermissionGranted) },
colors = TangemButtonColors(
backgroundColor = TangemColorPalette.Light4,

View file

@ -1,6 +1,5 @@
package com.tangem.features.managetokens.model
import androidx.compose.ui.res.stringResource
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ComponentScoped
@ -10,6 +9,7 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.components.SimpleOkDialog
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.message.ContentMessage
import com.tangem.domain.card.DerivePublicKeysUseCase
import com.tangem.domain.card.HasMissedDerivationsUseCase
@ -231,7 +231,7 @@ internal class CustomTokenFormModel @Inject constructor(
private fun showErrorDialog() {
val dialog = ContentMessage { onDismiss ->
SimpleOkDialog(
message = stringResource(R.string.common_unknown_error),
message = stringResourceSafe(R.string.common_unknown_error),
onDismissDialog = onDismiss,
)
}

View file

@ -16,7 +16,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -33,6 +32,7 @@ import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
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.managetokens.component.preview.PreviewCustomTokenFormComponent
@ -89,7 +89,7 @@ internal fun CustomTokenFormContent(model: CustomTokenFormUM, modifier: Modifier
.align(Alignment.BottomCenter)
.padding(bottom = TangemTheme.dimens.spacing16 + bottomBarHeight)
.fillMaxWidth(),
text = stringResource(id = R.string.custom_token_add_token),
text = stringResourceSafe(id = R.string.custom_token_add_token),
colors = TangemButtonsDefaults.primaryButtonColors,
enabled = model.canAddToken,
showProgress = model.isValidating,

Some files were not shown because too many files have changed in this diff Show more