diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..4fa62fd751 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7da64b0215..c9fed30508 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index 2e99af7238..439833b6dc 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index e0a2bf62d7..9ec82d584d 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -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 @@ -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() diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 8409782c2b..1bc3c9a85e 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsEventConverter.kt new file mode 100644 index 0000000000..559fc3418b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsEventConverter.kt @@ -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): Map { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt index 20597e0fb9..7e4e5085bc 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt @@ -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) { - fbAnalytics.logEvent(event, params.toBundle()) + fbAnalytics.logEvent( + eventConverter.convertEventName(event), + eventConverter.convertEventParams(params).toBundle(), + ) } override fun logErrorEvent(error: Throwable, params: Map) { - params.forEach { fbCrashlytics.setCustomKey(it.key, it.value) } + eventConverter.convertEventParams(params) + .forEach { fbCrashlytics.setCustomKey(it.key, it.value) } + fbCrashlytics.recordException(error) } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt b/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt deleted file mode 100644 index b6101bd4d8..0000000000 --- a/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.tangem.tap.common.extensions - -fun StringBuilder.breakLine(count: Int = 1): StringBuilder = append("\n".repeat(count)) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt index b14e9e4090..9af9912fa5 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index 4fed318b36..04d6fa4896 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -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> { return listOf( diff --git a/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt b/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt deleted file mode 100644 index 55e4c0b653..0000000000 --- a/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 740f5bd460..87fae53bc3 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -82,8 +82,4 @@ sealed class GlobalAction : Action { object Update : GlobalAction() } - - object FetchUserCountry : GlobalAction() { - data class Success(val countryCode: String) : GlobalAction() - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index b8865305c5..e7f1d6613d 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -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(), - ), - ) - } - } - } - } } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index d55f99d21b..c05d9bbc89 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -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 } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index 59dea35070..164992b425 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -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 { - 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) { diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index 471a9436e2..0d454c11e8 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -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) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt index 168fccb7e7..c4cfcfd7ff 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appcurrency/AppCurrencySelectorScreen.kt @@ -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, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt index 06e802b5e0..29290619e6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSelectorDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSelectorDialog.kt index 59af4ac24b..3dba72ed5d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSelectorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSelectorDialog.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index 57f1103703..c4454f830f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -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, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt index bbde2c8579..cbf005de4c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt @@ -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 } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt index d8107e0c4d..8315639120 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryScreen.kt @@ -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), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt index 8cdc6777a9..91558d9f57 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 6f35968117..f68ae0c367 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -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 = {}, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt index fe5164bf2f..3a86a4f67c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt @@ -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 = {}, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt index 7e5b3542cd..fb8d8a36df 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt @@ -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, ) diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt index 198f6930da..0a1f0fdaed 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt @@ -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 = { diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt index 1577eecad8..184bf8d37d 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index be6823d684..6b7987848b 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -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, ) diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt index e3e237954e..4d70d5f2f6 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/SearchCurrenciesButton.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/features/home/data/HomeRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/features/home/data/HomeRepositoryImpl.kt deleted file mode 100644 index fed3f2e394..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/data/HomeRepositoryImpl.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/domain/HomeRepository.kt b/app/src/main/java/com/tangem/tap/features/home/domain/HomeRepository.kt deleted file mode 100644 index ad489ae0fe..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/domain/HomeRepository.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt deleted file mode 100644 index 55ebe3e10d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt +++ /dev/null @@ -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") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 87a615ef97..ffe384bee5 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index cde9a3b7df..bde2638529 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -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") } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index e6166576ce..8859c88746 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -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)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index d47862a648..99c704189c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -176,7 +176,6 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch OnboardingHelper.handleTopUpAction( walletManager = walletManager, scanResponse = scanResponse, - globalState = globalState, ) } is OnboardingNoteAction.Done -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index ef29db32d4..37ecb6d41f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -299,7 +299,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) { OnboardingHelper.handleTopUpAction( walletManager = walletManager, scanResponse = onboardingManager?.scanResponse ?: return, - globalState = globalState, ) } TwinCardsAction.Done -> { diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt index 1f3b36e57d..54da091df6 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt @@ -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( diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt index 4206df1904..c23f77abe1 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 9edfafa699..c109672c4b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt index 0ecd71bd5d..592678f735 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt index 57f226a9d7..2dc63dd5dd 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt @@ -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, ), ) diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt index 7fb08b3681..23661bc2ad 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index db70565310..db4a2735bd 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/routing/toggle/RoutingFeatureToggles.kt b/app/src/main/java/com/tangem/tap/routing/toggle/RoutingFeatureToggles.kt new file mode 100644 index 0000000000..6554684732 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/routing/toggle/RoutingFeatureToggles.kt @@ -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") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index e59897708c..8cbd01ea2e 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -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) { diff --git a/ci_resources/build_8gb_ram_ci_gradle.properties b/ci_resources/build_8gb_ram_ci_gradle.properties new file mode 100644 index 0000000000..45c763f199 --- /dev/null +++ b/ci_resources/build_8gb_ram_ci_gradle.properties @@ -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 \ No newline at end of file diff --git a/ci_resources/tests_8gb_ram_ci_gradle.properties b/ci_resources/tests_8gb_ram_ci_gradle.properties new file mode 100644 index 0000000000..78577a0975 --- /dev/null +++ b/ci_resources/tests_8gb_ram_ci_gradle.properties @@ -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 diff --git a/ci_resources/x86_64_libs/ld-linux-x86-64.so.2 b/ci_resources/x86_64_libs/ld-linux-x86-64.so.2 new file mode 100755 index 0000000000..8ef4df1a3d Binary files /dev/null and b/ci_resources/x86_64_libs/ld-linux-x86-64.so.2 differ diff --git a/ci_resources/x86_64_libs/libc.so.6 b/ci_resources/x86_64_libs/libc.so.6 new file mode 100755 index 0000000000..4b600449a0 Binary files /dev/null and b/ci_resources/x86_64_libs/libc.so.6 differ diff --git a/ci_resources/x86_64_libs/libdl.so.2 b/ci_resources/x86_64_libs/libdl.so.2 new file mode 100644 index 0000000000..2862f8ef7e Binary files /dev/null and b/ci_resources/x86_64_libs/libdl.so.2 differ diff --git a/ci_resources/x86_64_libs/libgcc_s.so.1 b/ci_resources/x86_64_libs/libgcc_s.so.1 new file mode 100644 index 0000000000..0e7b8a5939 Binary files /dev/null and b/ci_resources/x86_64_libs/libgcc_s.so.1 differ diff --git a/ci_resources/x86_64_libs/libm.so.6 b/ci_resources/x86_64_libs/libm.so.6 new file mode 100644 index 0000000000..7a473b15f3 Binary files /dev/null and b/ci_resources/x86_64_libs/libm.so.6 differ diff --git a/ci_resources/x86_64_libs/libpthread.so.0 b/ci_resources/x86_64_libs/libpthread.so.0 new file mode 100644 index 0000000000..3e09ebef74 Binary files /dev/null and b/ci_resources/x86_64_libs/libpthread.so.0 differ diff --git a/ci_resources/x86_64_libs/librt.so.1 b/ci_resources/x86_64_libs/librt.so.1 new file mode 100644 index 0000000000..8edf9c4312 Binary files /dev/null and b/ci_resources/x86_64_libs/librt.so.1 differ diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountButtons.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountButtons.kt index b279e47f6a..f1d8e921a8 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountButtons.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountButtons.kt @@ -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 diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/SendDoneButtons.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/SendDoneButtons.kt index 8689591d9b..4b5db2927a 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/SendDoneButtons.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/SendDoneButtons.kt @@ -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) diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt index 4f4e74d559..0502230706 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt @@ -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, diff --git a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt index cb7a3743ec..c534f12308 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt @@ -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, ) \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index adf9f08d85..2f18a10314 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -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.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.addValidateTransactionNotifications( dustValue: BigDecimal, - fee: Fee?, validationError: Throwable?, cryptoCurrency: CryptoCurrency, + minAdaValue: BigDecimal?, // TODO revert to Fee, after swap TxFee refactored onReduceClick: ( reduceAmountTo: BigDecimal, notification: Class, @@ -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), ), ) } diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index c4feab6dbf..c7dc28c8b7 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -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" } ] diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt deleted file mode 100644 index 4c08c5ce73..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt +++ /dev/null @@ -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(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) - } -} \ No newline at end of file diff --git a/core/res/build.gradle.kts b/core/res/build.gradle.kts index c2fce27feb..c444a12b01 100644 --- a/core/res/build.gradle.kts +++ b/core/res/build.gradle.kts @@ -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 } \ No newline at end of file diff --git a/core/res/src/main/java/com/tangem/core/res/Resources.kt b/core/res/src/main/java/com/tangem/core/res/Resources.kt new file mode 100644 index 0000000000..46ceaa8b3a --- /dev/null +++ b/core/res/src/main/java/com/tangem/core/res/Resources.kt @@ -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.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) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt index e741b087fe..ac57417425 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt @@ -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, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt index 0e39a22357..4a87ade6e2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt @@ -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() diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt index ef0308abd3..df498f267a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt @@ -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 diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt index 79c75c96e6..ef23a4babc 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/PasteButton.kt @@ -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( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt index 8ef37d1f7c..52bdac7eab 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt @@ -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, ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt index c6dd8c39f9..86009a8f7f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt @@ -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), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/NonFiatContentBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/NonFiatContentBlock.kt index 5e486a5078..7682b67444 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/NonFiatContentBlock.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/NonFiatContentBlock.kt @@ -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, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenCryptoAmount.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenCryptoAmount.kt index 77720d8cd9..0ea91f89b4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenCryptoAmount.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenCryptoAmount.kt @@ -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) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt index 1a4d6d8dae..34bd4345fa 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt @@ -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) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt index c79e40c723..a3f882b7ba 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt @@ -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, ) -} \ No newline at end of file +} + +@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(collection = listOf(true, false)) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/NetworkTitleItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/NetworkTitleItem.kt deleted file mode 100644 index 8ca65a2982..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/NetworkTitleItem.kt +++ /dev/null @@ -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(collection = listOf(true, false)) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt index 23332310aa..9b0e9235ed 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt @@ -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 * diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt index af0ff0f89b..8c7bdbc6d9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt @@ -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, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt index 3a7f89969c..4f8201f07c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TxHistoryTitle.kt @@ -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, ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/String.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/String.kt index 63fb8f00bd..054b2829e2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/String.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/String.kt @@ -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() diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt index efcf847a05..080fa3c043 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt @@ -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 diff --git a/core/ui/src/main/res/drawable/img_card_visa.webp b/core/ui/src/main/res/drawable/img_card_visa.webp new file mode 100644 index 0000000000..bd2804ca99 Binary files /dev/null and b/core/ui/src/main/res/drawable/img_card_visa.webp differ diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt index cfa0536804..d0b50ceab4 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -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() } } diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt index 302680f4c6..ce7a49ad8e 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt @@ -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() } } diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt index 1311a3016e..4d87eecc86 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt @@ -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) } } \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt index e373d8d496..cd511d679b 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt @@ -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) } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/DomainState.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/DomainState.kt deleted file mode 100644 index 67be91d2dd..0000000000 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/DomainState.kt +++ /dev/null @@ -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 \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/DomainStore.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/DomainStore.kt deleted file mode 100644 index a141cd5f9a..0000000000 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/DomainStore.kt +++ /dev/null @@ -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> = 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>() - - RE_STORE_HUBS.forEach { - val reducedState = it.reduce(action, assembleReducedDomainState) - - assembleReducedDomainState = if (reducedState != assembleReducedDomainState) { - reducedStatesByAction.add(action to assembleReducedDomainState) - reducedState - } else { - assembleReducedDomainState - } - } - - return assembleReducedDomainState -} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/ReStoreHub.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/ReStoreHub.kt deleted file mode 100644 index fcef0871d2..0000000000 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/ReStoreHub.kt +++ /dev/null @@ -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 or the , because this can lead to destabilization of - * a state behavior. - * All ReStoreHub's must be marked as internal - */ -internal interface ReStoreHub { - fun getMiddleware(): Middleware - fun reduce(action: Action, domainState: StoreState): StoreState -} - -internal interface ReStoreReducer { - 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( - private val name: String, - private val dispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher(), -) : ReStoreHub { - - val globalState: DomainGlobalState - get() = domainStore.state.globalState - - val hubScope = CoroutineScope( - Job() + dispatcher + CoroutineName(name) + FeatureCoroutineExceptionHandler.create(name), - ) - - private val actionsAndJobs = mutableMapOf() - - override fun getMiddleware(): Middleware { - 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) - protected abstract fun getReducer(): ReStoreReducer - - protected abstract fun getHubState(storeState: DomainState): State - protected abstract fun updateStoreState(storeState: DomainState, newHubState: State): DomainState -} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/extensions/Dispatch.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/extensions/Dispatch.kt deleted file mode 100644 index 36db6ef7ed..0000000000 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/extensions/Dispatch.kt +++ /dev/null @@ -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) } } -} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalAction.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalAction.kt deleted file mode 100644 index 90bf41578e..0000000000 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalAction.kt +++ /dev/null @@ -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() -} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt deleted file mode 100644 index 18f081f8a7..0000000000 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt +++ /dev/null @@ -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("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) { - if (action !is DomainGlobalAction) return - } - - override fun getReducer(): ReStoreReducer = DomainGlobalReducer() -} - -private class DomainGlobalReducer : ReStoreReducer { - - 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 - } - } -} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt deleted file mode 100644 index bd6efc230c..0000000000 --- a/domain/legacy/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt +++ /dev/null @@ -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, -) \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index edd1284284..e091569e10 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -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 diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt index 98302fc6f7..8054977653 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt @@ -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 -> { diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/WalletConnectBlock.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/WalletConnectBlock.kt index b2e05c0504..e9c6048867 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/WalletConnectBlock.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/WalletConnectBlock.kt @@ -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, ) diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt index a427f0fc29..450ed613ad 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt @@ -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, ) }, diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt index 1fcc978ace..87bd32ab50 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/ui/DisclaimerScreen.kt @@ -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, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt index 1e4f48feec..7ae0b35b09 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt @@ -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, ) } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt index 1b01e1e8bf..fd105e3e8c 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt @@ -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, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt index 2fed1003f0..1d76d45e8a 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.RectangleShape 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.PreviewParameterProvider @@ -28,6 +27,7 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.rows.ChainRow import com.tangem.core.ui.components.rows.model.ChainRowUM import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.tokens.model.Network @@ -137,7 +137,7 @@ private fun Header(header: CustomTokenSelectorUM.HeaderUM, modifier: Modifier = modifier = Modifier .padding(top = TangemTheme.dimens.spacing12) .padding(horizontal = TangemTheme.dimens.spacing12), - text = stringResource(R.string.add_custom_token_choose_network), + text = stringResourceSafe(R.string.add_custom_token_choose_network), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) @@ -189,13 +189,14 @@ private fun CustomDerivationButton( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { Text( - text = stringResource(id = R.string.custom_token_custom_derivation), + text = stringResourceSafe(id = R.string.custom_token_custom_derivation), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, ) Text( modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), - text = enteredDerivationPath ?: stringResource(id = R.string.custom_token_custom_derivation_title), + text = enteredDerivationPath + ?: stringResourceSafe(id = R.string.custom_token_custom_derivation_title), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.primary1, ) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt index f7643e76c1..fa7699bc52 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.draw.rotate 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.PreviewParameterProvider @@ -55,6 +54,7 @@ import com.tangem.core.ui.components.snackbar.TangemSnackbarHost import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.res.LocalHapticManager import com.tangem.core.ui.res.LocalSnackbarHostState @@ -165,7 +165,7 @@ private fun SaveChangesButton( label = "save_button_visibility", ) { TangemButton( - text = stringResource(id = R.string.common_save), + text = stringResourceSafe(id = R.string.common_save), icon = if (showIcon) { TangemButtonIconPosition.End(R.drawable.ic_tangem_24) } else { @@ -277,7 +277,7 @@ private fun SearchNothingFoundText(modifier: Modifier = Modifier) { contentAlignment = Alignment.Center, ) { Text( - text = stringResource(R.string.markets_search_token_no_result_title), + text = stringResourceSafe(R.string.markets_search_token_no_result_title), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/OnboardingManageTokensContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/OnboardingManageTokensContent.kt index d40b7fb3e2..e9752d8611 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/OnboardingManageTokensContent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/OnboardingManageTokensContent.kt @@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -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 @@ -20,6 +19,7 @@ import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.components.snackbar.TangemSnackbarHost import com.tangem.core.ui.event.EventEffect +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 @@ -102,7 +102,7 @@ private fun FloatingActionButton(config: OnboardingManageTokensUM.ActionButtonCo is OnboardingManageTokensUM.ActionButtonConfig.Continue -> ContinueButton(config = config, modifier = modifier) is OnboardingManageTokensUM.ActionButtonConfig.Later -> SecondaryButton( modifier = modifier, - text = stringResource(id = R.string.common_later), + text = stringResourceSafe(id = R.string.common_later), showProgress = config.showProgress, onClick = config.onClick, ) @@ -117,7 +117,7 @@ private fun ContinueButton( if (config.showTangemIcon) { PrimaryButtonIconEnd( modifier = modifier, - text = stringResource(id = R.string.common_continue), + text = stringResourceSafe(id = R.string.common_continue), iconResId = R.drawable.ic_tangem_24, showProgress = config.showProgress, onClick = config.onClick, @@ -125,7 +125,7 @@ private fun ContinueButton( } else { PrimaryButton( modifier = modifier, - text = stringResource(id = R.string.common_continue), + text = stringResourceSafe(id = R.string.common_continue), showProgress = config.showProgress, onClick = config.onClick, ) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/component/AddCustomTokenDescription.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/component/AddCustomTokenDescription.kt index 3b95cd9e8c..6bd84f1f08 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/component/AddCustomTokenDescription.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/component/AddCustomTokenDescription.kt @@ -4,8 +4,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.managetokens.impl.R @@ -13,7 +13,7 @@ import com.tangem.features.managetokens.impl.R internal fun AddCustomTokenDescription(modifier: Modifier = Modifier) { Text( modifier = modifier.fillMaxWidth(fraction = 0.7f), - text = stringResource(id = R.string.custom_token_subtitle), + text = stringResourceSafe(id = R.string.custom_token_subtitle), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/CurrencyUnsupportedDialog.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/CurrencyUnsupportedDialog.kt index 659b007c29..076cca7f32 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/CurrencyUnsupportedDialog.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/CurrencyUnsupportedDialog.kt @@ -1,11 +1,11 @@ package com.tangem.features.managetokens.ui.dialog import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButtonUM import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.features.managetokens.impl.R @Composable @@ -14,7 +14,7 @@ internal fun CurrencyUnsupportedDialog(title: TextReference, message: TextRefere title = title.resolveReference(), message = message.resolveReference(), confirmButton = DialogButtonUM( - title = stringResource(R.string.common_ok), + title = stringResourceSafe(R.string.common_ok), onClick = onDismiss, ), onDismissDialog = onDismiss, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/CustomDerivationInputDialog.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/CustomDerivationInputDialog.kt index be41894576..d9cb84c75d 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/CustomDerivationInputDialog.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/CustomDerivationInputDialog.kt @@ -4,7 +4,6 @@ import android.content.res.Configuration import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState -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 @@ -12,6 +11,7 @@ import com.tangem.core.ui.components.AdditionalTextInputDialogUM import com.tangem.core.ui.components.DialogButtonUM import com.tangem.core.ui.components.TextInputDialog import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.managetokens.component.CustomTokenDerivationInputComponent import com.tangem.features.managetokens.component.preview.PreviewCustomTokenDerivationInputComponent @@ -23,21 +23,21 @@ internal fun CustomDerivationInputDialog(model: CustomDerivationInputUM, onDismi val value by rememberUpdatedState(newValue = model.value) TextInputDialog( - title = stringResource(id = R.string.custom_token_custom_derivation_title), + title = stringResourceSafe(id = R.string.custom_token_custom_derivation_title), fieldValue = value, confirmButton = DialogButtonUM( - title = stringResource(id = R.string.common_ok), + title = stringResourceSafe(id = R.string.common_ok), enabled = model.isConfirmEnabled, onClick = model.onConfirm, ), dismissButton = DialogButtonUM( - title = stringResource(id = R.string.common_cancel), + title = stringResourceSafe(id = R.string.common_cancel), onClick = onDismiss, ), onDismissDialog = onDismiss, onValueChange = model.updateValue, textFieldParams = AdditionalTextInputDialogUM( - label = model.error?.resolveReference() ?: stringResource(id = R.string.custom_token_derivation_path), + label = model.error?.resolveReference() ?: stringResourceSafe(id = R.string.custom_token_derivation_path), isError = model.error != null, ), ) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/HasLinkedTokensWarning.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/HasLinkedTokensWarning.kt index 848d654e08..3e84202ce6 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/HasLinkedTokensWarning.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/HasLinkedTokensWarning.kt @@ -1,9 +1,9 @@ package com.tangem.features.managetokens.ui.dialog import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButtonUM +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.domain.managetokens.model.ManagedCryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.features.managetokens.impl.R @@ -11,18 +11,18 @@ import com.tangem.features.managetokens.impl.R @Composable internal fun HasLinkedTokensWarning(currency: ManagedCryptoCurrency, network: Network, onDismiss: () -> Unit) { BasicDialog( - title = stringResource( + title = stringResourceSafe( R.string.token_details_unable_hide_alert_title, currency.name, ), - message = stringResource( + message = stringResourceSafe( R.string.token_details_unable_hide_alert_message, currency.name, currency.symbol, network.name, ), confirmButton = DialogButtonUM( - title = stringResource(R.string.common_ok), + title = stringResourceSafe(R.string.common_ok), onClick = onDismiss, ), onDismissDialog = onDismiss, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/HideTokenWarning.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/HideTokenWarning.kt index 46b9cc6a39..547791ec51 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/HideTokenWarning.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/dialog/HideTokenWarning.kt @@ -1,27 +1,27 @@ package com.tangem.features.managetokens.ui.dialog import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButtonUM +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.domain.managetokens.model.ManagedCryptoCurrency import com.tangem.features.managetokens.impl.R @Composable internal fun HideTokenWarning(currency: ManagedCryptoCurrency, onConfirm: () -> Unit, onDismiss: () -> Unit) { BasicDialog( - title = stringResource( + title = stringResourceSafe( R.string.token_details_hide_alert_title, currency.name, ), - message = stringResource(R.string.token_details_hide_alert_message), + message = stringResourceSafe(R.string.token_details_hide_alert_message), confirmButton = DialogButtonUM( - title = stringResource(R.string.token_details_hide_alert_hide), + title = stringResourceSafe(R.string.token_details_hide_alert_hide), warning = true, onClick = onConfirm, ), dismissButton = DialogButtonUM( - title = stringResource(R.string.common_cancel), + title = stringResourceSafe(R.string.common_cancel), onClick = onDismiss, ), onDismissDialog = onDismiss, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt index 987689d400..76a4172080 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle @@ -15,6 +14,7 @@ import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.PreviewShimmerContainer @@ -34,7 +34,12 @@ internal fun Description( append(description.resolveReference()) } withStyle(SpanStyle(color = TangemTheme.colors.text.accent)) { - append(" " + stringResource(R.string.common_read_more).replace(' ', StringsSigns.NON_BREAKING_SPACE)) + append( + " " + stringResourceSafe(R.string.common_read_more).replace( + ' ', + StringsSigns.NON_BREAKING_SPACE, + ), + ) } } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ExchangesBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ExchangesBottomSheet.kt index 8a0040876c..7fc3e9974b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ExchangesBottomSheet.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ExchangesBottomSheet.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity -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 @@ -27,10 +26,7 @@ import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.token.TokenItem import com.tangem.core.ui.components.token.state.TokenItemState -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.stringReference +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.markets.details.impl.ui.state.ExchangesBottomSheetContent @@ -92,7 +88,7 @@ internal fun ExchangesBottomSheet(config: TangemBottomSheetConfig) { @Composable private fun Title(@StringRes textResId: Int, onBackClick: () -> Unit) { TangemTopAppBar( - title = stringResource(id = textResId), + title = stringResourceSafe(id = textResId), startButton = TopAppBarButtonUM.Back(onBackClicked = onBackClick), ) } @@ -128,7 +124,7 @@ private fun Error(content: ExchangesBottomSheetContent.Error, modifier: Modifier horizontalAlignment = Alignment.CenterHorizontally, ) { Text( - text = stringResource(id = content.message), + text = stringResourceSafe(id = content.message), color = TangemTheme.colors.text.tertiary, textAlign = TextAlign.Center, style = TangemTheme.typography.caption1, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt index 2b02fdb118..5825ba908b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -16,6 +15,7 @@ import com.tangem.core.ui.components.block.information.InformationBlock import com.tangem.core.ui.components.buttons.chip.Chip import com.tangem.core.ui.components.inputrow.inner.DividerContainer 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.core.ui.utils.PreviewShimmerContainer @@ -31,7 +31,7 @@ internal fun LinksBlock(state: LinksUM, modifier: Modifier = Modifier) { contentHorizontalPadding = 0.dp, title = { Text( - text = stringResource(id = R.string.markets_token_details_links), + text = stringResourceSafe(id = R.string.markets_token_details_links), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, maxLines = 1, @@ -41,22 +41,22 @@ internal fun LinksBlock(state: LinksUM, modifier: Modifier = Modifier) { content = { Column { SubBlock( - title = stringResource(id = R.string.markets_token_details_official_links), + title = stringResourceSafe(id = R.string.markets_token_details_official_links), links = state.officialLinks, onLinkClick = state.onLinkClick, ) SubBlock( - title = stringResource(id = R.string.markets_token_details_social), + title = stringResourceSafe(id = R.string.markets_token_details_social), links = state.social, onLinkClick = state.onLinkClick, ) SubBlock( - title = stringResource(id = R.string.markets_token_details_repository), + title = stringResourceSafe(id = R.string.markets_token_details_repository), links = state.repository, onLinkClick = state.onLinkClick, ) SubBlock( - title = stringResource(id = R.string.markets_token_details_blockchain_site), + title = stringResourceSafe(id = R.string.markets_token_details_blockchain_site), links = state.blockchainSite, onLinkClick = state.onLinkClick, lastBlock = true, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MetricsBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MetricsBlock.kt index 4825c2ab32..9d33b61034 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MetricsBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/MetricsBlock.kt @@ -1,11 +1,12 @@ package com.tangem.features.markets.details.impl.ui.components import android.content.res.Configuration -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.TextButton @@ -14,6 +15,7 @@ import com.tangem.core.ui.components.block.information.GridItems import com.tangem.core.ui.components.block.information.InformationBlock import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.PreviewShimmerContainer @@ -33,7 +35,7 @@ internal fun MetricsBlock(state: MetricsUM, modifier: Modifier = Modifier) { modifier = modifier, title = { Text( - text = stringResource(id = R.string.markets_token_details_metrics), + text = stringResourceSafe(id = R.string.markets_token_details_metrics), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, maxLines = 1, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt index c111de7630..1c63ecd0b4 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt @@ -2,14 +2,12 @@ package com.tangem.features.markets.details.impl.ui.components import android.content.res.Configuration import androidx.compose.foundation.layout.* -import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.platform.LocalDensity -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 @@ -21,13 +19,14 @@ import com.tangem.core.ui.components.block.information.InformationBlock import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons import com.tangem.core.ui.components.progressbar.LinearProgressIndicator import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemAnimations import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.PreviewShimmerContainer import com.tangem.domain.markets.PriceChangeInterval -import com.tangem.features.markets.details.impl.ui.state.PricePerformanceUM import com.tangem.features.markets.details.impl.ui.getText +import com.tangem.features.markets.details.impl.ui.state.PricePerformanceUM import com.tangem.features.markets.impl.R import kotlinx.collections.immutable.persistentListOf @@ -39,7 +38,7 @@ internal fun PricePerformanceBlock(state: PricePerformanceUM, modifier: Modifier modifier = modifier, title = { Text( - text = stringResource(id = R.string.markets_token_details_price_performance), + text = stringResourceSafe(id = R.string.markets_token_details_price_performance), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, maxLines = 1, @@ -106,13 +105,13 @@ private fun Content(state: PricePerformanceUM.Value, modifier: Modifier = Modifi horizontalArrangement = Arrangement.SpaceBetween, ) { Text( - text = stringResource(R.string.markets_token_details_low), + text = stringResourceSafe(R.string.markets_token_details_low), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, ) SpacerW8() Text( - text = stringResource(R.string.markets_token_details_high), + text = stringResourceSafe(R.string.markets_token_details_high), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/AddToPortfolioBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/AddToPortfolioBottomSheet.kt index 73f6dca547..7d8382790c 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/AddToPortfolioBottomSheet.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/AddToPortfolioBottomSheet.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -37,6 +36,7 @@ import com.tangem.core.ui.components.currency.icon.CoinIcon import com.tangem.core.ui.components.rows.ArrowRow import com.tangem.core.ui.components.rows.BlockchainRow import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.res.LocalHapticManager import com.tangem.core.ui.res.TangemTheme @@ -154,7 +154,7 @@ private fun ContinueButton( ) .navigationBarsPadding() .fillMaxWidth(), - text = stringResource(R.string.common_continue), + text = stringResourceSafe(R.string.common_continue), icon = if (enabled && isTangemIconVisible) { TangemButtonIconPosition.End(R.drawable.ic_tangem_24) } else { @@ -178,7 +178,7 @@ private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifi modifier = modifier, title = { Text( - text = stringResource(R.string.markets_select_network), + text = stringResourceSafe(R.string.markets_select_network), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) @@ -279,7 +279,7 @@ private fun ScanWalletWarning(modifier: Modifier = Modifier) { contentDescription = null, ) Text( - text = stringResource(R.string.markets_generate_addresses_notification), + text = stringResourceSafe(R.string.markets_generate_addresses_notification), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt index abd9b2ccdc..51596ceb54 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.util.fastForEachIndexed @@ -24,6 +23,7 @@ import com.tangem.core.ui.components.buttons.SecondarySmallButton import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.markets.impl.R @@ -68,7 +68,7 @@ internal fun MyPortfolio(state: MyPortfolioUM, modifier: Modifier = Modifier) { @Composable private fun Title() { Text( - text = stringResource(R.string.markets_common_my_portfolio), + text = stringResourceSafe(R.string.markets_common_my_portfolio), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) @@ -150,7 +150,7 @@ fun UnavailableAssetForWallet(modifier: Modifier = Modifier) { private fun UnavailableContent(@StringRes textId: Int, modifier: Modifier = Modifier) { Text( modifier = modifier, - text = stringResource(textId), + text = stringResourceSafe(textId), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) @@ -163,14 +163,14 @@ private fun AddFirstTokenContent(state: MyPortfolioUM.AddFirstToken, modifier: M verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { Text( - text = stringResource(R.string.markets_add_to_my_portfolio_description), + text = stringResourceSafe(R.string.markets_add_to_my_portfolio_description), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) PrimaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.markets_add_to_portfolio_button), + text = stringResourceSafe(R.string.markets_add_to_portfolio_button), onClick = state.onAddClick, ) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WarningDialog.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WarningDialog.kt index 70e52d864c..e03f24de02 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WarningDialog.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WarningDialog.kt @@ -1,12 +1,12 @@ package com.tangem.features.markets.portfolio.impl.ui import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButtonUM 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.stringResourceSafe import com.tangem.features.markets.impl.R @Composable @@ -19,7 +19,7 @@ internal fun WarningDialog( title = title.resolveReference(), message = message.resolveReference(), confirmButton = DialogButtonUM( - title = stringResource(R.string.common_ok), + title = stringResourceSafe(R.string.common_ok), onClick = onDismiss, ), onDismissDialog = onDismiss, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/ui/TokenMarketBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/ui/TokenMarketBlock.kt index 8191f0addf..8f233b53ab 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/ui/TokenMarketBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/ui/TokenMarketBlock.kt @@ -9,7 +9,6 @@ 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.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.common.ui.charts.MarketChartMini @@ -21,6 +20,7 @@ import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.block.BlockCard import com.tangem.core.ui.components.marketprice.PriceChangeInPercent import com.tangem.core.ui.components.marketprice.PriceChangeType +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.markets.details.impl.model.formatter.toChartType @@ -75,7 +75,7 @@ private fun LeftSide( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), ) { Text( - text = stringResource(id = R.string.wallet_marketplace_block_title, symbol), + text = stringResourceSafe(id = R.string.wallet_marketplace_block_title, symbol), color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.subtitle2, ) @@ -99,7 +99,7 @@ private fun LeftSide( ) Text( modifier = Modifier.alignByBaseline(), - text = stringResource(id = R.string.wallet_marketprice_block_update_time), + text = stringResourceSafe(id = R.string.wallet_marketprice_block_update_time), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt index 71d013cc58..18815666be 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -33,6 +32,7 @@ import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -149,9 +149,9 @@ private fun Title(isInSearchMode: Boolean, modifier: Modifier = Modifier) { Text( modifier = modifier, text = if (isInSearchMode) { - stringResource(id = R.string.markets_search_result_title) + stringResourceSafe(id = R.string.markets_search_result_title) } else { - stringResource(id = R.string.markets_common_title) + stringResourceSafe(id = R.string.markets_common_title) }, style = TangemTheme.typography.h3, color = TangemTheme.colors.text.primary1, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt index f4dd833503..77b086ff3b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt @@ -10,12 +10,12 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.stringResource import com.tangem.core.ui.components.buttons.SecondarySmallButton import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.components.list.InfiniteListHandler import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.markets.impl.R import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM @@ -160,7 +160,7 @@ private fun ShowTokensUnder100kItem(onShowTokensClick: () -> Unit, modifier: Mod horizontalAlignment = Alignment.CenterHorizontally, ) { Text( - text = stringResource(R.string.markets_search_see_tokens_under_100k), + text = stringResourceSafe(R.string.markets_search_see_tokens_under_100k), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.tertiary, ) @@ -180,7 +180,7 @@ private fun SearchNothingFoundText(modifier: Modifier = Modifier) { contentAlignment = Alignment.Center, ) { Text( - text = stringResource(R.string.markets_search_token_no_result_title), + text = stringResourceSafe(R.string.markets_search_token_no_result_title), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/UnableToLoadData.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/UnableToLoadData.kt index 4cec85a065..c96d848bac 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/UnableToLoadData.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/UnableToLoadData.kt @@ -7,11 +7,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.buttons.SecondarySmallButton import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.markets.impl.R @@ -24,7 +24,7 @@ internal fun UnableToLoadData(onRetryClick: () -> Unit, modifier: Modifier = Mod horizontalAlignment = Alignment.CenterHorizontally, ) { Text( - text = stringResource(R.string.markets_loading_error_title), + text = stringResourceSafe(R.string.markets_loading_error_title), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/done/impl/ui/OnboardingDone.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/done/impl/ui/OnboardingDone.kt index d357369c9f..f3c8aa0111 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/done/impl/ui/OnboardingDone.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/done/impl/ui/OnboardingDone.kt @@ -8,8 +8,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -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 import androidx.compose.ui.unit.dp import com.airbnb.lottie.compose.LottieAnimation @@ -17,6 +17,7 @@ import com.airbnb.lottie.compose.LottieCompositionSpec import com.airbnb.lottie.compose.animateLottieCompositionAsState import com.airbnb.lottie.compose.rememberLottieComposition 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.features.onboarding.v2.impl.R @@ -49,16 +50,17 @@ internal fun OnboardingDone(onContinueClick: () -> Unit, modifier: Modifier = Mo SpacerH32() Text( modifier = Modifier.padding(horizontal = 32.dp), - text = stringResource(R.string.onboarding_done_header), + text = stringResourceSafe(R.string.onboarding_done_header), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, ) SpacerH12() Text( modifier = Modifier.padding(horizontal = 32.dp), - text = stringResource(R.string.onboarding_subtitle_success_tangem_wallet_onboarding), + text = stringResourceSafe(R.string.onboarding_subtitle_success_tangem_wallet_onboarding), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, ) SpacerH(72.dp) } @@ -69,7 +71,7 @@ internal fun OnboardingDone(onContinueClick: () -> Unit, modifier: Modifier = Mo .padding(horizontal = 16.dp) .padding(bottom = 16.dp) .navigationBarsPadding(), - text = stringResource(R.string.onboarding_button_continue_wallet), + text = stringResourceSafe(R.string.onboarding_button_continue_wallet), onClick = onContinueClick, ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt index 750a119975..6e7321f878 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt @@ -33,6 +33,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWall import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.Wallet1ChooseOptionComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.MultiWalletFinalizeComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.Wallet1ScanPrimaryComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.MultiWalletSeedPhraseComponent import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState @@ -59,6 +60,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor CreateWallet -> WalletArtworksState.Folded ChooseBackupOption -> WalletArtworksState.Fan SeedPhrase -> WalletArtworksState.Folded + ScanPrimary -> WalletArtworksState.Folded AddBackupDevice -> WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.First) Finalize -> WalletArtworksState.Stack() Done -> error("Done state should not be used as starting state") @@ -93,11 +95,14 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor ) val backButtonClickFlow = MutableSharedFlow() + override val innerNavigation: InnerNavigation = object : InnerNavigation { override val state = innerNavigationStateFlow override fun pop(onComplete: (Boolean) -> Unit) { - if (childStack.active.configuration == SeedPhrase) { + val config = childStack.active.configuration + + if (config == SeedPhrase || config == Finalize) { componentScope.launch { backButtonClickFlow.emit(Unit) } } else { model.onBack() @@ -152,6 +157,11 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor backButtonClickFlow = backButtonClickFlow, onBack = { stackNavigation.pop() }, ) + ScanPrimary -> Wallet1ScanPrimaryComponent( + context = childContext, + params = childParams, + onDone = { handleNavigationEvent(AddBackupDevice) }, + ) AddBackupDevice -> MultiWalletBackupComponent( context = childContext, params = childParams, @@ -160,6 +170,8 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor Finalize -> MultiWalletFinalizeComponent( context = childContext, params = childParams, + backButtonClickFlow = backButtonClickFlow, + onBack = { model.onBack() }, onEvent = ::handleFinalizeComponentEvent, ) Done -> error("Unexpected Done state") diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt index 2f2a18e497..e4b8f341f3 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.model +import androidx.compose.runtime.Stable import androidx.compose.ui.text.input.TextFieldValue import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ComponentScoped @@ -20,6 +21,7 @@ import javax.inject.Inject private const val MINIMUM_ACCESS_CODE_LENGTH = 4 +@Stable @ComponentScoped internal class MultiWalletAccessCodeModel @Inject constructor( paramsContainer: ParamsContainer, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeBS.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeBS.kt index ca6f0b7c5f..0eee40db84 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeBS.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeBS.kt @@ -4,12 +4,12 @@ import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemAnimations import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui.state.MultiWalletAccessCodeUM @@ -65,9 +65,9 @@ private fun Content(state: MultiWalletAccessCodeUM, modifier: Modifier = Modifie .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .imePadding(), text = if (state.step == ConfirmAccessCode) { - stringResource(R.string.common_confirm) + stringResourceSafe(R.string.common_confirm) } else { - stringResource(R.string.common_continue) + stringResourceSafe(R.string.common_continue) }, onClick = state.onContinue, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt index 4aa51c8e79..38e68c7642 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt @@ -1,6 +1,9 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -9,12 +12,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.OutlineTextFieldWithIcon +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.onboarding.v2.impl.R @@ -35,17 +38,17 @@ internal fun MultiWalletAccessCodeEnter( ) { Text( text = if (reEnterAccessCodeState) { - stringResource(R.string.onboarding_access_code_repeat_code_title) + stringResourceSafe(R.string.onboarding_access_code_repeat_code_title) } else { - stringResource(R.string.onboarding_access_code_intro_title) + stringResourceSafe(R.string.onboarding_access_code_intro_title) }, style = TangemTheme.typography.subtitle1, ) Text( text = if (reEnterAccessCodeState) { - stringResource(R.string.onboarding_access_code_repeat_code_hint) + stringResourceSafe(R.string.onboarding_access_code_repeat_code_hint) } else { - stringResource(R.string.onboarding_access_code_hint) + stringResourceSafe(R.string.onboarding_access_code_hint) }, style = TangemTheme.typography.body1, ) @@ -73,7 +76,7 @@ internal fun MultiWalletAccessCodeEnter( } else { state.onAccessCodeFirstChange }, - label = stringResource(id = R.string.onboarding_wallet_info_title_third), + label = stringResourceSafe(id = R.string.onboarding_wallet_info_title_third), isError = state.codesNotMatchError || state.atLeast4CharError, visualTransformation = if (state.accessCodeHidden) { PasswordVisualTransformation() @@ -82,9 +85,9 @@ internal fun MultiWalletAccessCodeEnter( }, caption = when { state.codesNotMatchError && reEnterAccessCodeState -> - stringResource(R.string.onboarding_access_codes_doesnt_match) + stringResourceSafe(R.string.onboarding_access_codes_doesnt_match) state.atLeast4CharError && !reEnterAccessCodeState -> - stringResource(R.string.onboarding_access_code_too_short) + stringResourceSafe(R.string.onboarding_access_code_too_short) else -> null }, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeIntro.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeIntro.kt index 3172592edb..0b4c0fe713 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeIntro.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeIntro.kt @@ -8,12 +8,12 @@ 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.res.stringResource import androidx.compose.ui.res.vectorResource 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.SpacerW +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.onboarding.v2.impl.R @@ -26,7 +26,7 @@ internal fun MultiWalletAccessCodeIntro(modifier: Modifier = Modifier) { verticalArrangement = Arrangement.spacedBy(12.dp), ) { Text( - text = stringResource(R.string.onboarding_access_code_intro_title), + text = stringResourceSafe(R.string.onboarding_access_code_intro_title), style = TangemTheme.typography.h2, ) Icon( @@ -41,18 +41,18 @@ internal fun MultiWalletAccessCodeIntro(modifier: Modifier = Modifier) { verticalArrangement = Arrangement.spacedBy(24.dp), ) { DescriptionItem( - title = stringResource(R.string.onboarding_access_code_feature_1_title), - body = stringResource(R.string.onboarding_access_code_feature_1_description), + title = stringResourceSafe(R.string.onboarding_access_code_feature_1_title), + body = stringResourceSafe(R.string.onboarding_access_code_feature_1_description), iconRes = R.drawable.ic_feature_1, ) DescriptionItem( - title = stringResource(R.string.onboarding_access_code_feature_2_title), - body = stringResource(R.string.onboarding_access_code_feature_2_description), + title = stringResourceSafe(R.string.onboarding_access_code_feature_2_title), + body = stringResourceSafe(R.string.onboarding_access_code_feature_2_description), iconRes = R.drawable.ic_feature_2, ) DescriptionItem( - title = stringResource(R.string.onboarding_access_code_feature_3_title), - body = stringResource(R.string.onboarding_access_code_feature_3_description), + title = stringResourceSafe(R.string.onboarding_access_code_feature_3_title), + body = stringResourceSafe(R.string.onboarding_access_code_feature_3_description), iconRes = R.drawable.ic_feature_3, ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt index a38f4c6a9a..6b63fc8c48 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt @@ -8,8 +8,8 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onboarding.v2.impl.R -import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.MultiWalletBackupModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.MultiWalletBackup import kotlinx.coroutines.flow.update diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt index ecf5661097..9d8fb21173 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model +import androidx.compose.runtime.Stable import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError @@ -28,6 +29,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject +@Stable @ComponentScoped class MultiWalletBackupModel @Inject constructor( paramsContainer: ParamsContainer, @@ -82,15 +84,16 @@ class MultiWalletBackupModel @Inject constructor( } private fun startBackupWallet() { - if (state.value.numberOfBackupCards == 0) { + if (state.value.numberOfBackupCards == 0 && scanResponse.primaryCard != null) { backupService.discardSavedBackup() } - val primaryCard = scanResponse.primaryCard + val primaryCard = scanResponse.primaryCard if (primaryCard != null) { backupService.setPrimaryCard(primaryCard) - addBackupCardWithService() } + + addBackupCardWithService() } private fun setNumberOfBackupCards(number: Int) { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt index 9bf7b55ef5..f82b028c2a 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt @@ -1,17 +1,22 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.BasicDialog +import com.tangem.core.ui.components.DialogButtonUM +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SecondaryButtonIconEnd 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.onboarding.v2.impl.R @@ -55,7 +60,8 @@ private fun ColumnScope.Content(state: MultiWalletBackupUM) { Column( Modifier .fillMaxWidth() - .padding(start = 32.dp, end = 32.dp, bottom = 74.dp) + .verticalScroll(rememberScrollState()) + .padding(start = 32.dp, end = 32.dp, bottom = 16.dp) .weight(1f), horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -83,7 +89,7 @@ private fun ColumnScope.Content(state: MultiWalletBackupUM) { iconResId = R.drawable.ic_tangem_24, enabled = state.addBackupButtonEnabled, showProgress = state.addBackupButtonLoading, - text = stringResource(R.string.onboarding_button_add_backup_card), + text = stringResourceSafe(R.string.onboarding_button_add_backup_card), onClick = state.onAddBackupClick, ) @@ -91,7 +97,7 @@ private fun ColumnScope.Content(state: MultiWalletBackupUM) { modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .fillMaxWidth(), - text = stringResource(R.string.onboarding_button_finalize_backup), + text = stringResourceSafe(R.string.onboarding_button_finalize_backup), enabled = state.finalizeButtonEnabled, onClick = state.onFinalizeButtonClick, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/Wallet1ChooseOptionComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/Wallet1ChooseOptionComponent.kt index c5f8c63d19..33bd477297 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/Wallet1ChooseOptionComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/Wallet1ChooseOptionComponent.kt @@ -32,7 +32,7 @@ class Wallet1ChooseOptionComponent( } params.parentParams.titleProvider.changeTitle( - text = resourceReference(R.string.onboarding_create_wallet_header), + text = resourceReference(R.string.onboarding_getting_started), ) componentScope.launch { @@ -45,6 +45,7 @@ class Wallet1ChooseOptionComponent( @Composable override fun Content(modifier: Modifier) { Wallet1ChooseOption( + canSkipBackup = model.canSkipBackup, onBackupClick = { onNextStep(OnboardingMultiWalletState.Step.AddBackupDevice) }, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt index 0577ed1513..69d37f496f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt @@ -1,10 +1,12 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.model +import androidx.compose.runtime.Stable import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.card.repository.CardRepository +import com.tangem.domain.common.TapWorkarounds.canSkipBackup import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.models.UserWallet @@ -18,6 +20,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject +@Stable @ComponentScoped internal class Wallet1ChooseOptionModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, @@ -36,6 +39,8 @@ internal class Wallet1ChooseOptionModel @Inject constructor( analyticsHandler.send(OnboardingEvent.Backup.ScreenOpened) } + val canSkipBackup = params.multiWalletState.value.currentScanResponse.card.canSkipBackup + fun onSkipClick() { if (skipClicked) return skipClicked = true diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt index 049dd64f2b..b873a9e6b8 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/ui/Wallet1ChooseOption.kt @@ -1,20 +1,29 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.ui import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SecondaryButton +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.onboarding.v2.impl.R @Composable -fun Wallet1ChooseOption(onSkipClick: () -> Unit, onBackupClick: () -> Unit, modifier: Modifier = Modifier) { +fun Wallet1ChooseOption( + canSkipBackup: Boolean, + onSkipClick: () -> Unit, + onBackupClick: () -> Unit, + modifier: Modifier = Modifier, +) { Column( modifier = modifier .fillMaxSize() @@ -26,12 +35,13 @@ fun Wallet1ChooseOption(onSkipClick: () -> Unit, onBackupClick: () -> Unit, modi Column( Modifier .fillMaxWidth() - .padding(start = 32.dp, end = 32.dp, bottom = 24.dp) + .verticalScroll(rememberScrollState()) + .padding(start = 32.dp, end = 32.dp, bottom = 16.dp) .weight(1f), horizontalAlignment = Alignment.CenterHorizontally, ) { Text( - text = stringResource(R.string.onboarding_create_wallet_header), + text = stringResourceSafe(R.string.onboarding_wallet_info_title_first), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, @@ -39,7 +49,7 @@ fun Wallet1ChooseOption(onSkipClick: () -> Unit, onBackupClick: () -> Unit, modi ) Text( - text = stringResource(R.string.onboarding_create_wallet_body), + text = stringResourceSafe(R.string.onboarding_wallet_info_subtitle_first), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, @@ -47,21 +57,35 @@ fun Wallet1ChooseOption(onSkipClick: () -> Unit, onBackupClick: () -> Unit, modi ) } - PrimaryButtonIconEnd( + PrimaryButton( modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) .fillMaxWidth(), - iconResId = R.drawable.ic_tangem_24, - text = stringResource(R.string.onboarding_button_backup_now), + text = stringResourceSafe(R.string.onboarding_button_backup_now), onClick = onBackupClick, ) - SecondaryButton( - modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) - .fillMaxWidth(), - text = stringResource(R.string.onboarding_button_skip_backup), - onClick = onSkipClick, + if (canSkipBackup) { + SecondaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = stringResourceSafe(R.string.onboarding_button_skip_backup), + onClick = onSkipClick, + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview { + Wallet1ChooseOption( + canSkipBackup = true, + onSkipClick = {}, + onBackupClick = {}, + modifier = Modifier.fillMaxSize(), ) } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt index f23f57f93a..88f37db96d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.essenty.lifecycle.doOnStart import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.extensions.resourceReference @@ -25,17 +26,19 @@ internal class MultiWalletCreateWalletComponent( private val model: MultiWalletCreateWalletModel = getOrCreateModel(params) init { - params.innerNavigation.update { - it.copy( - stackSize = 2, - stackMaxSize = 8, + lifecycle.doOnStart { + params.innerNavigation.update { + it.copy( + stackSize = 2, + stackMaxSize = 8, + ) + } + + params.parentParams.titleProvider.changeTitle( + text = resourceReference(R.string.onboarding_create_wallet_header), ) } - params.parentParams.titleProvider.changeTitle( - text = resourceReference(R.string.onboarding_create_wallet_header), - ) - componentScope.launch { model.onDone.collect(onNextStep) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt index b7b153a6f7..c41f5bdff9 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model +import androidx.compose.runtime.Stable import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -11,12 +12,17 @@ import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.wallets.builder.UserWalletBuilder +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase +import com.tangem.domain.wallets.usecase.SaveWalletUseCase import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.resetCardDialog -import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState +import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState.Step import com.tangem.sdk.api.TangemSdkManager import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableSharedFlow @@ -27,6 +33,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @Suppress("LongParameterList") +@Stable @ComponentScoped internal class MultiWalletCreateWalletModel @Inject constructor( paramsContainer: ParamsContainer, @@ -36,6 +43,8 @@ internal class MultiWalletCreateWalletModel @Inject constructor( private val getCardInfoUseCase: GetCardInfoUseCase, private val cardRepository: CardRepository, private val analyticsHandler: AnalyticsEventHandler, + private val generateWalletNameUseCase: GenerateWalletNameUseCase, + private val saveWalletUseCase: SaveWalletUseCase, ) : Model() { private val params = paramsContainer.require() @@ -61,7 +70,7 @@ internal class MultiWalletCreateWalletModel @Inject constructor( showOtherOptionsButton = params.parentParams.withSeedPhraseFlow, onOtherOptionsClick = { modelScope.launch { - onDone.emit(OnboardingMultiWalletState.Step.SeedPhrase) + onDone.emit(Step.SeedPhrase) } }, dialog = null, @@ -69,7 +78,7 @@ internal class MultiWalletCreateWalletModel @Inject constructor( ) val uiState: StateFlow = _uiState - val onDone = MutableSharedFlow() + val onDone = MutableSharedFlow() init { analyticsHandler.send(OnboardingEvent.CreateWallet.ScreenOpened) @@ -96,13 +105,14 @@ internal class MultiWalletCreateWalletModel @Inject constructor( cardRepository.startCardActivation(cardId = result.data.card.cardId) - if (params.parentParams.withSeedPhraseFlow) { - onDone.emit(OnboardingMultiWalletState.Step.AddBackupDevice) - } else { - onDone.emit(OnboardingMultiWalletState.Step.ChooseBackupOption) - } - analyticsHandler.send(OnboardingEvent.CreateWallet.WalletCreatedSuccessfully()) + + val cardDoesNotSupportBackup = result.data.card.settings.isBackupAllowed.not() + when { + cardDoesNotSupportBackup -> createWalletAndNavigateBackWithDone() + params.parentParams.withSeedPhraseFlow -> onDone.emit(Step.AddBackupDevice) + else -> onDone.emit(Step.ChooseBackupOption) + } } is CompletionResult.Failure -> { @@ -115,6 +125,32 @@ internal class MultiWalletCreateWalletModel @Inject constructor( } } + private fun createWalletAndNavigateBackWithDone() { + modelScope.launch { + val scanResponse = params.multiWalletState.value.currentScanResponse + + val userWallet = createUserWallet(scanResponse) + saveWalletUseCase(userWallet, canOverride = true) + .onRight { + cardRepository.finishCardActivation(scanResponse.card.cardId) + + // save user wallet for manage tokens screen + params.multiWalletState.update { + it.copy(resultUserWallet = userWallet) + } + + onDone.emit(Step.Done) + } + } + } + + private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet { + return requireNotNull( + value = UserWalletBuilder(scanResponse, generateWalletNameUseCase).build(), + lazyMessage = { "User wallet not created" }, + ) + } + private fun handleActivationError() { _uiState.update { state -> state.copy( diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt index a8ff5a761f..0404814a1f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt @@ -1,11 +1,12 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -15,6 +16,7 @@ import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.components.SecondaryButton 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.onboarding.v2.impl.R @@ -48,7 +50,8 @@ internal fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Column( Modifier .fillMaxWidth() - .padding(start = 32.dp, end = 32.dp, bottom = 24.dp) + .verticalScroll(rememberScrollState()) + .padding(start = 32.dp, end = 32.dp, bottom = 16.dp) .weight(1f), horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -74,7 +77,7 @@ internal fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) .fillMaxWidth(), iconResId = R.drawable.ic_tangem_24, - text = stringResource(R.string.onboarding_create_wallet_button_create_wallet), + text = stringResourceSafe(R.string.onboarding_create_wallet_button_create_wallet), onClick = state.onCreateWalletClick, ) @@ -83,7 +86,7 @@ internal fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .fillMaxWidth(), - text = stringResource(R.string.onboarding_create_wallet_options_button_options), + text = stringResourceSafe(R.string.onboarding_create_wallet_options_button_options), onClick = state.onOtherOptionsClick, ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/MultiWalletFinalizeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/MultiWalletFinalizeComponent.kt index c6ebe6cd81..22025b3798 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/MultiWalletFinalizeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/MultiWalletFinalizeComponent.kt @@ -1,5 +1,6 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize +import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -12,6 +13,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.model.MultiWalletFinalizeModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.ui.MultiWalletFinalize +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -19,6 +21,8 @@ import kotlinx.coroutines.launch internal class MultiWalletFinalizeComponent( context: AppComponentContext, params: MultiWalletChildParams, + backButtonClickFlow: SharedFlow, + onBack: () -> Unit, onEvent: (Event) -> Unit, ) : AppComponentContext by context, MultiWalletChildComponent { @@ -40,12 +44,21 @@ internal class MultiWalletFinalizeComponent( onEvent(it) } } + + componentScope.launch { + backButtonClickFlow.collect { model.onBack() } + } + componentScope.launch { + model.onBackFlow.collect { onBack() } + } } @Composable override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() + BackHandler { model.onBack() } + MultiWalletFinalize( state = state, modifier = modifier, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt index 805acccfca..761574ca26 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.model +import androidx.compose.runtime.Stable import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.core.decompose.di.ComponentScoped @@ -29,14 +30,12 @@ import com.tangem.sdk.api.BackupServiceHolder import com.tangem.sdk.api.TangemSdkManager import com.tangem.utils.StringsSigns import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject @Suppress("LongParameterList") +@Stable @ComponentScoped internal class MultiWalletFinalizeModel @Inject constructor( paramsContainer: ParamsContainer, @@ -59,6 +58,7 @@ internal class MultiWalletFinalizeModel @Inject constructor( private var walletHasBackupError = false val uiState = _uiState.asStateFlow() + val onBackFlow = MutableSharedFlow() val onEvent = MutableSharedFlow() @@ -72,6 +72,12 @@ internal class MultiWalletFinalizeModel @Inject constructor( } } + fun onBack() { + if (uiState.value.scanPrimary) { + modelScope.launch { onBackFlow.emit(Unit) } + } + } + private fun getInitialState(): MultiWalletFinalizeUM { val backupService = backupServiceHolder.backupService.get() ?: return MultiWalletFinalizeUM() val initialStep = getInitialStep() @@ -193,18 +199,24 @@ internal class MultiWalletFinalizeModel @Inject constructor( private fun finishBackup() { modelScope.launch { val scanResponse = params.multiWalletState.value.currentScanResponse - val userWallet = createUserWallet(scanResponse) - when (params.parentParams.mode) { + val userWalletCreated = createUserWallet(scanResponse) + + val userWallet = when (params.parentParams.mode) { OnboardingMultiWalletComponent.Mode.Onboarding -> { userWalletsListManager.save( - userWallet = userWallet.copy( + userWallet = userWalletCreated.copy( scanResponse = scanResponse.updateScanResponseAfterBackup(), ), canOverride = true, ) + userWalletCreated } OnboardingMultiWalletComponent.Mode.AddBackup -> { + val userWallet = userWalletsListManager.userWallets.first() + .firstOrNull { it.scanResponse.primaryCard?.cardId == scanResponse.primaryCard?.cardId } + ?: userWalletCreated + userWalletsListManager.update( userWalletId = userWallet.walletId, update = { wallet -> @@ -213,6 +225,8 @@ internal class MultiWalletFinalizeModel @Inject constructor( ) }, ) + + userWallet } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/ui/MultiWalletFinalize.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/ui/MultiWalletFinalize.kt index 9bbfe88b5e..24a3780233 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/ui/MultiWalletFinalize.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/ui/MultiWalletFinalize.kt @@ -1,15 +1,17 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.ui import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp 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.res.TangemThemePreview import com.tangem.features.onboarding.v2.impl.R @@ -27,13 +29,14 @@ internal fun MultiWalletFinalize(state: MultiWalletFinalizeUM, modifier: Modifie Column( Modifier .fillMaxWidth() - .padding(start = 32.dp, end = 32.dp, bottom = 74.dp) + .padding(start = 32.dp, end = 32.dp, bottom = 16.dp) + .verticalScroll(rememberScrollState()) .weight(1f), horizontalAlignment = Alignment.CenterHorizontally, ) { Text( modifier = Modifier.padding(top = 16.dp), - text = stringResource( + text = stringResourceSafe( when { state.scanPrimary && state.isRing -> R.string.common_origin_ring state.scanPrimary && state.isRing.not() -> R.string.common_origin_card @@ -49,9 +52,9 @@ internal fun MultiWalletFinalize(state: MultiWalletFinalizeUM, modifier: Modifie Text( modifier = Modifier.padding(top = 12.dp), text = if (state.isRing) { - stringResource(R.string.onboarding_subtitle_scan_ring) + stringResourceSafe(R.string.onboarding_subtitle_scan_ring) } else { - stringResource( + stringResourceSafe( R.string.onboarding_subtitle_scan_backup_card_format, state.cardNumber, ) @@ -66,7 +69,7 @@ internal fun MultiWalletFinalize(state: MultiWalletFinalizeUM, modifier: Modifie modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .fillMaxWidth(), - text = stringResource( + text = stringResourceSafe( if (state.isRing) R.string.onboarding_button_backup_ring else R.string.onboarding_button_backup_origin, ), iconResId = R.drawable.ic_tangem_24, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/Wallet1ScanPrimaryComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/Wallet1ScanPrimaryComponent.kt new file mode 100644 index 0000000000..a6d80fd63b --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/Wallet1ScanPrimaryComponent.kt @@ -0,0 +1,46 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams +import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.Wallet1ScanPrimaryModel +import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.ui.Wallet1ScanPrimary +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +internal class Wallet1ScanPrimaryComponent( + context: AppComponentContext, + params: MultiWalletChildParams, + onDone: () -> Unit, +) : AppComponentContext by context, ComposableContentComponent { + + private val model: Wallet1ScanPrimaryModel = getOrCreateModel(params) + + init { + params.innerNavigation.update { + it.copy( + stackSize = 4, + stackMaxSize = 8, + ) + } + + params.parentParams.titleProvider.changeTitle( + text = resourceReference(R.string.onboarding_navbar_title_creating_backup), + ) + + componentScope.launch { model.onDone.collect { onDone() } } + } + + @Composable + override fun Content(modifier: Modifier) { + Wallet1ScanPrimary( + isRing = model.isRing, + onScanPrimaryClick = model::onScanPrimaryClick, + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/Wallet1ScanPrimaryModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/Wallet1ScanPrimaryModel.kt new file mode 100644 index 0000000000..504e556fbc --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/model/Wallet1ScanPrimaryModel.kt @@ -0,0 +1,43 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model + +import androidx.compose.runtime.Stable +import com.tangem.common.CompletionResult +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams +import com.tangem.sdk.api.BackupServiceHolder +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@Stable +@ComponentScoped +internal class Wallet1ScanPrimaryModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val backupServiceHolder: BackupServiceHolder, +) : Model() { + + private val params = paramsContainer.require() + private val scanResponse = params.multiWalletState.value.currentScanResponse + + val isRing = scanResponse.cardTypesResolver.isRing() + val onDone = MutableSharedFlow() + + fun onScanPrimaryClick() { + val backupService = backupServiceHolder.backupService.get() ?: return + val iconScanRes = R.drawable.img_hand_scan_ring.takeIf { isRing } + backupService.readPrimaryCard(iconScanRes = iconScanRes, cardId = scanResponse.card.cardId) { result -> + when (result) { + is CompletionResult.Success -> { + modelScope.launch { onDone.emit(Unit) } + } + is CompletionResult.Failure -> Unit + } + } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/ui/Wallet1ScanPrimary.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/ui/Wallet1ScanPrimary.kt new file mode 100644 index 0000000000..e15c5a640a --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/scanprimary/ui/Wallet1ScanPrimary.kt @@ -0,0 +1,74 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.PrimaryButtonIconEnd +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.onboarding.v2.impl.R + +@Composable +internal fun Wallet1ScanPrimary(isRing: Boolean, onScanPrimaryClick: () -> Unit, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxSize() + .navigationBarsPadding(), + verticalArrangement = Arrangement.Bottom, + ) { + Column( + Modifier + .fillMaxWidth() + .padding(start = 32.dp, end = 32.dp, bottom = 16.dp) + .verticalScroll(rememberScrollState()) + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + modifier = Modifier.padding(top = 16.dp), + text = stringResourceSafe(R.string.onboarding_title_scan_origin_card), + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + style = TangemTheme.typography.h2, + ) + + Text( + modifier = Modifier.padding(top = 12.dp), + text = stringResourceSafe(R.string.onboarding_subtitle_scan_primary), + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + style = TangemTheme.typography.body1, + ) + } + + PrimaryButtonIconEnd( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = stringResourceSafe( + if (isRing) R.string.onboarding_button_backup_ring else R.string.onboarding_button_backup_origin, + ), + iconResId = R.drawable.ic_tangem_24, + onClick = onScanPrimaryClick, + ) + } +} + +@Preview +@Composable +private fun Preview() { + TangemThemePreview { + Wallet1ScanPrimary( + isRing = true, + onScanPrimaryClick = {}, + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt index ad00ff8019..cf3cf6d141 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model +import androidx.compose.runtime.Stable import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -33,6 +34,7 @@ import javax.inject.Inject // | DON'T FIXME !!! MODIFY WITH CAUTION !!! | // ============================================= @Suppress("LongParameterList") +@Stable @ComponentScoped internal class MultiWalletSeedPhraseModel @Inject constructor( paramsContainer: ParamsContainer, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/Utils.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/Utils.kt index af12fa1b39..dbfeee4003 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/Utils.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/Utils.kt @@ -4,6 +4,7 @@ import android.net.Uri import com.tangem.utils.SupportedLanguages import java.util.Locale +// TODO: [REDACTED_JIRA] internal fun seedPhraseLearnMoreUrl(): String { val language = Locale.getDefault().language diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt index 10c3a39e4c..5512fd5d07 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt @@ -1,6 +1,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui import androidx.compose.animation.* +import com.tangem.core.ui.extensions.stringResourceSafe import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow @@ -14,7 +15,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -55,49 +55,52 @@ internal fun MultiWalletSeedPhraseImport(state: MultiWalletSeedPhraseUM.Import, Column( modifier = Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()), + .imePadding(), ) { - Text( - text = stringResource(id = R.string.onboarding_seed_import_message), - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.secondary, - textAlign = TextAlign.Center, - modifier = Modifier - .padding(start = 36.dp, end = 36.dp, top = 24.dp, bottom = 16.dp) - .fillMaxWidth(), - ) - - PhraseBlock( - state = state, - modifier = Modifier.padding(horizontal = 16.dp), - ) - - OutlineTextFieldWithIcon( - modifier = modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - value = state.passPhrase, - onValueChange = state.passPhraseChange, - iconResId = R.drawable.ic_information_24, - iconColor = TangemTheme.colors.icon.informative, - label = stringResource(id = R.string.common_passphrase), - placeholder = stringResource(id = R.string.send_optional_field), - onIconClick = state.onPassphraseInfoClick, - ) - - Box(Modifier.weight(1f)) { - PrimaryButtonIconEnd( + Column( + Modifier + .verticalScroll(rememberScrollState()) + .weight(1f), + ) { + Text( + text = stringResourceSafe(id = R.string.onboarding_seed_import_message), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, modifier = Modifier - .align(Alignment.BottomStart) - .padding(16.dp) + .padding(start = 36.dp, end = 36.dp, top = 24.dp, bottom = 16.dp) .fillMaxWidth(), - text = stringResource(id = R.string.common_import), - iconResId = R.drawable.ic_tangem_24, - enabled = state.createWalletEnabled, - showProgress = state.createWalletProgress, - onClick = state.createWalletClick, + ) + + PhraseBlock( + state = state, + modifier = Modifier.padding(horizontal = 16.dp), + ) + + OutlineTextFieldWithIcon( + modifier = modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + value = state.passPhrase, + onValueChange = state.passPhraseChange, + iconResId = R.drawable.ic_information_24, + iconColor = TangemTheme.colors.icon.informative, + label = stringResourceSafe(id = R.string.common_passphrase), + placeholder = stringResourceSafe(id = R.string.send_optional_field), + onIconClick = state.onPassphraseInfoClick, ) } + + PrimaryButtonIconEnd( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + text = stringResourceSafe(id = R.string.common_import), + iconResId = R.drawable.ic_tangem_24, + enabled = state.createWalletEnabled, + showProgress = state.createWalletProgress, + onClick = state.createWalletClick, + ) } val keyboard by keyboardAsState() diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseStart.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseStart.kt index aff9adb6f1..b889ab83ba 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseStart.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseStart.kt @@ -10,7 +10,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.LineBreak import androidx.compose.ui.text.style.TextAlign @@ -19,6 +18,7 @@ import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.SpacerW8 +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.onboarding.v2.impl.R @@ -51,7 +51,7 @@ internal fun MultiWalletSeedPhraseStart(state: MultiWalletSeedPhraseUM.Start, mo Text( modifier = Modifier .padding(horizontal = TangemTheme.dimens.size12, vertical = TangemTheme.dimens.size4), - text = stringResource(id = R.string.onboarding_seed_phrase_intro_legacy), + text = stringResourceSafe(id = R.string.onboarding_seed_phrase_intro_legacy), color = TangemTheme.colors.text.warning, style = TangemTheme.typography.body1, ) @@ -71,12 +71,12 @@ private fun BodyContent(state: MultiWalletSeedPhraseUM.Start, modifier: Modifier verticalArrangement = Arrangement.spacedBy(16.dp), ) { Text( - text = stringResource(id = R.string.onboarding_seed_intro_title), + text = stringResourceSafe(id = R.string.onboarding_seed_intro_title), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, ) Text( - text = stringResource(id = R.string.onboarding_seed_intro_message), + text = stringResourceSafe(id = R.string.onboarding_seed_intro_message), style = TangemTheme.typography.body1 .copy(lineBreak = LineBreak.Paragraph), color = TangemTheme.colors.text.secondary, @@ -119,7 +119,7 @@ private fun ReadMoreBlock(state: MultiWalletSeedPhraseUM.Start, modifier: Modifi Text( modifier = Modifier.align(Alignment.CenterVertically), style = TangemTheme.typography.button, - text = stringResource(id = R.string.onboarding_seed_button_read_more), + text = stringResourceSafe(id = R.string.onboarding_seed_button_read_more), ) } } @@ -135,13 +135,13 @@ private fun Buttons(state: MultiWalletSeedPhraseUM.Start, modifier: Modifier = M ) { SecondaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.onboarding_seed_intro_button_generate), + text = stringResourceSafe(id = R.string.onboarding_seed_intro_button_generate), onClick = state.onGenerateSeedPhraseClicked, ) SecondaryButton( modifier = Modifier .fillMaxWidth(), - text = stringResource(id = R.string.onboarding_seed_intro_button_import), + text = stringResourceSafe(id = R.string.onboarding_seed_intro_button_import), onClick = state.onImportSeedPhraseClicked, ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt index 94ffccee63..1c9b8abf0f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt @@ -8,14 +8,14 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.res.pluralStringResource -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.LayoutDirection import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons +import com.tangem.core.ui.extensions.pluralStringResourceSafe +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.onboarding.v2.impl.R @@ -31,26 +31,32 @@ internal fun MultiWalletSeedPhraseWords( modifier: Modifier = Modifier, ) { Column(modifier.fillMaxSize()) { - SegmentSeedBlock( - modifier = Modifier.padding(vertical = 20.dp), - state = state, - ) - - TitleBlock(state) - - SeedPhraseGridBlock( - mnemonicGridItems = state.words, + Column( modifier = Modifier - .weight(1f) - .fillMaxWidth() - .padding(top = 20.dp, bottom = 32.dp), - ) + .verticalScroll(rememberScrollState()) + .imePadding() + .weight(1f), + ) { + SegmentSeedBlock( + modifier = Modifier.padding(vertical = 20.dp), + state = state, + ) + + TitleBlock(state) + + SeedPhraseGridBlock( + mnemonicGridItems = state.words, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp, bottom = 32.dp), + ) + } PrimaryButton( modifier = Modifier .fillMaxWidth() .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), - text = stringResource(id = R.string.common_continue), + text = stringResourceSafe(id = R.string.common_continue), onClick = state.onContinueClick, ) } @@ -70,7 +76,7 @@ private fun SegmentSeedBlock(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, }, ) { Text( - text = pluralStringResource( + text = pluralStringResourceSafe( id = R.plurals.onboarding_seed_generate_words_count, count = if (it == 0) 12 else 24, if (it == 0) 12 else 24, @@ -95,14 +101,14 @@ private fun TitleBlock(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifi verticalArrangement = Arrangement.spacedBy(16.dp), ) { Text( - text = stringResource(id = R.string.onboarding_seed_generate_title), + text = stringResourceSafe(id = R.string.onboarding_seed_generate_title), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth(), ) Text( - text = pluralStringResource( + text = pluralStringResourceSafe( id = R.plurals.onboarding_seed_generate_message_words_count, count = if (state.words24OptionSelected.not()) 12 else 24, if (state.words24OptionSelected.not()) 12 else 24, @@ -118,8 +124,7 @@ private fun TitleBlock(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifi @Composable private fun SeedPhraseGridBlock(mnemonicGridItems: ImmutableList, modifier: Modifier = Modifier) { VerticalGrid( - modifier = modifier - .verticalScroll(rememberScrollState()), + modifier = modifier, items = mnemonicGridItems, ) { item -> Row( diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt index f8ea2bbdba..9d37d02677 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt @@ -5,9 +5,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -18,6 +16,7 @@ import com.tangem.core.ui.components.DialogButtonUM import com.tangem.core.ui.components.OutlineTextField import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.extensions.resolveReference +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.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM @@ -48,48 +47,50 @@ internal fun MultiWalletSeedPhraseWordsCheck( Column( modifier = modifier .fillMaxSize() - .verticalScroll(rememberScrollState()), + .imePadding(), ) { - Text( - text = stringResource(R.string.onboarding_seed_user_validation_title), - style = TangemTheme.typography.h2, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - modifier = Modifier - .padding(top = 48.dp, start = 36.dp, end = 36.dp, bottom = 16.dp) - .fillMaxWidth(), - ) - Text( - text = stringResource(R.string.onboarding_seed_user_validation_message), - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.secondary, - textAlign = TextAlign.Center, - modifier = Modifier - .padding(horizontal = 36.dp) - .fillMaxWidth(), - ) - - Fields( - state = state, - modifier = Modifier - .padding(vertical = 30.dp, horizontal = 16.dp), - ) - - Box( - Modifier.weight(1f), + Column( + Modifier + .verticalScroll(rememberScrollState()) + .weight(1f), ) { - PrimaryButtonIconEnd( + Text( + text = stringResourceSafe(R.string.onboarding_seed_user_validation_title), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, modifier = Modifier - .align(Alignment.BottomStart) - .padding(16.dp) + .padding(top = 48.dp, start = 36.dp, end = 36.dp, bottom = 16.dp) .fillMaxWidth(), - text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), - iconResId = R.drawable.ic_tangem_24, - enabled = state.createWalletButtonEnabled, - showProgress = state.createWalletButtonProgress, - onClick = state.onCreateWalletButtonClick, + ) + Text( + text = stringResourceSafe(R.string.onboarding_seed_user_validation_message), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(horizontal = 36.dp) + .fillMaxWidth(), + ) + + Fields( + state = state, + modifier = Modifier + .padding(vertical = 30.dp, horizontal = 16.dp), ) } + + PrimaryButtonIconEnd( + modifier = Modifier + .padding(16.dp) + .imePadding() + .fillMaxWidth(), + text = stringResourceSafe(id = R.string.onboarding_create_wallet_button_create_wallet), + iconResId = R.drawable.ic_tangem_24, + enabled = state.createWalletButtonEnabled, + showProgress = state.createWalletButtonProgress, + onClick = state.onCreateWalletButtonClick, + ) } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/PassphraseInfoBottomSheet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/PassphraseInfoBottomSheet.kt index fdf30402ea..99c65af89b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/PassphraseInfoBottomSheet.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/PassphraseInfoBottomSheet.kt @@ -11,13 +11,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +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.onboarding.v2.impl.R @@ -50,7 +50,7 @@ fun PassphraseInfoBottomSheetContent(onDismiss: () -> Unit) { ) Text( - text = stringResource(id = R.string.common_passphrase), + text = stringResourceSafe(id = R.string.common_passphrase), modifier = Modifier .padding(top = TangemTheme.dimens.size40) .align(Alignment.CenterHorizontally), @@ -59,7 +59,7 @@ fun PassphraseInfoBottomSheetContent(onDismiss: () -> Unit) { ) Text( - text = stringResource(id = R.string.onboarding_bottom_sheet_passphrase_description), + text = stringResourceSafe(id = R.string.onboarding_bottom_sheet_passphrase_description), modifier = Modifier .padding(top = TangemTheme.dimens.size16) .padding(horizontal = TangemTheme.dimens.size24) @@ -75,7 +75,7 @@ fun PassphraseInfoBottomSheetContent(onDismiss: () -> Unit) { .padding(top = TangemTheme.dimens.size40) .padding(bottom = TangemTheme.dimens.size32) .fillMaxWidth(), - text = stringResource(id = R.string.common_ok), + text = stringResourceSafe(id = R.string.common_ok), onClick = onDismiss, ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt index 1de409e3a2..1aee88709b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt @@ -9,6 +9,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.Mul import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.model.Wallet1ChooseOptionModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model.MultiWalletCreateWalletModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.model.MultiWalletFinalizeModel +import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.Wallet1ScanPrimaryModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.MultiWalletSeedPhraseModel import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel import dagger.Binds @@ -66,4 +67,9 @@ internal interface ModelModule { @IntoMap @ClassKey(Wallet1ChooseOptionModel::class) fun provideModel7(model: Wallet1ChooseOptionModel): Model + + @Binds + @IntoMap + @ClassKey(Wallet1ScanPrimaryModel::class) + fun provideModel8(model: Wallet1ScanPrimaryModel): Model } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt index 0b74c9f64f..ad4786a6b7 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt @@ -103,11 +103,19 @@ internal class OnboardingMultiWalletModel @Inject constructor( } private fun getInitialStep(): OnboardingMultiWalletState.Step { - val card = params.scanResponse.card + val scanResponse = params.scanResponse + val card = scanResponse.card val backupService = backupServiceHolder.backupService.get()!! return when { + // interrupted backup backupService.hasIncompletedBackup -> OnboardingMultiWalletState.Step.Finalize + + // Add backup button + // Wallet1 without backup and userwallet's scanResponse doesn't contain primary card. + card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup && + scanResponse.primaryCard == null -> OnboardingMultiWalletState.Step.ScanPrimary + card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> OnboardingMultiWalletState.Step.AddBackupDevice card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt index 294dd6049d..1906d6c2aa 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt @@ -17,6 +17,8 @@ data class OnboardingMultiWalletState( /** * Wallet1 + * ScanPrimary -> | + * | * CreateWallet -> ChooseBackupOption -> AddBackupDevice -> Finalize -> [Done] * | * -> [Done] @@ -27,7 +29,7 @@ data class OnboardingMultiWalletState( * -> AddBackupDevice -> Finalize -> [Done] */ enum class Step { - CreateWallet, ChooseBackupOption, SeedPhrase, AddBackupDevice, Finalize, Done + CreateWallet, ChooseBackupOption, SeedPhrase, ScanPrimary, AddBackupDevice, Finalize, Done } @JvmInline diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt index 42d6d6046d..7c1d7416ed 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt @@ -7,7 +7,9 @@ import com.tangem.features.onboarding.v2.impl.R fun screenTitleByStep(step: OnboardingMultiWalletState.Step): TextReference = when (step) { OnboardingMultiWalletState.Step.CreateWallet -> resourceReference(R.string.onboarding_create_wallet_header) - OnboardingMultiWalletState.Step.AddBackupDevice -> + OnboardingMultiWalletState.Step.ScanPrimary, + OnboardingMultiWalletState.Step.AddBackupDevice, + -> resourceReference(R.string.onboarding_navbar_title_creating_backup) OnboardingMultiWalletState.Step.Finalize -> resourceReference(R.string.onboarding_button_finalize_backup) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt index 2c5b1af15f..ae225a9f48 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt @@ -49,9 +49,9 @@ internal fun OnboardingMultiWallet( url2 = state.artwork2Url, url3 = state.artwork3Url, modifier = Modifier - .weight(.56f) .padding(horizontal = 34.dp) .padding(top = 20.dp) + .weight(.4f) .fillMaxWidth(), state = artworksState, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt index fe903926b8..67982e4535 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt @@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @Suppress("UnusedPrivateMember") -class DefaultOnboardingVisaComponent @AssistedInject constructor( +internal class DefaultOnboardingVisaComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted private val params: OnboardingVisaComponent.Params, ) : OnboardingVisaComponent, AppComponentContext by appComponentContext { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt index 2c3e0eb8dd..0a5bb21947 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt @@ -9,7 +9,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.model.OnboardingVisaAccessCodeModel import kotlinx.coroutines.flow.SharedFlow -class OnboardingVisaAccessCodeComponent( +internal class OnboardingVisaAccessCodeComponent( appComponentContext: AppComponentContext, private val params: Params, ) : ComposableContentComponent, AppComponentContext by appComponentContext { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt index 678c620c27..d75af43b85 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt @@ -10,7 +10,7 @@ import javax.inject.Inject @Stable @ComponentScoped -class OnboardingVisaAccessCodeModel @Inject constructor( +internal class OnboardingVisaAccessCodeModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val paramsContainer: ParamsContainer, ) : Model() { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt new file mode 100644 index 0000000000..cf16e457eb --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt @@ -0,0 +1,167 @@ +package com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.common.ui.navigationButtons.NavigationButton +import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton +import com.tangem.core.ui.components.OutlineTextFieldWithIcon +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.resourceReference +import com.tangem.core.ui.res.TangemAnimations +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state.OnboardingVisaAccessCodeUM +import kotlinx.coroutines.delay + +@Composable +internal fun OnboardingVisaAccessCode(state: OnboardingVisaAccessCodeUM, modifier: Modifier = Modifier) { + Column( + modifier + .fillMaxSize() + .navigationBarsPadding(), + ) { + AnimatedContent( + modifier = Modifier + .padding(top = 48.dp) + .weight(1f), + targetState = state.step, + transitionSpec = TangemAnimations.AnimatedContent + .slide { initial, target -> initial.ordinal < target.ordinal }, + ) { step -> + Content( + state = state, + reEnterAccessCodeState = step == OnboardingVisaAccessCodeUM.Step.ReEnter, + ) + } + + SpacerH(20.dp) + + NavigationPrimaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .imePadding(), + primaryButton = NavigationButton( + textReference = when (state.step) { + OnboardingVisaAccessCodeUM.Step.Enter -> resourceReference(R.string.common_continue) + OnboardingVisaAccessCodeUM.Step.ReEnter -> + resourceReference(R.string.onboarding_create_wallet_button_create_wallet) + }, + onClick = state.onContinue, + iconRes = R.drawable.ic_tangem_24, + isIconVisible = state.step == OnboardingVisaAccessCodeUM.Step.ReEnter, + ), + ) + } +} + +@Suppress("LongMethod") +@Composable +private fun Content( + state: OnboardingVisaAccessCodeUM, + reEnterAccessCodeState: Boolean, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + modifier = Modifier + .padding(horizontal = 32.dp), + text = if (reEnterAccessCodeState) { + "Re-enter your access code" // TODO + } else { + "Create access code" // TODO + }, + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + + SpacerH16() + + // TODO + Text( + modifier = Modifier + .padding(horizontal = 32.dp), + text = "The access code will be used manage your payment account and protect it from unauthorized access", + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + + SpacerH32() + + val focusRequester = remember { FocusRequester() } + + OutlineTextFieldWithIcon( + modifier = modifier + .padding(horizontal = 16.dp) + .focusRequester(focusRequester) + .fillMaxWidth(), + iconResId = if (state.accessCodeHidden) { + R.drawable.ic_eye_outline_24 + } else { + R.drawable.ic_eye_off_outline_24 + }, + iconColor = TangemTheme.colors.icon.primary1, + onIconClick = state.onAccessCodeHideClick, + value = if (reEnterAccessCodeState) { + state.accessCodeSecond + } else { + state.accessCodeFirst + }, + onValueChange = if (reEnterAccessCodeState) { + state.onAccessCodeSecondChange + } else { + state.onAccessCodeFirstChange + }, + label = stringResource(id = R.string.onboarding_wallet_info_title_third), + isError = state.codesNotMatchError || state.atLeast4CharError, + visualTransformation = if (state.accessCodeHidden) { + PasswordVisualTransformation() + } else { + VisualTransformation.None + }, + caption = when { + state.codesNotMatchError && reEnterAccessCodeState -> + stringResource(R.string.onboarding_access_codes_doesnt_match) + state.atLeast4CharError && !reEnterAccessCodeState -> + stringResource(R.string.onboarding_access_code_too_short) + else -> null + }, + ) + + LaunchedEffect(Unit) { + delay(timeMillis = 200) + focusRequester.requestFocus() + } + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview { + OnboardingVisaAccessCode( + state = OnboardingVisaAccessCodeUM(), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/state/OnboardingVisaAccessCodeUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/state/OnboardingVisaAccessCodeUM.kt new file mode 100644 index 0000000000..68719daa53 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/state/OnboardingVisaAccessCodeUM.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state + +import androidx.compose.ui.text.input.TextFieldValue + +data class OnboardingVisaAccessCodeUM( + val step: Step = Step.Enter, + val accessCodeFirst: TextFieldValue = TextFieldValue(""), + val accessCodeSecond: TextFieldValue = TextFieldValue(""), + val codesNotMatchError: Boolean = false, + val atLeast4CharError: Boolean = false, + val accessCodeHidden: Boolean = true, + val onAccessCodeFirstChange: (TextFieldValue) -> Unit = {}, + val onAccessCodeSecondChange: (TextFieldValue) -> Unit = {}, + val onContinue: () -> Unit = {}, + val onAccessCodeHideClick: () -> Unit = {}, +) { + enum class Step { + Enter, ReEnter + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt index 8dab8ddac4..0194489e69 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt @@ -6,8 +6,10 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.OnboardingVisaWelcome +import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.state.OnboardingVisaWelcomeUM -class OnboardingVisaWelcomeComponent( +internal class OnboardingVisaWelcomeComponent( appComponentContext: AppComponentContext, private val params: Params, ) : ComposableContentComponent, AppComponentContext by appComponentContext { @@ -15,9 +17,15 @@ class OnboardingVisaWelcomeComponent( @Composable override fun Content(modifier: Modifier) { BackHandler(onBack = remember(this) { { params.onBack() } }) + + OnboardingVisaWelcome( + modifier = modifier, + state = OnboardingVisaWelcomeUM(), + ) } data class Params( val onBack: () -> Unit, + val startActivation: () -> Unit, ) } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt new file mode 100644 index 0000000000..61c2b6e0d8 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt @@ -0,0 +1,124 @@ +package com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.state.OnboardingVisaWelcomeUM + +@Composable +internal fun OnboardingVisaWelcome(state: OnboardingVisaWelcomeUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxSize() + .navigationBarsPadding(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Column( + Modifier + .padding( + top = 20.dp, + bottom = 32.dp, + start = 32.dp, + end = 32.dp, + ) + .fillMaxSize() + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Artwork() + + SpacerH(50.dp) + + Text( + text = when (state.mode) { + OnboardingVisaWelcomeUM.Mode.Hello -> "Hello, ${state.userName}!" + OnboardingVisaWelcomeUM.Mode.WelcomeBack -> "Welcome back, ${state.userName}!" + }, + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + ) + + SpacerH12() + + Text( + text = when (state.mode) { + OnboardingVisaWelcomeUM.Mode.Hello -> "bla bla bla //TODO" + OnboardingVisaWelcomeUM.Mode.WelcomeBack -> "bla bla bla? //TODO" + }, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + ) + } + + when (state.mode) { + OnboardingVisaWelcomeUM.Mode.Hello -> { + PrimaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = "Start Activation", + onClick = state.onContinueClick, + ) + } + OnboardingVisaWelcomeUM.Mode.WelcomeBack -> { + PrimaryButtonIconEnd( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = "Start Activation", + iconResId = R.drawable.ic_tangem_24, + onClick = state.onContinueClick, + ) + } + } + } +} + +@Composable +fun Artwork(modifier: Modifier = Modifier) { + Box(modifier) { + val circleColor = TangemTheme.colors.background.secondary + + Canvas( + modifier = Modifier + .align(Alignment.Center) + .size(254.dp) + .fillMaxSize(), + ) { + drawCircle( + color = circleColor, + radius = size.width / 2, + center = center, + ) + } + + AsyncImage( + model = R.drawable.img_card_visa, + contentDescription = null, + modifier = Modifier + .align(Alignment.Center) + .size(128.dp), // Adjust the size as needed + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview { + OnboardingVisaWelcome(state = OnboardingVisaWelcomeUM()) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/state/OnboardingVisaWelcomeUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/state/OnboardingVisaWelcomeUM.kt new file mode 100644 index 0000000000..9e1f6ba81b --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/state/OnboardingVisaWelcomeUM.kt @@ -0,0 +1,11 @@ +package com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.state + +internal data class OnboardingVisaWelcomeUM( + val mode: Mode = Mode.Hello, + val userName: String = "", + val onContinueClick: () -> Unit = {}, +) { + enum class Mode { + Hello, WelcomeBack + } +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt index 51e56a32da..bdeb4fe70e 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt @@ -13,10 +13,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.SpacerW8 +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R import com.tangem.feature.onboarding.presentation.wallet2.model.AboutState @@ -56,7 +56,7 @@ fun AboutSeedPhraseScreen(state: AboutState, modifier: Modifier = Modifier) { SecondaryButton( modifier = Modifier .fillMaxWidth(), - text = stringResource(id = R.string.onboarding_seed_intro_button_generate), + text = stringResourceSafe(id = R.string.onboarding_seed_intro_button_generate), enabled = state.buttonGenerateSeedPhrase.enabled, showProgress = state.buttonGenerateSeedPhrase.showProgress, onClick = state.buttonGenerateSeedPhrase.onClick, @@ -66,7 +66,7 @@ fun AboutSeedPhraseScreen(state: AboutState, modifier: Modifier = Modifier) { SecondaryButton( modifier = Modifier .fillMaxWidth(), - text = stringResource(id = R.string.onboarding_seed_intro_button_import), + text = stringResourceSafe(id = R.string.onboarding_seed_intro_button_import), enabled = state.buttonImportSeedPhrase.enabled, showProgress = state.buttonImportSeedPhrase.showProgress, onClick = state.buttonImportSeedPhrase.onClick, @@ -105,7 +105,7 @@ private fun EditIconBlock() { horizontal = TangemTheme.dimens.size12, vertical = TangemTheme.dimens.size4, ), - text = stringResource(id = R.string.onboarding_seed_phrase_intro_legacy), + text = stringResourceSafe(id = R.string.onboarding_seed_phrase_intro_legacy), color = TangemTheme.colors.text.warning, style = TangemTheme.typography.body1, ) @@ -143,7 +143,7 @@ private fun ReadMoreBlock(state: AboutState) { ) SpacerW8() Text( - text = stringResource(id = R.string.onboarding_seed_button_read_more), + text = stringResourceSafe(id = R.string.onboarding_seed_button_read_more), color = TangemTheme.colors.text.primary1, ) } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt index c98eb683e9..b9ee1576da 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt @@ -4,10 +4,10 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import com.tangem.core.ui.R import com.tangem.core.ui.components.OutlineTextField import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description @@ -29,7 +29,6 @@ fun CheckSeedPhraseScreen(state: CheckSeedPhraseState, modifier: Modifier = Modi .weight(1f) .fillMaxWidth(), ) { - item { OnboardingDescriptionBlock( modifier = Modifier @@ -59,7 +58,7 @@ fun CheckSeedPhraseScreen(state: CheckSeedPhraseState, modifier: Modifier = Modi PrimaryButtonIconEnd( modifier = Modifier .fillMaxWidth(), - text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), + text = stringResourceSafe(id = R.string.onboarding_create_wallet_button_create_wallet), iconResId = R.drawable.ic_tangem_24, enabled = state.buttonCreateWallet.enabled, showProgress = state.buttonCreateWallet.showProgress, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index de2ffddcbc..8f9c0f7056 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -14,7 +14,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -22,8 +21,9 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import com.tangem.core.ui.components.* -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.feature.onboarding.R import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseState @@ -49,7 +49,7 @@ fun ImportSeedPhraseScreen(state: ImportSeedPhraseState, modifier: Modifier = Mo OnboardingDescriptionBlock( modifier = Modifier.padding(top = TangemTheme.dimens.size16), ) { - DescriptionSubTitleText(text = stringResource(id = R.string.onboarding_seed_import_message)) + DescriptionSubTitleText(text = stringResourceSafe(id = R.string.onboarding_seed_import_message)) } PhraseBlock( modifier = Modifier @@ -70,7 +70,7 @@ fun ImportSeedPhraseScreen(state: ImportSeedPhraseState, modifier: Modifier = Mo PrimaryButtonIconEnd( modifier = Modifier .fillMaxWidth(), - text = stringResource(id = R.string.common_import), + text = stringResourceSafe(id = R.string.common_import), iconResId = R.drawable.ic_tangem_24, enabled = state.buttonCreateWallet.enabled, showProgress = state.buttonCreateWallet.showProgress, @@ -108,8 +108,8 @@ private fun PassphraseBlock( onValueChange = passphraseField.onTextFieldValueChanged, iconResId = R.drawable.ic_information_24, iconColor = TangemTheme.colors.icon.informative, - label = stringResource(id = R.string.common_passphrase), - placeholder = stringResource(id = R.string.send_optional_field), + label = stringResourceSafe(id = R.string.common_passphrase), + placeholder = stringResourceSafe(id = R.string.send_optional_field), onIconClick = onPassphraseInfoClick, ) } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt index 62a4ffde12..5691a1b124 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt @@ -8,11 +8,11 @@ import androidx.compose.ui.Modifier 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 coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R import com.tangem.feature.onboarding.presentation.wallet2.model.IntroState @@ -49,7 +49,7 @@ fun IntroScreen(state: IntroState, modifier: Modifier = Modifier) { PrimaryButtonIconEnd( modifier = Modifier .fillMaxWidth(), - text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), + text = stringResourceSafe(id = R.string.onboarding_create_wallet_button_create_wallet), iconResId = R.drawable.ic_tangem_24, enabled = state.buttonCreateWallet.enabled, showProgress = state.buttonCreateWallet.showProgress, @@ -60,7 +60,7 @@ fun IntroScreen(state: IntroState, modifier: Modifier = Modifier) { SecondaryButton( modifier = Modifier .fillMaxWidth(), - text = stringResource(id = R.string.onboarding_create_wallet_options_button_options), + text = stringResourceSafe(id = R.string.onboarding_create_wallet_options_button_options), enabled = state.buttonOtherOptions.enabled, showProgress = state.buttonOtherOptions.showProgress, onClick = state.buttonOtherOptions.onClick, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt index 9228432a75..cf5f16d4d0 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/PassphraseInfoBottomSheet.kt @@ -11,12 +11,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.onboarding.R @@ -50,7 +50,7 @@ fun PassphraseInfoBottomSheetContent(content: ShowPassphraseInfoBottomSheetConte ) Text( - text = stringResource(id = R.string.common_passphrase), + text = stringResourceSafe(id = R.string.common_passphrase), modifier = Modifier .padding(top = TangemTheme.dimens.size40) .align(Alignment.CenterHorizontally), @@ -59,7 +59,7 @@ fun PassphraseInfoBottomSheetContent(content: ShowPassphraseInfoBottomSheetConte ) Text( - text = stringResource(id = R.string.onboarding_bottom_sheet_passphrase_description), + text = stringResourceSafe(id = R.string.onboarding_bottom_sheet_passphrase_description), modifier = Modifier .padding(top = TangemTheme.dimens.size16) .padding(horizontal = TangemTheme.dimens.size24) @@ -75,7 +75,7 @@ fun PassphraseInfoBottomSheetContent(content: ShowPassphraseInfoBottomSheetConte .padding(top = TangemTheme.dimens.size40) .padding(bottom = TangemTheme.dimens.size32) .fillMaxWidth(), - text = stringResource(id = R.string.common_ok), + text = stringResourceSafe(id = R.string.common_ok), onClick = content.onOkClick, ) } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt index 2d767dc78a..e5d110febd 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt @@ -9,13 +9,13 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.pluralStringResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons +import com.tangem.core.ui.extensions.pluralStringResourceSafe +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.onboarding.R @@ -60,7 +60,7 @@ fun YourSeedPhraseScreen(state: YourSeedPhraseState, modifier: Modifier = Modifi firstActionContent = { PrimaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.common_continue), + text = stringResourceSafe(id = R.string.common_continue), onClick = state.buttonContinue.onClick, ) }, @@ -77,10 +77,10 @@ private fun TitleBlock(state: SegmentSeedState) { .fillMaxWidth(), ) { Column { - DescriptionTitleText(text = stringResource(id = R.string.onboarding_seed_generate_title)) + DescriptionTitleText(text = stringResourceSafe(id = R.string.onboarding_seed_generate_title)) SpacerH16() DescriptionSubTitleText( - text = pluralStringResource( + text = pluralStringResourceSafe( id = R.plurals.onboarding_seed_generate_message_words_count, count = state.selectedSeedType.count, state.selectedSeedType.count, @@ -101,7 +101,7 @@ private fun SegmentSeedBlock(state: SegmentSeedState, modifier: Modifier = Modif }, ) { Text( - text = pluralStringResource( + text = pluralStringResourceSafe( id = R.plurals.onboarding_seed_generate_words_count, count = it.count, it.count, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt index 5dc8a2a1c0..e0ee2d086e 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt @@ -8,10 +8,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.sp import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme /** @@ -31,9 +31,9 @@ fun OnboardingDescriptionBlock(modifier: Modifier = Modifier, content: @Composab @Composable fun Description(@StringRes titleRes: Int, @StringRes subTitleRes: Int) { Column { - DescriptionTitleText(text = stringResource(id = titleRes)) + DescriptionTitleText(text = stringResourceSafe(id = titleRes)) SpacerH16() - DescriptionSubTitleText(text = stringResource(id = subTitleRes)) + DescriptionSubTitleText(text = stringResourceSafe(id = subTitleRes)) } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ui/ConfirmResidencyBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ui/ConfirmResidencyBottomSheet.kt index d91f5881bb..4fedef0994 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ui/ConfirmResidencyBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ui/ConfirmResidencyBottomSheet.kt @@ -8,7 +8,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -23,6 +22,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent 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.onramp.confirmresidency.ConfirmResidencyComponent @@ -55,7 +55,7 @@ internal fun ConfirmResidencyBottomSheetContent(model: ConfirmResidencyUM, modif modifier = Modifier .fillMaxWidth() .padding(top = TangemTheme.dimens.spacing10), - text = stringResource(id = R.string.onramp_residency_bottomsheet_title), + text = stringResourceSafe(id = R.string.onramp_residency_bottomsheet_title), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, @@ -114,7 +114,7 @@ private fun CountryContent(name: String, flagUrl: String, isCountrySupported: Bo if (!isCountrySupported) { Text( modifier = Modifier.padding(top = TangemTheme.dimens.spacing6), - text = stringResource(id = R.string.onramp_residency_bottomsheet_country_not_supported), + text = stringResourceSafe(id = R.string.onramp_residency_bottomsheet_country_not_supported), color = TangemTheme.colors.text.warning, style = TangemTheme.typography.body2, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index 366e4ba313..514f0a3316 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -1,7 +1,6 @@ package com.tangem.features.onramp.main.model import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.res.stringResource import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.tangem.common.ui.alerts.models.AlertDemoModeUM @@ -14,6 +13,7 @@ import com.tangem.core.navigation.url.UrlOpener 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.message.ContentMessage import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.onramp.* @@ -322,7 +322,7 @@ internal class OnrampMainComponentModel @Inject constructor( ) val dismissButton = DialogButtonUM( - title = stringResource(id = R.string.common_cancel), + title = stringResourceSafe(id = R.string.common_cancel), onClick = onDismiss, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampProviderContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampProviderContent.kt index 381a77989d..e13fd96310 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampProviderContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampProviderContent.kt @@ -13,11 +13,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle import com.tangem.core.ui.extensions.appendSpace +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.main.entity.OnrampProviderBlockUM @@ -47,7 +47,7 @@ private fun OnrampProviderBlock(state: OnrampProviderBlockUM.Content, modifier: Column(modifier = Modifier.weight(1F)) { Text( text = buildAnnotatedString { - append(stringResource(id = R.string.onramp_pay_with)) + append(stringResourceSafe(id = R.string.onramp_pay_with)) appendSpace() withStyle( style = SpanStyle( @@ -63,7 +63,7 @@ private fun OnrampProviderBlock(state: OnrampProviderBlockUM.Content, modifier: ) Text( text = buildAnnotatedString { - append(stringResource(id = R.string.onramp_via)) + append(stringResourceSafe(id = R.string.onramp_via)) appendSpace() append(state.providerName) }, @@ -87,7 +87,7 @@ private fun OnrampProviderBlock(state: OnrampProviderBlockUM.Content, modifier: horizontal = TangemTheme.dimens.spacing6, vertical = TangemTheme.dimens.spacing1, ), - text = stringResource(id = R.string.express_provider_best_rate), + text = stringResourceSafe(id = R.string.express_provider_best_rate), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.primary2, ) @@ -105,7 +105,7 @@ private fun OnrampProviderLoading(modifier: Modifier = Modifier) { verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { Text( - text = stringResource(id = R.string.express_provider), + text = stringResourceSafe(id = R.string.express_provider), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) @@ -119,7 +119,7 @@ private fun OnrampProviderLoading(modifier: Modifier = Modifier) { modifier = Modifier.size(TangemTheme.dimens.size16), ) Text( - text = stringResource(id = R.string.express_fetch_best_rates), + text = stringResourceSafe(id = R.string.express_fetch_best_rates), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt index 9918d1f05e..29b255f412 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -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 @@ -33,6 +32,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent 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.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.onramp.model.OnrampPaymentMethod @@ -61,7 +61,7 @@ internal fun SelectProviderBottomSheetContent(state: SelectPaymentAndProviderUM, horizontalAlignment = Alignment.CenterHorizontally, ) { Text( - text = stringResource(id = R.string.onramp_choose_provider_title_hint), + text = stringResourceSafe(id = R.string.onramp_choose_provider_title_hint), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, @@ -102,7 +102,7 @@ private fun PaymentMethodBlock( PaymentMethodIcon(imageUrl = state.imageUrl) Column(modifier = Modifier.weight(1F)) { Text( - text = stringResource(id = R.string.onramp_pay_with), + text = stringResourceSafe(id = R.string.onramp_pay_with), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) @@ -191,7 +191,7 @@ private fun AvailableProviderItem(state: ProviderListItemUM.Available, modifier: when { state.isBestRate -> { Text( - text = stringResource(R.string.express_provider_best_rate), + text = stringResourceSafe(R.string.express_provider_best_rate), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.constantWhite, modifier = Modifier @@ -267,7 +267,7 @@ private fun OnrampMoreProviders() { .padding(top = 16.dp), ) Text( - text = stringResource(R.string.express_more_providers_soon), + text = stringResourceSafe(R.string.express_more_providers_soon), style = TangemTheme.typography.caption2, color = TangemTheme.colors.icon.informative, modifier = Modifier diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt index f15be660c5..2a78d7fded 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt @@ -1,6 +1,5 @@ package com.tangem.features.onramp.redirect.model -import androidx.compose.ui.res.stringResource import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -10,10 +9,7 @@ import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButtonUM import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM -import com.tangem.core.ui.extensions.combinedReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.message.ContentMessage import com.tangem.domain.onramp.GetOnrampRedirectUrlUseCase import com.tangem.domain.onramp.model.error.OnrampError @@ -90,9 +86,9 @@ internal class OnrampRedirectModel @Inject constructor( ) val contentMessage = ContentMessage { onDismiss -> BasicDialog( - message = stringResource(id = R.string.common_unknown_error), + message = stringResourceSafe(id = R.string.common_unknown_error), confirmButton = DialogButtonUM( - title = stringResource(id = R.string.common_ok), + title = stringResourceSafe(id = R.string.common_ok), onClick = { params.onBack() onDismiss() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt index 6fbf4c8d16..24c8fbdcb3 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/ui/SelectCountryBottomSheet.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import coil.compose.AsyncImage import com.tangem.core.ui.components.CircleShimmer @@ -24,6 +23,7 @@ import com.tangem.core.ui.components.buttons.SecondarySmallButton import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selectcountry.entity.CountryItemState @@ -67,7 +67,7 @@ private fun LazyListScope.countryListError(state: CountryListUM.Error) { verticalArrangement = Arrangement.Center, ) { Text( - text = stringResource(id = R.string.markets_loading_error_title), + text = stringResourceSafe(id = R.string.markets_loading_error_title), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt index 8177c32631..472d72efcb 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt @@ -11,7 +11,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource import androidx.compose.ui.util.fastForEach import coil.compose.AsyncImage import com.tangem.core.ui.components.CircleShimmer @@ -24,6 +23,7 @@ import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM @@ -67,7 +67,7 @@ private fun LazyListScope.currencyListError(state: CurrenciesListUM.Error) { verticalArrangement = Arrangement.Center, ) { Text( - text = stringResource(id = R.string.markets_loading_error_title), + text = stringResourceSafe(id = R.string.markets_loading_error_title), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt index 79bcd5e533..6d6e01358c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt @@ -1,6 +1,5 @@ package com.tangem.features.onramp.selecttoken.model -import androidx.compose.ui.res.stringResource import arrow.core.getOrElse import com.tangem.common.routing.AppRouter import com.tangem.common.ui.alerts.models.AlertDemoModeUM @@ -14,6 +13,7 @@ import com.tangem.core.decompose.ui.UiMessageSender 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.message.ContentMessage import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -141,7 +141,7 @@ internal class OnrampOperationModel @Inject constructor( ) val dismissButton = DialogButtonUM( - title = stringResource(id = R.string.common_cancel), + title = stringResourceSafe(id = R.string.common_cancel), onClick = onDismiss, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt index efbc6b5cfa..0347a146bd 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt @@ -10,9 +10,9 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.rememberHideKeyboardNestedScrollConnection import com.tangem.features.onramp.impl.R @@ -39,7 +39,7 @@ internal fun OnrampSelectToken( stickyHeader(key = "header") { AppBarWithBackButton( onBackClick = state.onBackClick, - text = stringResource(id = state.titleResId), + text = stringResourceSafe(id = state.titleResId), iconRes = R.drawable.ic_close_24, containerColor = TangemTheme.colors.background.secondary, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/ui/OnrampSettingsContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/ui/OnrampSettingsContent.kt index 949a3af30e..8a0d062131 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/ui/OnrampSettingsContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/ui/OnrampSettingsContent.kt @@ -13,11 +13,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.util.fastForEach import coil.compose.AsyncImage import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.WindowInsetsZero import com.tangem.features.onramp.impl.R @@ -35,7 +35,7 @@ internal fun OnrampSettingsContent(state: OnrampSettingsUM, modifier: Modifier = AppBarWithBackButton( modifier = Modifier.statusBarsPadding(), onBackClick = state.onBack, - text = stringResource(id = R.string.onramp_settings_title), + text = stringResourceSafe(id = R.string.onramp_settings_title), iconRes = R.drawable.ic_close_24, ) }, @@ -81,7 +81,7 @@ private fun ResidenceSection(state: OnrampSettingsItemUM.Residence, modifier: Mo verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringResource(id = R.string.onramp_settings_residence), + text = stringResourceSafe(id = R.string.onramp_settings_residence), color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.subtitle2, ) @@ -103,7 +103,7 @@ private fun ResidenceSection(state: OnrampSettingsItemUM.Residence, modifier: Mo ) } Text( - text = stringResource(id = R.string.onramp_settings_residence_description), + text = stringResourceSafe(id = R.string.onramp_settings_residence_description), color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.caption2, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt index e021c732e7..39ea071d73 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -27,10 +26,7 @@ import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.containers.FooterContainer import com.tangem.core.ui.components.transactions.TransactionDoneTitle -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.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.DateTimeFormatters @@ -73,7 +69,7 @@ private fun Content(state: OnrampSuccessComponentUM.Content, onBackClick: () -> }, bottomBar = { PrimaryButton( - text = stringResource(R.string.common_close), + text = stringResourceSafe(R.string.common_close), onClick = onBackClick, modifier = Modifier .fillMaxWidth() @@ -171,7 +167,7 @@ private fun AmountProviderBlock(providerName: TextReference, providerImageUrl: S horizontalArrangement = Arrangement.spacedBy(6.dp), ) { Text( - text = stringResource(R.string.common_with), + text = stringResourceSafe(R.string.common_with), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt index bd45bcea37..da3b5ead84 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt @@ -14,7 +14,6 @@ import androidx.compose.runtime.Composable 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.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -25,10 +24,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.rows.NetworkTitle import com.tangem.core.ui.components.token.TokenItem import com.tangem.core.ui.components.token.state.TokenItemState -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.stringReference +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.dashedBorder @@ -98,7 +94,7 @@ private fun RemoveButton(state: ExchangeCardUM.RemoveButtonUM?) { state ?: return@AnimatedVisibility Text( - text = stringResource(id = R.string.manage_tokens_remove), + text = stringResourceSafe(id = R.string.manage_tokens_remove), modifier = Modifier.clickable( indication = ripple(bounded = false), interactionSource = remember { MutableInteractionSource() }, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt index 7208284962..bf873aa6f5 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt @@ -13,9 +13,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.rememberHideKeyboardNestedScrollConnection import com.tangem.features.onramp.impl.R @@ -59,7 +59,7 @@ internal fun SwapSelectTokens( stickyHeader(key = "header") { AppBarWithBackButton( onBackClick = state.onBackClick, - text = stringResource(id = R.string.common_swap), + text = stringResourceSafe(id = R.string.common_swap), iconRes = R.drawable.ic_close_24, containerColor = TangemTheme.colors.background.secondary, ) diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt index cb06aa540c..7db2a37be4 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt @@ -12,12 +12,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.core.content.ContextCompat import com.tangem.core.ui.components.SimpleSettingsRow import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.qrscanning.impl.R @@ -39,7 +39,7 @@ private fun CameraDeniedBottomSheet(content: CameraDeniedBottomSheetConfig) { val context = LocalContext.current Column { SimpleSettingsRow( - title = stringResource(id = R.string.qr_scanner_camera_denied_settings_button), + title = stringResourceSafe(id = R.string.qr_scanner_camera_denied_settings_button), icon = R.drawable.ic_settings_24, onItemsClick = { val intent = Intent( @@ -50,12 +50,12 @@ private fun CameraDeniedBottomSheet(content: CameraDeniedBottomSheetConfig) { }, ) SimpleSettingsRow( - title = stringResource(id = R.string.qr_scanner_camera_denied_gallery_button), + title = stringResourceSafe(id = R.string.qr_scanner_camera_denied_gallery_button), icon = R.drawable.ic_gallery_24, onItemsClick = content.onGalleryClick, ) SimpleSettingsRow( - title = stringResource(id = R.string.common_close), + title = stringResourceSafe(id = R.string.common_close), icon = R.drawable.ic_close_24, onItemsClick = content.onCancelClick, modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing16), @@ -75,12 +75,12 @@ private fun CameraDeniedBottomSheetHeader(modifier: Modifier = Modifier) { verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), ) { Text( - text = stringResource(id = R.string.qr_scanner_camera_denied_title), + text = stringResourceSafe(id = R.string.qr_scanner_camera_denied_title), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.primary1, ) Text( - text = stringResource(id = R.string.qr_scanner_camera_denied_text), + text = stringResourceSafe(id = R.string.qr_scanner_camera_denied_text), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.secondary, ) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt index b1245ee718..d2c8244273 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt @@ -11,13 +11,13 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.google.accompanist.web.WebView import com.google.accompanist.web.rememberWebViewState import com.tangem.core.ui.components.appbar.TangemTopAppBar +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.webview.applySafeSettings @@ -36,7 +36,7 @@ internal fun AgreementBottomSheetContent(url: String, bottomBarHeight: Dp) { .fillMaxSize() .verticalScroll(rememberScrollState()), ) { - TangemTopAppBar(title = stringResource(id = R.string.details_referral_title)) + TangemTopAppBar(title = stringResourceSafe(id = R.string.details_referral_title)) AgreementHtmlView(url = url) Spacer(modifier = Modifier.height(bottomBarHeight)) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt index e005f91c7b..2dc7e37ea1 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt @@ -9,20 +9,20 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.ClickableText import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview -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.feature.referral.presentation.R @Composable internal fun AgreementText(@StringRes firstPartResId: Int, onClick: () -> Unit) { - val agreementText = annotatedAgreementString(firstPart = stringResource(firstPartResId)) + val agreementText = annotatedAgreementString(firstPart = stringResourceSafe(firstPartResId)) ClickableText( text = agreementText, modifier = Modifier @@ -46,10 +46,10 @@ private fun annotatedAgreementString(firstPart: String): AnnotatedString { append("$firstPart ") } withStyle(SpanStyle(color = TangemTheme.colors.text.accent)) { - append(stringResource(id = R.string.common_terms_and_conditions)) + append(stringResourceSafe(id = R.string.common_terms_and_conditions)) } withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) { - append(" ${stringResource(id = R.string.referral_tos_suffix)}") + append(" ${stringResourceSafe(id = R.string.referral_tos_suffix)}") } } } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index 077fb745d0..0849b5314f 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -7,9 +7,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding 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.PrimaryButtonIconEnd +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.referral.presentation.R @@ -23,7 +23,7 @@ internal fun NonParticipateBottomBlock(onAgreementClick: () -> Unit, onParticipa ) PrimaryButtonIconEnd( - text = stringResource(id = R.string.referral_button_participate), + text = stringResourceSafe(id = R.string.referral_button_participate), iconResId = R.drawable.ic_tangem_24, onClick = onParticipateClick, modifier = Modifier diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 300498f2e8..2057f834aa 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -18,15 +18,16 @@ import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.pluralStringResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.core.content.ContextCompat.startActivity +import com.tangem.core.res.getStringSafe import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.components.rows.RoundableCornersRow +import com.tangem.core.ui.extensions.pluralStringResourceSafe +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.referral.domain.models.ExpectedAward @@ -98,10 +99,10 @@ private fun CounterAndAwards(purchasedWalletCount: Int, expectedAwards: Expected @Composable private fun Counter(purchasedWalletCount: Int, overallItemsLastIndex: Int) { RoundableCornersRow( - startText = stringResource(id = R.string.referral_friends_bought_title), + startText = stringResourceSafe(id = R.string.referral_friends_bought_title), startTextColor = TangemTheme.colors.text.tertiary, startTextStyle = TangemTheme.typography.subtitle2, - endText = pluralStringResource( + endText = pluralStringResourceSafe( id = R.plurals.referral_wallets_purchased_count, count = purchasedWalletCount, purchasedWalletCount, @@ -122,14 +123,14 @@ private fun Awards(expectedAwards: ExpectedAwards, overallItemsLastIndex: Int, i ) RoundableCornersRow( startText = if (expectedAwards.expectedAwards.isNotEmpty()) { - stringResource(id = R.string.referral_expected_awards) + stringResourceSafe(id = R.string.referral_expected_awards) } else { - stringResource(id = R.string.referral_no_expected_awards) + stringResourceSafe(id = R.string.referral_no_expected_awards) }, startTextColor = TangemTheme.colors.text.tertiary, startTextStyle = TangemTheme.typography.subtitle2, endText = if (expectedAwards.expectedAwards.isNotEmpty()) { - pluralStringResource( + pluralStringResourceSafe( id = R.plurals.referral_number_of_wallets, count = expectedAwards.numberOfWallets, expectedAwards.numberOfWallets, @@ -201,9 +202,9 @@ private fun LessMoreButton(isExpanded: MutableState) { ) { Text( text = if (isExpanded.value) { - stringResource(id = R.string.referral_less) + stringResourceSafe(id = R.string.referral_less) } else { - stringResource(id = R.string.referral_more) + stringResourceSafe(id = R.string.referral_more) }, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.subtitle2, @@ -261,7 +262,7 @@ private fun PersonalCodeCard(code: String) { verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { Text( - text = stringResource(id = R.string.referral_promo_code_title), + text = stringResourceSafe(id = R.string.referral_promo_code_title), color = TangemTheme.colors.text.tertiary, maxLines = 1, style = TangemTheme.typography.subtitle2, @@ -294,7 +295,7 @@ private fun AdditionalButtons( horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), ) { PrimaryButtonIconStart( - text = stringResource(id = R.string.common_copy), + text = stringResourceSafe(id = R.string.common_copy), iconResId = R.drawable.ic_copy_24, onClick = { onCopyClick.invoke() @@ -303,7 +304,7 @@ private fun AdditionalButtons( coroutineScope.launch { snackbarHostState.showSnackbar( - message = resources.getString(R.string.referral_promo_code_copied), + message = resources.getStringSafe(R.string.referral_promo_code_copied), duration = SnackbarDuration.Short, ) } @@ -313,7 +314,7 @@ private fun AdditionalButtons( val context = LocalContext.current PrimaryButtonIconStart( - text = stringResource(id = R.string.common_share), + text = stringResourceSafe(id = R.string.common_share), iconResId = R.drawable.ic_share_24, onClick = { onShareClick.invoke() diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 9fe33b7bd6..cb76191b92 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -14,13 +14,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.res.getStringSafe import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH24 @@ -28,6 +28,7 @@ import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar import com.tangem.core.ui.components.snackbar.TangemSnackbar +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.referral.domain.models.ExpectedAward @@ -42,7 +43,6 @@ import kotlinx.coroutines.launch * Referral program screen for participant and non-participant * * @param stateHolder state holder - * @param modifier modifier */ @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -56,7 +56,7 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) { topBar = { AppBarWithBackButton( modifier = Modifier.statusBarsPadding(), - text = stringResource(R.string.details_referral_title), + text = stringResourceSafe(R.string.details_referral_title), onBackClick = stateHolder.headerState.onBackClicked, ) }, @@ -97,7 +97,7 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) { coroutineScope.launch { val result = snackbarHostState.showSnackbar( message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable), - actionLabel = resources.getString(R.string.warning_button_ok), + actionLabel = resources.getStringSafe(R.string.warning_button_ok), duration = SnackbarDuration.Indefinite, ) @@ -153,7 +153,7 @@ private fun Header() { ) SpacerH24() Text( - text = stringResource(id = R.string.referral_title), + text = stringResourceSafe(id = R.string.referral_title), modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing50), color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, @@ -270,7 +270,7 @@ private fun Condition(@DrawableRes iconResId: Int, infoBlock: @Composable () -> @Composable private fun InfoForYou(award: String, networkName: String, address: String? = null) { - ConditionInfo(title = stringResource(id = R.string.referral_point_currencies_title)) { + ConditionInfo(title = stringResourceSafe(id = R.string.referral_point_currencies_title)) { Text( formatAwardConditionsString( quantity = award, @@ -285,7 +285,7 @@ private fun InfoForYou(award: String, networkName: String, address: String? = nu @Composable private fun formatAwardConditionsString(quantity: String, network: String, address: String): AnnotatedString { - val rawString = stringResource(R.string.referral_point_currencies_description, quantity, network, address) + val rawString = stringResourceSafe(R.string.referral_point_currencies_description, quantity, network, address) val pattern = Regex("\\^\\^(.*?)\\^\\^") var startIndex = 0 @@ -315,20 +315,20 @@ private fun formatAwardConditionsString(quantity: String, network: String, addre @Composable private fun InfoForYourFriend(discount: String) { - ConditionInfo(title = stringResource(id = R.string.referral_point_discount_title)) { + ConditionInfo(title = stringResourceSafe(id = R.string.referral_point_discount_title)) { Text( text = buildAnnotatedString { - append(stringResource(id = R.string.referral_point_discount_description_prefix)) + append(stringResourceSafe(id = R.string.referral_point_discount_description_prefix)) withStyle(SpanStyle(color = TangemTheme.colors.text.primary1)) { append( String.format( - stringResource(id = R.string.referral_point_discount_description_value), + stringResourceSafe(id = R.string.referral_point_discount_description_value), " $discount", ), ) } append(" ") - append(stringResource(id = R.string.referral_point_discount_description_suffix)) + append(stringResourceSafe(id = R.string.referral_point_discount_description_suffix)) }, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, @@ -371,9 +371,11 @@ private fun ShimmerInfo() { private fun Resources.getMessageForErrorSnackbar(throwable: Throwable): String { return when { - throwable is DemoModeException -> getString(R.string.alert_demo_feature_disabled) - throwable.cause != null -> getString(R.string.referral_error_failed_to_load_info_with_reason, throwable.cause) - else -> getString(R.string.referral_error_failed_to_load_info) + throwable is DemoModeException -> getStringSafe(R.string.alert_demo_feature_disabled) + throwable.cause != null -> { + getStringSafe(R.string.referral_error_failed_to_load_info_with_reason, requireNotNull(throwable.cause)) + } + else -> getStringSafe(R.string.referral_error_failed_to_load_info) } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 5716d8be03..3918f10bb4 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -1,6 +1,7 @@ package com.tangem.features.send.impl.presentation.state.confirm import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.common.ui.amountScreen.models.AmountState @@ -235,7 +236,7 @@ internal class SendNotificationFactory( addExistentialWarningNotification( existentialDeposit = currencyCheck.existentialDeposit, feeAmount = feeState.fee?.amount?.value.orZero(), - receivedAmount = sendingAmount, + sendingAmount = sendingAmount, cryptoCurrencyStatus = cryptoCurrencyStatus, onReduceClick = clickIntents::onAmountReduceByClick, ) @@ -248,7 +249,7 @@ internal class SendNotificationFactory( ) addValidateTransactionNotifications( dustValue = currencyCheck.dustValue.orZero(), - fee = feeState.fee, + minAdaValue = (feeState.fee as? Fee.CardanoToken)?.minAdaValue, validationError = validationError, cryptoCurrency = currency, onReduceClick = clickIntents::onAmountReduceToClick, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt index 27229a8cd5..aa5604d797 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt @@ -4,13 +4,13 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource import com.tangem.common.ui.alerts.models.AlertUM import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButtonUM import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.SendEvent @@ -58,7 +58,7 @@ internal fun SendAlert(state: AlertUM, onDismiss: () -> Unit) { }, ) dismissButton = DialogButtonUM( - title = stringResource(id = R.string.common_cancel), + title = stringResourceSafe(id = R.string.common_cancel), onClick = onDismiss, ) } else { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index 4c59863b0c..1bc9b85f4b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -18,7 +18,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.ui.amountScreen.models.AmountState @@ -131,7 +130,7 @@ private fun SendNavigationButton( } TangemButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(buttonTextId), + text = stringResourceSafe(buttonTextId), icon = buttonIcon, enabled = isButtonEnabled, onClick = { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt index 096fbe1d8b..2c83d49d07 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign @@ -18,6 +17,7 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider +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.send.impl.R @@ -76,8 +76,8 @@ internal fun SendSpeedSelector( @Composable private fun FooterText(onReadMoreClick: () -> Unit) { - val linkText = stringResource(R.string.common_read_more) - val fullString = stringResource(R.string.common_fee_selector_footer, linkText) + val linkText = stringResourceSafe(R.string.common_read_more) + val fullString = stringResourceSafe(R.string.common_fee_selector_footer, linkText) val linkTextPosition = fullString.length - linkText.length val defaultStyle = TangemTheme.colors.text.tertiary val linkStyle = TangemTheme.colors.text.accent diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index ea78244e14..aaa589bde5 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -17,16 +17,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -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 import com.tangem.core.ui.components.containers.FooterContainer import com.tangem.core.ui.components.inputrow.InputRowRecipient -import com.tangem.core.ui.extensions.orMaskWithStars -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.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.send.impl.R @@ -167,7 +163,7 @@ private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Bo TangemTheme.dimens.radius0 } Text( - text = stringResource(titleRes), + text = stringResourceSafe(titleRes), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, modifier = Modifier diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt index 4d5830190e..99c44da3df 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -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 @@ -18,6 +17,7 @@ import com.tangem.common.ui.amountScreen.utils.getFiatReference import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.fee import com.tangem.core.ui.format.bigdecimal.format @@ -41,7 +41,7 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isClickDisabled: Boolean, o .padding(TangemTheme.dimens.spacing12), ) { Text( - text = stringResource(R.string.common_network_fee_title), + text = stringResourceSafe(R.string.common_network_fee_title), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.secondary, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt index d65fdea64d..ff3fda113f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt @@ -20,13 +20,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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 import com.tangem.common.ui.amountScreen.ui.AmountBlock import com.tangem.core.ui.components.transactions.TransactionDoneTitle import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -122,11 +122,12 @@ private fun LazyListScope.tapHelp(isDisplay: Boolean, modifier: Modifier = Modif ).plus(fadeIn()), exit = slideOutVertically().plus(fadeOut()), ) { + modifier + .fillMaxWidth() Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier - .fillMaxWidth() - .animateItemPlacement() + modifier = Modifier + .animateItem(fadeInSpec = null, fadeOutSpec = null) .padding(top = TangemTheme.dimens.spacing20), ) { val background = TangemTheme.colors.button.secondary @@ -137,7 +138,7 @@ private fun LazyListScope.tapHelp(isDisplay: Boolean, modifier: Modifier = Modif modifier = Modifier, ) Text( - text = stringResource(id = R.string.send_summary_tap_hint), + text = stringResourceSafe(id = R.string.send_summary_tap_hint), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.secondary, modifier = Modifier diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt index d96cda409d..c46880d541 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt @@ -1,5 +1,6 @@ package com.tangem.features.staking.impl.presentation.state.transformers.notifications +import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.notifications.NotificationUM import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification @@ -197,7 +198,7 @@ internal class AddStakingNotificationsTransformer( addExistentialWarningNotification( existentialDeposit = currencyCheck.existentialDeposit, feeAmount = feeState?.fee?.amount?.value.orZero(), - receivedAmount = sendingAmount, + sendingAmount = sendingAmount, cryptoCurrencyStatus = cryptoCurrencyStatus, onReduceClick = prevState.clickIntents::onAmountReduceByClick, ) @@ -212,7 +213,7 @@ internal class AddStakingNotificationsTransformer( // blockchain specific addValidateTransactionNotifications( dustValue = currencyCheck.dustValue.orZero(), - fee = feeState?.fee, + minAdaValue = (feeState?.fee as? Fee.CardanoToken)?.minAdaValue, validationError = validatorError, cryptoCurrency = cryptoCurrency, onReduceClick = prevState.clickIntents::onAmountReduceToClick, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingEventEffect.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingEventEffect.kt index 18426cab00..43d5493022 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingEventEffect.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingEventEffect.kt @@ -4,7 +4,6 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource import com.tangem.common.ui.alerts.models.AlertUM import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButtonUM @@ -12,6 +11,7 @@ import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.shareText +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.events.StakingEvent @@ -64,7 +64,7 @@ internal fun StakingAlert(state: AlertUM, onDismiss: () -> Unit) { ) dismissButton = DialogButtonUM( - title = stringResource(id = R.string.common_cancel), + title = stringResourceSafe(id = R.string.common_cancel), onClick = onDismiss, ) } else { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 95038b16d9..d213a829f3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.graphics.Brush 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.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle @@ -144,7 +143,7 @@ private fun LazyListScope.activeStakingBlock( if (innerYieldBalanceState.balances.isNotEmpty()) { item(ACTIVE_STAKING_BLOCK_KEY) { Text( - text = stringResource(id = R.string.staking_your_stakes), + text = stringResourceSafe(id = R.string.staking_your_stakes), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, modifier = Modifier @@ -214,7 +213,7 @@ private fun BannerBlock(onClick: () -> Unit) { .padding(TangemTheme.dimens.spacing16), text = buildAnnotatedString { withStyle(SpanStyle(Brush.linearGradient(textGradientColors))) { - append(stringResource(R.string.staking_details_banner_text)) + append(stringResourceSafe(R.string.staking_details_banner_text)) } }, style = TangemTheme.typography.h2, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingTosText.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingTosText.kt index 2f239888e8..b5a6372037 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingTosText.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingTosText.kt @@ -2,10 +2,10 @@ package com.tangem.features.staking.impl.presentation.ui import androidx.compose.foundation.text.ClickableText import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import com.tangem.core.ui.extensions.appendColored +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.staking.impl.R @@ -17,9 +17,9 @@ private const val PRIVACY_POLICY_URL = "https://docs.stakek.it/docs/privacy-poli @Composable internal fun StakingTosText(onTextClick: (String) -> Unit) { - val termsOfUse = stringResource(R.string.common_terms_of_use) - val privacyPolicy = stringResource(R.string.common_privacy_policy) - val tosText = stringResource(R.string.staking_legal, termsOfUse, privacyPolicy) + val termsOfUse = stringResourceSafe(R.string.common_terms_of_use) + val privacyPolicy = stringResourceSafe(R.string.common_privacy_policy) + val tosText = stringResourceSafe(R.string.staking_legal, termsOfUse, privacyPolicy) val clickableAnnotation = buildAnnotatedString { append(tosText.substringBefore(termsOfUse)) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt index 1bcd5c9c60..6afc60b7b3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalDensity -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 @@ -113,7 +112,7 @@ internal fun StakingValidatorListContent( private fun RowScope.ValidatorLabel(isStrategicPartner: Boolean) { if (isStrategicPartner) { Text( - text = stringResource(R.string.staking_validators_label), + text = stringResourceSafe(R.string.staking_validators_label), style = TangemTheme.typography.caption1, color = TangemTheme.colors.icon.constant, modifier = Modifier diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt index 8817faede7..cfd283c0d1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt @@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -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 @@ -21,6 +20,7 @@ import com.tangem.common.ui.amountScreen.utils.getFiatReference import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.fee import com.tangem.core.ui.format.bigdecimal.format @@ -46,7 +46,7 @@ internal fun StakingFeeBlock(feeState: FeeState, isTransactionSent: Boolean) { .padding(TangemTheme.dimens.spacing12), ) { Text( - text = stringResource(R.string.common_network_fee_title), + text = stringResourceSafe(R.string.common_network_fee_title), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/Warning.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/Warning.kt deleted file mode 100644 index 4d0d6904c4..0000000000 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/Warning.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.feature.swap.domain.models.domain - -import java.math.BigDecimal - -sealed class Warning { - - data class ExistentialDepositWarning( - val existentialDeposit: BigDecimal, - val minAvailableAmount: BigDecimal, - ) : Warning() - - data class MinAmountWarning(val dustValue: BigDecimal) : Warning() - - data class ReduceAmountWarning(val tezosFeeThreshold: BigDecimal) : Warning() - - sealed class Cardano : Warning() { - - data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Cardano() - - data object InsufficientBalanceToTransferCoin : Cardano() - - data class InsufficientBalanceToTransferToken(val tokenName: String) : Cardano() - } -} \ No newline at end of file diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index 6183efe111..a42a8db9a0 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -1,6 +1,7 @@ package com.tangem.feature.swap.domain.models.ui import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.feature.swap.domain.models.ExpressDataError import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.* @@ -26,7 +27,9 @@ sealed interface SwapState { val permissionState: PermissionDataState = PermissionDataState.Empty, val swapDataModel: SwapDataModel? = null, val txFee: TxFeeState, - val warnings: List = emptyList(), + val currencyCheck: CryptoCurrencyCheck? = null, + val validationResult: Throwable? = null, + val minAdaValue: BigDecimal?, val swapProvider: SwapProvider, ) : SwapState diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index 3154fa4293..871f5f5e70 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -9,6 +9,7 @@ import com.tangem.feature.swap.domain.models.domain.PermissionOptions import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.domain.SwapProvider import com.tangem.feature.swap.domain.models.ui.* +import java.math.BigDecimal interface SwapInteractor { @@ -39,6 +40,7 @@ interface SwapInteractor { toToken: CryptoCurrencyStatus, providers: List, amountToSwap: String, + reduceBalanceBy: BigDecimal, selectedFee: FeeType = FeeType.NORMAL, ): Map @@ -70,6 +72,7 @@ interface SwapInteractor { selectedFee: FeeType, fromToken: CryptoCurrencyStatus, amountToSwap: String, + reduceBalanceBy: BigDecimal, ): SwapState.QuotesLoadedState /** diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 6ad0d0ff86..270b21d6bb 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -2,23 +2,23 @@ package com.tangem.feature.swap.domain import arrow.core.Either import arrow.core.getOrElse -import com.tangem.blockchain.common.* import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain.* +import com.tangem.blockchain.common.TransactionExtras import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.core.ui.utils.BigDecimalFormatter -import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase +import com.tangem.domain.tokens.GetCurrencyCheckUseCase import com.tangem.domain.tokens.model.* -import com.tangem.domain.tokens.model.FeePaidCurrency +import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.QuotesRepository @@ -36,7 +36,6 @@ import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.feature.swap.domain.models.ui.* -import com.tangem.lib.crypto.BlockchainUtils import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.ProxyAmount @@ -71,6 +70,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( private val validateTransactionUseCase: ValidateTransactionUseCase, private val estimateFeeUseCase: EstimateFeeUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, + private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, private val amountFormatter: AmountFormatter, @Assisted private val userWalletId: UserWalletId, ) : SwapInteractor { @@ -251,6 +251,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( toToken: CryptoCurrencyStatus, providers: List, amountToSwap: String, + reduceBalanceBy: BigDecimal, selectedFee: FeeType, ): Map { Timber.i( @@ -291,6 +292,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( toToken = toToken, provider = provider, amount = amount, + reduceBalanceBy = reduceBalanceBy, isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough, ) } @@ -369,11 +371,13 @@ internal class SwapInteractorImpl @AssistedInject constructor( toToken: CryptoCurrencyStatus, provider: SwapProvider, amount: SwapAmount, + reduceBalanceBy: BigDecimal, isBalanceWithoutFeeEnough: Boolean, ): Pair { return provider to loadCexQuoteData( networkId = networkId, amount = amount, + reduceBalanceBy = reduceBalanceBy, fromTokenStatus = fromToken, toTokenStatus = toToken, isAllowedToSpend = true, @@ -386,111 +390,33 @@ internal class SwapInteractorImpl @AssistedInject constructor( fromTokenStatus: CryptoCurrencyStatus, amount: SwapAmount, feeState: TxFeeState, - minAdaValue: BigDecimal?, - ): List { - val fromToken = fromTokenStatus.currency - val warnings = mutableListOf() - manageExistentialDepositWarning(warnings, userWalletId, amount, fromToken, feeState) - manageDustWarning(warnings, feeState, userWalletId, fromTokenStatus, amount) - manageReduceAmountWarning(warnings, fromTokenStatus, amount) - manageTransactionValidationWarnings( - warnings = warnings, - fromToken = fromToken, - amount = amount, - feeState = feeState, - userWalletId = userWalletId, - minAdaValue = minAdaValue, - ) - return warnings - } - - private suspend fun manageExistentialDepositWarning( - warnings: MutableList, - userWalletId: UserWalletId, - amount: SwapAmount, - fromToken: CryptoCurrency, - txFee: TxFeeState, - ) { - val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, fromToken.network) - if (existentialDeposit != null) { - val nativeBalance = userWalletManager.getNativeTokenBalance( - fromToken.network.backendId, - fromToken.network.derivationPath.value, - ) ?: ProxyAmount.empty() - // ignore if amount is bigger than balance - if (amount.value > nativeBalance.value) { - return - } - val fee = when (txFee) { - TxFeeState.Empty -> BigDecimal.ZERO - is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeValue - is TxFeeState.SingleFeeState -> txFee.fee.feeValue - } - val minAvailableAmount = nativeBalance.value - existentialDeposit - fee - if (nativeBalance.value.minus(amount.value + fee) < existentialDeposit) { - warnings.add(Warning.ExistentialDepositWarning(existentialDeposit, minAvailableAmount)) - } - } - } - - private suspend fun manageDustWarning( - warnings: MutableList, - feeState: TxFeeState, - userWalletId: UserWalletId, - fromTokenStatus: CryptoCurrencyStatus, - amount: SwapAmount, - ) { - if (BlockchainUtils.isCardano(fromTokenStatus.currency.network.id.value)) return - + ): CryptoCurrencyCheck { val fee = when (feeState) { TxFeeState.Empty -> BigDecimal.ZERO is TxFeeState.MultipleFeeState -> feeState.priorityFee.feeValue is TxFeeState.SingleFeeState -> feeState.fee.feeValue } + val statusValue = fromTokenStatus.value as? CryptoCurrencyStatus.Loaded + val balanceAfterTransaction = statusValue?.let { it.amount - amount.value - fee } - val dustValue = currencyChecksRepository.getDustValue(userWalletId, fromTokenStatus.currency.network) ?: return + val currencyCheck = getCurrencyCheckUseCase( + userWalletId = userWalletId, + currencyStatus = fromTokenStatus, + amount = amount.value, + fee = fee, + balanceAfterTransaction = balanceAfterTransaction, + ) - val change = when (fromTokenStatus.currency) { - is CryptoCurrency.Coin -> { - val balance = fromTokenStatus.value.amount ?: BigDecimal.ZERO - balance - (fee + amount.value) - } - is CryptoCurrency.Token -> { - val nativeTokenBalance = userWalletManager.getNativeTokenBalance( - fromTokenStatus.currency.network.id.value, - fromTokenStatus.currency.network.derivationPath.value, - ) - - nativeTokenBalance?.value?.minus(fee) ?: BigDecimal.ZERO - } - } - - val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO - - if (amount.value < dustValue || isChangeLowerThanDust) { - warnings.add(Warning.MinAmountWarning(dustValue)) - } - } - - private fun manageReduceAmountWarning( - warnings: MutableList, - fromTokenStatus: CryptoCurrencyStatus, - amount: SwapAmount, - ) { - val isTezos = fromTokenStatus.currency.network.id.value == Blockchain.Tezos.id - if (isTezos && amount.value == fromTokenStatus.value.amount) { - warnings.add(Warning.ReduceAmountWarning(Blockchain.Tezos.minimalAmount())) - } + return currencyCheck } private suspend fun manageTransactionValidationWarnings( - warnings: MutableList, - fromToken: CryptoCurrency, + fromToken: CryptoCurrencyStatus, amount: SwapAmount, feeState: TxFeeState, userWalletId: UserWalletId, - minAdaValue: BigDecimal?, - ) { + ): Throwable? { + val currency = fromToken.currency val fee = Fee.Common( amount = Amount( value = when (feeState) { @@ -498,66 +424,20 @@ internal class SwapInteractorImpl @AssistedInject constructor( is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue is TxFeeState.SingleFeeState -> feeState.fee.feeValue }, - blockchain = Blockchain.fromId(fromToken.network.id.value), + blockchain = Blockchain.fromId(currency.network.id.value), ), ) - validateTransactionUseCase( - amount = amount.value.convertToSdkAmount(fromToken), + val result = validateTransactionUseCase( + amount = amount.value.convertToSdkAmount(currency), fee = fee, memo = null, - destination = getTokenAddress(fromToken), + destination = getTokenAddress(fromToken.currency), userWalletId = userWalletId, - network = fromToken.network, - ).fold( - ifLeft = { - addCardanoTransactionValidationError( - warnings = warnings, - error = it as? BlockchainSdkError.Cardano ?: return@fold, - fromToken = fromToken, - userWalletId = userWalletId, - ) - }, - ifRight = { - minAdaValue?.let { - warnings.add( - Warning.Cardano.MinAdaValueCharged( - tokenName = fromToken.name, - minAdaValue = minAdaValue.parseBigDecimal(fromToken.decimals), - ), - ) - } - }, - ) - } + network = currency.network, + ).leftOrNull() - private suspend fun addCardanoTransactionValidationError( - warnings: MutableList, - error: BlockchainSdkError.Cardano, - fromToken: CryptoCurrency, - userWalletId: UserWalletId, - ) { - when (error) { - BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> { - Warning.Cardano.InsufficientBalanceToTransferToken(fromToken.name) - } - BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> { - when (fromToken) { - is CryptoCurrency.Coin -> Warning.Cardano.InsufficientBalanceToTransferCoin - is CryptoCurrency.Token -> { - Warning.Cardano.InsufficientBalanceToTransferToken(fromToken.name) - } - } - } - BlockchainSdkError.Cardano.InsufficientRemainingBalance, - BlockchainSdkError.Cardano.InsufficientSendingAdaAmount, - -> { - val dustValue = currencyChecksRepository.getDustValue(userWalletId, fromToken.network) ?: return - - Warning.MinAmountWarning(dustValue) - } - } - .let(warnings::add) // add warning to the list + return result } override suspend fun onSwap( @@ -621,6 +501,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( selectedFee: FeeType, fromToken: CryptoCurrencyStatus, amountToSwap: String, + reduceBalanceBy: BigDecimal, ): SwapState.QuotesLoadedState { val amountDecimal = toBigDecimalOrNull(amountToSwap) if (amountDecimal == null || amountDecimal.signum() == 0) { @@ -631,6 +512,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( networkId = fromToken.currency.network.backendId, txFee = state.txFee, amount = amount, + reduceBalanceBy = reduceBalanceBy, fromToken = fromToken.currency, ) val fee = when (val txFee = state.txFee) { @@ -1150,6 +1032,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( private suspend fun loadCexQuoteData( networkId: String, amount: SwapAmount, + reduceBalanceBy: BigDecimal, fromTokenStatus: CryptoCurrencyStatus, toTokenStatus: CryptoCurrencyStatus, provider: SwapProvider, @@ -1175,6 +1058,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( networkId = networkId, txFee = txFee, amount = amount, + reduceBalanceBy = reduceBalanceBy, fromToken = fromToken, ) val amountToRequest = if (includeFeeInAmount is IncludeFeeInAmount.Included) { @@ -1206,7 +1090,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( isAllowedToSpend = isAllowedToSpend, isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough, txFee = txFee, - transactionFee = txFeeResult?.getOrNull(), + transactionFee = txFeeResult.getOrNull(), includeFeeInAmount = includeFeeInAmount, ) } @@ -1238,12 +1122,18 @@ internal class SwapInteractorImpl @AssistedInject constructor( txFeeState = txFee, provider = provider, ).copy( - warnings = manageWarnings( + currencyCheck = manageWarnings( fromTokenStatus = fromToken, amount = amount, feeState = txFee, - minAdaValue = (transactionFee?.normal as? Fee.CardanoToken)?.minAdaValue, ), + validationResult = manageTransactionValidationWarnings( + fromToken = fromToken, + amount = amount, + feeState = txFee, + userWalletId = userWalletId, + ), + minAdaValue = (transactionFee?.normal as? Fee.CardanoToken)?.minAdaValue, ) when (provider.type) { @@ -1321,6 +1211,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( networkId: String, txFee: TxFeeState, amount: SwapAmount, + reduceBalanceBy: BigDecimal, fromToken: CryptoCurrency, ): IncludeFeeInAmount { val feeValue = when (txFee) { @@ -1340,13 +1231,20 @@ internal class SwapInteractorImpl @AssistedInject constructor( IncludeFeeInAmount.BalanceNotEnough } } - else -> getIncludeFeeAmountForCoinFee(networkId, amount, feeValue, fromToken) + else -> getIncludeFeeAmountForCoinFee( + networkId = networkId, + amount = amount, + reduceBalanceBy = reduceBalanceBy, + feeValue = feeValue, + fromToken = fromToken, + ) } } private suspend fun getIncludeFeeAmountForCoinFee( networkId: String, amount: SwapAmount, + reduceBalanceBy: BigDecimal, feeValue: BigDecimal, fromToken: CryptoCurrency, ): IncludeFeeInAmount { @@ -1355,26 +1253,27 @@ internal class SwapInteractorImpl @AssistedInject constructor( networkId, fromToken.network.derivationPath.value, ) ?: ProxyAmount.empty() + val reducedBalance = tokenForFeeBalance.value - reduceBalanceBy val amountWithFee = amount.value + feeValue return when { fromToken is CryptoCurrency.Token -> { - if (feeValue > tokenForFeeBalance.value || tokenForFeeBalance.value.signum() == 0) { + if (feeValue > reducedBalance || reducedBalance.signum() == 0) { IncludeFeeInAmount.BalanceNotEnough } else { IncludeFeeInAmount.Excluded } } - amount.value > tokenForFeeBalance.value -> { + amount.value > reducedBalance -> { IncludeFeeInAmount.BalanceNotEnough } - amountWithFee < tokenForFeeBalance.value -> { + amountWithFee < reducedBalance -> { IncludeFeeInAmount.Excluded } else -> { if (feeValue < amount.value) { IncludeFeeInAmount.Included( SwapAmount( - tokenForFeeBalance.value - feeValue, + reducedBalance - feeValue, getNativeToken(fromToken.network.backendId).decimals, ), ) @@ -1478,11 +1377,16 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) swapState.copy( permissionState = PermissionDataState.Empty, - warnings = manageWarnings( + currencyCheck = manageWarnings( fromTokenStatus = fromToken, amount = amount, feeState = txFeeState, - minAdaValue = null, // no ADA in DEX + ), + validationResult = manageTransactionValidationWarnings( + fromToken = fromToken, + amount = amount, + feeState = txFeeState, + userWalletId = userWalletId, ), preparedSwapConfigState = preparedSwapConfigState, ) @@ -1578,6 +1482,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( swapDataModel = swapData, txFee = txFeeState, swapProvider = provider, + minAdaValue = null, ) } diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index ddbb0c1e9a..bd52ee999a 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -60,7 +60,6 @@ dependencies { implementation(deps.compose.ui.tooling) implementation(deps.compose.coil) implementation(deps.compose.constraintLayout) - implementation(deps.compose.accompanist.systemUiController) /** Api */ implementation(projects.features.swap.api) @@ -71,7 +70,6 @@ dependencies { /** Other libraries */ implementation(deps.compose.shimmer) - implementation(deps.compose.accompanist.webView) implementation(deps.compose.accompanist.systemUiController) implementation(deps.kotlin.serialization) implementation(deps.kotlin.immutable.collections) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index 8b21ec6919..50efc0c19f 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -3,9 +3,9 @@ package com.tangem.feature.swap.models import androidx.annotation.DrawableRes import androidx.compose.ui.text.input.TextFieldValue import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState +import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.R import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.extensions.TextReference @@ -14,12 +14,15 @@ import com.tangem.feature.swap.domain.models.ui.PriceImpact import com.tangem.feature.swap.models.states.FeeItemState import com.tangem.feature.swap.models.states.ProviderState import com.tangem.feature.swap.models.states.events.SwapEvent +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf internal data class SwapStateHolder( val sendCardData: SwapCardState, val receiveCardData: SwapCardState, val blockchainId: String, // not the same as networkId, its local id in app - val warnings: List = emptyList(), + val notifications: ImmutableList = persistentListOf(), + val isInsufficientFunds: Boolean, val event: StateEvent = consumedEvent(), val changeCardsButtonState: ChangeCardsButtonState = ChangeCardsButtonState.ENABLED, val providerState: ProviderState, @@ -106,35 +109,6 @@ data class LegalState( val onClick: (String) -> Unit, ) -sealed interface SwapWarning { - data class PermissionNeeded(val notificationConfig: NotificationConfig) : SwapWarning - data object InsufficientFunds : SwapWarning - data class NoAvailableTokensToSwap(val notificationConfig: NotificationConfig) : SwapWarning - data class GenericWarning( - val title: TextReference? = null, - val message: TextReference? = null, - val onClick: () -> Unit, - ) : SwapWarning - - data class GeneralError(val notificationConfig: NotificationConfig) : SwapWarning - data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning - data class GeneralWarning(val notificationConfig: NotificationConfig) : SwapWarning - data class GeneralInformational(val notificationConfig: NotificationConfig) : SwapWarning - data class TransactionInProgressWarning(val title: TextReference, val description: TextReference) : SwapWarning - data class NeedReserveToCreateAccount(val notificationConfig: NotificationConfig) : SwapWarning - data class ReduceAmount(val notificationConfig: NotificationConfig) : SwapWarning - - sealed interface Cardano : SwapWarning { - val notificationConfig: NotificationConfig - - data class MinAdaValueCharged(override val notificationConfig: NotificationConfig) : Cardano - - data class InsufficientBalanceToTransferCoin(override val notificationConfig: NotificationConfig) : Cardano - - data class InsufficientBalanceToTransferToken(override val notificationConfig: NotificationConfig) : Cardano - } -} - enum class ChangeCardsButtonState { ENABLED, DISABLED, UPDATE_IN_PROGRESS } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt index c25fc38225..05e17e542b 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt @@ -4,6 +4,7 @@ import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.ui.TxFee +import java.math.BigDecimal data class UiActions( val onSearchEntered: (String) -> Unit, @@ -15,8 +16,8 @@ data class UiActions( val onChangeCardsClicked: () -> Unit, val onBackClicked: () -> Unit, val onMaxAmountSelected: () -> Unit, - val onReduceAmount: (SwapAmount) -> Unit, - val onLeaveExistentialDeposit: (SwapAmount) -> Unit, + val onReduceToAmount: (SwapAmount) -> Unit, + val onReduceByAmount: (SwapAmount, reduceBy: BigDecimal) -> Unit, val openPermissionBottomSheet: () -> Unit, val onChangeApproveType: (ApproveType) -> Unit, // region new actions diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/SwapNotificationUM.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/SwapNotificationUM.kt new file mode 100644 index 0000000000..7736b9a69d --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/SwapNotificationUM.kt @@ -0,0 +1,205 @@ +package com.tangem.feature.swap.models.states + +import com.tangem.common.ui.R +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.components.notifications.NotificationConfig +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.networkIconResId +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.feature.swap.domain.models.ExpressDataError +import com.tangem.feature.swap.utils.getExpressErrorMessage +import com.tangem.feature.swap.utils.getExpressErrorTitle + +internal object SwapNotificationUM { + + sealed class Error( + title: TextReference, + subtitle: TextReference, + iconResId: Int = R.drawable.ic_alert_24, + buttonState: NotificationConfig.ButtonsState? = null, + onCloseClick: (() -> Unit)? = null, + ) : NotificationUM.Error( + title = title, + subtitle = subtitle, + iconResId = iconResId, + buttonState = buttonState, + onCloseClick = onCloseClick, + ) { + data class GenericError( + val title: TextReference = resourceReference(id = R.string.common_warning), + val subtitle: TextReference?, + val onConfirmClick: () -> Unit, + ) : Error( + title = title, + subtitle = subtitle ?: resourceReference(id = R.string.common_unknown_error), + buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( + text = resourceReference(R.string.common_ok), + onClick = onConfirmClick, + ), + ) + + data object ApprovalInProgressWarning : Error( + title = resourceReference(R.string.warning_express_approval_in_progress_title), + subtitle = resourceReference(R.string.warning_express_approval_in_progress_message), + ) + + data class TransactionInProgressWarning( + val currencySymbol: String, + ) : Error( + title = resourceReference(R.string.warning_express_active_transaction_title), + subtitle = resourceReference( + id = R.string.warning_express_active_transaction_message, + formatArgs = wrappedList(currencySymbol), + ), + ) + + data class UnableToCoverFeeWarning( + val fromToken: CryptoCurrency, + val currencyName: String, + val currencySymbol: String, + val feeCurrency: CryptoCurrency?, + val onConfirmClick: (CryptoCurrency) -> Unit, + ) : Error( + title = resourceReference( + R.string.warning_express_not_enough_fee_for_token_tx_title, + wrappedList(fromToken.network.name), + ), + subtitle = resourceReference( + R.string.warning_express_not_enough_fee_for_token_tx_description, + wrappedList(currencyName, currencySymbol), + ), + iconResId = fromToken.networkIconResId, + buttonState = feeCurrency?.let { + NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.common_buy_currency, wrappedList(currencySymbol)), + onClick = { onConfirmClick(it) }, + ) + }, + ) + + data class MinimalAmountError( + val amount: String, + ) : Error( + title = resourceReference( + id = R.string.warning_express_too_minimal_amount_title, + formatArgs = wrappedList(amount), + ), + subtitle = resourceReference(R.string.warning_express_wrong_amount_description), + ) + + data class MaximumAmountError( + val amount: String, + ) : Error( + title = resourceReference( + id = R.string.warning_express_too_maximum_amount_title, + formatArgs = wrappedList(amount), + ), + subtitle = resourceReference(R.string.warning_express_wrong_amount_description), + iconResId = R.drawable.ic_alert_circle_24, + ) + } + + sealed class Warning( + title: TextReference, + subtitle: TextReference, + iconResId: Int = R.drawable.img_attention_20, + buttonsState: NotificationConfig.ButtonsState? = null, + onCloseClick: (() -> Unit)? = null, + ) : NotificationUM.Warning( + title = title, + subtitle = subtitle, + iconResId = iconResId, + buttonsState = buttonsState, + onCloseClick = onCloseClick, + ) { + data class NoAvailableTokensToSwap( + val tokenName: String, + ) : Warning( + title = resourceReference( + com.tangem.feature.swap.presentation.R.string.warning_express_no_exchangeable_coins_title, + ), + subtitle = resourceReference( + id = com.tangem.feature.swap.presentation.R.string.warning_express_no_exchangeable_coins_description, + formatArgs = wrappedList(tokenName), + ), + ) + + data class NeedReserveToCreateAccount( + val amount: String, + val token: String, + ) : Warning( + title = resourceReference( + id = R.string.send_notification_invalid_reserve_amount_title, + formatArgs = wrappedList("$amount $token"), + ), + subtitle = resourceReference(R.string.send_notification_invalid_reserve_amount_text), + ) + + data class ReduceAmount( + val currencyName: String, + val amount: String, + val onConfirmClick: () -> Unit, + ) : Warning( + title = resourceReference(R.string.send_notification_high_fee_title), + subtitle = resourceReference( + R.string.send_notification_high_fee_text, + wrappedList(currencyName, amount), + ), + buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( + text = resourceReference( + R.string.xtz_withdrawal_message_reduce, + wrappedList(amount), + ), + onClick = onConfirmClick, + ), + ) + + data class ExpressError( + val expressDataError: ExpressDataError, + val onConfirmClick: () -> Unit, + ) : Warning( + title = getExpressErrorTitle(expressDataError), + subtitle = getExpressErrorMessage(expressDataError), + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.warning_button_refresh), + onClick = onConfirmClick, + ), + ) + + data class ExpressGeneralError( + val code: Int, + val onConfirmClick: () -> Unit, + ) : Warning( + title = TextReference.Res(R.string.warning_express_refresh_required_title), + subtitle = TextReference.Res(R.string.express_error_code, wrappedList(code)), + buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( + text = TextReference.Res(R.string.warning_button_refresh), + onClick = onConfirmClick, + ), + ) + } + + sealed class Info( + title: TextReference, + subtitle: TextReference, + iconResId: Int = R.drawable.ic_alert_circle_24, + ) : NotificationUM.Info( + title = title, + subtitle = subtitle, + iconResId = iconResId, + ) { + data class PermissionNeeded( + val providerName: String, + val fromTokenSymbol: String, + ) : Info( + title = resourceReference(R.string.express_provider_permission_needed), + subtitle = resourceReference( + id = R.string.give_permission_swap_subtitle, + formatArgs = wrappedList(providerName, fromTokenSymbol), + ), + iconResId = R.drawable.ic_locked_24, + ) + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/WebViewBottomSheetConfig.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/WebViewBottomSheetConfig.kt deleted file mode 100644 index ee2bce3961..0000000000 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/WebViewBottomSheetConfig.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.feature.swap.models.states - -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent - -internal data class WebViewBottomSheetConfig(val url: String) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapNotificationsFactory.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapNotificationsFactory.kt new file mode 100644 index 0000000000..d648e03355 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapNotificationsFactory.kt @@ -0,0 +1,345 @@ +package com.tangem.feature.swap.presentation + +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addExistentialWarningNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.feature.swap.domain.models.ExpressDataError +import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.domain.IncludeFeeInAmount +import com.tangem.feature.swap.domain.models.domain.SwapFeeState +import com.tangem.feature.swap.domain.models.ui.* +import com.tangem.feature.swap.models.UiActions +import com.tangem.feature.swap.models.states.FeeItemState +import com.tangem.feature.swap.models.states.SwapNotificationUM +import com.tangem.lib.crypto.BlockchainUtils +import com.tangem.lib.crypto.BlockchainUtils.getTezosThreshold +import com.tangem.lib.crypto.BlockchainUtils.isTezos +import com.tangem.utils.extensions.orZero +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList +import java.math.BigDecimal + +internal class SwapNotificationsFactory( + private val actions: UiActions, +) { + + fun getInitialErrorStateNotifications(code: Int, onRefreshClick: () -> Unit): ImmutableList { + return persistentListOf( + SwapNotificationUM.Warning.ExpressGeneralError( + code = code, + onConfirmClick = onRefreshClick, + ), + ) + } + + fun getGeneralErrorStateNotifications( + notifications: ImmutableList, + message: TextReference?, + onClick: () -> Unit, + ): ImmutableList { + val updatedNotifications = notifications.toMutableList() + updatedNotifications.add( + SwapNotificationUM.Error.GenericError( + subtitle = message, + onConfirmClick = onClick, + ), + ) + + return updatedNotifications.toPersistentList() + } + + fun getNotAvailableStateNotifications(fromCurrencyName: String): ImmutableList { + return persistentListOf( + SwapNotificationUM.Warning.NoAvailableTokensToSwap(fromCurrencyName), + ) + } + + fun getQuotesErrorStateNotifications( + expressDataError: ExpressDataError, + fromToken: CryptoCurrency, + feeItem: FeeItemState, + includeFeeInAmount: IncludeFeeInAmount, + ): ImmutableList { + return buildList { + add(getWarningForError(expressDataError, fromToken, actions.onRetryClick)) + if (includeFeeInAmount is IncludeFeeInAmount.Included && feeItem is FeeItemState.Content) { + add( + NotificationUM.Warning.FeeCoverageNotification( + feeItem.amountCrypto, + feeItem.amountFiatFormatted, + ), + ) + } + }.toPersistentList() + } + + fun getApprovalInProgressStateNotification( + notifications: ImmutableList, + ): ImmutableList { + val updatedNotifications = notifications + .filterNot { it is SwapNotificationUM.Info.PermissionNeeded } + .toMutableList() + + updatedNotifications.add(0, SwapNotificationUM.Error.ApprovalInProgressWarning) + + return updatedNotifications.toPersistentList() + } + + fun getConfirmationStateNotifications( + quoteModel: SwapState.QuotesLoadedState, + fromToken: CryptoCurrency, + feeCryptoCurrencyStatus: CryptoCurrencyStatus?, + selectedFeeType: FeeType, + providerName: String, + ): ImmutableList { + val warnings = buildList { + maybeAddDomainWarnings(quoteModel, feeCryptoCurrencyStatus, selectedFeeType) + maybeAddNeedReserveToCreateAccountWarning(quoteModel) + maybeAddPermissionNeededWarning(quoteModel, fromToken, providerName) + maybeAddNetworkFeeCoverageWarning(quoteModel, selectedFeeType) + maybeAddUnableCoverFeeWarning(quoteModel, fromToken) + maybeAddTransactionInProgressWarning(quoteModel) + } + return warnings.toPersistentList() + } + + private fun MutableList.maybeAddTransactionInProgressWarning( + quoteModel: SwapState.QuotesLoadedState, + ) { + if (quoteModel.permissionState is PermissionDataState.PermissionLoading) { + add(SwapNotificationUM.Error.ApprovalInProgressWarning) + } else if (quoteModel.preparedSwapConfigState.hasOutgoingTransaction) { + add( + SwapNotificationUM.Error.TransactionInProgressWarning( + currencySymbol = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.network.currencySymbol, + ), + ) + } + } + + private fun MutableList.maybeAddDomainWarnings( + quoteModel: SwapState.QuotesLoadedState, + feeCryptoCurrencyStatus: CryptoCurrencyStatus?, + selectedFeeType: FeeType, + ) { + val fromCurrencyStatus = quoteModel.fromTokenInfo.cryptoCurrencyStatus + val includeFeeInAmount = quoteModel.preparedSwapConfigState.includeFeeInAmount + val amount = quoteModel.fromTokenInfo.tokenAmount + val amountToRequest = if (includeFeeInAmount is IncludeFeeInAmount.Included) { + includeFeeInAmount.amountSubtractFee + } else { + amount + } + val fee = when (val feeState = quoteModel.txFee) { + TxFeeState.Empty -> null + is TxFeeState.MultipleFeeState -> if (feeState.normalFee.feeType == selectedFeeType) { + feeState.normalFee + } else { + feeState.priorityFee + } + is TxFeeState.SingleFeeState -> feeState.fee + } + val isCardano = BlockchainUtils.isCardano(fromCurrencyStatus.currency.network.id.value) + // blockchain specific + + addExistentialWarningNotification( + existentialDeposit = quoteModel.currencyCheck?.existentialDeposit, + feeAmount = fee?.feeValue.orZero(), + sendingAmount = amount.value, + cryptoCurrencyStatus = fromCurrencyStatus, + onReduceClick = { reduceBy, reduceByDiff, _ -> + actions.onReduceByAmount( + amount.copy(value = amount.value.minus(reduceByDiff)), + reduceBy, + ) + }, + ) + addValidateTransactionNotifications( + dustValue = quoteModel.currencyCheck?.dustValue.orZero(), + validationError = quoteModel.validationResult, + cryptoCurrency = fromCurrencyStatus.currency, + minAdaValue = quoteModel.minAdaValue, + onReduceClick = { reduceTo, _ -> + actions.onReduceToAmount(amount.copy(value = reduceTo)) + }, + ) + if (!isCardano) { + addDustWarningNotification( + dustValue = quoteModel.currencyCheck?.dustValue, + feeValue = fee?.feeValue.orZero(), + sendingAmount = amountToRequest.value, + cryptoCurrencyStatus = fromCurrencyStatus, + feeCurrencyStatus = feeCryptoCurrencyStatus, + ) + } + addReserveAmountErrorNotification( + reserveAmount = quoteModel.currencyCheck?.reserveAmount, + sendingAmount = amountToRequest.value, + cryptoCurrency = fromCurrencyStatus.currency, + isAccountFunded = false, + ) + addReduceAmountNotification( + cryptoCurrencyStatus = fromCurrencyStatus, + fromAmount = quoteModel.fromTokenInfo.tokenAmount, + onReduceByAmount = actions.onReduceByAmount, + ) + addTransactionLimitErrorNotification( + utxoLimit = quoteModel.currencyCheck?.utxoAmountLimit, + cryptoCurrency = fromCurrencyStatus.currency, + onReduceClick = { reduceTo, _ -> + actions.onReduceToAmount(amountToRequest.copy(value = reduceTo)) + }, + ) + } + + private fun MutableList.maybeAddNeedReserveToCreateAccountWarning( + quoteModel: SwapState.QuotesLoadedState, + ) { + val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value + if (status is CryptoCurrencyStatus.NoAccount) { + val amount = quoteModel.toTokenInfo.tokenAmount.value + val amountToCreateAccount = status.amountToCreateAccount + val currencyTo = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency + if (amount < amountToCreateAccount) { + add( + SwapNotificationUM.Warning.NeedReserveToCreateAccount( + status.amountToCreateAccount.parseBigDecimal(currencyTo.decimals), + currencyTo.symbol, + ), + ) + } + } + } + + private fun MutableList.maybeAddPermissionNeededWarning( + quoteModel: SwapState.QuotesLoadedState, + fromToken: CryptoCurrency, + providerName: String, + ) { + if (!quoteModel.preparedSwapConfigState.isAllowedToSpend && + quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough && + quoteModel.permissionState is PermissionDataState.PermissionReadyForRequest + ) { + add( + SwapNotificationUM.Info.PermissionNeeded( + providerName = fromToken.symbol, + fromTokenSymbol = providerName, + ), + ) + } + } + + private fun MutableList.maybeAddNetworkFeeCoverageWarning( + quoteModel: SwapState.QuotesLoadedState, + selectedFeeType: FeeType, + ) { + when (quoteModel.preparedSwapConfigState.includeFeeInAmount) { + is IncludeFeeInAmount.Included -> { + val fee = selectFeeByType(selectedFeeType, quoteModel.txFee) ?: return + if (needShowNetworkFeeCoverageWarningShow(quoteModel)) { + add( + NotificationUM.Warning.FeeCoverageNotification( + fee.feeCryptoFormattedWithNative, + fee.feeFiatFormattedWithNative, + ), + ) + } + } + else -> Unit + } + } + + private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? { + return when (txFeeState) { + TxFeeState.Empty -> null + is TxFeeState.SingleFeeState -> txFeeState.fee + is TxFeeState.MultipleFeeState -> when (feeType) { + FeeType.NORMAL -> txFeeState.normalFee + FeeType.PRIORITY -> txFeeState.priorityFee + } + } + } + + private fun MutableList.maybeAddUnableCoverFeeWarning( + quoteModel: SwapState.QuotesLoadedState, + fromToken: CryptoCurrency, + ) { + val feeEnoughState = quoteModel.preparedSwapConfigState.feeState as? SwapFeeState.NotEnough ?: return + val needShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough && + quoteModel.permissionState !is PermissionDataState.PermissionLoading && + feeEnoughState.feeCurrency != fromToken + if (needShowCoverWarning) { + add( + SwapNotificationUM.Error.UnableToCoverFeeWarning( + fromToken = fromToken, + feeCurrency = feeEnoughState.feeCurrency, + currencyName = feeEnoughState.currencyName ?: fromToken.network.name, + currencySymbol = feeEnoughState.currencySymbol ?: fromToken.network.currencySymbol, + onConfirmClick = actions.onBuyClick, + ), + ) + } + } + + private fun MutableList.addReduceAmountNotification( + cryptoCurrencyStatus: CryptoCurrencyStatus, + fromAmount: SwapAmount, + onReduceByAmount: (SwapAmount, BigDecimal) -> Unit, + ) { + val isTezos = isTezos(cryptoCurrencyStatus.currency.network.id.value) + val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + val threshold = getTezosThreshold() + val isTotalBalance = fromAmount.value >= balance && balance > threshold + if (isTezos && isTotalBalance) { + add( + SwapNotificationUM.Warning.ReduceAmount( + currencyName = cryptoCurrencyStatus.currency.name, + amount = threshold.toPlainString(), + onConfirmClick = { + val patchedAmount = fromAmount.copy( + value = fromAmount.value - threshold, + ) + onReduceByAmount(patchedAmount, threshold) + }, + ), + ) + } + } + + private fun getWarningForError( + expressDataError: ExpressDataError, + fromToken: CryptoCurrency, + onRetryClick: () -> Unit, + ): NotificationUM { + return when (expressDataError) { + is ExpressDataError.ExchangeTooSmallAmountError -> SwapNotificationUM.Error.MinimalAmountError( + expressDataError.amount.value.format { + crypto(fromToken.symbol, fromToken.decimals) + }, + ) + is ExpressDataError.ExchangeTooBigAmountError -> SwapNotificationUM.Error.MaximumAmountError( + expressDataError.amount.value.format { + crypto(fromToken.symbol, fromToken.decimals) + }, + ) + else -> SwapNotificationUM.Warning.ExpressError( + expressDataError, + onConfirmClick = onRetryClick, + ) + } + } + + private fun needShowNetworkFeeCoverageWarningShow(quoteModel: SwapState.QuotesLoadedState): Boolean { + return quoteModel.currencyCheck?.existentialDeposit == null + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt index f183c74cbd..40d3a41298 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.text.ClickableText import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign @@ -17,10 +16,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.rows.SelectorRowItem -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.stringReference +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.domain.models.ui.FeeType @@ -69,7 +65,7 @@ private fun ChooseFeeBottomSheetContent(content: ChooseFeeBottomSheetConfig) { @Composable private fun FooterBlock(readMore: TextReference, readMoreUrl: String, onReadMoreClick: (String) -> Unit) { val linkText = readMore.resolveReference() - val fullString = stringResource(R.string.common_fee_selector_footer, linkText) + val fullString = stringResourceSafe(R.string.common_fee_selector_footer, linkText) val linkTextPosition = fullString.length - linkText.length val annotatedString = buildAnnotatedString { withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt index 3663417055..e2db69b9db 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt @@ -12,13 +12,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig 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.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig @@ -43,7 +43,7 @@ fun ChooseProviderBottomSheet(config: TangemBottomSheetConfig) { private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetConfig) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( - text = stringResource(R.string.express_choose_providers_subtitle), + text = stringResourceSafe(R.string.express_choose_providers_subtitle), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, modifier = Modifier @@ -89,7 +89,7 @@ private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetC tint = TangemTheme.colors.icon.informative, ) Text( - text = stringResource(R.string.express_more_providers_soon), + text = stringResourceSafe(R.string.express_more_providers_soon), style = TangemTheme.typography.caption2, color = TangemTheme.colors.icon.informative, modifier = Modifier diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt index 9bf6689b1c..7e1c08759d 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt @@ -14,7 +14,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -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 @@ -25,6 +24,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.RectangleShimmer 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.core.ui.utils.GRAY_SCALE_ALPHA @@ -122,7 +122,7 @@ private fun ProviderContentState( Row { if (state.namePrefix == ProviderState.PrefixType.PROVIDED_BY) { Text( - text = stringResource(id = R.string.express_by_provider), + text = stringResourceSafe(id = R.string.express_by_provider), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, modifier = Modifier.padding(end = TangemTheme.dimens.spacing4), @@ -262,7 +262,7 @@ private fun ProviderLoadingState(modifier: Modifier = Modifier) { Box(modifier = modifier.fillMaxWidth()) { Column { Text( - text = stringResource(R.string.express_provider), + text = stringResourceSafe(R.string.express_provider), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.secondary, modifier = Modifier.padding(start = TangemTheme.dimens.spacing12), @@ -282,7 +282,7 @@ private fun ProviderLoadingState(modifier: Modifier = Modifier) { strokeWidth = TangemTheme.dimens.size2, ) Text( - text = stringResource(R.string.express_fetch_best_rates), + text = stringResourceSafe(R.string.express_fetch_best_rates), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), @@ -357,7 +357,7 @@ private fun BestTradeItem(modifier: Modifier = Modifier) { ), ) { 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.padding(horizontal = TangemTheme.dimens.spacing6), @@ -374,7 +374,7 @@ private fun PermissionBadgeItem(modifier: Modifier = Modifier) { ), ) { Text( - text = stringResource(id = R.string.express_provider_permission_needed), + text = stringResourceSafe(id = R.string.express_provider_permission_needed), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.tertiary, modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing6), @@ -391,7 +391,7 @@ private fun RecommendedItem(modifier: Modifier = Modifier) { ), ) { Text( - text = stringResource(id = R.string.express_provider_recommended), + text = stringResourceSafe(id = R.string.express_provider_recommended), style = TangemTheme.typography.caption1, color = TangemTheme.colors.icon.accent, modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing6), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index cc94ac10b4..73ff14265e 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -4,13 +4,16 @@ import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import com.tangem.common.ui.alerts.models.AlertDemoModeUM import com.tangem.common.ui.bottomsheet.permission.state.* +import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter -import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent import com.tangem.core.ui.extensions.* import com.tangem.core.ui.format.bigdecimal.* +import com.tangem.core.ui.format.bigdecimal.anyDecimals +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency @@ -20,21 +23,24 @@ import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter import com.tangem.feature.swap.converters.TokensDataConverter import com.tangem.feature.swap.domain.models.ExpressDataError import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.feature.swap.domain.models.domain.* +import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType +import com.tangem.feature.swap.domain.models.domain.IncludeFeeInAmount +import com.tangem.feature.swap.domain.models.domain.NetworkInfo +import com.tangem.feature.swap.domain.models.domain.SwapProvider import com.tangem.feature.swap.domain.models.ui.* import com.tangem.feature.swap.models.* import com.tangem.feature.swap.models.states.* import com.tangem.feature.swap.models.states.events.SwapEvent import com.tangem.feature.swap.presentation.R +import com.tangem.feature.swap.presentation.SwapNotificationsFactory import com.tangem.feature.swap.utils.formatToUIRepresentation -import com.tangem.feature.swap.utils.getExpressErrorMessage -import com.tangem.feature.swap.utils.getExpressErrorTitle import com.tangem.feature.swap.viewmodels.SwapProcessDataState import com.tangem.utils.Provider import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.StringsSigns.PERCENT import com.tangem.utils.StringsSigns.TILDE_SIGN import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal import java.math.RoundingMode @@ -60,6 +66,10 @@ internal class StateBuilder( appCurrencyProvider = appCurrencyProvider, ) + private val notificationsFactory by lazy(LazyThreadSafetyMode.NONE) { + SwapNotificationsFactory(actions) + } + fun createInitialLoadingState( initialCurrencyFrom: CryptoCurrency, initialCurrencyTo: CryptoCurrency?, @@ -110,6 +120,7 @@ internal class StateBuilder( providerState = ProviderState.Empty(), shouldShowMaxAmount = false, priceImpact = PriceImpact.Empty(), + isInsufficientFunds = false, ) } @@ -141,18 +152,7 @@ internal class StateBuilder( ), canSelectAnotherToken = true, ), - warnings = listOf( - SwapWarning.NoAvailableTokensToSwap( - notificationConfig = NotificationConfig( - title = resourceReference(R.string.warning_express_no_exchangeable_coins_title), - subtitle = resourceReference( - id = R.string.warning_express_no_exchangeable_coins_description, - formatArgs = wrappedList(fromToken.currency.name), - ), - iconResId = R.drawable.img_attention_20, - ), - ), - ), + notifications = notificationsFactory.getNotAvailableStateNotifications(fromToken.currency.name), fee = FeeItemState.Empty, swapButton = SwapButton( enabled = false, @@ -211,7 +211,7 @@ internal class StateBuilder( networkIconRes = getActiveIconRes(toToken.network.id.value), isBalanceHidden = isBalanceHiddenProvider(), ), - warnings = emptyList(), + notifications = persistentListOf(), fee = FeeItemState.Empty, swapButton = SwapButton(enabled = false, onClick = {}), providerState = ProviderState.Loading(), @@ -235,6 +235,7 @@ internal class StateBuilder( uiStateHolder: SwapStateHolder, quoteModel: SwapState.QuotesLoadedState, fromToken: CryptoCurrency, + feeCryptoCurrencyStatus: CryptoCurrencyStatus?, swapProvider: SwapProvider, bestRatedProviderId: String, isNeedBestRateBadge: Boolean, @@ -243,9 +244,10 @@ internal class StateBuilder( ): SwapStateHolder { if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder - val warnings = getWarningsForSuccessState( + val notifications = notificationsFactory.getConfirmationStateNotifications( quoteModel = quoteModel, fromToken = fromToken, + feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, selectedFeeType = selectedFeeType, providerName = swapProvider.name, ) @@ -304,7 +306,8 @@ internal class StateBuilder( balance = toCurrencyStatus.getFormattedAmount(isNeedSymbol = false), isBalanceHidden = isBalanceHiddenProvider(), ), - warnings = warnings, + isInsufficientFunds = isInsufficientFundsCondition(quoteModel), + notifications = notifications, permissionState = convertPermissionState( lastPermissionState = uiStateHolder.permissionState, permissionDataState = quoteModel.permissionState, @@ -314,7 +317,7 @@ internal class StateBuilder( ), fee = feeState, swapButton = SwapButton( - enabled = getSwapButtonEnabled(quoteModel), + enabled = getSwapButtonEnabled(notifications), onClick = actions.onSwapClick, ), changeCardsButtonState = if (isReverseSwapPossible) { @@ -363,281 +366,17 @@ internal class StateBuilder( ) } - @Suppress("LongMethod", "CyclomaticComplexMethod") - private fun getWarningsForSuccessState( - quoteModel: SwapState.QuotesLoadedState, - fromToken: CryptoCurrency, - selectedFeeType: FeeType, - providerName: String, - ): List { - val warnings = mutableListOf() - maybeAddDomainWarnings(quoteModel, warnings) - maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings) - maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken, providerName) - maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType) - maybeAddUnableCoverFeeWarning(quoteModel, fromToken, warnings) - maybeAddInsufficientFundsWarning(quoteModel, warnings) - maybeAddTransactionInProgressWarning(quoteModel, warnings) - return warnings - } - - private fun maybeAddTransactionInProgressWarning( - quoteModel: SwapState.QuotesLoadedState, - warnings: MutableList, - ) { - if (quoteModel.permissionState is PermissionDataState.PermissionLoading) { - warnings.add( - SwapWarning.TransactionInProgressWarning( - title = resourceReference(R.string.warning_express_approval_in_progress_title), - description = resourceReference(R.string.warning_express_approval_in_progress_message), - ), - ) - } else if (quoteModel.preparedSwapConfigState.hasOutgoingTransaction) { - warnings.add( - SwapWarning.TransactionInProgressWarning( - title = resourceReference(R.string.warning_express_active_transaction_title), - description = resourceReference( - id = R.string.warning_express_active_transaction_message, - formatArgs = wrappedList( - quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.network.currencySymbol, - ), - ), - ), - ) - } - } - - private fun maybeAddDomainWarnings(quoteModel: SwapState.QuotesLoadedState, warnings: MutableList) { - quoteModel.warnings.forEach { - when (it) { - is Warning.ExistentialDepositWarning -> { - warnings.add(createLeaveExistentialDepositWarning(quoteModel, it)) - } - is Warning.MinAmountWarning -> { - warnings.add( - SwapWarning.GeneralError( - NotificationConfig( - title = resourceReference(R.string.send_notification_invalid_amount_title), - subtitle = resourceReference( - R.string.warning_express_dust_message, - wrappedList( - it.dustValue.toPlainString(), - it.dustValue.toPlainString(), - ), - ), - iconResId = R.drawable.ic_alert_circle_24, - ), - ), - ) - } - is Warning.ReduceAmountWarning -> { - warnings.add( - SwapWarning.ReduceAmount( - notificationConfig = createReduceAmountNotificationConfig( - currencyName = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.name, - amount = it.tezosFeeThreshold.toPlainString(), - onConfirmClick = { - val fromAmount = quoteModel.fromTokenInfo.tokenAmount - val patchedAmount = fromAmount.copy( - value = fromAmount.value - it.tezosFeeThreshold, - ) - actions.onReduceAmount(patchedAmount) - }, - ), - ), - ) - } - Warning.Cardano.InsufficientBalanceToTransferCoin -> { - warnings.add(createInsufficientBalanceToTransferCoin()) - } - is Warning.Cardano.InsufficientBalanceToTransferToken -> { - warnings.add(createInsufficientBalanceToTransferToken(tokenName = it.tokenName)) - } - is Warning.Cardano.MinAdaValueCharged -> { - warnings.add(createMinAdaValueCharged(minAdaValue = it.minAdaValue, tokenName = it.tokenName)) - } - } - } - } - - private fun maybeAddNeedReserveToCreateAccountWarning( - quoteModel: SwapState.QuotesLoadedState, - warnings: MutableList, - ) { - val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value - if (status is CryptoCurrencyStatus.NoAccount) { - val amount = quoteModel.toTokenInfo.tokenAmount.value - val amountToCreateAccount = status.amountToCreateAccount - - if (amount < amountToCreateAccount) { - warnings.add( - SwapWarning.NeedReserveToCreateAccount( - notificationConfig = createActivateAccountNotificationConfig( - status.amountToCreateAccount, - quoteModel.toTokenInfo.cryptoCurrencyStatus.currency.symbol, - ), - ), - ) - } - } - } - - private fun createLeaveExistentialDepositWarning( - quoteModel: SwapState.QuotesLoadedState, - domainWarning: Warning.ExistentialDepositWarning, - ): SwapWarning { - val fromCurrency = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency - val deposit = domainWarning.existentialDeposit.format { crypto(fromCurrency).uncapped() } - - return SwapWarning.GeneralError( - NotificationConfig( - title = resourceReference(R.string.send_notification_existential_deposit_title), - subtitle = resourceReference( - R.string.send_notification_existential_deposit_text, - wrappedList(deposit), - ), - iconResId = R.drawable.ic_alert_circle_24, - buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( - text = resourceReference(R.string.common_ok), - onClick = { - actions.onLeaveExistentialDeposit( - SwapAmount( - domainWarning.minAvailableAmount, - fromCurrency.decimals, - ), - ) - }, - ), - ), - ) - } - - private fun maybeAddPermissionNeededWarning( - quoteModel: SwapState.QuotesLoadedState, - warnings: MutableList, - fromToken: CryptoCurrency, - providerName: String, - ) { - if (!quoteModel.preparedSwapConfigState.isAllowedToSpend && - quoteModel.preparedSwapConfigState.feeState is SwapFeeState.Enough && - quoteModel.permissionState is PermissionDataState.PermissionReadyForRequest - ) { - warnings.add( - SwapWarning.PermissionNeeded( - createPermissionNotificationConfig(fromToken.symbol, providerName), - ), - ) - } - } - - private fun maybeAddNetworkFeeCoverageWarning( - quoteModel: SwapState.QuotesLoadedState, - warnings: MutableList, - selectedFeeType: FeeType, - ) { - when (quoteModel.preparedSwapConfigState.includeFeeInAmount) { - is IncludeFeeInAmount.Included -> { - val fee = selectFeeByType(selectedFeeType, quoteModel.txFee) ?: return - if (needShowNetworkFeeCoverageWarningShow(quoteModel)) { - warnings.add( - SwapWarning.GeneralWarning( - createNetworkFeeCoverageNotificationConfig( - fee.feeCryptoFormattedWithNative, - fee.feeFiatFormattedWithNative, - ), - ), - ) - } - } - else -> Unit - } - } - - private fun needShowNetworkFeeCoverageWarningShow(quoteModel: SwapState.QuotesLoadedState): Boolean { - return quoteModel.warnings.none { it is Warning.ExistentialDepositWarning } - } - - private fun selectFeeByType(feeType: FeeType, txFeeState: TxFeeState): TxFee? { - return when (txFeeState) { - TxFeeState.Empty -> null - is TxFeeState.SingleFeeState -> txFeeState.fee - is TxFeeState.MultipleFeeState -> when (feeType) { - FeeType.NORMAL -> txFeeState.normalFee - FeeType.PRIORITY -> txFeeState.priorityFee - } - } - } - - private fun maybeAddUnableCoverFeeWarning( - quoteModel: SwapState.QuotesLoadedState, - fromToken: CryptoCurrency, - warnings: MutableList, - ) { - val feeEnoughState = quoteModel.preparedSwapConfigState.feeState as? SwapFeeState.NotEnough ?: return - val needShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough && - quoteModel.permissionState !is PermissionDataState.PermissionLoading && - feeEnoughState.feeCurrency != fromToken - if (needShowCoverWarning) { - warnings.add( - SwapWarning.UnableToCoverFeeWarning( - createUnableToCoverFeeNotificationConfig( - fromToken = fromToken, - feeCurrency = feeEnoughState.feeCurrency, - currencyName = feeEnoughState.currencyName ?: fromToken.network.name, - currencySymbol = feeEnoughState.currencySymbol ?: fromToken.network.currencySymbol, - ), - ), - ) - } - } - - private fun maybeAddInsufficientFundsWarning( - quoteModel: SwapState.QuotesLoadedState, - warnings: MutableList, - ) { - // check isBalanceEnough, but for dex includeFeeInAmount always Excluded - if (isInsufficientFundsCondition(quoteModel)) { - warnings.add(SwapWarning.InsufficientFunds) - } - } - private fun isInsufficientFundsCondition(quoteModel: SwapState.QuotesLoadedState): Boolean { return !quoteModel.preparedSwapConfigState.isBalanceEnough && quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included } - private fun getSwapButtonEnabled(quoteModel: SwapState.QuotesLoadedState): Boolean { - val status = quoteModel.toTokenInfo.cryptoCurrencyStatus.value - if (status is CryptoCurrencyStatus.NoAccount) { - val amount = quoteModel.toTokenInfo.tokenAmount.value - val amountToCreateAccount = status.amountToCreateAccount - - if (amount < amountToCreateAccount) { - return false - } - } - - val preparedSwapConfigState = quoteModel.preparedSwapConfigState - // check has has outgoing transaction - if (preparedSwapConfigState.hasOutgoingTransaction) return false - - // check has MinAmountWarning warning - val hasCriticalWarning = quoteModel.warnings.any { - it is Warning.MinAmountWarning || - it is Warning.Cardano.InsufficientBalanceToTransferCoin || - it is Warning.Cardano.InsufficientBalanceToTransferToken || - it is Warning.ExistentialDepositWarning - } - - if (hasCriticalWarning) return false - - return when (preparedSwapConfigState.includeFeeInAmount) { - IncludeFeeInAmount.BalanceNotEnough -> false - IncludeFeeInAmount.Excluded -> - preparedSwapConfigState.isAllowedToSpend && - preparedSwapConfigState.isBalanceEnough && - preparedSwapConfigState.feeState is SwapFeeState.Enough - is IncludeFeeInAmount.Included -> true + private fun getSwapButtonEnabled(notifications: ImmutableList): Boolean { + return notifications.none { + it is SwapNotificationUM.Error || it is NotificationUM.Error || + it is SwapNotificationUM.Warning.ExpressError || it is SwapNotificationUM.Warning.ExpressGeneralError || + it is SwapNotificationUM.Warning.NoAvailableTokensToSwap || + it is SwapNotificationUM.Warning.NeedReserveToCreateAccount } } @@ -653,15 +392,13 @@ internal class StateBuilder( ): SwapStateHolder { if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder - val warnings = mutableListOf() - warnings.add(getWarningForError(expressDataError, fromToken.cryptoCurrencyStatus.currency)) - if (includeFeeInAmount is IncludeFeeInAmount.Included && uiStateHolder.fee is FeeItemState.Content) { - val feeCoverageNotification = createNetworkFeeCoverageNotificationConfig( - uiStateHolder.fee.amountCrypto, - uiStateHolder.fee.amountFiatFormatted, - ) - warnings.add(SwapWarning.GeneralWarning(feeCoverageNotification)) - } + val notifications = notificationsFactory.getQuotesErrorStateNotifications( + expressDataError = expressDataError, + fromToken = fromToken.cryptoCurrencyStatus.currency, + feeItem = uiStateHolder.fee, + includeFeeInAmount = includeFeeInAmount, + ) + val providerState = getProviderStateForError( swapProvider = swapProvider, fromToken = fromToken.cryptoCurrencyStatus.currency, @@ -700,7 +437,7 @@ internal class StateBuilder( balance = fromToken.cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false), ), receiveCardData = receiveCardData, - warnings = warnings, + notifications = notifications, permissionState = GiveTxPermissionState.Empty, fee = FeeItemState.Empty, swapButton = SwapButton( @@ -754,57 +491,6 @@ internal class StateBuilder( } } - private fun getWarningForError(expressDataError: ExpressDataError, fromToken: CryptoCurrency): SwapWarning { - val providerErrorMessage = getExpressErrorMessage(expressDataError) - val providerErrorTitle = getExpressErrorTitle(expressDataError) - return when (expressDataError) { - is ExpressDataError.ExchangeTooSmallAmountError -> SwapWarning.GeneralError( - notificationConfig = NotificationConfig( - title = resourceReference( - id = R.string.warning_express_too_minimal_amount_title, - formatArgs = wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)), - ), - subtitle = resourceReference(R.string.warning_express_wrong_amount_description), - iconResId = R.drawable.ic_alert_circle_24, - ), - ) - is ExpressDataError.ExchangeTooBigAmountError -> SwapWarning.GeneralError( - notificationConfig = NotificationConfig( - title = resourceReference( - id = R.string.warning_express_too_maximum_amount_title, - formatArgs = wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)), - ), - subtitle = resourceReference(R.string.warning_express_wrong_amount_description), - iconResId = R.drawable.ic_alert_circle_24, - ), - ) - is ExpressDataError.ProviderDifferentAmountError -> SwapWarning.GeneralError( - notificationConfig = NotificationConfig( - title = resourceReference(id = R.string.common_error), - subtitle = resourceReference( - R.string.express_error_provider_amount_roundup, - formatArgs = wrappedList( - expressDataError.code, - expressDataError.fromProviderAmount.format { simple(decimals = expressDataError.decimals) }, - ), - ), - iconResId = R.drawable.ic_alert_circle_24, - ), - ) - else -> SwapWarning.GeneralWarning( - notificationConfig = NotificationConfig( - title = providerErrorTitle, - subtitle = providerErrorMessage, - iconResId = R.drawable.img_attention_20, - buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( - text = resourceReference(R.string.warning_button_refresh), - onClick = actions.onRetryClick, - ), - ), - ) - } - } - fun createQuotesEmptyAmountState( uiStateHolder: SwapStateHolder, emptyAmountState: SwapState.EmptyAmountState, @@ -843,7 +529,8 @@ internal class StateBuilder( balance = toTokenStatus?.getFormattedAmount(isNeedSymbol = false) ?: DASH_SIGN, isBalanceHidden = isBalanceHiddenProvider(), ), - warnings = emptyList(), + notifications = persistentListOf(), + isInsufficientFunds = false, fee = FeeItemState.Empty, swapButton = SwapButton( enabled = false, @@ -1002,19 +689,8 @@ internal class StateBuilder( fun createInitialErrorState(uiState: SwapStateHolder, code: Int, onRefreshClick: () -> Unit): SwapStateHolder { return uiState.copy( - warnings = listOf( - SwapWarning.GeneralWarning( - notificationConfig = NotificationConfig( - title = TextReference.Res(R.string.warning_express_refresh_required_title), - subtitle = TextReference.Res(R.string.express_error_code, wrappedList(code)), - iconResId = R.drawable.ic_alert_triangle_20, - buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( - text = TextReference.Res(R.string.warning_button_refresh), - onClick = onRefreshClick, - ), - ), - ), - ), + isInsufficientFunds = false, + notifications = notificationsFactory.getInitialErrorStateNotifications(code, onRefreshClick), ) } @@ -1051,17 +727,9 @@ internal class StateBuilder( } fun loadingPermissionState(uiState: SwapStateHolder): SwapStateHolder { - val warnings = uiState.warnings.filterNot { it is SwapWarning.PermissionNeeded }.toMutableList() - warnings.add( - 0, - SwapWarning.TransactionInProgressWarning( - title = resourceReference(R.string.warning_express_approval_in_progress_title), - description = resourceReference(R.string.warning_express_approval_in_progress_message), - ), - ) return uiState.copy( permissionState = GiveTxPermissionState.InProgress, - warnings = warnings, + notifications = notificationsFactory.getApprovalInProgressStateNotification(uiState.notifications), ) } @@ -1211,17 +879,14 @@ internal class StateBuilder( fun clearAlert(uiState: SwapStateHolder): SwapStateHolder = uiState.copy(event = consumedEvent()) - fun addWarning(uiState: SwapStateHolder, message: TextReference?, onClick: () -> Unit): SwapStateHolder { - val renewWarnings = uiState.warnings.toMutableList() - renewWarnings.add( - SwapWarning.GenericWarning( + fun addNotification(uiState: SwapStateHolder, message: TextReference?, onClick: () -> Unit): SwapStateHolder { + return uiState.copy( + notifications = notificationsFactory.getGeneralErrorStateNotifications( + notifications = uiState.notifications, message = message, onClick = onClick, ), ) - return uiState.copy( - warnings = renewWarnings, - ) } private fun convertPermissionState( @@ -1272,17 +937,6 @@ internal class StateBuilder( } } - fun showWebViewBottomSheet(uiState: SwapStateHolder, url: String, onDismiss: () -> Unit): SwapStateHolder { - val config = WebViewBottomSheetConfig(url = url) - return uiState.copy( - bottomSheetConfig = TangemBottomSheetConfig( - isShow = true, - onDismissRequest = onDismiss, - content = config, - ), - ) - } - fun showPermissionBottomSheet(uiState: SwapStateHolder, onDismiss: () -> Unit): SwapStateHolder { val permissionState = uiState.permissionState if (permissionState is GiveTxPermissionState.ReadyForRequest) { @@ -1368,21 +1022,6 @@ internal class StateBuilder( } } - fun updateSelectedProvider(uiState: SwapStateHolder, selectedProviderId: String): SwapStateHolder { - val config = uiState.bottomSheetConfig?.content as? ChooseProviderBottomSheetConfig - return if (config != null) { - uiState.copy( - bottomSheetConfig = uiState.bottomSheetConfig.copy( - content = config.copy( - selectedProviderId = selectedProviderId, - ), - ), - ) - } else { - uiState - } - } - fun showSelectFeeBottomSheet( uiState: SwapStateHolder, selectedFee: FeeType, @@ -1420,21 +1059,6 @@ internal class StateBuilder( } } - fun updateSelectedFeeBottomSheet(uiState: SwapStateHolder, selectedFee: FeeType): SwapStateHolder { - val config = uiState.bottomSheetConfig?.content as? ChooseFeeBottomSheetConfig - return if (config != null) { - uiState.copy( - bottomSheetConfig = uiState.bottomSheetConfig.copy( - content = config.copy( - selectedFee = selectedFee, - ), - ), - ) - } else { - uiState - } - } - private fun TxFeeState.MultipleFeeState.toFeeItemState(): ImmutableList { return listOf( FeeItemState.Content( @@ -1483,122 +1107,6 @@ internal class StateBuilder( } } - // region warnings - private fun createPermissionNotificationConfig(fromTokenSymbol: String, providerName: String): NotificationConfig { - return NotificationConfig( - title = resourceReference(R.string.express_provider_permission_needed), - subtitle = resourceReference( - id = R.string.give_permission_swap_subtitle, - formatArgs = wrappedList(providerName, fromTokenSymbol), - ), - iconResId = R.drawable.ic_locked_24, - ) - } - - private fun createActivateAccountNotificationConfig(amount: BigDecimal, token: String): NotificationConfig { - return NotificationConfig( - title = resourceReference( - id = R.string.send_notification_invalid_reserve_amount_title, - formatArgs = wrappedList("$amount $token"), - ), - subtitle = resourceReference(R.string.send_notification_invalid_reserve_amount_text), - iconResId = R.drawable.img_attention_20, - ) - } - - private fun createReduceAmountNotificationConfig( - currencyName: String, - amount: String, - onConfirmClick: () -> Unit, - ): NotificationConfig { - return NotificationConfig( - title = resourceReference(R.string.send_notification_high_fee_title), - subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(currencyName, amount)), - iconResId = R.drawable.img_attention_20, - buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( - text = resourceReference(R.string.xtz_withdrawal_message_reduce, wrappedList(amount)), - onClick = onConfirmClick, - ), - ) - } - - private fun createUnableToCoverFeeNotificationConfig( - fromToken: CryptoCurrency, - feeCurrency: CryptoCurrency?, - currencyName: String, - currencySymbol: String, - ): NotificationConfig { - val buttonState = feeCurrency?.let { - NotificationConfig.ButtonsState.SecondaryButtonConfig( - text = resourceReference(R.string.common_buy_currency, wrappedList(currencySymbol)), - onClick = { actions.onBuyClick(it) }, - ) - } - return NotificationConfig( - title = resourceReference( - R.string.warning_express_not_enough_fee_for_token_tx_title, - wrappedList(fromToken.network.name), - ), - subtitle = resourceReference( - R.string.warning_express_not_enough_fee_for_token_tx_description, - wrappedList(currencyName, currencySymbol), - ), - iconResId = fromToken.networkIconResId, - buttonsState = buttonState, - ) - } - - private fun createNetworkFeeCoverageNotificationConfig( - cryptoAmount: String, - fiatAmount: String, - ): NotificationConfig { - return NotificationConfig( - title = resourceReference(R.string.send_network_fee_warning_title), - subtitle = resourceReference( - R.string.common_network_fee_warning_content, - wrappedList(cryptoAmount, fiatAmount), - ), - iconResId = R.drawable.img_attention_20, - ) - } - - private fun createMinAdaValueCharged(minAdaValue: String, tokenName: String): SwapWarning { - return SwapWarning.Cardano.MinAdaValueCharged( - NotificationConfig( - title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title), - subtitle = resourceReference( - id = R.string.cardano_coin_will_be_send_with_token_description, - formatArgs = wrappedList(minAdaValue, tokenName), - ), - iconResId = R.drawable.img_attention_20, - ), - ) - } - - private fun createInsufficientBalanceToTransferCoin(): SwapWarning { - return SwapWarning.Cardano.InsufficientBalanceToTransferCoin( - NotificationConfig( - title = resourceReference(id = R.string.cardano_max_amount_has_token_title), - subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description), - iconResId = R.drawable.ic_alert_circle_24, - ), - ) - } - - private fun createInsufficientBalanceToTransferToken(tokenName: String): SwapWarning { - return SwapWarning.Cardano.InsufficientBalanceToTransferToken( - NotificationConfig( - title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title), - subtitle = resourceReference( - id = R.string.cardano_insufficient_balance_to_send_token_description, - formatArgs = wrappedList(tokenName), - ), - iconResId = R.drawable.ic_alert_circle_24, - ), - ) - } - // end region - private fun getShortAddressValue(fullAddress: String): String { check(fullAddress.length > ADDRESS_MIN_LENGTH) { "Invalid address" } val firstAddressPart = fullAddress.substring(startIndex = 0, endIndex = ADDRESS_FIRST_PART_LENGTH) @@ -1712,14 +1220,6 @@ internal class StateBuilder( return amount.format { crypto(symbol, currency.decimals) } } - @Suppress("UnusedPrivateMember") - private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String { - val fiatAmount = value.fiatAmount ?: return DASH_SIGN - val appCurrency = appCurrencyProvider() - - return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol) - } - private fun getFormattedFiatAmount(amount: BigDecimal?): String { val appCurrency = appCurrencyProvider() @@ -1735,10 +1235,6 @@ internal class StateBuilder( return this.divide(to, min(rateDecimals, MAX_DECIMALS_TO_SHOW), RoundingMode.HALF_UP) } - private fun toBigDecimalOrNull(text: String): BigDecimal? { - return text.replace(",", ".").toBigDecimalOrNull() - } - private fun getLocaleName(): String { return if (Locale.getDefault().language == "ru") { RU_LOCALE diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapEventEffect.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapEventEffect.kt index 7d5355c7a7..343dbdad65 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapEventEffect.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapEventEffect.kt @@ -3,7 +3,6 @@ package com.tangem.feature.swap.ui import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource import com.tangem.common.ui.alerts.models.AlertUM import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButtonUM @@ -11,6 +10,7 @@ import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.shareText +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.feature.swap.models.states.events.SwapEvent import com.tangem.feature.swap.presentation.R @@ -53,7 +53,7 @@ internal fun SwapAlert(state: AlertUM, onDismiss: () -> Unit) { }, ) val dismissButton = DialogButtonUM( - title = stringResource(id = R.string.common_cancel), + title = stringResourceSafe(id = R.string.common_cancel), onClick = onDismiss, ) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt index 7379f7ad2a..e474858740 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt @@ -6,16 +6,15 @@ import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import com.tangem.common.ui.bottomsheet.permission.GiveTxPermissionBottomSheet import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.WindowInsetsZero import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.states.ChooseFeeBottomSheetConfig import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig -import com.tangem.feature.swap.models.states.WebViewBottomSheetConfig import com.tangem.feature.swap.presentation.R @Composable @@ -26,7 +25,7 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) { modifier = Modifier.systemBarsPadding(), topBar = { AppBarWithBackButton( - text = stringResource(R.string.common_swap), + text = stringResourceSafe(R.string.common_swap), onBackClick = stateHolder.onBackClicked, iconRes = R.drawable.ic_close_24, ) @@ -40,20 +39,13 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) { modifier = Modifier.padding(scaffoldPaddings), ) - stateHolder.bottomSheetConfig?.let { config -> + if (stateHolder.bottomSheetConfig != null) { + val config = stateHolder.bottomSheetConfig + when (config.content) { - is GiveTxPermissionBottomSheetConfig -> { - GiveTxPermissionBottomSheet(config = config) - } - is ChooseProviderBottomSheetConfig -> { - ChooseProviderBottomSheet(config = config) - } - is ChooseFeeBottomSheetConfig -> { - ChooseFeeBottomSheet(config = config) - } - is WebViewBottomSheetConfig -> { - WebViewBottomSheet(config = config) - } + is GiveTxPermissionBottomSheetConfig -> GiveTxPermissionBottomSheet(config = config) + is ChooseProviderBottomSheetConfig -> ChooseProviderBottomSheet(config = config) + is ChooseFeeBottomSheetConfig -> ChooseFeeBottomSheet(config = config) } } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 54d6940303..8fb11f33fa 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -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.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -24,13 +23,10 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.constraintlayout.compose.ConstraintLayout import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState +import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.* import com.tangem.core.ui.components.notifications.Notification -import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.getActiveIconResByCoinId -import com.tangem.core.ui.extensions.orMaskWithStars -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.domain.models.ui.FeeType @@ -38,7 +34,9 @@ import com.tangem.feature.swap.domain.models.ui.PriceImpact import com.tangem.feature.swap.models.* import com.tangem.feature.swap.models.states.FeeItemState import com.tangem.feature.swap.models.states.ProviderState +import com.tangem.feature.swap.models.states.SwapNotificationUM import com.tangem.feature.swap.presentation.R +import kotlinx.collections.immutable.persistentListOf @Suppress("LongMethod") @Composable @@ -69,7 +67,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi FeeItemBlock(state = state.fee) - if (state.warnings.isNotEmpty()) SwapWarnings(warnings = state.warnings) + if (state.notifications.isNotEmpty()) SwapNotifications(notifications = state.notifications) MainButton(state = state, onPermissionWarningClick = state.onShowPermissionBottomSheet) @@ -84,7 +82,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi if (state.shouldShowMaxAmount && keyboard is Keyboard.Opened) { Text( - text = stringResource(id = R.string.send_max_amount_label), + text = stringResourceSafe(id = R.string.send_max_amount_label), style = TangemTheme.typography.button, color = TangemTheme.colors.text.primary1, modifier = Modifier @@ -206,7 +204,7 @@ private fun getAnnotatedStringForLegalsWithClick( return if (tos != null && policy != null) { val tosTitle = tos.title.resolveReference() val policyTitle = policy.title.resolveReference() - val fullString = stringResource(id = R.string.express_legal_two_placeholders, tosTitle, policyTitle) + val fullString = stringResourceSafe(id = R.string.express_legal_two_placeholders, tosTitle, policyTitle) val tosIndex = fullString.indexOf(tosTitle) val policyIndex = fullString.indexOf(policyTitle) val string = buildAnnotatedString { @@ -238,7 +236,7 @@ private fun getAnnotatedStringForLegalsWithClick( val legal = requireNotNull(tos ?: policy) { "tos or policy must not be null" } val legalTitle = legal.title .resolveReference() - val fullString = stringResource(id = R.string.express_legal_one_placeholder, legal) + val fullString = stringResourceSafe(id = R.string.express_legal_one_placeholder, legal) val legalIndex = fullString.indexOf(legalTitle) val string = buildAnnotatedString { withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) { @@ -302,69 +300,27 @@ private fun SwapButton(state: SwapStateHolder, modifier: Modifier = Modifier) { @Suppress("LongMethod", "CyclomaticComplexMethod") @Composable -private fun SwapWarnings(warnings: List) { +private fun SwapNotifications(notifications: List) { Column( modifier = Modifier .background(color = TangemTheme.colors.background.secondary) .fillMaxWidth(), ) { - warnings.forEach { warning -> - when (warning) { - is SwapWarning.PermissionNeeded -> { - Notification( - config = warning.notificationConfig, - ) - } - is SwapWarning.GenericWarning -> { - val message = warning.message?.resolveReference() - ?: stringResource(id = R.string.common_unknown_error) + notifications.forEach { notification -> + when (notification) { + is SwapNotificationUM.Error.GenericError -> { RefreshableWarningCard( - title = stringResource(id = R.string.common_warning), - description = message, - onClick = warning.onClick, + title = notification.title.resolveReference(), + description = notification.config.subtitle.resolveReference(), + onClick = notification.onConfirmClick, ) } - is SwapWarning.NoAvailableTokensToSwap -> { - Notification( - config = warning.notificationConfig, - ) - } - is SwapWarning.GeneralError -> { - Notification( - config = warning.notificationConfig, - iconTint = TangemTheme.colors.icon.warning, - ) - } - is SwapWarning.UnableToCoverFeeWarning -> { - Notification( - config = warning.notificationConfig, - ) - } - is SwapWarning.GeneralWarning -> { - Notification( - config = warning.notificationConfig, - ) - } - is SwapWarning.GeneralInformational -> { - Notification( - config = warning.notificationConfig, - iconTint = TangemTheme.colors.icon.accent, - ) - } - is SwapWarning.NeedReserveToCreateAccount -> { - Notification( - config = warning.notificationConfig, - ) - } - is SwapWarning.ReduceAmount -> { - Notification( - config = warning.notificationConfig, - ) - } - is SwapWarning.TransactionInProgressWarning -> { + is SwapNotificationUM.Error.ApprovalInProgressWarning, + is SwapNotificationUM.Error.TransactionInProgressWarning, + -> { CardWithIcon( - title = warning.title.resolveReference(), - description = warning.description.resolveReference(), + title = notification.config.title?.resolveReference().orEmpty(), + description = notification.config.subtitle.resolveReference(), icon = { CircularProgressIndicator( modifier = Modifier @@ -375,8 +331,20 @@ private fun SwapWarnings(warnings: List) { }, ) } - is SwapWarning.Cardano -> Notification(config = warning.notificationConfig) - SwapWarning.InsufficientFunds -> Unit + else -> { + Notification( + config = notification.config, + iconTint = when (notification) { + is SwapNotificationUM.Error.UnableToCoverFeeWarning, + is NotificationUM.Error.TokenExceedsBalance, + is NotificationUM.Error.ExceedsBalance, + is NotificationUM.Info, + is NotificationUM.Warning, + -> null + is NotificationUM.Error -> TangemTheme.colors.icon.warning + }, + ) + } } SpacerH8() } @@ -387,18 +355,18 @@ private fun SwapWarnings(warnings: List) { private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> Unit) { // order is important when { - state.warnings.any { it is SwapWarning.InsufficientFunds } -> { + state.isInsufficientFunds -> { PrimaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.swapping_insufficient_funds), + text = stringResourceSafe(id = R.string.swapping_insufficient_funds), enabled = false, onClick = state.swapButton.onClick, ) } - state.warnings.any { it is SwapWarning.PermissionNeeded } -> { + state.notifications.any { it is SwapNotificationUM.Info.PermissionNeeded } -> { PrimaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.give_permission_title), + text = stringResourceSafe(id = R.string.give_permission_title), enabled = true, onClick = onPermissionWarningClick, ) @@ -406,7 +374,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U else -> { PrimaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.swapping_swap_action), + text = stringResourceSafe(id = R.string.swapping_swap_action), iconResId = R.drawable.ic_tangem_24, enabled = state.swapButton.enabled, onClick = state.swapButton.onClick, @@ -459,21 +427,12 @@ private val state = SwapStateHolder( isClickable = true, onClick = {}, ), - warnings = listOf( - SwapWarning.PermissionNeeded( - notificationConfig = NotificationConfig( - title = stringReference("Give Permission"), - subtitle = stringReference("To continue swapping you need to give permission to Tangem"), - iconResId = R.drawable.ic_locked_24, - ), - ), - SwapWarning.NoAvailableTokensToSwap( - notificationConfig = NotificationConfig( - title = stringReference("No tokens"), - subtitle = stringReference("Swap tokens not available"), - iconResId = R.drawable.img_attention_20, - ), + notifications = persistentListOf( + SwapNotificationUM.Info.PermissionNeeded( + providerName = "Provider", + fromTokenSymbol = "POL", ), + SwapNotificationUM.Warning.NoAvailableTokensToSwap("POLYGON"), ), swapButton = SwapButton(enabled = true, onClick = {}), onRefresh = {}, @@ -484,6 +443,7 @@ private val state = SwapStateHolder( providerState = ProviderState.Loading(), priceImpact = PriceImpact.Empty(), shouldShowMaxAmount = true, + isInsufficientFunds = false, tosState = TosState( tosLink = LegalState( title = stringReference("Terms of Use"), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt index a0d643fffd..c06a97f04e 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter 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.SpacerH12 @@ -27,6 +26,7 @@ import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.extensions.orMaskWithStars 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.feature.swap.models.SwapSelectTokenStateHolder @@ -60,12 +60,12 @@ fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit) }, topBar = { ExpandableSearchView( - title = stringResource(R.string.swapping_token_list_title), + title = stringResourceSafe(R.string.swapping_token_list_title), onBackClick = onBack, - placeholderSearchText = stringResource(id = R.string.common_search_tokens), + placeholderSearchText = stringResourceSafe(id = R.string.common_search_tokens), onSearchChange = state.onSearchEntered, onSearchDisplayClose = { state.onSearchEntered("") }, - subtitle = stringResource(id = R.string.express_exchange_token_list_subtitle), + subtitle = stringResourceSafe(id = R.string.express_exchange_token_list_subtitle), ) }, ) @@ -92,7 +92,7 @@ private fun EmptyTokensList(modifier: Modifier = Modifier) { .padding(top = TangemTheme.dimens.spacing16) .padding(horizontal = TangemTheme.dimens.spacing30) .align(Alignment.CenterHorizontally), - text = stringResource(id = R.string.exchange_tokens_empty_tokens), + text = stringResourceSafe(id = R.string.exchange_tokens_empty_tokens), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, textAlign = TextAlign.Center, @@ -113,7 +113,7 @@ private fun TokensNotFound(modifier: Modifier = Modifier) { .padding(top = TangemTheme.dimens.spacing32) .padding(horizontal = TangemTheme.dimens.spacing30) .align(Alignment.TopCenter), - text = stringResource(id = R.string.express_token_list_empty_search), + text = stringResourceSafe(id = R.string.express_token_list_empty_search), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.tertiary, textAlign = TextAlign.Center, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt index b5c056c643..c1cafdc166 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.* import com.tangem.core.ui.components.appbar.AppBarWithBackButton @@ -19,6 +18,7 @@ import com.tangem.core.ui.components.inputrow.InputRowImage import com.tangem.core.ui.components.transactions.TransactionDoneTitle import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -135,7 +135,7 @@ private fun SwapSuccessScreenButtons( if (txUrl.isNotBlank()) { Row { 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), @@ -143,7 +143,7 @@ private fun SwapSuccessScreenButtons( if (showStatusButton) { SpacerW12() SecondaryButtonIconStart( - text = stringResource(id = R.string.express_cex_status_button_title), + text = stringResourceSafe(id = R.string.express_cex_status_button_title), iconResId = R.drawable.ic_arrow_top_right_24, onClick = onStatusClick, modifier = Modifier.weight(1f), @@ -153,7 +153,7 @@ private fun SwapSuccessScreenButtons( SpacerH12() } PrimaryButton( - text = stringResource(id = textRes), + text = stringResourceSafe(id = textRes), enabled = true, onClick = onDoneClick, modifier = Modifier.fillMaxWidth(), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index e6866c7ba9..512577aaaa 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -38,6 +37,7 @@ import coil.request.ImageRequest import com.tangem.core.ui.R import com.tangem.core.ui.components.* import com.tangem.core.ui.extensions.resolveReference +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.ImageBackgroundContrastChecker @@ -74,7 +74,7 @@ fun TransactionCard( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.Start, ) { - Header(balance = stringResource(R.string.common_balance, balance), type = type) + Header(balance = stringResourceSafe(R.string.common_balance, balance), type = type) Content( type = type, @@ -134,7 +134,7 @@ fun TransactionCardEmpty( horizontalAlignment = Alignment.Start, ) { Header( - balance = stringResource(id = R.string.swapping_token_not_available), + balance = stringResourceSafe(id = R.string.swapping_token_not_available), type = type, ) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/WebViewBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/WebViewBottomSheet.kt deleted file mode 100644 index 00a27d8a22..0000000000 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/WebViewBottomSheet.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.tangem.feature.swap.ui - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.tooling.preview.Preview -import com.google.accompanist.web.WebView -import com.google.accompanist.web.rememberWebViewState -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.feature.swap.models.states.WebViewBottomSheetConfig - -@Composable -fun WebViewBottomSheet(config: TangemBottomSheetConfig) { - TangemBottomSheet( - config = config, - containerColor = TangemTheme.colors.background.tertiary, - ) { content: WebViewBottomSheetConfig -> - WebViewBottomSheetContent( - WebViewBottomSheetConfig(url = content.url), - ) - } -} - -@Composable -private fun WebViewBottomSheetContent(content: WebViewBottomSheetConfig) { - val state = rememberWebViewState(content.url) - val isInPreviewMode = LocalInspectionMode.current - Column( - modifier = Modifier - .fillMaxHeight() - .verticalScroll(rememberScrollState()), - ) { - WebView( - state = state, - modifier = Modifier - .background(TangemTheme.colors.background.secondary), - captureBackPresses = false, - onCreated = { - if (!isInPreviewMode) { - it.settings.apply { - javaScriptEnabled = false - allowFileAccess = false - } - } - }, - ) - } -} - -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_WebViewBottomSheetContent() { - TangemThemePreview { - WebViewBottomSheetContent( - content = WebViewBottomSheetConfig( - url = "https://tangem.com/en/", - ), - ) - } -} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt index b35cb05405..3d53f18f52 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapProcessDataState.kt @@ -7,13 +7,16 @@ import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData import com.tangem.feature.swap.domain.models.ui.SwapState import com.tangem.feature.swap.domain.models.ui.TokensDataStateExpress import com.tangem.feature.swap.domain.models.ui.TxFee +import java.math.BigDecimal data class SwapProcessDataState( // Initial network id val fromCryptoCurrency: CryptoCurrencyStatus? = null, val toCryptoCurrency: CryptoCurrencyStatus? = null, + val feePaidCryptoCurrency: CryptoCurrencyStatus? = null, // Amount from input val amount: String? = null, + val reduceBalanceBy: BigDecimal = BigDecimal.ZERO, val approveDataModel: RequestApproveStateData? = null, val swapDataModel: SwapDataModel? = null, val selectedFee: TxFee? = null, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 431017f917..bdffaf4f6b 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -25,10 +25,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType -import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase -import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase -import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase -import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase +import com.tangem.domain.tokens.* import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.Network @@ -44,8 +41,8 @@ import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.ui.* import com.tangem.feature.swap.models.SwapStateHolder -import com.tangem.feature.swap.models.SwapWarning import com.tangem.feature.swap.models.UiActions +import com.tangem.feature.swap.models.states.SwapNotificationUM import com.tangem.feature.swap.presentation.R import com.tangem.feature.swap.router.SwapNavScreen import com.tangem.feature.swap.router.SwapRouter @@ -81,6 +78,7 @@ internal class SwapViewModel @Inject constructor( private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase, private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, + private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val getCardInfoUseCase: GetCardInfoUseCase, private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase, @@ -142,6 +140,7 @@ internal class SwapViewModel @Inject constructor( // shows currency order (direct - swap initial to selected, reversed = selected to initial) private var isOrderReversed = false private val lastAmount = mutableStateOf(INITIAL_AMOUNT) + private val lastReducedBalanceBy = mutableStateOf(BigDecimal.ZERO) private var swapRouter: SwapRouter by Delegates.notNull() private val isUserResolvableError: (SwapState) -> Boolean = { @@ -302,6 +301,7 @@ internal class SwapViewModel @Inject constructor( fromToken = fromCurrencyStatus, toToken = toCurrencyStatus, amount = lastAmount.value, + reduceBalanceBy = lastReducedBalanceBy.value, toProvidersList = findSwapProviders(fromCurrencyStatus, toCurrencyStatus), ) } @@ -319,6 +319,7 @@ internal class SwapViewModel @Inject constructor( fromToken: CryptoCurrencyStatus, toToken: CryptoCurrencyStatus, amount: String, + reduceBalanceBy: BigDecimal, toProvidersList: List, isSilent: Boolean = false, ) { @@ -337,6 +338,7 @@ internal class SwapViewModel @Inject constructor( fromToken = fromToken, toToken = toToken, amount = amount, + reduceBalanceBy = reduceBalanceBy, toProvidersList = toProvidersList, ), ) @@ -352,6 +354,7 @@ internal class SwapViewModel @Inject constructor( toToken = toCurrency, amount = amount, isSilent = isSilent, + reduceBalanceBy = dataState.reduceBalanceBy, toProvidersList = findSwapProviders(fromCurrency, toCurrency), ) } @@ -361,6 +364,7 @@ internal class SwapViewModel @Inject constructor( fromToken: CryptoCurrencyStatus, toToken: CryptoCurrencyStatus, amount: String, + reduceBalanceBy: BigDecimal, toProvidersList: List, ): PeriodicTask> { return PeriodicTask( @@ -370,6 +374,7 @@ internal class SwapViewModel @Inject constructor( runCatching(dispatchers.io) { dataState = dataState.copy( amount = amount, + reduceBalanceBy = reduceBalanceBy, swapDataModel = null, approveDataModel = null, ) @@ -378,6 +383,7 @@ internal class SwapViewModel @Inject constructor( toToken = toToken, providers = toProvidersList, amountToSwap = amount, + reduceBalanceBy = reduceBalanceBy, selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL, ) } @@ -400,7 +406,7 @@ internal class SwapViewModel @Inject constructor( }, onError = { Timber.e("Error when loading quotes: $it") - uiState = stateBuilder.addWarning(uiState, null) { startLoadingQuotesFromLastState() } + uiState = stateBuilder.addNotification(uiState, null) { startLoadingQuotesFromLastState() } }, ) } @@ -415,13 +421,14 @@ internal class SwapViewModel @Inject constructor( uiStateHolder = uiState, quoteModel = state, fromToken = fromToken.currency, + feeCryptoCurrencyStatus = dataState.feePaidCryptoCurrency, swapProvider = provider, bestRatedProviderId = bestRatedProviderId, isNeedBestRateBadge = dataState.lastLoadedSwapStates.consideredProvidersStates().size > 1, selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL, isReverseSwapPossible = isReverseSwapPossible(), ) - if (uiState.warnings.any { it is SwapWarning.UnableToCoverFeeWarning }) { + if (uiState.notifications.any { it is SwapNotificationUM.Error.UnableToCoverFeeWarning }) { analyticsEventHandler.send( SwapEvents.NoticeNotEnoughFee( token = initialCurrencyFrom.symbol, @@ -814,6 +821,7 @@ internal class SwapViewModel @Inject constructor( fromToken = fromToken, toToken = toToken, amount = lastAmount.value, + reduceBalanceBy = lastReducedBalanceBy.value, toProvidersList = findSwapProviders(fromToken, toToken), ) swapRouter.openScreen(SwapNavScreen.Main) @@ -838,6 +846,13 @@ internal class SwapViewModel @Inject constructor( .onEach { Timber.d("${coin.id} balance is ${it.value.amount}") + dataState = dataState.copy( + feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase( + userWalletId = userWalletId, + cryptoCurrencyStatus = it, + ).getOrNull() ?: it, + ) + uiState = if (isFromCurrency) { dataState = dataState.copy(fromCryptoCurrency = it) stateBuilder.updateSendCurrencyBalance(uiState, it) @@ -876,6 +891,7 @@ internal class SwapViewModel @Inject constructor( ).getOrNull() val decimals = newFromToken.currency.decimals lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value) + lastReducedBalanceBy.value = BigDecimal.ZERO uiState = stateBuilder.updateSwapAmount( uiState = uiState, amountFormatted = inputNumberFormatter.formatWithThousands(lastAmount.value, decimals), @@ -887,13 +903,18 @@ internal class SwapViewModel @Inject constructor( fromToken = newFromToken, toToken = newToToken, amount = lastAmount.value, + reduceBalanceBy = lastReducedBalanceBy.value, toProvidersList = findSwapProviders(newFromToken, newToToken), ) } } } - private fun onAmountChanged(value: String) { + private fun onAmountChanged( + value: String, + forceQuotesUpdate: Boolean = false, + reduceBalanceBy: BigDecimal = BigDecimal.ZERO, + ) { viewModelScope.launch { val fromToken = dataState.fromCryptoCurrency val toToken = dataState.toCryptoCurrency @@ -905,6 +926,7 @@ internal class SwapViewModel @Inject constructor( fromToken, ).getOrNull() lastAmount.value = cutValue + lastReducedBalanceBy.value = reduceBalanceBy uiState = stateBuilder.updateSwapAmount( uiState = uiState, amountFormatted = inputNumberFormatter.formatWithThousands(cutValue, decimals), @@ -918,11 +940,12 @@ internal class SwapViewModel @Inject constructor( isAmountChangedByUser = true } - amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) { + amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY, forceUpdate = forceQuotesUpdate) { startLoadingQuotes( fromToken = fromToken, toToken = toToken, amount = lastAmount.value, + reduceBalanceBy = lastReducedBalanceBy.value, toProvidersList = findSwapProviders(fromToken, toToken), ) } @@ -938,12 +961,12 @@ internal class SwapViewModel @Inject constructor( } } - private fun onReduceAmountClicked(newAmount: SwapAmount) { - onAmountChanged(newAmount.formatToUIRepresentation()) - } - - private fun onLeaveExistentialDepositClicked(newAmount: SwapAmount) { - onAmountChanged(newAmount.formatToUIRepresentation()) + private fun onReduceAmountClicked(newAmount: SwapAmount, reduceBalanceBy: BigDecimal = BigDecimal.ZERO) { + onAmountChanged( + value = newAmount.formatToUIRepresentation(), + forceQuotesUpdate = true, + reduceBalanceBy = reduceBalanceBy, + ) } private fun onAmountSelected(selected: Boolean) { @@ -1004,8 +1027,8 @@ internal class SwapViewModel @Inject constructor( onSearchEntered("") }, onMaxAmountSelected = ::onMaxAmountClicked, - onReduceAmount = ::onReduceAmountClicked, - onLeaveExistentialDeposit = ::onLeaveExistentialDepositClicked, + onReduceToAmount = ::onReduceAmountClicked, + onReduceByAmount = ::onReduceAmountClicked, openPermissionBottomSheet = { singleTaskScheduler.cancelTask() analyticsEventHandler.send(SwapEvents.ButtonGivePermissionClicked) @@ -1044,6 +1067,7 @@ internal class SwapViewModel @Inject constructor( selectedFee = it.feeType, fromToken = fromToken, amountToSwap = amountToSwap, + reduceBalanceBy = dataState.reduceBalanceBy, ) setupLoadedState(selectedProvider, updatedState, fromToken) } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt index 1cc0845d8c..adadf6886f 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt @@ -8,10 +8,10 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.apptheme.model.AppThemeMode @@ -27,7 +27,7 @@ internal fun TesterActionsScreen(state: TesterActionsContentState, modifier: Mod ) { stickyHeader { AppBarWithBackButton( - text = stringResource(R.string.tester_actions), + text = stringResourceSafe(R.string.tester_actions), onBackClick = state.onBackClick, ) } @@ -36,7 +36,7 @@ internal fun TesterActionsScreen(state: TesterActionsContentState, modifier: Mod { (state.hideAllCurrenciesConfig as? HideAllCurrenciesConfig.Clickable)?.onClick?.invoke() ?: Unit } } TesterActionItem( - name = stringResource(R.string.hide_all_currencies), + name = stringResourceSafe(R.string.hide_all_currencies), progress = state.hideAllCurrenciesConfig is HideAllCurrenciesConfig.Progress, onClick = onClick, ) @@ -45,7 +45,7 @@ internal fun TesterActionsScreen(state: TesterActionsContentState, modifier: Mod val config = state.toggleAppThemeConfig TesterActionItem( - name = stringResource(id = R.string.toggle_app_theme, config.currentAppTheme.name), + name = stringResourceSafe(id = R.string.toggle_app_theme, config.currentAppTheme.name), onClick = config.onClick, ) } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/environments/ui/EnvironmentTogglesScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/environments/ui/EnvironmentTogglesScreen.kt index 10a459c2f3..06ad56fc8e 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/environments/ui/EnvironmentTogglesScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/environments/ui/EnvironmentTogglesScreen.kt @@ -16,11 +16,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.datasource.api.common.config.ApiConfig @@ -45,7 +45,7 @@ internal fun EnvironmentTogglesScreen(uiModel: EnvironmentTogglesScreenUM) { stickyHeader { AppBarWithBackButton( onBackClick = uiModel.onBackClick, - text = stringResource(id = uiModel.title), + text = stringResourceSafe(id = uiModel.title), ) } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt index 6fd2e7e7e9..d03303bda6 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -20,6 +19,7 @@ import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -68,7 +68,7 @@ internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) { item { PrimaryButton( - text = stringResource(id = R.string.restart_app), + text = stringResourceSafe(id = R.string.restart_app), onClick = state.onRestartAppClick, modifier = Modifier .fillMaxWidth() @@ -82,7 +82,7 @@ internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) { private fun TopAppBar(state: FeatureTogglesContentState.TopBarState, onBackClick: () -> Unit) { AppBarWithBackButtonAndIcon( onBackClick = onBackClick, - text = stringResource(id = R.string.feature_toggles), + text = stringResourceSafe(id = R.string.feature_toggles), iconRes = if (state is FeatureTogglesContentState.TopBarState.CustomSetup) { R.drawable.ic_refresh_24 } else { diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/ui/TesterMenuScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/ui/TesterMenuScreen.kt index dd31fe2f08..6e4b42119a 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/ui/TesterMenuScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/menu/ui/TesterMenuScreen.kt @@ -8,10 +8,10 @@ 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 com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.tester.impl.R @@ -33,7 +33,7 @@ internal fun TesterMenuScreen(state: TesterMenuContentState) { ) { AppBarWithBackButton( onBackClick = state.onBackClick, - text = stringResource(id = R.string.tester_menu), + text = stringResourceSafe(id = R.string.tester_menu), ) Column( @@ -46,26 +46,26 @@ internal fun TesterMenuScreen(state: TesterMenuContentState) { verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), ) { PrimaryButton( - text = stringResource(R.string.feature_toggles), + text = stringResourceSafe(R.string.feature_toggles), onClick = state.onFeatureTogglesClick, modifier = Modifier.fillMaxWidth(), ) PrimaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.excluded_blockchains), + text = stringResourceSafe(R.string.excluded_blockchains), onClick = state.onExcludedBlockchainsClick, ) PrimaryButton( - text = stringResource(R.string.environment_toggles), + text = stringResourceSafe(R.string.environment_toggles), onClick = state.onEnvironmentTogglesClick, modifier = Modifier.fillMaxWidth(), ) PrimaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.tester_actions), + text = stringResourceSafe(id = R.string.tester_actions), onClick = state.onTesterActionsClick, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt index 8d262a4334..599abba71d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt @@ -1,7 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.components import androidx.compose.runtime.Immutable -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle @@ -9,6 +8,7 @@ import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIco import com.tangem.core.ui.components.notifications.CurrencyNotificationConfig import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.tokens.model.CryptoCurrency @@ -58,8 +58,8 @@ internal sealed interface ExchangeStatusNotifications { ), subtitle = CurrencyNotificationConfig.AnnotatedSubtitle( valueProvider = { - val linkText = stringResource(R.string.common_read_more) - val fullString = stringResource( + val linkText = stringResourceSafe(R.string.common_read_more) + val fullString = stringResourceSafe( R.string.express_exchange_notification_refund_text, cryptoCurrency.symbol, linkText, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt index 54e6b0dc2b..a679980075 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -7,7 +7,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider @@ -17,6 +16,7 @@ import com.tangem.core.ui.components.buttons.HorizontalActionChips import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons import com.tangem.core.ui.extensions.orMaskWithStars import com.tangem.core.ui.extensions.resolveReference +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 @@ -49,7 +49,7 @@ internal fun TokenDetailsBalanceBlock( val (balanceTitle, toggleButtons, fiatBalance, cryptoBalance, actionChips) = createRefs() Text( - text = stringResource(id = R.string.common_balance_title), + text = stringResourceSafe(id = R.string.common_balance_title), color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.subtitle2, modifier = Modifier.constrainAs(balanceTitle) { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt index 9a7a77dd53..350dad39ff 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt @@ -12,10 +12,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color 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 +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.getGreyScaleColorFilter @@ -62,7 +62,7 @@ private fun NetworkInfoText(currency: TokenInfoBlockState.Currency) { when (currency) { TokenInfoBlockState.Currency.Native -> { Text( - text = stringResource(id = R.string.common_main_network), + text = stringResourceSafe(id = R.string.common_main_network), color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.caption2, ) @@ -104,7 +104,7 @@ private const val SEPARATOR = "%image%" @Composable private fun extractNetwork(tokenCurrency: TokenInfoBlockState.Currency.Token): ExtractedTokenNetworkText { val splitString = if (tokenCurrency.standardName != null) { - stringResource( + stringResourceSafe( id = R.string.token_details_token_type_subtitle, formatArgs = arrayOf( tokenCurrency.standardName, @@ -112,7 +112,7 @@ private fun extractNetwork(tokenCurrency: TokenInfoBlockState.Currency.Token): E ), ) } else { - stringResource( + stringResourceSafe( id = R.string.token_details_token_type_subtitle_no_standard, formatArgs = arrayOf( tokenCurrency.networkName, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressEstimate.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressEstimate.kt index 575ea130a4..cea568e0d5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressEstimate.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressEstimate.kt @@ -7,11 +7,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.inputrow.InputRowApprox import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.features.tokendetails.impl.R @@ -47,7 +47,7 @@ internal fun ExpressEstimate( ), ) { Text( - text = stringResource(id = R.string.express_estimated_amount), + text = stringResourceSafe(id = R.string.express_estimated_amount), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressProvider.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressProvider.kt index 860204ee74..ad15e423ed 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressProvider.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressProvider.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -25,6 +24,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.inputrow.InputRowBestRate import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -50,7 +50,7 @@ internal fun ExpressProvider( .fillMaxWidth(), ) { Text( - text = stringResource(id = R.string.express_provider), + text = stringResourceSafe(id = R.string.express_provider), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) @@ -62,7 +62,9 @@ internal fun ExpressProvider( .clickable { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) clipboardManager.setText(AnnotatedString(providerTxId)) - Toast.makeText(context, R.string.express_transaction_id_copied, Toast.LENGTH_SHORT).show() + Toast + .makeText(context, R.string.express_transaction_id_copied, Toast.LENGTH_SHORT) + .show() }, ) { Icon( @@ -76,7 +78,7 @@ internal fun ExpressProvider( ) Text( modifier = Modifier.align(Alignment.CenterVertically), - text = stringResource(R.string.express_transaction_id, providerTxId), + text = stringResourceSafe(R.string.express_transaction_id, providerTxId), maxLines = 1, overflow = TextOverflow.Ellipsis, style = TangemTheme.typography.body2, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBlock.kt index 7b5cd01dd1..7e0091568c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBlock.kt @@ -17,11 +17,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color 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.SpacerWMax import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.domain.models.domain.ExchangeStatus @@ -53,7 +53,7 @@ internal fun ExchangeStatusBlock( .padding(bottom = TangemTheme.dimens.spacing16), ) { Text( - text = stringResource(id = R.string.express_exchange_status_title), + text = stringResourceSafe(id = R.string.express_exchange_status_title), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) @@ -72,7 +72,7 @@ internal fun ExchangeStatusBlock( .padding(end = TangemTheme.dimens.spacing2), ) Text( - text = stringResource(id = R.string.common_go_to_provider), + text = stringResourceSafe(id = R.string.common_go_to_provider), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt index 305ac6bb39..2f2420d3a9 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt @@ -7,7 +7,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerH10 @@ -16,6 +15,7 @@ import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.notifications.CurrencyNotification import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications @@ -28,14 +28,14 @@ internal fun ExchangeStatusBottomSheetContent(state: ExpressTransactionStateUM.E Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16)) { SpacerH10() Text( - text = stringResource(id = R.string.express_exchange_status_title), + text = stringResourceSafe(id = R.string.express_exchange_status_title), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.primary1, modifier = Modifier.align(CenterHorizontally), ) SpacerH10() Text( - text = stringResource(id = R.string.express_exchange_status_subtitle), + text = stringResourceSafe(id = R.string.express_exchange_status_subtitle), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp/OnrampStatusBottomSheetContent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp/OnrampStatusBottomSheetContent.kt index bf278974e7..94d3245146 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp/OnrampStatusBottomSheetContent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp/OnrampStatusBottomSheetContent.kt @@ -6,7 +6,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.tangem.common.ui.expressStatus.ExpressStatusBlock import com.tangem.common.ui.expressStatus.ExpressStatusNotificationBlock @@ -16,6 +15,7 @@ import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressEstimate @@ -26,14 +26,14 @@ internal fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.Onr Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16)) { SpacerH10() Text( - text = stringResource(id = R.string.common_transaction_status), + text = stringResourceSafe(id = R.string.common_transaction_status), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.primary1, modifier = Modifier.align(CenterHorizontally), ) SpacerH10() Text( - text = stringResource(id = R.string.express_exchange_status_subtitle), + text = stringResourceSafe(id = R.string.express_exchange_status_subtitle), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingBalanceBlock.kt index 76bc8386d6..cd79642bbc 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingBalanceBlock.kt @@ -8,13 +8,13 @@ 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 androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.orMaskWithStars import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingBalanceBlock @@ -37,7 +37,7 @@ internal fun StakingBalanceBlock( modifier = Modifier.weight(1f), ) { Text( - text = stringResource(R.string.staking_native), + text = stringResourceSafe(R.string.staking_native), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing2), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingUnavailableBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingUnavailableBlock.kt index 2047a1f09f..067dbeca7a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingUnavailableBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingUnavailableBlock.kt @@ -1,12 +1,15 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking import android.content.res.Configuration -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text 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.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.tokendetails.impl.R @@ -18,12 +21,12 @@ internal fun StakingTemporaryUnavailableBlock(modifier: Modifier = Modifier) { modifier = modifier.fillMaxWidth(), ) { Text( - text = stringResource(R.string.staking_native), + text = stringResourceSafe(R.string.staking_native), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) Text( - text = stringResource(R.string.staking_notification_network_error_text), + text = stringResourceSafe(R.string.staking_notification_network_error_text), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt index a8ba9f84c2..5cd7f1b7c2 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt @@ -13,17 +13,19 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -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.* +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.extensions.resolveReference +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.getGreyScaleColorFilter -import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingBalanceBlock import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingAvailableBlock +import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingBalanceBlock import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingLoadingBlock import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingTemporaryUnavailableBlock import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM @@ -111,7 +113,7 @@ private fun StakingAvailableContent(state: StakingBlockUM.StakeAvailable, modifi } SecondaryButton( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.common_stake), + text = stringResourceSafe(id = R.string.common_stake), onClick = state.onStakeClicked, ) } diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index 0927a822e4..5ac744ef62 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -1,6 +1,5 @@ package com.tangem.feature.walletsettings.model -import androidx.compose.ui.res.stringResource import arrow.core.getOrElse import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate @@ -15,6 +14,7 @@ import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButtonUM import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.message.ContentMessage import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.common.util.cardTypesResolver @@ -96,10 +96,10 @@ internal class WalletSettingsModel @Inject constructor( messageSender.send( ContentMessage { onDismiss -> BasicDialog( - message = stringResource(R.string.user_wallet_list_delete_prompt), + message = stringResourceSafe(R.string.user_wallet_list_delete_prompt), onDismissDialog = onDismiss, confirmButton = DialogButtonUM( - title = stringResource(R.string.common_delete), + title = stringResourceSafe(R.string.common_delete), warning = true, onClick = { forgetWallet() @@ -107,7 +107,7 @@ internal class WalletSettingsModel @Inject constructor( }, ), dismissButton = DialogButtonUM( - title = stringResource(R.string.common_cancel), + title = stringResourceSafe(R.string.common_cancel), onClick = onDismiss, ), ) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/RenameWalletDialog.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/RenameWalletDialog.kt index 8cbce3fc72..0e6313e933 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/RenameWalletDialog.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/RenameWalletDialog.kt @@ -4,11 +4,11 @@ import android.content.res.Configuration import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.AdditionalTextInputDialogUM import com.tangem.core.ui.components.DialogButtonUM import com.tangem.core.ui.components.TextInputDialog +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.walletsettings.component.preview.PreviewRenameWalletComponent import com.tangem.feature.walletsettings.entity.RenameWalletUM @@ -19,21 +19,21 @@ internal fun RenameWalletDialog(model: RenameWalletUM, onDismiss: () -> Unit) { val value by rememberUpdatedState(newValue = model.walletNameValue) TextInputDialog( - title = stringResource(id = R.string.user_wallet_list_rename_popup_title), + title = stringResourceSafe(id = R.string.user_wallet_list_rename_popup_title), fieldValue = value, confirmButton = DialogButtonUM( - title = stringResource(id = R.string.common_ok), + title = stringResourceSafe(id = R.string.common_ok), enabled = model.isConfirmEnabled, onClick = model.onConfirm, ), dismissButton = DialogButtonUM( - title = stringResource(id = R.string.common_cancel), + title = stringResourceSafe(id = R.string.common_cancel), onClick = onDismiss, ), onDismissDialog = onDismiss, onValueChange = model.updateValue, textFieldParams = AdditionalTextInputDialogUM( - label = stringResource(id = R.string.user_wallet_list_rename_popup_placeholder), + label = stringResourceSafe(id = R.string.user_wallet_list_rename_popup_placeholder), ), ) } diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt index f3a06473f2..15a43446e9 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.appbar.TangemTopAppBar @@ -21,6 +20,7 @@ import com.tangem.core.ui.components.block.BlockCard import com.tangem.core.ui.components.block.BlockItem import com.tangem.core.ui.components.snackbar.TangemSnackbarHost import com.tangem.core.ui.extensions.resolveReference +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 @@ -57,7 +57,9 @@ internal fun WalletSettingsScreen( }, content = { paddingValues -> Content( - modifier = Modifier.padding(paddingValues).testTag(TestTags.WALLET_SETTINGS_SCREEN), + modifier = Modifier + .padding(paddingValues) + .testTag(TestTags.WALLET_SETTINGS_SCREEN), state = state, ) @@ -79,7 +81,7 @@ private fun Content(state: WalletSettingsUM, modifier: Modifier = Modifier) { item { Text( modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), - text = stringResource(id = R.string.wallet_settings_title), + text = stringResourceSafe(id = R.string.wallet_settings_title), style = TangemTheme.typography.h1, color = TangemTheme.colors.text.primary1, ) diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 88d3ff2a2c..c673d76a65 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -56,6 +56,7 @@ dependencies { implementation(projects.core.deepLinks) implementation(projects.core.deepLinks.global) implementation(projects.core.decompose) + implementation(projects.core.res) implementation(projects.libs.crypto) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt index fcff8bb806..614ce24018 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt @@ -33,15 +33,9 @@ internal object WalletScreenPreviewData { private val textContentTokensState = WalletTokensListState.ContentState.Content( items = persistentListOf( - TokensListItemUM.NetworkGroupTitle( - id = 1, - name = stringReference("Bitcoin"), - ), + TokensListItemUM.GroupTitle(id = 1, text = stringReference("Network Bitcoin")), TokensListItemUM.Token(state = tokenItemState), - TokensListItemUM.NetworkGroupTitle( - id = 2, - name = stringReference("Ethereum"), - ), + TokensListItemUM.GroupTitle(id = 2, text = stringReference("Network Ethereum")), TokensListItemUM.Token( state = tokenItemState.copy( id = "2", diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt index 1c79fc38cd..9a09f58f26 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback -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 @@ -32,9 +31,10 @@ import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.buttons.actions.RoundedActionButton import com.tangem.core.ui.components.token.TokenItem -import com.tangem.core.ui.components.tokenlist.internal.DraggableNetworkTitleItem +import com.tangem.core.ui.components.tokenlist.internal.DraggableGroupTitleItem import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.reordarable.ReorderableItem import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -176,10 +176,10 @@ private fun LazyItemScope.DraggableItem( val modifierWithBackground = itemModifier.background(color = TangemTheme.colors.background.primary) when (item) { - is DraggableItem.GroupHeader -> DraggableNetworkTitleItem( - modifier = modifierWithBackground, - networkName = item.networkName, + is DraggableItem.GroupHeader -> DraggableGroupTitleItem( + state = item.groupTitle, reorderableTokenListState = reorderableState, + modifier = modifierWithBackground, ) is DraggableItem.Token -> TokenItem( modifier = modifierWithBackground, @@ -232,7 +232,7 @@ private fun TopBar( contentAlignment = Alignment.CenterStart, ) { Text( - text = stringResource(id = R.string.organize_tokens_title), + text = stringResourceSafe(id = R.string.organize_tokens_title), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.primary1, maxLines = 1, @@ -287,12 +287,12 @@ private fun Actions(config: OrganizeTokensState.ActionsConfig, modifier: Modifie ) { SecondaryButton( modifier = Modifier.weight(1f), - text = stringResource(id = R.string.common_cancel), + text = stringResourceSafe(id = R.string.common_cancel), onClick = config.onCancelClick, ) PrimaryButton( modifier = Modifier.weight(1f), - text = stringResource(id = R.string.common_apply), + text = stringResourceSafe(id = R.string.common_apply), onClick = config.onApplyClick, showProgress = config.showApplyProgress, enabled = config.canApply, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/DraggableItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/DraggableItem.kt index 37387e16c8..b3443ebd3f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/DraggableItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/model/DraggableItem.kt @@ -2,6 +2,10 @@ package com.tangem.feature.wallet.presentation.organizetokens.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem.RoundingMode /** @@ -30,7 +34,16 @@ internal sealed class DraggableItem { val networkName: String, override val roundingMode: RoundingMode = RoundingMode.None, override val showShadow: Boolean = false, - ) : DraggableItem() + ) : DraggableItem() { + + val groupTitle = TokensListItemUM.GroupTitle( + id = id, + text = resourceReference( + id = R.string.wallet_network_group_title, + formatArgs = wrappedList(networkName), + ), + ) + } /** * Item for token. diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt index 8dddf4cecc..196ec4d3ba 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt @@ -3,7 +3,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM -import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.feature.wallet.impl.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -30,7 +31,13 @@ internal sealed class WalletTokensListState { data object Locked : ContentState() { override val items = persistentListOf( - TokensListItemUM.NetworkGroupTitle(id = 42, name = TextReference.Res(id = R.string.main_tokens)), + TokensListItemUM.GroupTitle( + id = 42, + text = resourceReference( + id = R.string.wallet_network_group_title, + formatArgs = wrappedList(resourceReference(id = R.string.main_tokens)), + ), + ), TokensListItemUM.Token(state = TokenItemState.Locked(id = "Locked#1")), ) override val organizeTokensButtonConfig = null diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index 11f5063820..c3d4c02d9c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -2,7 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert import com.tangem.common.ui.tokens.TokenItemStateConverter import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -10,6 +11,7 @@ import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.converter.Converter @@ -60,9 +62,12 @@ internal class TokenListStateConverter( } private fun MutableList.addGroup(group: NetworkGroup): List { - val groupTitle = TokensListItemUM.NetworkGroupTitle( + val groupTitle = TokensListItemUM.GroupTitle( id = group.network.hashCode(), - name = stringReference(group.network.name), + text = resourceReference( + id = R.string.wallet_network_group_title, + formatArgs = wrappedList(group.network.name), + ), ) add(groupTitle) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt index ca9ee260f3..a3b1644d47 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletAlert.kt @@ -1,13 +1,13 @@ package com.tangem.feature.wallet.presentation.wallet.ui import androidx.compose.runtime.* -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import com.tangem.core.ui.components.AdditionalTextInputDialogUM import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButtonUM import com.tangem.core.ui.components.TextInputDialog import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState @@ -35,7 +35,7 @@ private fun BasicAlert(state: WalletAlertState.Basic, onDismiss: () -> Unit) { }, ) dismissButton = DialogButtonUM( - title = stringResource(id = R.string.common_cancel), + title = stringResourceSafe(id = R.string.common_cancel), onClick = onDismiss, ) } else { @@ -75,7 +75,7 @@ private fun TextInputAlert(state: WalletAlertState.TextInput, onDismiss: () -> U onDismissDialog = onDismiss, onValueChange = { value = it }, title = state.title.resolveReference(), - dismissButton = DialogButtonUM(title = stringResource(id = R.string.common_cancel), onClick = onDismiss), + dismissButton = DialogButtonUM(title = stringResourceSafe(id = R.string.common_cancel), onClick = onDismiss), textFieldParams = AdditionalTextInputDialogUM( label = state.label.resolveReference(), isError = state.errorTextProvider(value.text) != null, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt index 19cd88f97f..03bf578454 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.AnnotatedString +import com.tangem.core.res.getStringSafe import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.resolveReference @@ -46,7 +47,7 @@ internal fun WalletEventEffect( is WalletEvent.CopyAddress -> { clipboardManager.setText(AnnotatedString(value.address)) snackbarHostState.showSnackbar( - message = resources.getString(R.string.wallet_notification_address_copied), + message = resources.getStringSafe(R.string.wallet_notification_address_copied), duration = SnackbarDuration.Short, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 2617c1d2e8..061f3c506b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -33,7 +33,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -58,6 +57,7 @@ import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar import com.tangem.core.ui.components.snackbar.TangemSnackbar import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.event.StateEvent +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.LocalWindowSize @@ -504,7 +504,7 @@ internal fun MarketsHint(isVisible: Boolean, modifier: Modifier = Modifier) { horizontalAlignment = Alignment.CenterHorizontally, ) { Text( - text = stringResource(R.string.markets_hint), + text = stringResourceSafe(R.string.markets_hint), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, textAlign = TextAlign.Center, @@ -552,12 +552,12 @@ private fun MarketsTooltipContent(modifier: Modifier = Modifier) { horizontalAlignment = Alignment.Start, ) { Text( - text = stringResource(id = R.string.markets_tooltip_title), + text = stringResourceSafe(id = R.string.markets_tooltip_title), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.primary1, ) Text( - text = stringResource(id = R.string.markets_tooltip_message), + text = stringResourceSafe(id = R.string.markets_tooltip_message), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt index ec20d2a8d6..eb10376318 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SecondaryButton @@ -19,6 +18,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.showcase.ShowcaseContent import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.requestPushPermission @@ -76,7 +76,7 @@ private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetC ), ) { SecondaryButton( - text = stringResource(R.string.common_later), + text = stringResourceSafe(R.string.common_later), onClick = { content.onNeverRequest() onDismiss() @@ -84,7 +84,7 @@ private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetC modifier = Modifier.weight(1f), ) PrimaryButton( - text = stringResource(R.string.common_allow), + text = stringResourceSafe(R.string.common_allow), onClick = { isClicked.value = true content.onRequest() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt index 6ceb82e75c..9e06c5bfcf 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt @@ -30,7 +30,6 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource -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 @@ -48,6 +47,7 @@ import com.tangem.core.ui.components.ResizableText import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.orMaskWithStars import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemDimens import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -252,7 +252,7 @@ private fun ManageWalletContextMenu( @Composable private fun MenuItem(@StringRes textResId: Int, imageVector: ImageVector, onClick: () -> Unit) { DropdownMenuItem( - text = { Text(text = stringResource(id = textResId), style = TangemTheme.typography.subtitle2) }, + text = { Text(text = stringResourceSafe(id = textResId), style = TangemTheme.typography.subtitle2) }, modifier = Modifier.background(color = TangemTheme.colors.background.secondary), trailingIcon = { Icon(imageVector = imageVector, contentDescription = null) }, onClick = onClick, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt index 49c11b5799..e4293a0ec9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt @@ -11,11 +11,11 @@ import androidx.compose.material3.Text 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 com.tangem.core.ui.components.tokenlist.TokenListItem import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState @@ -91,7 +91,7 @@ private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) { ) Text( - text = stringResource(id = R.string.main_empty_tokens_list_message), + text = stringResourceSafe(id = R.string.main_empty_tokens_list_message), modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing48), color = TangemTheme.colors.text.tertiary, textAlign = TextAlign.Center, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index cc51c50b34..02b79ae077 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -16,7 +16,7 @@ androidxBrowser = "1.4.0" androidxConstraintLayout = "2.1.4" androidxKtx = "1.9.0" androidxSplashScreen = "1.0.1" -androidxFragment = "1.5.3" +androidxFragment = "1.8.5" androidxLifecycle = "2.5.1" androidx-paging = "3.1.1" androidx-palette = "1.0.0" @@ -88,9 +88,9 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.19-883" +tangemBlockchainSdk = "develop-884" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.19-414" +tangemCardSdk = "develop-410" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem17" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ @@ -143,6 +143,7 @@ androidx-constraintLayout = { module = "androidx.constraintlayout:constraintlayo androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxKtx" } androidx-core-splashScreen = { module = "androidx.core:core-splashscreen", version.ref = "androidxSplashScreen" } androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidxFragment" } +androidx-fragment-compose = { module = "androidx.fragment:fragment-compose", version.ref = "androidxFragment" } androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "androidx-paging" } androidx-swipeRefreshLayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swipeRefreshLayout" } androidx-palette = { module = "androidx.palette:palette", version.ref = "androidx-palette" } @@ -247,7 +248,6 @@ timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } viewBindingDelegate = { module = "com.github.kirich1409:viewbindingpropertydelegate-noreflection", version.ref = "viewBindingDelegate" } xmlShimmer = { module = "com.github.skydoves:androidveil", version.ref = "xmlShimmer" } zxing-qrCore = { module = "com.google.zxing:core", version.ref = "zxingQrCode" } -mviCore-watcher = { module = "com.github.badoo.mvicore:mvicore-diff", version.ref = "mviCore" } kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index 80b0af4a35..1fbc46c22c 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -5,8 +5,10 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.compatibility.l2BlockchainsList import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId +import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.lib.crypto.converter.XrpTaggedAddressConverter import com.tangem.lib.crypto.models.XrpTaggedAddress +import java.math.BigDecimal /** * !!!IMPORTANT!!! @@ -116,6 +118,8 @@ object BlockchainUtils { return l2BlockchainsList.contains(blockchain) } + fun getTezosThreshold(): BigDecimal = Blockchain.Tezos.minimalAmount() + /** * Blockchains not affecting total balance counting on errors */