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..3886d9f58b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -75,6 +75,7 @@ dependencies {
implementation(projects.domain.markets)
implementation(projects.domain.manageTokens)
implementation(projects.domain.onramp)
+ implementation(projects.domain.promo)
implementation(projects.common)
implementation(projects.common.routing)
@@ -166,6 +167,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,11 +228,11 @@ dependencies {
implementation(deps.xmlShimmer)
implementation(deps.viewBindingDelegate)
implementation(deps.armadillo)
- implementation(deps.mviCore.watcher)
implementation(deps.kotlin.serialization)
implementation(deps.reownCore)
implementation(deps.reownWeb3)
implementation(deps.prettyLogger)
+ implementation(deps.decompose.ext.compose)
/** Testing libraries */
testImplementation(deps.test.coroutine)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fbb777f36c..4e84fb633b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,11 +10,6 @@
-
-
-
diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config
index f1aa2f2cbf..1f1c301f4e 160000
--- a/app/src/main/assets/tangem-app-config
+++ b/app/src/main/assets/tangem-app-config
@@ -1 +1 @@
-Subproject commit f1aa2f2cbf374ecba5da753d9f51c9df81749ef2
+Subproject commit 1f1c301f4e0286cf6ab4cd8ee9323c85de3dfaab
diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json
index 67b96dd4a9..380e985b29 100644
--- a/app/src/main/assets/testnet_tokens.json
+++ b/app/src/main/assets/testnet_tokens.json
@@ -759,6 +759,16 @@
}
]
},
+ {
+ "id": "vanar-chain",
+ "name": "Vanar Chain",
+ "symbol": "VANRY",
+ "networks": [
+ {
+ "networkId": "vanar-chain/test"
+ }
+ ]
+ },
{
"id": "casper-network",
"name": "Casper",
diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt
index 95b59844e2..80eac8dc24 100644
--- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt
+++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt
@@ -10,6 +10,7 @@ import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
+import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
@@ -36,7 +37,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
@@ -113,8 +113,6 @@ interface ApplicationEntryPoint {
fun getTransactionSignerFactory(): TransactionSignerFactory
- fun getHomeFeatureToggles(): HomeFeatureToggles
-
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
fun getOnrampFeatureToggles(): OnrampFeatureToggles
@@ -128,4 +126,6 @@ interface ApplicationEntryPoint {
fun getExcludedBlockchains(): ExcludedBlockchains
fun getAppLogsStore(): AppLogsStore
+
+ fun getClipboardManager(): ClipboardManager
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/DecomposeFragment.kt b/app/src/main/java/com/tangem/tap/DecomposeFragment.kt
index 296704a5e1..ad37670303 100644
--- a/app/src/main/java/com/tangem/tap/DecomposeFragment.kt
+++ b/app/src/main/java/com/tangem/tap/DecomposeFragment.kt
@@ -8,7 +8,6 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.decompose.ComposableContentComponent
-import com.tangem.core.ui.message.EventMessageEffect
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.utils.Provider
import dagger.hilt.android.AndroidEntryPoint
@@ -33,11 +32,6 @@ internal class DecomposeFragment : ComposeFragment() {
@Composable
override fun ScreenContent(modifier: Modifier) {
component.Content(modifier)
-
- EventMessageEffect(
- messageHandler = uiDependencies.eventMessageHandler,
- snackbarHostState = uiDependencies.globalSnackbarHostState,
- )
}
private class ComponentBuilder>(
diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt
index 2bc6faa40c..b5cd3eab19 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -7,17 +7,20 @@ import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.view.MotionEvent
-import android.view.View
import android.view.WindowManager
import androidx.activity.SystemBarStyle
+import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.Lifecycle
@@ -36,10 +39,14 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.di.RootAppComponentContext
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.navigation.email.EmailSender
+import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.message.EventMessageEffect
+import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.res.TangemColorPalette
+import com.tangem.core.ui.res.TangemTheme
import com.tangem.data.card.sdk.CardSdkOwner
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.card.ScanCardUseCase
@@ -85,8 +92,9 @@ import com.tangem.tap.features.main.model.Toast
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphAction
-import com.tangem.tap.routing.RoutingComponent
+import com.tangem.tap.routing.component.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 +217,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
lateinit var dispatchers: CoroutineDispatcherProvider
+ @Inject
+ internal lateinit var routingFeatureToggles: RoutingFeatureToggles
+
+ @Inject
+ internal lateinit var uiDependencies: UiDependencies
+
internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow
@@ -255,8 +269,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
installActivityDependencies()
observeAppThemeModeUpdates()
- setContentView(R.layout.activity_main)
- installRouting()
+ if (routingFeatureToggles.isNavigationRefactoringEnabled) {
+ setRootContent()
+ } else {
+ setContentView(R.layout.activity_main)
+ installRouting()
+ installEventMessageEffect()
+ }
+
initContent()
observeStateUpdates()
@@ -271,6 +291,43 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
lifecycle.addObserver(WindowObscurationObserver)
}
+ private fun installEventMessageEffect() {
+ binding.composeView.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ binding.composeView.setContent {
+ TangemTheme(
+ activity = this,
+ uiDependencies = uiDependencies,
+ overrideSystemBarColors = false,
+ ) {
+ EventMessageEffect(
+ onShowSnackbar = { message, _ ->
+ showSnackbar(
+ text = message.message.resolveReference(resources),
+ length = when (message.duration) {
+ SnackbarMessage.Duration.Short -> Snackbar.LENGTH_SHORT
+ SnackbarMessage.Duration.Long -> Snackbar.LENGTH_LONG
+ SnackbarMessage.Duration.Indefinite -> Snackbar.LENGTH_INDEFINITE
+ },
+ buttonTitle = message.actionLabel?.resolveReference(resources),
+ action = message.action,
+ onDismiss = message.onDismissRequest,
+ )
+ },
+ )
+ }
+ }
+ }
+
+ private fun setRootContent() {
+ val routingComponent = routingComponentFactory.create(
+ context = rootComponentContext,
+ )
+
+ setContent {
+ routingComponent.Content(Modifier.fillMaxSize())
+ }
+ }
+
private fun installRouting() {
val routingComponent = routingComponentFactory.create(
context = rootComponentContext,
@@ -297,6 +354,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
is RoutingComponent.Child.LegacyIntent -> {
startActivity(child.intent)
}
+ is RoutingComponent.Child.ComposableComponent -> error("Unsupported child: $child")
}
}
}
@@ -400,8 +458,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
override fun onResume() {
super.onResume()
- // TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop
- notificationsHandler = NotificationsHandler(binding.fragmentContainer)
+
+ if (!routingFeatureToggles.isNavigationRefactoringEnabled) {
+ // TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop
+ notificationsHandler = NotificationsHandler(binding.fragmentContainer)
+ }
navigateToInitialScreenIfNeeded(intent)
}
@@ -481,22 +542,22 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
}
- override fun showSnackbar(
- @StringRes text: Int,
- length: Int,
- @StringRes buttonTitle: Int?,
- action: View.OnClickListener?,
- ) {
- showSnackbar(getString(text), length, buttonTitle?.let(::getString), action)
+ override fun showSnackbar(@StringRes text: Int, length: Int, @StringRes buttonTitle: Int?, action: (() -> Unit)?) {
+ showSnackbar(
+ getString(text),
+ length,
+ buttonTitle?.let(::getString),
+ action = { action?.invoke() },
+ )
}
- override fun showSnackbar(
- text: TextReference,
- length: Int,
- buttonTitle: TextReference?,
- action: View.OnClickListener?,
- ) {
- showSnackbar(text.resolveReference(resources), length, buttonTitle?.resolveReference(resources), action)
+ override fun showSnackbar(text: TextReference, length: Int, buttonTitle: TextReference?, action: (() -> Unit)?) {
+ showSnackbar(
+ text = text.resolveReference(resources),
+ length = length,
+ buttonTitle = buttonTitle?.resolveReference(resources),
+ action = { action?.invoke() },
+ )
}
override fun dismissSnackbar() {
@@ -531,7 +592,13 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
return if (result) super.dispatchTouchEvent(event) else false
}
- private fun showSnackbar(text: String, length: Int, buttonTitle: String?, action: View.OnClickListener?) {
+ private fun showSnackbar(
+ text: String,
+ length: Int,
+ buttonTitle: String?,
+ action: (() -> Unit)? = null,
+ onDismiss: () -> Unit = {},
+ ) {
if (snackbar != null) return
snackbar = Snackbar.make(binding.fragmentContainer, text, length).apply {
@@ -542,12 +609,13 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
setTextColor(textColor)
if (buttonTitle != null && action != null) {
- setAction(buttonTitle, action)
+ setAction(buttonTitle, { action() })
}
addCallback(
object : BaseTransientBottomBar.BaseCallback() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
+ onDismiss()
snackbar = null
removeCallback(this)
}
diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt
index 05fd529eaa..83bba376a4 100644
--- a/app/src/main/java/com/tangem/tap/TangemApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt
@@ -20,6 +20,7 @@ import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.Basic.TransactionSent.WalletForm
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
+import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import com.tangem.datasource.connection.NetworkConnectionManager
@@ -58,7 +59,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
@@ -179,9 +179,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()
@@ -202,6 +199,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val appLogsStore: AppLogsStore
get() = entryPoint.getAppLogsStore()
+
+ private val clipboardManager: ClipboardManager
+ get() = entryPoint.getClipboardManager()
// endregion
override fun onCreate() {
@@ -292,7 +292,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
shareManager = shareManager,
appRouter = appRouter,
transactionSignerFactory = transactionSignerFactory,
- homeFeatureToggles = homeFeatureToggles,
getUserCountryUseCase = getUserCountryUseCase,
onrampFeatureToggles = onrampFeatureToggles,
environmentConfigStorage = environmentConfigStorage,
@@ -300,6 +299,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
onboardingRepository = onboardingRepository,
excludedBlockchains = excludedBlockchains,
appPreferencesStore = appPreferencesStore,
+ clipboardManager = clipboardManager,
),
),
)
diff --git a/app/src/main/java/com/tangem/tap/common/SnackbarHandler.kt b/app/src/main/java/com/tangem/tap/common/SnackbarHandler.kt
index 53228bf674..2d8f270f89 100644
--- a/app/src/main/java/com/tangem/tap/common/SnackbarHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/SnackbarHandler.kt
@@ -1,6 +1,5 @@
package com.tangem.tap.common
-import android.view.View
import androidx.annotation.StringRes
import com.google.android.material.snackbar.Snackbar
import com.tangem.core.ui.extensions.TextReference
@@ -11,14 +10,14 @@ interface SnackbarHandler {
@StringRes text: Int,
length: Int = Snackbar.LENGTH_INDEFINITE,
@StringRes buttonTitle: Int? = null,
- action: View.OnClickListener? = null,
+ action: (() -> Unit)? = null,
)
fun showSnackbar(
text: TextReference,
length: Int = Snackbar.LENGTH_INDEFINITE,
buttonTitle: TextReference? = null,
- action: View.OnClickListener? = null,
+ action: (() -> Unit)? = null,
)
fun dismissSnackbar()
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/clipboard/DefaultClipboardManager.kt b/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt
index 8b7ee07933..73f2ebd5ef 100644
--- a/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt
@@ -1,12 +1,52 @@
package com.tangem.tap.common.clipboard
import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ClipDescription.MIMETYPE_TEXT_PLAIN
+import android.os.Build
+import android.os.PersistableBundle
import com.tangem.core.ui.clipboard.ClipboardManager
+import timber.log.Timber
import android.content.ClipboardManager as AndroidClipboardManager
internal class DefaultClipboardManager(private val clipboardManager: AndroidClipboardManager) : ClipboardManager {
- override fun setText(label: String, text: String) {
- clipboardManager.setPrimaryClip(ClipData.newPlainText(label, text))
+ override fun setText(text: String, isSensitive: Boolean, label: String) {
+ val clip = ClipData.newPlainText(label, text).apply {
+ if (isSensitive) description.setAsSensitive()
+ }
+
+ clipboardManager.setPrimaryClip(clip)
+ }
+
+ override fun getText(default: String?): String? {
+ val clip = clipboardManager.primaryClip
+
+ if (clip == null || clip.itemCount == 0) {
+ Timber.d("Clipboard is empty")
+ return default
+ }
+
+ val clipDescription = clipboardManager.primaryClipDescription
+ if (clipDescription?.hasMimeType(MIMETYPE_TEXT_PLAIN) == false) {
+ Timber.d("Clipboard doesn't contain text")
+ return default
+ }
+
+ return clip.getItemAt(0).text?.toString()
+ }
+
+ private fun ClipDescription.setAsSensitive() {
+ extras = PersistableBundle().apply {
+ putBoolean(getExtraIsSensitiveFlag(), true)
+ }
+ }
+
+ private fun getExtraIsSensitiveFlag(): String {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ ClipDescription.EXTRA_IS_SENSITIVE
+ } else {
+ "android.content.extra.IS_SENSITIVE"
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt b/app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt
index bdab75088f..8013aa18f1 100644
--- a/app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt
@@ -4,8 +4,10 @@ import com.tangem.core.ui.clipboard.ClipboardManager
import timber.log.Timber
internal object MockClipboardManager : ClipboardManager {
- override fun setText(label: String, text: String) {
- /** Intentionnaly do nothing */
+
+ override fun setText(text: String, isSensitive: Boolean, label: String) {
Timber.w("Clipboard Manager not available")
}
+
+ override fun getText(default: String?): String? = null
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt b/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt
deleted file mode 100644
index ec4f3737f0..0000000000
--- a/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.tangem.tap.common.extensions
-
-import android.app.Activity
-import android.content.Intent
-import android.net.Uri
-import androidx.core.app.ShareCompat
-import androidx.core.content.ContextCompat
-import androidx.core.content.FileProvider
-import java.io.File
-
-/**
- * required since targetAndroid=30
- *
- *
- *
- *
- *
- *
- */
-@Deprecated("Use EmailSender instead")
-fun Activity.sendEmail(
- email: String,
- subject: String,
- message: String,
- file: File? = null,
- onFail: ((Exception) -> Unit)? = null,
-) {
- fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent {
- val builder = ShareCompat.IntentBuilder.from(this)
- .setType("message/rfc822")
- .setEmailTo(arrayOf(recipient))
- .setSubject(subject)
- .setText(text)
- file?.let { builder.setStream(FileProvider.getUriForFile(this, "$packageName.provider", it)) }
- return builder.intent
- }
-
- val originalIntent = createEmailShareIntent(email, subject, message, file)
- val emailFilterIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"))
-
- val originalIntentResults = packageManager.queryIntentActivities(originalIntent, 0)
- val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0)
-
- val targetedIntents = originalIntentResults
- .filter { originalResult ->
- emailFilterIntentResults.any {
- originalResult.activityInfo.packageName == it.activityInfo.packageName
- }
- }
- .map {
- createEmailShareIntent(email, subject, message, file).apply {
- setPackage(it.activityInfo.packageName)
- }
- }
- .toMutableList()
- try {
- val chooserIntent = Intent.createChooser(targetedIntents.removeAt(0), "Send mail...")
- chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
- chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- ContextCompat.startActivity(this, chooserIntent, null)
- } catch (ex: Exception) {
- onFail?.invoke(ex)
- }
-}
\ No newline at end of file
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..355da51669 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,19 @@
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 +33,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,25 +51,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
-
- val clip: ClipData = ClipData.newPlainText(label, value.toString())
- clipboard.setPrimaryClip(clip)
-}
-
-fun Context.getFromClipboard(default: CharSequence? = null): CharSequence? {
- val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
- ?: return default
- val clipData = clipboard.primaryClip ?: return default
- if (clipData.itemCount == 0) return default
-
- return clipData.getItemAt(0).text
-}
-
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
return context.getString(resId, *formatArgs)
}
\ No newline at end of file
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/common/ui/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/common/ui/AddressInfoBottomSheetDialog.kt
index 8b2cb093dd..18882c44dd 100644
--- a/app/src/main/java/com/tangem/tap/common/ui/AddressInfoBottomSheetDialog.kt
+++ b/app/src/main/java/com/tangem/tap/common/ui/AddressInfoBottomSheetDialog.kt
@@ -3,6 +3,7 @@ package com.tangem.tap.common.ui
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
+import android.widget.Toast
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.core.analytics.Analytics
@@ -10,6 +11,7 @@ import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.domain.model.WalletAddressData
+import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
@@ -51,7 +53,12 @@ internal class AddressInfoBottomSheetDialog(
tvAddress.text = data.address
btnFlCopyAddress.setOnClickListener {
Analytics.send(Token.Receive.ButtonCopyAddress())
- context.copyToClipboard(data.address)
+
+ Toast
+ .makeText(context, R.string.wallet_notification_address_copied, Toast.LENGTH_SHORT)
+ .show()
+
+ store.inject(DaggerGraphState::clipboardManager).setText(text = data.address, isSensitive = true)
}
btnFlShare.setOnClickListener {
Analytics.send(Token.Receive.ButtonShareAddress())
diff --git a/app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt b/app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt
index e38ae3919c..8d7beb3948 100644
--- a/app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt
+++ b/app/src/main/java/com/tangem/tap/core/navigation/email/AndroidEmailSender.kt
@@ -68,5 +68,6 @@ internal class AndroidEmailSender : EmailSender {
}
return builder.intent
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt b/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt
index 1128b32708..b59094d579 100644
--- a/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/RootAppComponentContextModule.kt
@@ -6,8 +6,9 @@ import com.arkivanov.decompose.defaultComponentContext
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.DefaultAppComponentContext
import com.tangem.core.decompose.di.DecomposeComponent
+import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.RootAppComponentContext
-import com.tangem.core.ui.UiDependencies
+import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@@ -27,13 +28,13 @@ internal object RootAppComponentContextModule {
@ActivityContext context: Context,
dispatchers: CoroutineDispatcherProvider,
componentBuilder: DecomposeComponent.Builder,
- uiDependencies: UiDependencies,
+ @GlobalUiMessageSender messageSender: UiMessageSender,
): AppComponentContext {
return DefaultAppComponentContext(
componentContext = (context as AppCompatActivity).defaultComponentContext(),
- messageHandler = uiDependencies.eventMessageHandler,
dispatchers = dispatchers,
hiltComponentBuilder = componentBuilder,
+ messageSender = messageSender,
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt b/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt
index 44631d4c05..de5f7ef9e3 100644
--- a/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt
@@ -1,6 +1,9 @@
package com.tangem.tap.di
import androidx.compose.material3.SnackbarHostState
+import com.tangem.core.decompose.di.GlobalUiMessageSender
+import com.tangem.core.decompose.ui.DefaultUiMessageSender
+import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.core.ui.message.EventMessageHandler
@@ -28,4 +31,11 @@ internal object UiDependenciesModule {
override val eventMessageHandler: EventMessageHandler = EventMessageHandler()
}
}
+
+ @Provides
+ @Singleton
+ @GlobalUiMessageSender
+ fun provideUiMessageSender(uiDependencies: UiDependencies): UiMessageSender {
+ return DefaultUiMessageSender(uiDependencies.eventMessageHandler)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/domain/PromoDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/PromoDomainModule.kt
new file mode 100644
index 0000000000..ccc91c699c
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/di/domain/PromoDomainModule.kt
@@ -0,0 +1,36 @@
+package com.tangem.tap.di.domain
+
+import com.tangem.domain.promo.PromoRepository
+import com.tangem.domain.promo.ShouldShowRingPromoUseCase
+import com.tangem.domain.promo.ShouldShowSwapPromoTokenUseCase
+import com.tangem.domain.promo.ShouldShowSwapPromoWalletUseCase
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal object PromoDomainModule {
+
+ @Provides
+ @Singleton
+ fun provideShouldShowSwapPromoWalletUseCase(
+ promoSettingsRepository: PromoRepository,
+ ): ShouldShowSwapPromoWalletUseCase {
+ return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository)
+ }
+
+ @Provides
+ @Singleton
+ fun provideShouldShowRingPromoUseCase(promoRepository: PromoRepository): ShouldShowRingPromoUseCase {
+ return ShouldShowRingPromoUseCase(promoRepository)
+ }
+
+ @Provides
+ @Singleton
+ fun provideShouldShowSwapPromoTokenUseCase(promoRepository: PromoRepository): ShouldShowSwapPromoTokenUseCase {
+ return ShouldShowSwapPromoTokenUseCase(promoRepository)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt
index 352171f53a..6871b3101c 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt
@@ -8,7 +8,6 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.settings.*
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.PermissionRepository
-import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
@@ -117,30 +116,6 @@ internal object SettingsDomainModule {
return IsWalletsScrollPreviewEnabled(settingsRepository = settingsRepository)
}
- @Provides
- @Singleton
- fun provideShouldShowSwapPromoWalletUseCase(
- promoSettingsRepository: PromoSettingsRepository,
- ): ShouldShowSwapPromoWalletUseCase {
- return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository)
- }
-
- @Provides
- @Singleton
- fun provideShouldShowRingPromoUseCase(
- promoSettingsRepository: PromoSettingsRepository,
- ): ShouldShowRingPromoUseCase {
- return ShouldShowRingPromoUseCase(promoSettingsRepository)
- }
-
- @Provides
- @Singleton
- fun provideShouldShowSwapPromoTokenUseCase(
- promoSettingsRepository: PromoSettingsRepository,
- ): ShouldShowSwapPromoTokenUseCase {
- return ShouldShowSwapPromoTokenUseCase(promoSettingsRepository)
- }
-
@Provides
@Singleton
fun provideDeleteDeprecatedLogsUseCase(settingsRepository: SettingsRepository): DeleteDeprecatedLogsUseCase {
diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
index 9bea3ac624..1014dca16a 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
@@ -1,12 +1,10 @@
package com.tangem.tap.di.domain
import com.tangem.domain.exchange.RampStateManager
-import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.repository.*
import com.tangem.domain.walletmanager.WalletManagersFacade
-import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@@ -118,13 +116,9 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
- swapRepository: SwapRepository,
currencyChecksRepository: CurrencyChecksRepository,
- showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
- promoRepository: PromoRepository,
stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider,
- rampStateManager: RampStateManager,
): GetCurrencyWarningsUseCase {
return GetCurrencyWarningsUseCase(
walletManagersFacade = walletManagersFacade,
@@ -132,12 +126,8 @@ internal object TokensDomainModule {
quotesRepository = quotesRepository,
networksRepository = networksRepository,
currencyChecksRepository = currencyChecksRepository,
- swapRepository = swapRepository,
- showSwapPromoTokenUseCase = showSwapPromoTokenUseCase,
- promoRepository = promoRepository,
stakingRepository = stakingRepository,
dispatchers = dispatchers,
- rampStateManager = rampStateManager,
)
}
diff --git a/app/src/main/java/com/tangem/tap/di/routing/RoutingComponentModule.kt b/app/src/main/java/com/tangem/tap/di/routing/RoutingComponentModule.kt
index aca0f1678a..628d889d40 100644
--- a/app/src/main/java/com/tangem/tap/di/routing/RoutingComponentModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/routing/RoutingComponentModule.kt
@@ -1,7 +1,7 @@
package com.tangem.tap.di.routing
-import com.tangem.tap.routing.RoutingComponent
-import com.tangem.tap.routing.impl.DefaultRoutingComponent
+import com.tangem.tap.routing.component.RoutingComponent
+import com.tangem.tap.routing.component.impl.DefaultRoutingComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
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/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
index fd477038f6..d4d46d362c 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
@@ -59,7 +59,6 @@ class WalletConnectMiddleware {
is WalletConnectAction.StartWalletConnect -> {
val uri = action.copiedUri
if (uri != null && isWalletConnectUri(uri)) {
- // TODO check
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
} else {
store.dispatchNavigationAction { push(AppRoute.QrScanning(SourceType.WALLET_CONNECT)) }
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..b25fe77799 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
@@ -5,21 +5,19 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.*
+import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
import com.tangem.wallet.R
import kotlinx.collections.immutable.persistentListOf
@@ -30,8 +28,6 @@ internal fun WalletConnectScreen(
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
- val context = LocalContext.current
-
SettingsScreensScaffold(
modifier = modifier,
content = {
@@ -46,7 +42,7 @@ internal fun WalletConnectScreen(
AddSessionFab(
onAddSession = {
Analytics.send(Settings.ButtonStartWalletConnectSession())
- state.onAddSession(context.getFromClipboard()?.toString())
+ state.onAddSession()
},
)
}
@@ -60,7 +56,7 @@ internal fun WalletConnectScreen(
private fun AddSessionFab(onAddSession: () -> Unit, modifier: Modifier = Modifier) {
FloatingActionButton(
onClick = onAddSession,
- backgroundColor = TangemTheme.colors.button.primary,
+ containerColor = TangemTheme.colors.button.primary,
contentColor = TangemTheme.colors.icon.primary2,
shape = RoundedCornerShape(16.dp),
modifier = modifier,
@@ -96,7 +92,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/details/ui/walletconnect/WalletConnectScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt
index a9a526616d..0a3d429cd6 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt
@@ -6,7 +6,7 @@ internal data class WalletConnectScreenState(
val sessions: ImmutableList,
val isLoading: Boolean = false,
val onRemoveSession: (String) -> Unit = {},
- val onAddSession: (String?) -> Unit = {},
+ val onAddSession: () -> Unit = {},
)
data class WcSessionForScreen(
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt
index 284583f633..2ee11076ee 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.features.details.ui.walletconnect
import androidx.lifecycle.*
import arrow.core.getOrElse
+import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
@@ -17,6 +18,7 @@ import javax.inject.Inject
@HiltViewModel
internal class WalletConnectViewModel @Inject constructor(
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
+ private val clipboardManager: ClipboardManager,
) : ViewModel(), DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
@@ -35,7 +37,11 @@ internal class WalletConnectViewModel @Inject constructor(
sessions.toImmutableList(),
isLoading = state.loading,
onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, sessions) },
- onAddSession = { copiedUri -> store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) },
+ onAddSession = {
+ store.dispatch(
+ WalletConnectAction.StartWalletConnect(copiedUri = clipboardManager.getText()),
+ )
+ },
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt
index f33229cf24..88f9d53d3b 100644
--- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt
@@ -57,6 +57,7 @@ internal fun StoriesScreen(
}
}
+ // todo refactor [REDACTED_TASK_KEY]
StoriesScreenContent(
modifier = Modifier
.fillMaxSize()
@@ -75,6 +76,7 @@ internal fun StoriesScreen(
)
}
+@Deprecated("Use StoriesContainer from core/ui")
@Suppress("LongMethod")
@Composable
private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: Modifier = Modifier) {
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/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
index 5341631157..89d3c728a6 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt
@@ -4,6 +4,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import org.rekotlin.StateType
+// todo refactor [REDACTED_TASK_KEY]
data class HomeState(
val scanInProgress: Boolean = false,
val stories: ImmutableList = Stories.entries.toImmutableList(),
diff --git a/app/src/main/java/com/tangem/tap/features/main/MainScreenStateHolder.kt b/app/src/main/java/com/tangem/tap/features/main/MainScreenStateHolder.kt
index 9bbeded200..c2c010454e 100644
--- a/app/src/main/java/com/tangem/tap/features/main/MainScreenStateHolder.kt
+++ b/app/src/main/java/com/tangem/tap/features/main/MainScreenStateHolder.kt
@@ -35,7 +35,7 @@ internal class MainScreenStateHolder(private val intents: MainIntents) {
stateFlowInternal.update { state ->
state.copy(
modalNotification = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = intents::onDismissBottomSheet,
content = notificationsFactory.createBalancesAreHiddenModalNotification(),
),
@@ -45,7 +45,7 @@ internal class MainScreenStateHolder(private val intents: MainIntents) {
fun updateWithoutModalNotification() {
stateFlowInternal.update { state ->
- state.copy(modalNotification = state.modalNotification?.copy(isShow = false))
+ state.copy(modalNotification = state.modalNotification?.copy(isShown = false))
}
}
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 d4f05a284a..375b285fb1 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")
}
}
@@ -129,7 +125,7 @@ internal class MainViewModel @Inject constructor(
it.isBalanceHidingNotificationEnabled && it.isBalanceHidden
}
.onEach {
- if (state.value.modalNotification?.isShow != true && !it.isUpdateFromToast) {
+ if (state.value.modalNotification?.isShown != true && !it.isUpdateFromToast) {
listenToFlipsUseCase.changeUpdateEnabled(false)
stateHolder.updateWithHiddenBalancesNotification()
router.push(AppRoute.ModalNotification)
diff --git a/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt
index ef72720cad..6e4e1517e4 100644
--- a/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/main/ui/ModalNotificationBottomSheetFragment.kt
@@ -50,7 +50,7 @@ internal class ModalNotificationBottomSheetFragment : ComposeBottomSheetFragment
}
LaunchedEffect(notification) {
- if (!notification.isShow) {
+ if (!notification.isShown) {
dismiss()
}
}
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/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt
index 7f20094f7b..60ecd8a44e 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt
@@ -37,7 +37,6 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.di.RootAppComponentContext
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.extensions.setStatusBarColor
-import com.tangem.core.ui.message.EventMessageEffect
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.windowsize.rememberWindowSize
import com.tangem.datasource.utils.isNullOrEmpty
@@ -301,10 +300,6 @@ class OnboardingWalletFragment :
windowSize = rememberWindowSize(activity = requireActivity()),
) {
onboardingManageTokensComponent?.Content(modifier = Modifier.fillMaxSize())
- EventMessageEffect(
- messageHandler = uiDependencies.eventMessageHandler,
- snackbarHostState = uiDependencies.globalSnackbarHostState,
- )
}
}
}
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 5b5d56d9d3..392f1aacd9 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,23 +39,22 @@ 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)
}
}
- 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)
}
}
@@ -73,7 +70,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)
@@ -92,15 +89,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/component/DefaultWelcomeComponent.kt b/app/src/main/java/com/tangem/tap/features/welcome/component/DefaultWelcomeComponent.kt
new file mode 100644
index 0000000000..b90ec7a20f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/welcome/component/DefaultWelcomeComponent.kt
@@ -0,0 +1,36 @@
+package com.tangem.tap.features.welcome.component
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.tangem.core.decompose.context.AppComponentContext
+import com.tangem.core.decompose.model.getOrCreateModel
+import com.tangem.tap.features.welcome.model.WelcomeModel
+import com.tangem.tap.features.welcome.ui.components.WelcomeScreen
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+internal class DefaultWelcomeComponent @AssistedInject constructor(
+ @Assisted context: AppComponentContext,
+ @Assisted params: WelcomeComponent.Params,
+) : WelcomeComponent, AppComponentContext by context {
+
+ private val model: WelcomeModel = getOrCreateModel(params)
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ val state by model.state.collectAsStateWithLifecycle()
+
+ WelcomeScreen(
+ modifier = modifier,
+ state = state,
+ )
+ }
+
+ @AssistedFactory
+ interface Factory : WelcomeComponent.Factory {
+ override fun create(context: AppComponentContext, params: WelcomeComponent.Params): DefaultWelcomeComponent
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/component/WelcomeComponent.kt b/app/src/main/java/com/tangem/tap/features/welcome/component/WelcomeComponent.kt
new file mode 100644
index 0000000000..07a92373e1
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/welcome/component/WelcomeComponent.kt
@@ -0,0 +1,14 @@
+package com.tangem.tap.features.welcome.component
+
+import com.tangem.common.routing.entity.SerializableIntent
+import com.tangem.core.decompose.factory.ComponentFactory
+import com.tangem.core.ui.decompose.ComposableContentComponent
+
+interface WelcomeComponent : ComposableContentComponent {
+
+ data class Params(
+ val intent: SerializableIntent?,
+ )
+
+ interface Factory : ComponentFactory
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/component/impl/PreviewWelcomeComponent.kt b/app/src/main/java/com/tangem/tap/features/welcome/component/impl/PreviewWelcomeComponent.kt
new file mode 100644
index 0000000000..94c08e4f70
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/welcome/component/impl/PreviewWelcomeComponent.kt
@@ -0,0 +1,20 @@
+package com.tangem.tap.features.welcome.component.impl
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.tangem.tap.features.welcome.component.WelcomeComponent
+import com.tangem.tap.features.welcome.ui.WelcomeScreenState
+import com.tangem.tap.features.welcome.ui.components.WelcomeScreen
+
+internal class PreviewWelcomeComponent(
+ private val initialState: WelcomeScreenState = WelcomeScreenState(),
+) : WelcomeComponent {
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ WelcomeScreen(
+ modifier = modifier,
+ state = initialState,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/di/ComponentModule.kt b/app/src/main/java/com/tangem/tap/features/welcome/di/ComponentModule.kt
new file mode 100644
index 0000000000..68f1c6121f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/welcome/di/ComponentModule.kt
@@ -0,0 +1,18 @@
+package com.tangem.tap.features.welcome.di
+
+import com.tangem.tap.features.welcome.component.DefaultWelcomeComponent
+import com.tangem.tap.features.welcome.component.WelcomeComponent
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal interface ComponentModule {
+
+ @Binds
+ @Singleton
+ fun bindWelcomeComponentFactory(factory: DefaultWelcomeComponent.Factory): WelcomeComponent.Factory
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/di/ModelModule.kt b/app/src/main/java/com/tangem/tap/features/welcome/di/ModelModule.kt
new file mode 100644
index 0000000000..1a90bd0bc5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/welcome/di/ModelModule.kt
@@ -0,0 +1,20 @@
+package com.tangem.tap.features.welcome.di
+
+import com.tangem.core.decompose.di.DecomposeComponent
+import com.tangem.core.decompose.model.Model
+import com.tangem.tap.features.welcome.model.WelcomeModel
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+@InstallIn(DecomposeComponent::class)
+internal interface ModelModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(WelcomeModel::class)
+ fun bindWelcomeModel(model: WelcomeModel): Model
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt
new file mode 100644
index 0000000000..143654b18d
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt
@@ -0,0 +1,137 @@
+package com.tangem.tap.features.welcome.model
+
+import com.tangem.common.core.TangemError
+import com.tangem.core.analytics.Analytics
+import com.tangem.core.decompose.model.Model
+import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.core.navigation.finisher.AppFinisher
+import com.tangem.domain.wallets.legacy.UserWalletsListError
+import com.tangem.tap.common.analytics.events.SignIn
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.features.details.ui.cardsettings.TextReference
+import com.tangem.tap.features.welcome.component.WelcomeComponent
+import com.tangem.tap.features.welcome.redux.WelcomeAction
+import com.tangem.tap.features.welcome.redux.WelcomeState
+import com.tangem.tap.features.welcome.ui.WelcomeScreenState
+import com.tangem.tap.features.welcome.ui.model.WarningModel
+import com.tangem.tap.store
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
+import org.rekotlin.StoreSubscriber
+import javax.inject.Inject
+
+// FIXME: Remove redux: [REDACTED_JIRA]
+internal class WelcomeModel @Inject constructor(
+ override val dispatchers: CoroutineDispatcherProvider,
+ private val appFinisher: AppFinisher,
+ paramsContainer: ParamsContainer,
+) : Model(), StoreSubscriber {
+
+ private val params: WelcomeComponent.Params = paramsContainer.require()
+ private val initialState: WelcomeScreenState = WelcomeScreenState(
+ onPopBack = appFinisher::finish,
+ onUnlockClick = this::unlockWallets,
+ onScanCardClick = this::scanCard,
+ onCloseError = this::closeError,
+ )
+
+ val state: MutableStateFlow = MutableStateFlow(initialState)
+
+ init {
+ store.dispatch(WelcomeAction.SetCoroutineScope(modelScope))
+
+ subscribeToStoreChanges()
+ initGlobalState()
+
+ val welcomeAction = if (params.intent != null) {
+ WelcomeAction.ProceedWithIntent(params.intent.toIntent())
+ } else {
+ WelcomeAction.ProceedWithBiometrics()
+ }
+
+ store.dispatch(welcomeAction)
+ }
+
+ private fun unlockWallets() {
+ Analytics.send(SignIn.ButtonBiometricSignIn())
+ store.dispatch(WelcomeAction.ProceedWithBiometrics())
+ }
+
+ private fun scanCard() {
+ Analytics.send(SignIn.ButtonCardSignIn())
+ store.dispatch(WelcomeAction.ProceedWithCard)
+ }
+
+ private fun closeError() {
+ store.dispatch(WelcomeAction.CloseError)
+ }
+
+ override fun newState(state: WelcomeState) {
+ val warning = createWarningIfNeeded(state.error)
+
+ this.state.update { prevState ->
+ prevState.copy(
+ showUnlockWithBiometricsProgress = state.isUnlockWithBiometricsInProgress,
+ showUnlockWithCardProgress = state.isUnlockWithCardInProgress,
+ warning = warning,
+ error = state.error
+ ?.takeIf { !it.silent && warning == null }
+ ?.let { e ->
+ e.messageResId?.let { TextReference.Res(it) }
+ ?: TextReference.Str(e.customMessage)
+ },
+ )
+ }
+ }
+
+ override fun onDestroy() {
+ store.dispatch(WelcomeAction.ClearCoroutineScope)
+ store.unsubscribe(subscriber = this)
+
+ super.onDestroy()
+ }
+
+ private fun createWarningIfNeeded(error: TangemError?): WarningModel? {
+ return when (error) {
+ is UserWalletsListError.BiometricsAuthenticationLockout -> WarningModel.BiometricsLockoutWarning(
+ isPermanent = error.isPermanent,
+ onDismiss = this::dismissWarning,
+ )
+ is UserWalletsListError.AllKeysInvalidated,
+ is UserWalletsListError.NoUserWalletSelected,
+ -> WarningModel.KeyInvalidatedWarning(
+ onDismiss = this::dismissWarning,
+ )
+ is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning(
+ onDismiss = this::clearUserWallets,
+ )
+ else -> null
+ }
+ }
+
+ private fun dismissWarning() {
+ state.update { prevState ->
+ prevState.copy(
+ warning = null,
+ )
+ }
+ closeError()
+ }
+
+ private fun clearUserWallets() {
+ store.dispatch(WelcomeAction.ClearUserWallets)
+ }
+
+ private fun subscribeToStoreChanges() {
+ store.subscribe(this) { appState ->
+ appState.skip { old, new -> old.welcomeState == new.welcomeState }
+ .select { it.welcomeState }
+ }
+ }
+
+ private fun initGlobalState() {
+ store.dispatch(GlobalAction.RestoreAppCurrency)
+ store.dispatch(GlobalAction.ExchangeManager.Init)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt
index 701042816e..3ede2ae446 100644
--- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt
+++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt
@@ -1,13 +1,15 @@
package com.tangem.tap.features.welcome.ui
-import androidx.compose.runtime.Immutable
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.tap.features.welcome.ui.model.WarningModel
-@Immutable
internal data class WelcomeScreenState(
+ val onPopBack: () -> Unit = {},
val showUnlockWithBiometricsProgress: Boolean = false,
val showUnlockWithCardProgress: Boolean = false,
val warning: WarningModel? = null,
val error: TextReference? = null,
+ val onUnlockClick: () -> Unit = {},
+ val onScanCardClick: () -> Unit = {},
+ val onCloseError: () -> Unit = {},
)
\ No newline at end of file
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/WelcomeScreen.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreen.kt
new file mode 100644
index 0000000000..8b90cbbdc9
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreen.kt
@@ -0,0 +1,101 @@
+package com.tangem.tap.features.welcome.ui.components
+
+import android.content.res.Configuration
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.material.SnackbarHost
+import androidx.compose.material.SnackbarHostState
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.tap.features.details.ui.cardsettings.TextReference
+import com.tangem.tap.features.details.ui.cardsettings.resolveReference
+import com.tangem.tap.features.welcome.component.WelcomeComponent
+import com.tangem.tap.features.welcome.component.impl.PreviewWelcomeComponent
+import com.tangem.tap.features.welcome.ui.WelcomeScreenState
+import com.tangem.tap.features.welcome.ui.model.WarningModel
+
+@Composable
+internal fun WelcomeScreen(state: WelcomeScreenState, modifier: Modifier = Modifier) {
+ val snackbarHostState = remember { SnackbarHostState() }
+ val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
+ val warning by rememberUpdatedState(newValue = state.warning)
+
+ BackHandler(onBack = state.onPopBack)
+
+ Box(
+ modifier = modifier
+ .background(TangemTheme.colors.background.primary)
+ .systemBarsPadding(),
+ ) {
+ WelcomeScreenContent(
+ showUnlockProgress = state.showUnlockWithBiometricsProgress,
+ showScanCardProgress = state.showUnlockWithCardProgress,
+ onUnlockClick = state.onUnlockClick,
+ onScanCardClick = state.onScanCardClick,
+ )
+
+ SnackbarHost(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(vertical = 16.dp)
+ .fillMaxWidth(),
+ hostState = snackbarHostState,
+ )
+ }
+
+ WarningDialog(warning)
+
+ LaunchedEffect(errorMessage, state.onCloseError) {
+ errorMessage?.let {
+ snackbarHostState.showSnackbar(it)
+ state.onCloseError()
+ }
+ }
+}
+
+// region Preview
+@Composable
+@Preview(showBackground = true, widthDp = 360)
+@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
+private fun Preview_WelcomeScreen(
+ @PreviewParameter(WelcomeComponentPreviewProvider::class) component: WelcomeComponent,
+) {
+ TangemThemePreview {
+ component.Content(Modifier)
+ }
+}
+
+private class WelcomeComponentPreviewProvider : PreviewParameterProvider {
+ override val values: Sequence
+ get() = sequenceOf(
+ PreviewWelcomeComponent(),
+ PreviewWelcomeComponent(
+ initialState = WelcomeScreenState(
+ showUnlockWithBiometricsProgress = true,
+ showUnlockWithCardProgress = true,
+ ),
+ ),
+ PreviewWelcomeComponent(
+ initialState = WelcomeScreenState(
+ error = TextReference.Str(value = "Error"),
+ ),
+ ),
+ PreviewWelcomeComponent(
+ initialState = WelcomeScreenState(
+ warning = WarningModel.KeyInvalidatedWarning(onDismiss = {}),
+ ),
+ ),
+ )
+}
+// endregion Preview
\ No newline at end of file
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..c9bbbdf31c 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
@@ -24,8 +24,12 @@ internal fun WelcomeScreenContent(
showScanCardProgress: Boolean,
onUnlockClick: () -> Unit,
onScanCardClick: () -> Unit,
+ modifier: Modifier = Modifier,
) {
- Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Column(
+ modifier = modifier,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
SpacerHMax()
Icon(
modifier = Modifier.size(TangemTheme.dimens.size96),
@@ -38,7 +42,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 +52,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 +65,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 +77,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/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt
index 5ee9a4ce7f..5488669d34 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt
@@ -109,6 +109,7 @@ internal val Blockchain.mercuryoNetwork: String?
Blockchain.PolygonZkEVM -> null
Blockchain.PolygonZkEVMTestnet -> null
Blockchain.Radiant -> null
+ Blockchain.Fact0rn -> null
Blockchain.BaseTestnet -> null
Blockchain.Moonriver -> null
Blockchain.MoonriverTestnet -> null
@@ -140,6 +141,8 @@ internal val Blockchain.mercuryoNetwork: String?
Blockchain.CoreTestnet -> null
Blockchain.Chiliz -> null
Blockchain.ChilizTestnet -> null
+ Blockchain.VanarChain -> null
+ Blockchain.VanarChainTestnet -> null
Blockchain.Unknown -> null
Blockchain.Xodex -> null
Blockchain.Canxium -> null
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt
index a4de7a4df9..4a16217522 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt
@@ -114,6 +114,7 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
PolygonZkEVM -> null
PolygonZkEVMTestnet -> null
Radiant -> null
+ Fact0rn -> null
BaseTestnet -> null
Moonriver -> null
MoonriverTestnet -> null
@@ -145,4 +146,6 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
Xodex -> null
Canxium -> null
Clore -> null
+ VanarChain -> null
+ VanarChainTestnet -> null
}
\ No newline at end of file
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..ef18e4fe77 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
@@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
+import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
@@ -35,7 +36,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 +69,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,
@@ -77,4 +76,5 @@ data class DaggerGraphState(
val onboardingRepository: OnboardingRepository? = null,
val excludedBlockchains: ExcludedBlockchains? = null,
val appPreferencesStore: AppPreferencesStore? = null,
+ val clipboardManager: ClipboardManager? = null,
) : StateType
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/routing/RootContent.kt b/app/src/main/java/com/tangem/tap/routing/RootContent.kt
new file mode 100644
index 0000000000..077688613f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/routing/RootContent.kt
@@ -0,0 +1,71 @@
+package com.tangem.tap.routing
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.core.content.ContextCompat.startActivity
+import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
+import com.arkivanov.decompose.router.stack.ChildStack
+import com.tangem.common.routing.AppRoute
+import com.tangem.core.ui.UiDependencies
+import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
+import com.tangem.core.ui.message.EventMessageEffect
+import com.tangem.core.ui.res.LocalSnackbarHostState
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.utils.WindowInsetsZero
+import com.tangem.tap.routing.component.RoutingComponent
+
+@Composable
+internal fun RootContent(
+ stack: ChildStack,
+ uiDependencies: UiDependencies,
+ modifier: Modifier = Modifier,
+) {
+ val context = LocalContext.current
+
+ TangemTheme(
+ activity = context as Activity,
+ uiDependencies = uiDependencies,
+ ) {
+ val snackbarHostState = LocalSnackbarHostState.current
+
+ Scaffold(
+ modifier = modifier,
+ containerColor = TangemTheme.colors.background.primary,
+ snackbarHost = {
+ TangemSnackbarHost(
+ modifier = Modifier.padding(all = 16.dp),
+ hostState = snackbarHostState,
+ )
+ },
+ contentWindowInsets = WindowInsetsZero,
+ content = { paddingValues ->
+ Children(
+ modifier = Modifier.padding(paddingValues),
+ stack = stack,
+ ) { child ->
+ when (val instance = child.instance) {
+ is RoutingComponent.Child.Initial -> Unit
+ is RoutingComponent.Child.ComposableComponent -> {
+ instance.component.Content(Modifier.fillMaxSize())
+ }
+ is RoutingComponent.Child.LegacyIntent -> {
+ // TODO: Remove and use it's own router: [REDACTED_JIRA]
+ startActivity(context, instance.intent, Bundle.EMPTY)
+ }
+ is RoutingComponent.Child.LegacyFragment,
+ -> error("Unsupported child: $instance")
+ }
+ }
+ },
+ )
+
+ EventMessageEffect()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/routing/RoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt
similarity index 59%
rename from app/src/main/java/com/tangem/tap/routing/RoutingComponent.kt
rename to app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt
index a7f5148b19..97fa1777e1 100644
--- a/app/src/main/java/com/tangem/tap/routing/RoutingComponent.kt
+++ b/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt
@@ -1,22 +1,28 @@
-package com.tangem.tap.routing
+package com.tangem.tap.routing.component
import android.content.Intent
+import androidx.compose.runtime.Immutable
import androidx.fragment.app.Fragment
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.value.Value
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.navigation.Router
+import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.utils.Provider
-internal interface RoutingComponent {
+internal interface RoutingComponent : ComposableContentComponent {
+ // TODO: Remove after full navigation refactoring
val router: Router
+ // TODO: Remove after full navigation refactoring
val stack: Value>
+ @Immutable
sealed class Child {
+ // TODO: Remove and find initial intent in RoutingComponent: [REDACTED_JIRA]
data object Initial : Child()
data class LegacyFragment(
@@ -25,6 +31,10 @@ internal interface RoutingComponent {
) : Child()
data class LegacyIntent(val intent: Intent) : Child()
+
+ data class ComposableComponent(
+ val component: ComposableContentComponent,
+ ) : Child()
}
interface Factory {
diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt
new file mode 100644
index 0000000000..2f709d7943
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt
@@ -0,0 +1,128 @@
+package com.tangem.tap.routing.component.impl
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
+import com.arkivanov.decompose.router.stack.ChildStack
+import com.arkivanov.decompose.router.stack.childStack
+import com.arkivanov.decompose.value.Value
+import com.arkivanov.decompose.value.observe
+import com.arkivanov.essenty.backhandler.BackCallback
+import com.arkivanov.essenty.lifecycle.doOnDestroy
+import com.google.android.material.snackbar.Snackbar
+import com.tangem.common.routing.AppRoute
+import com.tangem.core.decompose.context.AppComponentContext
+import com.tangem.core.decompose.context.childByContext
+import com.tangem.core.decompose.navigation.getOrCreateTyped
+import com.tangem.core.ui.UiDependencies
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.message.SnackbarMessage
+import com.tangem.tap.common.SnackbarHandler
+import com.tangem.tap.routing.RootContent
+import com.tangem.tap.routing.component.RoutingComponent
+import com.tangem.tap.routing.component.RoutingComponent.Child
+import com.tangem.tap.routing.configurator.AppRouterConfig
+import com.tangem.tap.routing.toggle.RoutingFeatureToggles
+import com.tangem.tap.routing.utils.ChildFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+@Suppress("LongParameterList")
+internal class DefaultRoutingComponent @AssistedInject constructor(
+ @Assisted context: AppComponentContext,
+ private val childFactory: ChildFactory,
+ private val appRouterConfig: AppRouterConfig,
+ private val uiDependencies: UiDependencies,
+ routingFeatureToggles: RoutingFeatureToggles,
+) : RoutingComponent,
+ AppComponentContext by context,
+ SnackbarHandler {
+
+ private val backCallback = BackCallback(priority = Int.MIN_VALUE, onBack = router::pop)
+
+ override val stack: Value> = childStack(
+ source = navigationProvider.getOrCreateTyped(),
+ serializer = AppRoute.serializer(),
+ initialConfiguration = getInitialRoute(),
+ handleBackButton = false,
+ childFactory = ::child,
+ )
+
+ init {
+ backHandler.register(backCallback)
+
+ lifecycle.doOnDestroy {
+ childFactory.doOnDestroy()
+ }
+
+ if (routingFeatureToggles.isNavigationRefactoringEnabled) {
+ appRouterConfig.routerScope = componentScope
+ appRouterConfig.componentRouter = router
+ appRouterConfig.snackbarHandler = this
+
+ stack.observe(lifecycle) { stack ->
+ val stackItems = stack.items.map { it.configuration }
+
+ if (appRouterConfig.stack != stackItems) {
+ appRouterConfig.stack = stackItems
+ }
+ }
+ }
+ }
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ val stack by this.stack.subscribeAsState()
+
+ RootContent(
+ modifier = modifier,
+ stack = stack,
+ uiDependencies = uiDependencies,
+ )
+ }
+
+ override fun showSnackbar(text: Int, length: Int, buttonTitle: Int?, action: (() -> Unit)?) {
+ showSnackbar(
+ text = resourceReference(text),
+ length = length,
+ buttonTitle = buttonTitle?.let(::resourceReference),
+ action = action,
+ )
+ }
+
+ override fun showSnackbar(text: TextReference, length: Int, buttonTitle: TextReference?, action: (() -> Unit)?) {
+ val message = SnackbarMessage(
+ message = text,
+ duration = when (length) {
+ Snackbar.LENGTH_SHORT -> SnackbarMessage.Duration.Short
+ Snackbar.LENGTH_LONG -> SnackbarMessage.Duration.Long
+ Snackbar.LENGTH_INDEFINITE -> SnackbarMessage.Duration.Indefinite
+ else -> SnackbarMessage.Duration.Short
+ },
+ actionLabel = buttonTitle,
+ action = action,
+ )
+
+ messageSender.send(message)
+ }
+
+ override fun dismissSnackbar() {
+ messageSender.send(SnackbarMessage(message = TextReference.EMPTY))
+ }
+
+ // TODO: Find correct initial route here: [REDACTED_JIRA]
+ private fun getInitialRoute(): AppRoute = AppRoute.Initial
+
+ private fun child(route: AppRoute, context: ComponentContext): Child {
+ return childFactory.createChild(route) { childByContext(context) }
+ }
+
+ @AssistedFactory
+ interface Factory : RoutingComponent.Factory {
+ override fun create(context: AppComponentContext): DefaultRoutingComponent
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/routing/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/impl/DefaultRoutingComponent.kt
deleted file mode 100644
index 77283b4c75..0000000000
--- a/app/src/main/java/com/tangem/tap/routing/impl/DefaultRoutingComponent.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.tangem.tap.routing.impl
-
-import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.router.stack.ChildStack
-import com.arkivanov.decompose.router.stack.childStack
-import com.arkivanov.decompose.value.Value
-import com.arkivanov.essenty.backhandler.BackCallback
-import com.arkivanov.essenty.lifecycle.doOnDestroy
-import com.tangem.common.routing.AppRoute
-import com.tangem.core.decompose.context.AppComponentContext
-import com.tangem.core.decompose.context.childByContext
-import com.tangem.core.decompose.navigation.getOrCreateTyped
-import com.tangem.tap.routing.RoutingComponent
-import com.tangem.tap.routing.RoutingComponent.Child
-import com.tangem.tap.routing.utils.ChildFactory
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-
-@Suppress("LongParameterList")
-internal class DefaultRoutingComponent @AssistedInject constructor(
- @Assisted context: AppComponentContext,
- private val childFactory: ChildFactory,
-) : RoutingComponent, AppComponentContext by context {
-
- private val backCallback = BackCallback(priority = Int.MIN_VALUE, onBack = router::pop)
-
- override val stack: Value> = childStack(
- source = navigationProvider.getOrCreateTyped(),
- serializer = AppRoute.serializer(), // TODO Maybe set this to null for AppRoute.Onboarding case. Need to check
- initialConfiguration = getInitialRoute(),
- handleBackButton = false,
- childFactory = ::child,
- )
-
- init {
- backHandler.register(backCallback)
-
- lifecycle.doOnDestroy {
- childFactory.doOnDestroy()
- }
- }
-
- private fun getInitialRoute(): AppRoute = AppRoute.Initial
-
- private fun child(route: AppRoute, context: ComponentContext): Child {
- return childFactory.createChild(route) { childByContext(context) }
- }
-
- @AssistedFactory
- interface Factory : RoutingComponent.Factory {
- override fun create(context: AppComponentContext): DefaultRoutingComponent
- }
-}
\ No newline at end of file
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..960a2bd499 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
@@ -33,15 +33,17 @@ import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCar
import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragment
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
+import com.tangem.tap.features.welcome.component.WelcomeComponent
import com.tangem.tap.features.welcome.ui.WelcomeFragment
-import com.tangem.tap.routing.RoutingComponent.Child
+import com.tangem.tap.routing.component.RoutingComponent.Child
+import com.tangem.tap.routing.toggle.RoutingFeatureToggles
import com.tangem.utils.Provider
import dagger.hilt.android.scopes.ActivityScoped
import java.util.WeakHashMap
import javax.inject.Inject
@ActivityScoped
-@Suppress("LongParameterList")
+@Suppress("LongParameterList", "LargeClass")
internal class ChildFactory @Inject constructor(
private val detailsComponentFactory: DetailsComponent.Factory,
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
@@ -54,6 +56,7 @@ internal class ChildFactory @Inject constructor(
private val sellCryptoComponentFactory: SellCryptoComponent.Factory,
private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory,
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
+ private val welcomeComponentFactory: WelcomeComponent.Factory,
private val sendRouter: SendRouter,
private val tokenDetailsRouter: TokenDetailsRouter,
private val walletRouter: WalletRouter,
@@ -61,12 +64,174 @@ 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) {
+ createChildNew(route, contextFactory)
+ } else {
+ createChildLegacy(route, contextFactory)
+ }
+ }
+
+ @Suppress("LongMethod", "CyclomaticComplexMethod")
+ private fun createChildNew(route: AppRoute, contextFactory: (route: AppRoute) -> AppComponentContext): Child {
componentContexts[route] = contextFactory(route)
+ // region Child creation
+ return when (route) {
+ is AppRoute.Initial -> {
+ Child.Initial
+ }
+ is AppRoute.Details -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = DetailsComponent.Params(route.userWalletId),
+ componentFactory = detailsComponentFactory,
+ )
+ }
+ is AppRoute.Disclaimer -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = DisclaimerComponent.Params(route.isTosAccepted),
+ componentFactory = disclaimerComponentFactory,
+ )
+ }
+ is AppRoute.ManageTokens -> {
+ val source = when (route.source) {
+ AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS
+ AppRoute.ManageTokens.Source.ONBOARDING -> ManageTokensSource.ONBOARDING
+ AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES
+ }
+
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = ManageTokensComponent.Params(route.userWalletId, source),
+ componentFactory = manageTokensComponentFactory,
+ )
+ }
+ is AppRoute.Welcome -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = WelcomeComponent.Params(
+ intent = route.intent,
+ ),
+ componentFactory = welcomeComponentFactory,
+ )
+ }
+ is AppRoute.TesterMenu -> {
+ Child.LegacyIntent(testerRouter.getEntryIntent())
+ }
+ is AppRoute.WalletSettings -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = WalletSettingsComponent.Params(route.userWalletId),
+ componentFactory = walletSettingsComponentFactory,
+ )
+ }
+ is AppRoute.MarketsTokenDetails -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = MarketsTokenDetailsComponent.Params(
+ token = route.token,
+ appCurrency = route.appCurrency,
+ showPortfolio = route.showPortfolio,
+ analyticsParams = route.analyticsParams?.let {
+ MarketsTokenDetailsComponent.AnalyticsParams(
+ blockchain = it.blockchain,
+ source = it.source,
+ )
+ },
+ ),
+ componentFactory = marketsTokenDetailsComponentFactory,
+ )
+ }
+ is AppRoute.Onramp -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = OnrampComponent.Params(
+ userWalletId = route.userWalletId,
+ cryptoCurrency = route.currency,
+ source = route.source,
+ ),
+ componentFactory = onrampComponentFactory,
+ )
+ }
+ is AppRoute.OnrampSuccess -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = OnrampSuccessComponent.Params(route.externalTxId),
+ componentFactory = onrampSuccessComponentFactory,
+ )
+ }
+ is AppRoute.BuyCrypto -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = BuyCryptoComponent.Params(userWalletId = route.userWalletId),
+ componentFactory = buyCryptoComponentFactory,
+ )
+ }
+ is AppRoute.SellCrypto -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = SellCryptoComponent.Params(userWalletId = route.userWalletId),
+ componentFactory = sellCryptoComponentFactory,
+ )
+ }
+ is AppRoute.SwapCrypto -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = SwapSelectTokensComponent.Params(userWalletId = route.userWalletId),
+ componentFactory = swapSelectTokensComponentFactory,
+ )
+ }
+ is AppRoute.Onboarding -> {
+ createComponentChild(
+ contextProvider = contextProvider(route, contextFactory),
+ params = OnboardingEntryComponent.Params(
+ scanResponse = route.scanResponse,
+ startBackupFlow = route.startFromBackup,
+ multiWalletMode = when (route.mode) {
+ AppRoute.Onboarding.Mode.Onboarding -> OnboardingEntryComponent.MultiWalletMode.Onboarding
+ AppRoute.Onboarding.Mode.AddBackup -> OnboardingEntryComponent.MultiWalletMode.AddBackup
+ },
+ ),
+ componentFactory = onboardingEntryComponentFactory,
+ )
+ }
+ is AppRoute.AccessCodeRecovery,
+ is AppRoute.AppCurrencySelector,
+ is AppRoute.ModalNotification,
+ is AppRoute.SaveWallet,
+ is AppRoute.Send,
+ is AppRoute.AppSettings,
+ is AppRoute.CardSettings,
+ is AppRoute.DetailsSecurity,
+ is AppRoute.Home,
+ is AppRoute.OnboardingNote,
+ is AppRoute.OnboardingOther,
+ is AppRoute.OnboardingTwins,
+ is AppRoute.OnboardingWallet,
+ is AppRoute.QrScanning,
+ is AppRoute.ReferralProgram,
+ is AppRoute.ResetToFactory,
+ is AppRoute.Swap,
+ is AppRoute.Wallet,
+ is AppRoute.WalletConnectSessions,
+ is AppRoute.CurrencyDetails,
+ is AppRoute.Staking,
+ is AppRoute.PushNotification,
+ -> error("Unsupported route: $route")
+ }
+ // endregion
+ }
+
+ @Suppress("LongMethod", "CyclomaticComplexMethod")
+ private fun createChildLegacy(route: AppRoute, contextFactory: (route: AppRoute) -> AppComponentContext): Child {
+ componentContexts[route] = contextFactory(route)
+
+ // region Child creation
return when (route) {
is AppRoute.Initial -> {
Child.Initial
@@ -248,6 +413,7 @@ internal class ChildFactory @Inject constructor(
)
}
}
+ // endregion
}
fun doOnDestroy() {
diff --git a/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt b/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt
index bdade79e4e..e7aa49f4d9 100644
--- a/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt
+++ b/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt
@@ -7,7 +7,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.tap.DecomposeFragment
-import com.tangem.tap.routing.RoutingComponent.Child
+import com.tangem.tap.routing.component.RoutingComponent.Child
import com.tangem.utils.Provider
internal fun AppRoute.asFragmentChild(fragmentProvider: Provider): Child {
@@ -37,4 +37,14 @@ internal fun > createComponentChild(
+ contextProvider: Provider,
+ params: P,
+ componentFactory: F,
+): Child {
+ val context = contextProvider()
+
+ return Child.ComposableComponent(componentFactory.create(context, params))
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index aa962825e1..dac7964d66 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -6,4 +6,10 @@
android:background="@color/background_primary"
android:filterTouchesWhenObscured="true">
+
+
\ No newline at end of file
diff --git a/ci_resources/build_ci_gradle.properties b/ci_resources/build_ci_gradle.properties
new file mode 100644
index 0000000000..bb0256c8c4
--- /dev/null
+++ b/ci_resources/build_ci_gradle.properties
@@ -0,0 +1,13 @@
+org.gradle.jvmargs=-Xmx7g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+org.gradle.parallel=true
+org.gradle.workers.max=8
+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=true
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/build.gradle.kts b/common/build.gradle.kts
index 6707a2bbb2..a1afc8ea37 100644
--- a/common/build.gradle.kts
+++ b/common/build.gradle.kts
@@ -1,7 +1,28 @@
plugins {
- alias(deps.plugins.kotlin.jvm)
+ alias(deps.plugins.android.library)
+ alias(deps.plugins.kotlin.android)
id("configuration")
}
+
+android {
+ namespace = "com.tangem.common"
+}
+
dependencies {
+
implementation(projects.core.utils)
+
+ // region Firebase libraries
+ implementation(platform(deps.firebase.bom))
+ implementation(deps.firebase.analytics)
+ implementation(deps.firebase.crashlytics)
+ implementation(deps.firebase.messaging)
+ // end
+
+ implementation(deps.timber)
+
+ implementation(deps.arrow.core)
+
+ implementation(deps.test.junit)
+ implementation(deps.test.truth)
}
\ No newline at end of file
diff --git a/common/src/main/java/com/tangem/common/TangemBlogUrlBuilder.kt b/common/src/main/kotlin/com/tangem/common/TangemBlogUrlBuilder.kt
similarity index 100%
rename from common/src/main/java/com/tangem/common/TangemBlogUrlBuilder.kt
rename to common/src/main/kotlin/com/tangem/common/TangemBlogUrlBuilder.kt
diff --git a/common/src/main/java/com/tangem/common/module/ModuleErrorCode.kt b/common/src/main/kotlin/com/tangem/common/module/ModuleErrorCode.kt
similarity index 100%
rename from common/src/main/java/com/tangem/common/module/ModuleErrorCode.kt
rename to common/src/main/kotlin/com/tangem/common/module/ModuleErrorCode.kt
diff --git a/common/src/main/java/com/tangem/common/module/ModuleMessage.kt b/common/src/main/kotlin/com/tangem/common/module/ModuleMessage.kt
similarity index 100%
rename from common/src/main/java/com/tangem/common/module/ModuleMessage.kt
rename to common/src/main/kotlin/com/tangem/common/module/ModuleMessage.kt
diff --git a/common/src/main/kotlin/com/tangem/common/uri/ExternalUrlValidator.kt b/common/src/main/kotlin/com/tangem/common/uri/ExternalUrlValidator.kt
new file mode 100644
index 0000000000..9f3973c29b
--- /dev/null
+++ b/common/src/main/kotlin/com/tangem/common/uri/ExternalUrlValidator.kt
@@ -0,0 +1,31 @@
+package com.tangem.common.uri
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics
+import timber.log.Timber
+import java.net.URI
+
+/**
+ * External url validator
+ *
+[REDACTED_AUTHOR]
+ */
+object ExternalUrlValidator {
+
+ private val trustedHost: List = listOf("tangem.com")
+
+ /** Check if [externalUri] is trusted */
+ fun isUriTrusted(externalUri: String): Boolean {
+ return try {
+ val uri = URI.create(externalUri)
+
+ uri.scheme == "https" && uri.host in trustedHost
+ } catch (e: Exception) {
+ val exception = IllegalStateException("Failed to validate URI: $externalUri", e)
+
+ Timber.e(exception)
+ FirebaseCrashlytics.getInstance().recordException(exception)
+
+ false
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/testDebug/kotlin/com/tangem/common/uri/ExternalUrlValidatorTest.kt b/common/src/testDebug/kotlin/com/tangem/common/uri/ExternalUrlValidatorTest.kt
new file mode 100644
index 0000000000..f03d64dde6
--- /dev/null
+++ b/common/src/testDebug/kotlin/com/tangem/common/uri/ExternalUrlValidatorTest.kt
@@ -0,0 +1,36 @@
+package com.tangem.common.uri
+
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+[REDACTED_AUTHOR]
+ */
+@RunWith(Parameterized::class)
+class ExternalUrlValidatorTest(private val model: Model) {
+
+ @Test
+ fun test() {
+ val actual = ExternalUrlValidator.isUriTrusted(externalUri = model.url)
+
+ Truth.assertThat(actual).isEqualTo(model.expected)
+ }
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Collection = listOf(
+ Model(url = "https://tangem.com", expected = true),
+ Model(url = "https://tange.com", expected = false),
+ Model(url = "https://fake.tangem.com", expected = false),
+ Model(url = "http://tangem.com", expected = false),
+ Model(url = "http://tandem.com", expected = false),
+ Model(url = "adawdawdassdw", expected = false),
+ )
+
+ data class Model(val url: String, val expected: Boolean)
+ }
+}
\ No newline at end of file
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..930c31ce42 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
@@ -7,27 +7,24 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
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
fun SendDoneButtons(
txUrl: String,
onExploreClick: () -> Unit,
- onShareClick: () -> Unit,
+ onShareClick: (String) -> Unit,
isVisible: Boolean,
modifier: Modifier = Modifier,
) {
val hapticFeedback = LocalHapticFeedback.current
- val context = LocalContext.current
AnimatedVisibility(
visible = isVisible && txUrl.isNotBlank(),
@@ -38,19 +35,18 @@ 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)
- context.shareText(txUrl)
- onShareClick()
+ onShareClick(txUrl)
},
modifier = Modifier.weight(1f),
)
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..4291b55a7f 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,
@@ -297,7 +296,7 @@ private fun Preview_GiveTxPermissionBottomSheet() {
TangemThemePreview {
GiveTxPermissionBottomSheet(
config = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {},
content = previewData,
),
diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressEstimate.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressEstimate.kt
index 85522dd450..2703481292 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressEstimate.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressEstimate.kt
@@ -7,12 +7,12 @@ 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.common.ui.R
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
@Suppress("LongParameterList")
@@ -47,7 +47,7 @@ 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/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressProvider.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressProvider.kt
index c83e14ae0a..1f8f4f8eb3 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressProvider.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/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 @@ 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 @@ 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 @@ 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/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt
index ce513e4e50..35905de8f7 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/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.state.ExpressTransactionStateUM
import com.tangem.core.ui.R
@@ -15,6 +14,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
@Composable
@@ -22,14 +22,14 @@ fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) {
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/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/excluded_blockchains_config.json b/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json
index ae77ec7b7c..a2862f948e 100644
--- a/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json
+++ b/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json
@@ -10,5 +10,13 @@
{
"name": "NEXA/test",
"version": "undefined"
+ },
+ {
+ "name": "fact0rn",
+ "version": "undefined"
+ },
+ {
+ "name": "vanar-chain",
+ "version": "undefined"
}
]
\ No newline at end of file
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 f7af0b38fc..60580e40b7 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": "undefined"
@@ -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/common/config/ApiConfig.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt
index 65bc7a1e66..a31ec2e6d8 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt
@@ -24,6 +24,7 @@ sealed class ApiConfig {
Express,
TangemTech,
StakeKit,
+ TangemVisaAuth,
}
private fun initializeId(): ID {
@@ -31,6 +32,7 @@ sealed class ApiConfig {
is Express -> ID.Express
is TangemTech -> ID.TangemTech
is StakeKit -> ID.StakeKit
+ is TangemVisaAuth -> ID.TangemVisaAuth
}
}
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt
new file mode 100644
index 0000000000..102bf875d4
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt
@@ -0,0 +1,20 @@
+package com.tangem.datasource.api.common.config
+
+import com.tangem.utils.Provider
+
+internal class TangemVisaAuth : ApiConfig() {
+
+ override val defaultEnvironment: ApiEnvironment = ApiEnvironment.STAGE
+
+ override val environmentConfigs = listOf(
+ createStageEnvironment(),
+ )
+
+ private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
+ environment = ApiEnvironment.STAGE,
+ baseUrl = "https://api-s.tangem.org/",
+ headers = createHeaders(),
+ )
+
+ private fun createHeaders() = mapOf>()
+}
\ No newline at end of file
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/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt
new file mode 100644
index 0000000000..cd538c51eb
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt
@@ -0,0 +1,5 @@
+package com.tangem.datasource.api.visa
+
+interface TangemVisaApi {
+ // TODO
+}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt
new file mode 100644
index 0000000000..858390cf7c
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt
@@ -0,0 +1,47 @@
+package com.tangem.datasource.api.visa
+
+import com.tangem.datasource.api.visa.models.response.GenerateNonceResponse
+import com.tangem.datasource.api.visa.models.response.JWTResponse
+import retrofit2.http.Field
+import retrofit2.http.FormUrlEncoded
+import retrofit2.http.Headers
+import retrofit2.http.POST
+
+interface TangemVisaAuthApi {
+
+ @FormUrlEncoded
+ @Headers("Content-Type: application/x-www-form-urlencoded")
+ @POST("auth/clients/mobile-app-android/nonce-challenge")
+ suspend fun generateNonceByWalletAddress(
+ @Field("customer_id") customerId: String? = null,
+ @Field("customer_wallet_address") customerWalletAddress: String,
+ ): GenerateNonceResponse
+
+ @FormUrlEncoded
+ @Headers("Content-Type: application/x-www-form-urlencoded")
+ @POST("auth/clients/mobile-app-android/nonce-challenge")
+ suspend fun generateNonceByCard(
+ @Field("card_id") cardId: String,
+ @Field("card_public_key") cardPublicKey: String,
+ ): GenerateNonceResponse
+
+ @FormUrlEncoded
+ @Headers("Content-Type: application/x-www-form-urlencoded")
+ @POST("auth/protocol/openid-connect/token")
+ suspend fun getAccessToken(
+ @Field("client_id") clientId: String = "mobile-app-android",
+ @Field("grant_type") grantType: String = "password",
+ @Field("session_id") sessionId: String,
+ @Field("signature") signature: String,
+ @Field("salt") salt: String?,
+ ): JWTResponse
+
+ @FormUrlEncoded
+ @Headers("Content-Type: application/x-www-form-urlencoded")
+ @POST("auth/protocol/openid-connect/token")
+ suspend fun refreshAccessToken(
+ @Field("client_id") clientId: String = "mobile-app-android",
+ @Field("grant_type") grantType: String = "refresh_token",
+ @Field("refresh_token") refreshToken: String,
+ ): JWTResponse
+}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/GenerateNonceResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/GenerateNonceResponse.kt
new file mode 100644
index 0000000000..002e43b16e
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/GenerateNonceResponse.kt
@@ -0,0 +1,10 @@
+package com.tangem.datasource.api.visa.models.response
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class GenerateNonceResponse(
+ @Json(name = "nonce") val nonce: String,
+ @Json(name = "session_id") val sessionId: String,
+)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/JWTResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/JWTResponse.kt
new file mode 100644
index 0000000000..e4fc0d2579
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/JWTResponse.kt
@@ -0,0 +1,16 @@
+package com.tangem.datasource.api.visa.models.response
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class JWTResponse(
+ @Json(name = "access_token") val accessToken: String,
+ @Json(name = "expires_in") val expiresIn: Int,
+ @Json(name = "refresh_expires_in") val refreshExpiresIn: Int,
+ @Json(name = "refresh_token") val refreshToken: String,
+ @Json(name = "token_type") val tokenType: String,
+ @Json(name = "not-before-policy") val notBeforePolicy: Int,
+ @Json(name = "session_state") val sessionState: String,
+ @Json(name = "scope") val scope: String,
+)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt
index c5f506627e..7daabbc3e8 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt
@@ -1,7 +1,7 @@
package com.tangem.datasource.di
import com.tangem.datasource.api.common.AuthProvider
-import com.tangem.datasource.api.common.config.ApiConfig
+import com.tangem.datasource.api.common.config.*
import com.tangem.datasource.api.common.config.Express
import com.tangem.datasource.api.common.config.StakeKit
import com.tangem.datasource.api.common.config.TangemTech
@@ -39,4 +39,8 @@ internal object ApiConfigsModule {
@IntoSet
fun provideTangemTechConfig(appVersionProvider: AppVersionProvider, authProvider: AuthProvider): ApiConfig =
TangemTech(appVersionProvider, authProvider)
+
+ @Provides
+ @IntoSet
+ fun provideTangemVisaConfig(): ApiConfig = TangemVisaAuth()
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt
index 8b38ca983a..84cc46d45c 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt
@@ -15,6 +15,7 @@ import com.tangem.datasource.api.onramp.OnrampApi
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.TangemTechApiV2
+import com.tangem.datasource.api.visa.TangemVisaAuthApi
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.utils.*
@@ -169,6 +170,21 @@ internal object NetworkModule {
)
}
+ @Provides
+ @Singleton
+ fun provideTangemVisaApi(
+ @NetworkMoshi moshi: Moshi,
+ @ApplicationContext context: Context,
+ apiConfigsManager: ApiConfigsManager,
+ ): TangemVisaAuthApi {
+ return createApi(
+ id = ApiConfig.ID.TangemVisaAuth,
+ moshi = moshi,
+ context = context,
+ apiConfigsManager = apiConfigsManager,
+ )
+ }
+
@Deprecated("use createApi instead")
private inline fun provideTangemTechApiInternal(
moshi: Moshi,
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt
index 3ed335349d..7d82c0cac3 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt
@@ -87,6 +87,19 @@ internal object BlockchainSDKConfigConverter : Converter createExpressModel()
is TangemTech -> createTangemTechModel()
is StakeKit -> createStakeKitModel()
+ is TangemVisaAuth -> createVisaAuthModel()
}
}
@@ -172,6 +173,16 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
)
}
+ private fun createVisaAuthModel(): Model {
+ return Model(
+ id = ApiConfig.ID.TangemVisaAuth,
+ expected = ApiEnvironmentConfig(
+ environment = ApiEnvironment.STAGE,
+ baseUrl = "https://api-s.tangem.org/",
+ ),
+ )
+ }
+
private fun String.checkHeaderValueOrEmpty(): String {
for (i in this.indices) {
val c = this[i]
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt
index 6ffebce9fe..d4453d3e6a 100644
--- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/context/DefaultAppComponentContext.kt
@@ -7,8 +7,6 @@ import com.tangem.core.decompose.navigation.AppNavigationProvider
import com.tangem.core.decompose.navigation.DefaultAppNavigationProvider
import com.tangem.core.decompose.navigation.DefaultRouter
import com.tangem.core.decompose.navigation.Router
-import com.tangem.core.decompose.ui.DefaultUiMessageSender
-import com.tangem.core.decompose.ui.UiMessageHandler
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.decompose.utils.ComponentCoroutineScope
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@@ -16,9 +14,9 @@ import kotlinx.coroutines.CoroutineScope
class DefaultAppComponentContext(
componentContext: ComponentContext,
- messageHandler: UiMessageHandler,
override val dispatchers: CoroutineDispatcherProvider,
override val hiltComponentBuilder: DecomposeComponent.Builder,
+ override val messageSender: UiMessageSender,
private val replaceRouter: Router? = null,
) : AppComponentContext, ComponentContext by componentContext {
@@ -26,8 +24,6 @@ class DefaultAppComponentContext(
override val componentScope: CoroutineScope = ComponentCoroutineScope(lifecycle, dispatchers)
- override val messageSender: UiMessageSender = DefaultUiMessageSender(messageHandler)
-
override val navigationProvider: AppNavigationProvider
get() = instanceKeeper.getOrCreate { DefaultAppNavigationProvider() }
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/RootAppComponentContext.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/RootAppComponentContext.kt
index b9e3987632..f6bbc51b20 100644
--- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/RootAppComponentContext.kt
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/RootAppComponentContext.kt
@@ -3,4 +3,7 @@ package com.tangem.core.decompose.di
import javax.inject.Qualifier
@Qualifier
-annotation class RootAppComponentContext
\ No newline at end of file
+annotation class RootAppComponentContext
+
+@Qualifier
+annotation class GlobalUiMessageSender
\ No newline at end of file
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/DefaultUiMessageSender.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/DefaultUiMessageSender.kt
index 59546d42e0..52da85b3c7 100644
--- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/DefaultUiMessageSender.kt
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/ui/DefaultUiMessageSender.kt
@@ -1,6 +1,6 @@
package com.tangem.core.decompose.ui
-internal class DefaultUiMessageSender(
+class DefaultUiMessageSender(
private val handler: UiMessageHandler,
) : UiMessageSender {
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/clipboard/ClipboardManager.kt b/core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt
index 2e6354a2e2..cfac79d5c4 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt
@@ -9,6 +9,19 @@ import androidx.compose.runtime.Stable
@Stable
interface ClipboardManager {
- /** Copies to clipboard [text] with optional [label] */
- fun setText(label: String = "", text: String)
+ /**
+ * Copies to clipboard [text] with optional [label]
+ *
+ * @param text text
+ * @param isSensitive flag that determines whether the text is sensitive
+ * @param label label
+ */
+ fun setText(text: String, isSensitive: Boolean, label: String = "")
+
+ /**
+ * Get text (text/plain) from clipboard
+ *
+ * @param default default value
+ */
+ fun getText(default: String? = null): String?
}
\ 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/TangemBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt
index 22399d6e4b..c433f996ea 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt
@@ -2,7 +2,6 @@ package com.tangem.core.ui.components.bottomsheets
import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
-import androidx.compose.foundation.background
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
@@ -99,7 +98,7 @@ inline fun DefaultBottomSheet(
crossinline title: @Composable (BoxScope.(T) -> Unit),
crossinline content: @Composable (ColumnScope.(T) -> Unit),
) {
- var isVisible by remember { mutableStateOf(value = config.isShow) }
+ var isVisible by remember { mutableStateOf(value = config.isShown) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
if (isVisible && config.content is T) {
@@ -114,8 +113,8 @@ inline fun DefaultBottomSheet(
)
}
- LaunchedEffect(key1 = config.isShow) {
- if (config.isShow) {
+ LaunchedEffect(key1 = config.isShown) {
+ if (config.isShown) {
isVisible = true
} else {
sheetState.collapse { isVisible = false }
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfig.kt
index d2eb1f9935..002cecf115 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfig.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfig.kt
@@ -3,12 +3,12 @@ package com.tangem.core.ui.components.bottomsheets
/**
* Tangem bottom sheet config
*
- * @property isShow flag that determine if bottom sheet is shown
+ * @property isShown flag that determine if bottom sheet is shown
* @property onDismissRequest lambda be invoked when bottom sheet is dismissed
* @property content content config
*/
data class TangemBottomSheetConfig(
- val isShow: Boolean,
+ val isShown: Boolean,
val onDismissRequest: () -> Unit,
val content: TangemBottomSheetConfigContent,
) {
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheet.kt
new file mode 100644
index 0000000000..f0f478d567
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheet.kt
@@ -0,0 +1,156 @@
+package com.tangem.core.ui.components.bottomsheets.message
+
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.*
+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.res.painterResource
+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.ui.R
+import com.tangem.core.ui.components.PrimaryButton
+import com.tangem.core.ui.components.SecondaryButton
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
+import com.tangem.core.ui.extensions.isNullOrEmpty
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+
+@Composable
+fun MessageBottomSheet(config: TangemBottomSheetConfig) {
+ TangemBottomSheet(config) { notification: MessageBottomSheetUM ->
+ Content(model = notification)
+ }
+}
+
+@Composable
+internal fun Content(model: MessageBottomSheetUM, modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier.padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(40.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Box(modifier = Modifier)
+
+ if (model.iconResId != null) {
+ Icon(
+ modifier = Modifier.size(48.dp),
+ painter = painterResource(model.iconResId),
+ tint = TangemTheme.colors.icon.primary1,
+ contentDescription = null,
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ if (!model.title.isNullOrEmpty()) {
+ Text(
+ text = model.title.resolveReference(),
+ style = TangemTheme.typography.h2,
+ color = TangemTheme.colors.text.primary1,
+ textAlign = TextAlign.Center,
+ )
+ }
+
+ Text(
+ text = model.message.resolveReference(),
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.secondary,
+ textAlign = TextAlign.Center,
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .padding(bottom = 16.dp)
+ .fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(12.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ if (model.primaryAction != null) {
+ PrimaryButton(
+ modifier = Modifier.fillMaxWidth(),
+ text = model.primaryAction.text.resolveReference(),
+ enabled = model.primaryAction.enabled,
+ onClick = model.primaryAction.onClick,
+ )
+ }
+
+ if (model.secondaryAction != null) {
+ SecondaryButton(
+ modifier = Modifier.fillMaxWidth(),
+ text = model.secondaryAction.text.resolveReference(),
+ enabled = model.secondaryAction.enabled,
+ onClick = model.secondaryAction.onClick,
+ )
+ }
+ }
+ }
+}
+
+// region Preview
+@Composable
+@Preview(showBackground = true, widthDp = 360)
+@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
+private fun Preview_NotificationBottomSheet(
+ @PreviewParameter(MessageBottomSheetUMPreviewProvider::class) params: MessageBottomSheetUM,
+) {
+ TangemThemePreview {
+ MessageBottomSheet(
+ config = TangemBottomSheetConfig(
+ content = params,
+ isShown = true,
+ onDismissRequest = {},
+ ),
+ )
+ }
+}
+
+private class MessageBottomSheetUMPreviewProvider : PreviewParameterProvider {
+ val balancesHiddenMessage = MessageBottomSheetUM(
+ iconResId = R.drawable.ic_eye_off_outline_24,
+ title = resourceReference(R.string.balance_hidden_title),
+ message = resourceReference(R.string.balance_hidden_description),
+ primaryAction = MessageBottomSheetUM.ActionUM(
+ text = resourceReference(R.string.balance_hidden_got_it_button),
+ onClick = {},
+ ),
+ secondaryAction = MessageBottomSheetUM.ActionUM(
+ text = resourceReference(R.string.balance_hidden_do_not_show_button),
+ onClick = {},
+ ),
+ )
+
+ override val values: Sequence
+ get() = sequenceOf(
+ balancesHiddenMessage,
+ balancesHiddenMessage.copy(title = null),
+ balancesHiddenMessage.copy(iconResId = null),
+ balancesHiddenMessage.copy(primaryAction = null),
+ balancesHiddenMessage.copy(secondaryAction = null),
+ balancesHiddenMessage.copy(
+ primaryAction = null,
+ secondaryAction = null,
+ ),
+ balancesHiddenMessage.copy(
+ title = null,
+ iconResId = null,
+ primaryAction = null,
+ secondaryAction = null,
+ ),
+ )
+}
+// endregion Preview
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUM.kt
new file mode 100644
index 0000000000..88a40bd846
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUM.kt
@@ -0,0 +1,20 @@
+package com.tangem.core.ui.components.bottomsheets.message
+
+import androidx.annotation.DrawableRes
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
+import com.tangem.core.ui.extensions.TextReference
+
+data class MessageBottomSheetUM(
+ @DrawableRes val iconResId: Int?,
+ val title: TextReference?,
+ val message: TextReference,
+ val primaryAction: ActionUM?,
+ val secondaryAction: ActionUM?,
+) : TangemBottomSheetConfigContent {
+
+ data class ActionUM(
+ val text: TextReference,
+ val enabled: Boolean = true,
+ val onClick: () -> Unit,
+ )
+}
\ No newline at end of file
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..d6f38a3567 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
@@ -20,16 +20,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
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
@@ -75,10 +73,9 @@ private fun TokenReceiveBottomSheetContent(content: TokenReceiveBottomSheetConfi
)
Buttons(
- address = selectedAddress.value,
snackbarHostState = snackbarHostState,
- onShareClick = content.onShareClick,
- onCopyClick = content.onCopyClick,
+ onShareClick = { content.onShareClick(selectedAddress.value) },
+ onCopyClick = { content.onCopyClick(selectedAddress.value) },
)
}
}
@@ -96,7 +93,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 +188,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,
@@ -229,14 +226,12 @@ private fun getName(content: TokenReceiveBottomSheetConfig, index: Int): String
@Composable
private fun Buttons(
- address: String,
snackbarHostState: SnackbarHostState,
onShareClick: () -> Unit,
onCopyClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val hapticFeedback = LocalHapticFeedback.current
- val clipboardManager = LocalClipboardManager.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val resources = context.resources
@@ -247,17 +242,16 @@ 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()
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
- clipboardManager.setText(AnnotatedString(address))
coroutineScope.launch {
snackbarHostState.showSnackbar(
- message = resources.getString(R.string.wallet_notification_address_copied),
+ message = resources.getStringSafe(R.string.wallet_notification_address_copied),
)
}
},
@@ -265,13 +259,12 @@ 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()
-
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
- context.shareText(address)
+
+ onShareClick()
},
)
}
@@ -286,7 +279,7 @@ private fun Preview_TokenReceiveBottomSheet(
) {
TangemThemePreview {
val config = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
content = params,
onDismissRequest = {},
)
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheetConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheetConfig.kt
index 29d19f5300..eee9fdb3bd 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheetConfig.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheetConfig.kt
@@ -9,6 +9,6 @@ data class TokenReceiveBottomSheetConfig(
val network: String,
val addresses: ImmutableList,
val showMemoDisclaimer: Boolean,
- val onCopyClick: () -> Unit,
- val onShareClick: () -> Unit,
+ val onCopyClick: (String) -> Unit,
+ val onShareClick: (String) -> Unit,
) : TangemBottomSheetConfigContent
\ No newline at end of file
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/progressbar/LinearProgressIndicator.kt b/core/ui/src/main/java/com/tangem/core/ui/components/progressbar/LinearProgressIndicator.kt
index 41d6020243..05cbf5db85 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/progressbar/LinearProgressIndicator.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/progressbar/LinearProgressIndicator.kt
@@ -2,7 +2,9 @@
package com.tangem.core.ui.components.progressbar
+import android.content.res.Configuration
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
@@ -15,10 +17,12 @@ import androidx.compose.ui.layout.layout
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.progressBarRangeInfo
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
import kotlin.math.abs
/**
@@ -123,4 +127,18 @@ private fun DrawScope.drawLinearIndicator(
}
private fun DrawScope.drawLinearIndicatorBackground(color: Color, strokeWidth: Float, strokeCap: StrokeCap) =
- drawLinearIndicator(0f, 1f, color, strokeWidth, strokeCap)
\ No newline at end of file
+ drawLinearIndicator(0f, 1f, color, strokeWidth, strokeCap)
+
+// region Preview
+@Preview(showBackground = true, widthDp = 360)
+@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun LinearProgressIndicator_Preview() {
+ TangemThemePreview {
+ LinearProgressIndicator(
+ progress = { 0.5f },
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+}
+// endregion
\ No newline at end of file
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/snackbar/TangemSnackbarHost.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt
index d1cd229f6d..9f9fdabff9 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt
@@ -9,15 +9,18 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
+import com.tangem.core.ui.res.LocalSnackbarHostState
import com.tangem.core.ui.res.TangemThemePreview
/**
* Tangem snackbar host.
- * Based on Material3 component. It's best way to show [TangemSnackbar].
+ * Based on Material3 component. It's the best way to show [TangemSnackbar].
*
- * @param hostState snackbar host state
- * @param modifier modifier
- * @param actionOnNewLine flag that indicates if the action should be displayed on a new line (default: false)
+ * @param modifier Modifier to be applied to the layout.
+ * @param hostState SnackbarHostState that will be used to show snackbar.
+ * Optional parameter. By default, it uses [LocalSnackbarHostState.current].
+ * @param actionOnNewLine Flag that indicates if the action should be displayed on a new line.
+ * Optional parameter, `false` by default.
*
* @see Figma
@@ -25,7 +28,11 @@ import com.tangem.core.ui.res.TangemThemePreview
[REDACTED_AUTHOR]
*/
@Composable
-fun TangemSnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier, actionOnNewLine: Boolean = false) {
+fun TangemSnackbarHost(
+ modifier: Modifier = Modifier,
+ hostState: SnackbarHostState = LocalSnackbarHostState.current,
+ actionOnNewLine: Boolean = false,
+) {
SnackbarHost(hostState = hostState, modifier = modifier) { data ->
TangemSnackbar(data = data, actionOnNewLine = actionOnNewLine)
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/stories/StoriesContainer.kt b/core/ui/src/main/java/com/tangem/core/ui/components/stories/StoriesContainer.kt
new file mode 100644
index 0000000000..f1cd043fd6
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/stories/StoriesContainer.kt
@@ -0,0 +1,106 @@
+package com.tangem.core.ui.components.stories
+
+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.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import com.tangem.core.ui.components.stories.inner.StoriesClickableArea
+import com.tangem.core.ui.components.stories.inner.StoriesProgressBar
+import com.tangem.core.ui.components.stories.inner.StoriesStepStateMachine
+import com.tangem.core.ui.components.stories.model.StoriesContentConfig
+import com.tangem.core.ui.components.stories.model.StoryConfig
+import com.tangem.core.ui.res.TangemColorPalette
+import com.tangem.core.ui.res.TangemThemePreview
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+
+/**
+ * Stories container component.
+ * Implements base logic for switching story pages and handling clicks
+ *
+ * @param config stories configuration
+ * @param modifier composable modifier
+ * @param isPauseStories pause stories progression externally
+ * @param currentStoryContent content of current displaying story
+ */
+@Composable
+inline fun StoriesContainer(
+ config: StoriesContentConfig,
+ modifier: Modifier = Modifier,
+ isPauseStories: Boolean = false,
+ crossinline currentStoryContent: @Composable BoxScope.(T, Boolean) -> Unit,
+) {
+ var isPressed by remember { mutableStateOf(value = false) }
+ val storyState by remember(config) {
+ mutableStateOf(
+ StoriesStepStateMachine(
+ stories = config.stories,
+ isRepeatable = config.isRestartable,
+ ),
+ )
+ }
+
+ val isPaused = isPressed || isPauseStories
+
+ Box(modifier = modifier) {
+ StoriesClickableArea(
+ onPress = { isPressed = it },
+ onPreviousStory = storyState::prevStory,
+ onNextStory = storyState::nextStory,
+ )
+
+ StoriesProgressBar(
+ steps = storyState.steps,
+ currentStep = storyState.currentIndex.intValue,
+ stepDuration = storyState.currentStory.duration,
+ paused = isPaused,
+ onStepFinish = storyState::nextStory,
+ )
+
+ currentStoryContent(storyState.currentStory, isPaused)
+ }
+}
+
+// region Preview
+@Preview(showBackground = true, widthDp = 360)
+@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun StoriesContainer_Preview() {
+ TangemThemePreview {
+ StoriesContainer(
+ config = StoryContainerPreviewProviderData(),
+ modifier = Modifier.background(Color(0xFF010101)),
+ isPauseStories = false,
+ currentStoryContent = { pageConfig, _ ->
+ Text(
+ text = pageConfig.title,
+ color = TangemColorPalette.White,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ },
+ )
+ }
+}
+
+private data class StoryContainerPreviewProviderData(
+ override val stories: ImmutableList = persistentListOf(
+ StoryConfigPreviewProviderData(title = "Tangem"),
+ StoryConfigPreviewProviderData(title = "Cold"),
+ StoryConfigPreviewProviderData(title = "Wallet"),
+ ),
+ override val isRestartable: Boolean = true,
+) : StoriesContentConfig
+
+private data class StoryConfigPreviewProviderData(
+ override val duration: Int = 3_000,
+ val title: String,
+) : StoryConfig
+// endregion
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/stories/inner/StoriesClickableArea.kt b/core/ui/src/main/java/com/tangem/core/ui/components/stories/inner/StoriesClickableArea.kt
new file mode 100644
index 0000000000..e5d18fd061
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/stories/inner/StoriesClickableArea.kt
@@ -0,0 +1,56 @@
+package com.tangem.core.ui.components.stories.inner
+
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+
+private const val STORY_HOLD_DELAY = 200
+
+@Composable
+fun StoriesClickableArea(onPress: (Boolean) -> Unit, onPreviousStory: () -> Unit, onNextStory: () -> Unit) {
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Box(
+ Modifier
+ .weight(1f)
+ .fillMaxHeight()
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onPress = {
+ val pressStartTime = System.currentTimeMillis()
+ onPress(true)
+ this.tryAwaitRelease()
+ val pressEndTime = System.currentTimeMillis()
+ val totalPressTime = pressEndTime - pressStartTime
+ if (totalPressTime < STORY_HOLD_DELAY) onPreviousStory()
+ onPress(false)
+ },
+ )
+ },
+ )
+ Box(
+ Modifier
+ .weight(1f)
+ .fillMaxHeight()
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onPress = {
+ val pressStartTime = System.currentTimeMillis()
+ onPress(true)
+ this.tryAwaitRelease()
+ val pressEndTime = System.currentTimeMillis()
+ val totalPressTime = pressEndTime - pressStartTime
+ if (totalPressTime < STORY_HOLD_DELAY) onNextStory()
+ onPress(false)
+ },
+ )
+ },
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/stories/inner/StoriesProgressBar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/stories/inner/StoriesProgressBar.kt
new file mode 100644
index 0000000000..f2c504316e
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/stories/inner/StoriesProgressBar.kt
@@ -0,0 +1,127 @@
+package com.tangem.core.ui.components.stories.inner
+
+import android.provider.Settings
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.*
+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.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.components.SpacerW4
+import com.tangem.core.ui.res.TangemColorPalette
+import com.tangem.core.ui.res.TangemTheme
+import kotlinx.coroutines.delay
+
+private const val STORIES_ANIMATION_SPEED_ZERO_DURATION = 3000L
+const val STORY_DURATION = 8_000
+
+private suspend fun animationProgress(
+ paused: Boolean,
+ animatorSpeed: Float,
+ stepDuration: Int,
+ progress: Animatable,
+ onStepFinish: () -> Unit,
+) {
+ if (paused) {
+ progress.stop()
+ } else {
+ if (animatorSpeed == 0f) {
+ progress.snapTo(1f)
+ delay(STORIES_ANIMATION_SPEED_ZERO_DURATION)
+ } else {
+ progress.animateTo(
+ targetValue = 1f,
+ animationSpec = tween(
+ durationMillis = (stepDuration * (1f - progress.value)).toInt(),
+ easing = LinearEasing,
+ ),
+ )
+ progress.snapTo(0f)
+ }
+ onStepFinish()
+ }
+}
+
+@Composable
+fun StoriesProgressBar(
+ steps: Int,
+ currentStep: Int,
+ paused: Boolean = false,
+ stepDuration: Int = STORY_DURATION,
+ onStepFinish: () -> Unit = {},
+) {
+ val progress = remember(currentStep) { Animatable(initialValue = 0f) }
+
+ val context = LocalContext.current
+ val animatorSpeed = Settings.Global.getFloat(
+ context.contentResolver,
+ Settings.Global.ANIMATOR_DURATION_SCALE,
+ 1f,
+ )
+
+ LaunchedEffect(paused, currentStep, animatorSpeed) {
+ animationProgress(
+ paused = paused,
+ animatorSpeed = animatorSpeed,
+ stepDuration = stepDuration,
+ progress = progress,
+ onStepFinish = onStepFinish,
+ )
+ }
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .padding(
+ start = 16.dp,
+ end = 16.dp,
+ top = 16.dp,
+ ),
+ ) {
+ for (index in 0..steps) {
+ Row(
+ modifier = Modifier
+ .height(2.dp)
+ .weight(1f)
+ .clip(RoundedCornerShape(2.dp))
+ .background(TangemColorPalette.White.copy(alpha = .2f)),
+ ) {
+ Box(
+ modifier = Modifier
+ .clip(RoundedCornerShape(2.dp))
+ .background(TangemColorPalette.White)
+ .fillMaxHeight()
+ .let {
+ when (index) {
+ currentStep -> it.fillMaxWidth(progress.value)
+ in 0..currentStep -> it.fillMaxWidth(fraction = 1f)
+ else -> it
+ }
+ },
+ )
+ }
+ SpacerW4()
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun StoriesProgressBarPreview() {
+ Box(
+ modifier = Modifier
+ .wrapContentSize()
+ .background(TangemColorPalette.Black)
+ .padding(vertical = TangemTheme.dimens.spacing16),
+ ) {
+ StoriesProgressBar(steps = 5, currentStep = 3, paused = false)
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/stories/inner/StoriesStepStateMachine.kt b/core/ui/src/main/java/com/tangem/core/ui/components/stories/inner/StoriesStepStateMachine.kt
new file mode 100644
index 0000000000..37c26dd443
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/stories/inner/StoriesStepStateMachine.kt
@@ -0,0 +1,39 @@
+package com.tangem.core.ui.components.stories.inner
+
+import androidx.compose.runtime.IntState
+import androidx.compose.runtime.asIntState
+import androidx.compose.runtime.mutableIntStateOf
+import kotlinx.collections.immutable.ImmutableList
+
+class StoriesStepStateMachine(
+ private val stories: ImmutableList,
+ private val isRepeatable: Boolean,
+) {
+
+ private var _currentIndex = mutableIntStateOf(FIRST_INDEX)
+
+ val steps = stories.lastIndex
+ val currentIndex: IntState
+ get() = _currentIndex.asIntState()
+
+ val currentStory
+ get() = stories[currentIndex.intValue.coerceIn(FIRST_INDEX, steps)]
+
+ fun nextStory(): Boolean {
+ _currentIndex.intValue = if (currentIndex.intValue == steps && isRepeatable) {
+ FIRST_INDEX
+ } else {
+ currentIndex.intValue.inc().coerceAtMost(stories.size)
+ }
+
+ return currentIndex.intValue > steps
+ }
+
+ fun prevStory() {
+ _currentIndex.intValue = currentIndex.intValue.dec().coerceAtLeast(FIRST_INDEX)
+ }
+
+ private companion object {
+ const val FIRST_INDEX = 0
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/stories/model/StoriesContentConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/stories/model/StoriesContentConfig.kt
new file mode 100644
index 0000000000..68cc624895
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/stories/model/StoriesContentConfig.kt
@@ -0,0 +1,18 @@
+package com.tangem.core.ui.components.stories.model
+
+import kotlinx.collections.immutable.ImmutableList
+
+/**
+ * Config for stories component
+ *
+ * @property stories configuration list
+ * @property isRestartable indicates than stories progressions starts
+ */
+interface StoriesContentConfig {
+ val stories: ImmutableList
+ val isRestartable: Boolean
+}
+
+interface StoryConfig {
+ val duration: Int
+}
\ No newline at end of file
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/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt
index 0ec7a47466..d3d5ab0171 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt
@@ -84,6 +84,7 @@ fun getActiveIconRes(blockchainId: String): Int {
"canxium" -> R.drawable.img_canxium_22
"chiliz", "chiliz/test" -> R.drawable.img_chiliz_22
"clore-ai" -> R.drawable.img_clore_22
+ "fact0rn" -> R.drawable.img_fact0rn_22
else -> R.drawable.ic_alert_24
}
}
@@ -166,6 +167,7 @@ fun getActiveIconResByCoinId(coinId: String): Int {
"canxium" -> R.drawable.img_canxium_22
"chiliz", "chiliz/test" -> R.drawable.img_chiliz_22
"clore-ai" -> R.drawable.img_clore_22
+ "fact0rn" -> R.drawable.img_fact0rn_22
else -> R.drawable.ic_alert_24
}
}
@@ -251,6 +253,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int {
"canxium" -> R.drawable.ic_canxium_22
"chiliz", "chiliz/test" -> R.drawable.ic_chiliz_22
"clore-ai" -> R.drawable.ic_clore_22
+ "fact0rn" -> R.drawable.ic_fact0rn_22
else -> R.drawable.ic_alert_24
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/Context.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/Context.kt
deleted file mode 100644
index 17439269f9..0000000000
--- a/core/ui/src/main/java/com/tangem/core/ui/extensions/Context.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.tangem.core.ui.extensions
-
-import android.content.Context
-import android.content.Intent
-import androidx.core.content.ContextCompat
-
-fun Context.shareText(text: String) {
- val sendIntent: Intent = Intent().apply {
- action = Intent.ACTION_SEND
- putExtra(Intent.EXTRA_TEXT, text)
- type = "text/plain"
- }
- val shareIntent = Intent.createChooser(sendIntent, null)
- ContextCompat.startActivity(this, shareIntent, null)
-}
\ No newline at end of file
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/java/com/tangem/core/ui/message/EventMessage.kt b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt
index f500f27e90..0828f90e8d 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt
@@ -1,11 +1,14 @@
package com.tangem.core.ui.message
-import androidx.compose.material3.SnackbarDuration
+import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.ui.UiMessage
+import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.message.SnackbarMessage.Duration
/**
* Event that is used to show a message in the UI.
@@ -19,16 +22,40 @@ sealed interface EventMessage : UiMessage
* Shows a snackbar.
*
* @param message The message to show.
- * @param duration The duration of the snackbar.
- * @param actionLabel The label of the action button.
- * @param action The action to perform when the action button is clicked.
+ * @param duration The duration of the snackbar. Optional, [Duration.Short] by default.
+ * @param onDismissRequest The action to perform when the snackbar is dismissed. Optional, empty lambda by default.
+ * @param actionLabel The label of the action button. Optional, `null` by default.
+ * @param action The action to perform when the action button is clicked. Optional, `null` by default.
* */
data class SnackbarMessage(
val message: TextReference,
- val duration: SnackbarDuration = SnackbarDuration.Short,
+ val duration: Duration = Duration.Short,
+ val onDismissRequest: () -> Unit = {},
val actionLabel: TextReference? = null,
val action: (() -> Unit)? = null,
-) : EventMessage
+) : EventMessage {
+
+ /**
+ * The duration of the snackbar.
+ * */
+ enum class Duration {
+
+ /**
+ * Shows the snackbar for a short period of time.
+ * */
+ Short,
+
+ /**
+ * Shows the snackbar for a long period of time.
+ * */
+ Long,
+
+ /**
+ * Shows the snackbar indefinitely, until dismissed or action performed.
+ * */
+ Indefinite,
+ }
+}
/**
* Shows a [content] in the UI.
@@ -44,4 +71,170 @@ data class ContentMessage(val content: Content) : EventMessage {
@Composable
operator fun invoke(onDismiss: () -> Unit)
}
+}
+
+/**
+ * Shows an alert dialog.
+ *
+ * @param message The message to show.
+ * @param title The title of the dialog. Optional, `null` by default.
+ * @param firstAction The first dialog action to perform.
+ * @param secondAction The second dialog action to perform. Optional, `null` by default.
+ * Performing the second action always dismisses the dialog.
+ * @param isDismissable If `false` then dialog can not be dismissed by back button click or by outside click.
+ * @param dismissOnFirstAction If `true` then dialog is dismissed when first action is performed,
+ * ignoring lambda passed to [EventMessageAction.onClick].
+ * `true` by default.
+ * @param onDismissRequest The action to perform when the dialog is dismissed.
+ * */
+data class DialogMessage(
+ val message: TextReference,
+ val title: TextReference? = null,
+ val firstAction: EventMessageAction,
+ val secondAction: EventMessageAction? = null,
+ val isDismissable: Boolean = true,
+ val dismissOnFirstAction: Boolean = true,
+ val onDismissRequest: () -> Unit = {},
+) : EventMessage {
+
+ companion object {
+
+ /**
+ * Builder for [DialogMessage].
+ *
+ * @param message The message to show.
+ * @param title The title of the dialog. Optional, `null` by default.
+ * @param isDismissable If `false` then dialog can not be dismissed by back button click or by outside click.
+ * `true` by default.
+ * @param dismissOnFirstAction If `true` then dialog is dismissed when first action is performed,
+ * ignoring lambda passed to [EventMessageAction.onClick].
+ * `true` by default.
+ * @param onDismissRequest The action to perform when the dialog is dismissed.
+ * @param firstActionBuilder The builder for the first action.
+ * By default, it builds an action with title "OK".
+ * @param secondActionBuilder The builder for the second action. Optional, `null` by default.
+ * Performing the second action always dismisses the dialog.
+ *
+ * @return [DialogMessage] with the specified parameters.
+ * */
+ operator fun invoke(
+ message: TextReference,
+ title: TextReference? = null,
+ isDismissable: Boolean = true,
+ dismissOnFirstAction: Boolean = true,
+ onDismissRequest: () -> Unit = {},
+ firstActionBuilder: EventMessageAction.BuilderScope.() -> EventMessageAction = { okAction() },
+ secondActionBuilder: (EventMessageAction.BuilderScope.() -> EventMessageAction)? = null,
+ ): DialogMessage {
+ val buttonsScope = EventMessageAction.BuilderScope(onDismissRequest)
+
+ return DialogMessage(
+ message = message,
+ title = title,
+ isDismissable = isDismissable,
+ dismissOnFirstAction = dismissOnFirstAction,
+ onDismissRequest = onDismissRequest,
+ firstAction = firstActionBuilder(buttonsScope),
+ secondAction = secondActionBuilder?.invoke(buttonsScope),
+ )
+ }
+ }
+}
+
+/**
+ * Shows a bottom sheet.
+ *
+ * @param iconResId The icon to show in the bottom sheet.
+ * Optional, `null` by default.
+ * @param title The title of the bottom sheet. Optional, `null` by default.
+ * @param message The message to show in the bottom sheet.
+ * @param firstAction The first action to perform. Optional, `null` by default.
+ * @param secondAction The second action to perform. Optional, `null` by default.
+ * @param onDismissRequest The action to perform when the bottom sheet is dismissed.
+ * */
+data class BottomSheetMessage(
+ @DrawableRes val iconResId: Int? = null,
+ val title: TextReference? = null,
+ val message: TextReference,
+ val firstAction: EventMessageAction? = null,
+ val secondAction: EventMessageAction? = null,
+ val onDismissRequest: () -> Unit = {},
+) : EventMessage {
+
+ companion object {
+
+ /**
+ * Builder for [BottomSheetMessage].
+ *
+ * @param iconResId The icon to show in the bottom sheet. Optional, `null` by default.
+ * @param title The title of the bottom sheet. Optional, `null` by default.
+ * @param message The message to show in the bottom sheet.
+ * @param onDismissRequest The action to perform when the bottom sheet is dismissed.
+ * @param firstActionBuilder The builder for the first action. Optional, `null` by default.
+ * @param secondActionBuilder The builder for the second action. Optional, `null` by default.
+ * */
+ operator fun invoke(
+ @DrawableRes iconResId: Int?,
+ title: TextReference?,
+ message: TextReference,
+ onDismissRequest: () -> Unit = {},
+ firstActionBuilder: (EventMessageAction.BuilderScope.() -> EventMessageAction)? = null,
+ secondActionBuilder: (EventMessageAction.BuilderScope.() -> EventMessageAction)? = null,
+ ): BottomSheetMessage {
+ val buttonsScope = EventMessageAction.BuilderScope(onDismissRequest)
+
+ return BottomSheetMessage(
+ iconResId = iconResId,
+ title = title,
+ message = message,
+ onDismissRequest = onDismissRequest,
+ firstAction = firstActionBuilder?.invoke(buttonsScope),
+ secondAction = secondActionBuilder?.invoke(buttonsScope),
+ )
+ }
+ }
+}
+
+/**
+ * Represents an action button in the dialog.
+ *
+ * @param title The title of the action.
+ * @param warning If `true` then the action is highlighted as a warning.
+ * @param enabled If `false` then the action is disabled.
+ * @param onClick The action to perform when the button is clicked.
+ * */
+data class EventMessageAction(
+ val title: TextReference,
+ val warning: Boolean = false,
+ val enabled: Boolean = true,
+ val onClick: () -> Unit,
+) {
+
+ /**
+ * Scope for building buttons for the message.
+ *
+ * @param onDismissRequest The action to perform when the dialog is dismissed.
+ * */
+ class BuilderScope internal constructor(val onDismissRequest: () -> Unit) {
+
+ /**
+ * Builds an action with title "OK".
+ *
+ * @param onClick The action to perform when the button is clicked. By default, it dismisses the message.
+ * */
+ fun okAction(onClick: () -> Unit = onDismissRequest) = EventMessageAction(
+ title = resourceReference(id = R.string.common_ok),
+ onClick = onClick,
+ )
+
+ /**
+ * Builds an action with title "Cancel".
+ *
+ * @param onClick The action to perform when the button is clicked. By default, it dismisses the message.
+ * */
+ fun cancelAction(onClick: () -> Unit = onDismissRequest) = EventMessageAction(
+ title = resourceReference(id = R.string.common_cancel),
+ onClick = onClick,
+ )
+ }
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt
index 41475b987a..7a4451f689 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt
@@ -1,26 +1,49 @@
package com.tangem.core.ui.message
import android.content.Context
+import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
+import com.tangem.core.ui.components.BasicDialog
+import com.tangem.core.ui.components.DialogButtonUM
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
+import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheet
+import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUM
import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.res.LocalEventMessageHandler
+import com.tangem.core.ui.res.LocalSnackbarHostState
@Composable
-fun EventMessageEffect(messageHandler: EventMessageHandler, snackbarHostState: SnackbarHostState) {
+fun EventMessageEffect(
+ messageHandler: EventMessageHandler = LocalEventMessageHandler.current,
+ snackbarHostState: SnackbarHostState = LocalSnackbarHostState.current,
+ onShowSnackbar: suspend (SnackbarMessage, Context) -> Unit = { message, context ->
+ showSnackbar(snackbarHostState, message, context)
+ },
+) {
val messageEvent by messageHandler.collectAsState()
val context = LocalContext.current
+
var contentMessage: ContentMessage? by remember { mutableStateOf(value = null) }
+ var dialogMessage: DialogMessage? by remember { mutableStateOf(value = null) }
+ var bottomSheetMessage: BottomSheetMessage? by remember { mutableStateOf(value = null) }
EventEffect(event = messageEvent) { message ->
when (message) {
+ is SnackbarMessage -> {
+ onShowSnackbar(message, context)
+ }
is ContentMessage -> {
contentMessage = message
}
- is SnackbarMessage -> {
- showSnackbar(snackbarHostState, message, context)
+ is DialogMessage -> {
+ dialogMessage = message
+ }
+ is BottomSheetMessage -> {
+ bottomSheetMessage = message
}
}
}
@@ -28,16 +51,110 @@ fun EventMessageEffect(messageHandler: EventMessageHandler, snackbarHostState: S
contentMessage?.content?.invoke(
onDismiss = { contentMessage = null },
)
+
+ dialogMessage?.let { message ->
+ MessageDialog(
+ message = message,
+ onDismissRequest = {
+ dialogMessage = null
+ message.onDismissRequest()
+ },
+ )
+ }
+
+ bottomSheetMessage?.let { message ->
+ MessageBottomSheet(
+ message = message,
+ onDismissRequest = {
+ bottomSheetMessage = null
+ message.onDismissRequest()
+ },
+ )
+ }
+}
+
+@Composable
+private fun MessageBottomSheet(message: BottomSheetMessage, onDismissRequest: () -> Unit) {
+ val config = TangemBottomSheetConfig(
+ isShown = true,
+ content = MessageBottomSheetUM(
+ iconResId = message.iconResId,
+ title = message.title,
+ message = message.message,
+ primaryAction = message.firstAction?.let { action ->
+ MessageBottomSheetUM.ActionUM(
+ text = action.title,
+ enabled = action.enabled,
+ onClick = {
+ action.onClick()
+ onDismissRequest()
+ },
+ )
+ },
+ secondaryAction = message.secondAction?.let { action ->
+ MessageBottomSheetUM.ActionUM(
+ text = action.title,
+ enabled = action.enabled,
+ onClick = {
+ action.onClick()
+ onDismissRequest()
+ },
+ )
+ },
+ ),
+ onDismissRequest = onDismissRequest,
+ )
+
+ MessageBottomSheet(config)
+}
+
+@Composable
+private fun MessageDialog(message: DialogMessage, onDismissRequest: () -> Unit) {
+ BasicDialog(
+ message = message.message.resolveReference(),
+ title = message.title?.resolveReference(),
+ confirmButton = message.firstAction.let { action ->
+ DialogButtonUM(
+ title = action.title.resolveReference(),
+ warning = action.warning,
+ enabled = action.enabled,
+ onClick = {
+ action.onClick()
+
+ if (message.dismissOnFirstAction) {
+ onDismissRequest()
+ }
+ },
+ )
+ },
+ dismissButton = message.secondAction?.let { action ->
+ DialogButtonUM(
+ title = action.title.resolveReference(),
+ warning = action.warning,
+ enabled = action.enabled,
+ onClick = {
+ action.onClick()
+ onDismissRequest()
+ },
+ )
+ },
+ onDismissDialog = onDismissRequest,
+ )
}
private suspend fun showSnackbar(snackbarHostState: SnackbarHostState, message: SnackbarMessage, context: Context) {
val result = snackbarHostState.showSnackbar(
message = message.message.resolveReference(context.resources),
actionLabel = message.actionLabel?.resolveReference(context.resources),
- duration = message.duration,
+ duration = when (message.duration) {
+ SnackbarMessage.Duration.Short -> SnackbarDuration.Short
+ SnackbarMessage.Duration.Long -> SnackbarDuration.Long
+ SnackbarMessage.Duration.Indefinite -> SnackbarDuration.Indefinite
+ },
)
- if (result == SnackbarResult.ActionPerformed) {
- message.action?.invoke()
+ when (result) {
+ SnackbarResult.Dismissed -> message.onDismissRequest()
+ SnackbarResult.ActionPerformed -> message.action?.invoke()
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
index 372a8c50f9..821dcd5bde 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
@@ -1,5 +1,7 @@
package com.tangem.core.ui.res
+import android.app.Activity
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material.Colors
@@ -10,13 +12,42 @@ import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import com.google.accompanist.systemuicontroller.rememberSystemUiController
+import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.components.TangemShimmer
import com.tangem.core.ui.haptic.DefaultHapticManager
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.haptic.VibratorHapticManager
+import com.tangem.core.ui.message.EventMessageHandler
import com.tangem.core.ui.windowsize.WindowSize
+import com.tangem.core.ui.windowsize.rememberWindowSize
+import com.tangem.domain.apptheme.model.AppThemeMode
import com.valentinilk.shimmer.Shimmer
+@Composable
+fun TangemTheme(
+ activity: Activity,
+ uiDependencies: UiDependencies,
+ typography: TangemTypography = TangemTheme.typography,
+ dimens: TangemDimens = TangemTheme.dimens,
+ overrideSystemBarColors: Boolean = true,
+ content: @Composable () -> Unit,
+) {
+ val appThemeMode by uiDependencies.appThemeModeHolder.appThemeMode
+ val windowSize = rememberWindowSize(activity = activity)
+
+ TangemTheme(
+ isDark = shouldUseDarkTheme(appThemeMode),
+ windowSize = windowSize,
+ vibratorHapticManager = uiDependencies.vibratorHapticManager,
+ snackbarHostState = uiDependencies.globalSnackbarHostState,
+ eventMessageHandler = uiDependencies.eventMessageHandler,
+ overrideSystemBarColors = overrideSystemBarColors,
+ typography = typography,
+ dimens = dimens,
+ content = content,
+ )
+}
+
@Composable
fun TangemTheme(
isDark: Boolean = false,
@@ -24,6 +55,7 @@ fun TangemTheme(
typography: TangemTypography = TangemTheme.typography,
dimens: TangemDimens = TangemTheme.dimens,
vibratorHapticManager: VibratorHapticManager? = null,
+ eventMessageHandler: EventMessageHandler = remember { EventMessageHandler() },
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
overrideSystemBarColors: Boolean = true,
content: @Composable () -> Unit,
@@ -64,6 +96,7 @@ fun TangemTheme(
LocalIsInDarkTheme provides isDark,
LocalHapticManager provides hapticManager,
LocalSnackbarHostState provides snackbarHostState,
+ LocalEventMessageHandler provides eventMessageHandler,
LocalWindowSize provides windowSize,
) {
CompositionLocalProvider(
@@ -256,6 +289,10 @@ val LocalSnackbarHostState = staticCompositionLocalOf {
error("No SnackbarHostState provided")
}
+val LocalEventMessageHandler = staticCompositionLocalOf {
+ error("No EventMessageHandler provided")
+}
+
val LocalWindowSize = staticCompositionLocalOf {
error("No WindowSize provided")
}
@@ -266,4 +303,20 @@ val LocalTangemShimmer = staticCompositionLocalOf {
val LocalMainBottomSheetColor = staticCompositionLocalOf> {
error("No MainBottomSheetColor provided")
+}
+
+/**
+ * Determines whether the dark theme should be used based on the given [AppThemeMode].
+ *
+ * @param appThemeMode The application theme mode.
+ * @return `true` if the dark theme should be used, `false` otherwise.
+ */
+@Composable
+@ReadOnlyComposable
+private fun shouldUseDarkTheme(appThemeMode: AppThemeMode): Boolean {
+ return when (appThemeMode) {
+ AppThemeMode.FORCE_DARK -> true
+ AppThemeMode.FORCE_LIGHT -> false
+ AppThemeMode.FOLLOW_SYSTEM -> isSystemInDarkTheme()
+ }
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt
index c403c5036e..d1c1e10b32 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt
@@ -2,17 +2,13 @@ package com.tangem.core.ui.screen
import android.app.Activity
import android.content.Context
-import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.core.ui.windowsize.rememberWindowSize
-import com.tangem.domain.apptheme.model.AppThemeMode
/**
* Interface representing a Compose screen with common theming and content composition properties.
@@ -60,34 +56,13 @@ internal fun ComposeScreen.createComposeView(
): ComposeView {
return ComposeView(context).apply {
setContent {
- val appThemeMode by uiDependencies.appThemeModeHolder.appThemeMode
- val windowSize = rememberWindowSize(activity = activity)
-
TangemTheme(
- isDark = shouldUseDarkTheme(appThemeMode),
- windowSize = windowSize,
- vibratorHapticManager = uiDependencies.vibratorHapticManager,
- snackbarHostState = uiDependencies.globalSnackbarHostState,
+ activity = activity,
+ uiDependencies = uiDependencies,
overrideSystemBarColors = overrideSystemBarColors,
) {
ScreenContent(modifier = screenModifier)
}
}
}
-}
-
-/**
- * Determines whether the dark theme should be used based on the given [AppThemeMode].
- *
- * @param appThemeMode The application theme mode.
- * @return `true` if the dark theme should be used, `false` otherwise.
- */
-@Composable
-@ReadOnlyComposable
-private fun shouldUseDarkTheme(appThemeMode: AppThemeMode): Boolean {
- return when (appThemeMode) {
- AppThemeMode.FORCE_DARK -> true
- AppThemeMode.FORCE_LIGHT -> false
- AppThemeMode.FOLLOW_SYSTEM -> isSystemInDarkTheme()
- }
}
\ No newline at end of file
diff --git a/core/ui/src/main/res/drawable/ic_fact0rn_22.xml b/core/ui/src/main/res/drawable/ic_fact0rn_22.xml
new file mode 100644
index 0000000000..2c71132ba0
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_fact0rn_22.xml
@@ -0,0 +1,10 @@
+
+
+
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/ui/src/main/res/drawable/img_fact0rn_22.xml b/core/ui/src/main/res/drawable/img_fact0rn_22.xml
new file mode 100644
index 0000000000..db98cf00a7
--- /dev/null
+++ b/core/ui/src/main/res/drawable/img_fact0rn_22.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
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/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt
index 4b02448af9..9ca9d26d6a 100644
--- a/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt
+++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/NetworkOperations.kt
@@ -257,6 +257,7 @@ private fun Blockchain.getSupportedTransactionExtras(): Network.TransactionExtra
Blockchain.PolygonZkEVM,
Blockchain.PolygonZkEVMTestnet,
Blockchain.Radiant,
+ Blockchain.Fact0rn,
Blockchain.Base,
Blockchain.BaseTestnet,
Blockchain.Moonriver,
@@ -289,6 +290,8 @@ private fun Blockchain.getSupportedTransactionExtras(): Network.TransactionExtra
Blockchain.Canxium,
Blockchain.Chiliz,
Blockchain.ChilizTestnet,
+ Blockchain.VanarChain,
+ Blockchain.VanarChainTestnet,
-> Network.TransactionExtrasType.NONE
// endregion
}
diff --git a/data/promo/build.gradle.kts b/data/promo/build.gradle.kts
index b11dc26ed9..03bcb7be3b 100644
--- a/data/promo/build.gradle.kts
+++ b/data/promo/build.gradle.kts
@@ -17,8 +17,9 @@ dependencies {
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
- implementation(projects.domain.tokens)
- implementation(projects.domain.tokens.models)
+ implementation(projects.domain.promo)
+ implementation(projects.domain.promo.models)
+ implementation(projects.domain.wallets.models)
implementation(projects.core.datasource)
implementation(projects.core.utils)
diff --git a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt
index 0a81c2200c..4714406f4d 100644
--- a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt
+++ b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt
@@ -3,13 +3,25 @@ package com.tangem.data.promo
import com.tangem.data.promo.converters.PromoResponseConverter
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
-import com.tangem.domain.promo.PromoBanner
-import com.tangem.domain.tokens.repository.PromoRepository
+import com.tangem.datasource.local.preferences.AppPreferencesStore
+import com.tangem.datasource.local.preferences.PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY
+import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY
+import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY
+import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_RING_PROMO_KEY
+import com.tangem.datasource.local.preferences.utils.get
+import com.tangem.datasource.local.preferences.utils.store
+import com.tangem.domain.promo.PromoRepository
+import com.tangem.domain.promo.models.PromoBanner
+import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
internal class DefaultPromoRepository(
private val tangemApi: TangemTechApi,
+ private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : PromoRepository {
@@ -41,6 +53,42 @@ internal class DefaultPromoRepository(
}.getOrNull()
}
+ override fun isReadyToShowWalletSwapPromo(): Flow {
+ return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY, true)
+ }
+
+ override fun isReadyToShowTokenSwapPromo(): Flow {
+ return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY, true)
+ }
+
+ override suspend fun setNeverToShowWalletSwapPromo() {
+ appPreferencesStore.store(
+ key = IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY,
+ value = false,
+ )
+ }
+
+ override suspend fun setNeverToShowTokenSwapPromo() {
+ appPreferencesStore.store(
+ key = IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY,
+ value = false,
+ )
+ }
+
+ override fun isReadyToShowRingPromo(userWalletId: UserWalletId): Flow {
+ return combine(
+ flow = appPreferencesStore
+ .get(key = ADDED_WALLETS_WITH_RING_KEY, default = emptySet())
+ .map { userWalletId.stringValue in it },
+ flow2 = appPreferencesStore.get(key = SHOULD_SHOW_RING_PROMO_KEY, default = true),
+ transform = { isRingAdded, shouldShowRingPromo -> isRingAdded && shouldShowRingPromo },
+ )
+ }
+
+ override suspend fun setNeverToShowRingPromo() {
+ appPreferencesStore.store(key = SHOULD_SHOW_RING_PROMO_KEY, value = false)
+ }
+
private companion object {
private const val CHANGELLY_NAME = "changelly"
private const val OKX = "okx"
diff --git a/data/promo/src/main/java/com/tangem/data/promo/converters/PromoResponseConverter.kt b/data/promo/src/main/java/com/tangem/data/promo/converters/PromoResponseConverter.kt
index 151675506b..cc418e808c 100644
--- a/data/promo/src/main/java/com/tangem/data/promo/converters/PromoResponseConverter.kt
+++ b/data/promo/src/main/java/com/tangem/data/promo/converters/PromoResponseConverter.kt
@@ -1,7 +1,7 @@
package com.tangem.data.promo.converters
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
-import com.tangem.domain.promo.PromoBanner
+import com.tangem.domain.promo.models.PromoBanner
import com.tangem.utils.converter.Converter
import org.joda.time.DateTime
diff --git a/data/promo/src/main/java/com/tangem/data/promo/di/PromoDataModule.kt b/data/promo/src/main/java/com/tangem/data/promo/di/PromoDataModule.kt
index 5ed2c6855f..102afaa289 100644
--- a/data/promo/src/main/java/com/tangem/data/promo/di/PromoDataModule.kt
+++ b/data/promo/src/main/java/com/tangem/data/promo/di/PromoDataModule.kt
@@ -2,7 +2,8 @@ package com.tangem.data.promo.di
import com.tangem.data.promo.DefaultPromoRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
-import com.tangem.domain.tokens.repository.PromoRepository
+import com.tangem.datasource.local.preferences.AppPreferencesStore
+import com.tangem.domain.promo.PromoRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@@ -18,8 +19,9 @@ internal object PromoDataModule {
@Singleton
fun providePromoRepository(
tangemTechApi: TangemTechApi,
+ appPreferencesStore: AppPreferencesStore,
dispatchers: CoroutineDispatcherProvider,
): PromoRepository {
- return DefaultPromoRepository(tangemTechApi, dispatchers)
+ return DefaultPromoRepository(tangemTechApi, appPreferencesStore, dispatchers)
}
}
\ No newline at end of file
diff --git a/data/settings/build.gradle.kts b/data/settings/build.gradle.kts
index 992a502c03..6856b1236f 100644
--- a/data/settings/build.gradle.kts
+++ b/data/settings/build.gradle.kts
@@ -17,7 +17,6 @@ dependencies {
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.settings)
- implementation(projects.domain.wallets.models)
implementation(deps.androidx.datastore)
diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt
deleted file mode 100644
index 8a2b5aa1d9..0000000000
--- a/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.tangem.data.settings
-
-import com.tangem.datasource.local.preferences.AppPreferencesStore
-import com.tangem.datasource.local.preferences.PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY
-import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY
-import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY
-import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_RING_PROMO_KEY
-import com.tangem.datasource.local.preferences.utils.get
-import com.tangem.datasource.local.preferences.utils.store
-import com.tangem.domain.settings.repositories.PromoSettingsRepository
-import com.tangem.domain.wallets.models.UserWalletId
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
-
-/**
- * Repository for showing swap promo notification.
- */
-class DefaultPromoSettingsRepository(
- private val appPreferencesStore: AppPreferencesStore,
-) : PromoSettingsRepository {
- override fun isReadyToShowWalletSwapPromo(): Flow {
- return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY, true)
- }
-
- override fun isReadyToShowTokenSwapPromo(): Flow {
- return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY, true)
- }
-
- override suspend fun setNeverToShowWalletSwapPromo() {
- appPreferencesStore.store(
- key = IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY,
- value = false,
- )
- }
-
- override suspend fun setNeverToShowTokenSwapPromo() {
- appPreferencesStore.store(
- key = IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY,
- value = false,
- )
- }
-
- override fun isReadyToShowRingPromo(userWalletId: UserWalletId): Flow {
- return combine(
- flow = appPreferencesStore
- .get(key = ADDED_WALLETS_WITH_RING_KEY, default = emptySet())
- .map { userWalletId.stringValue in it },
- flow2 = appPreferencesStore.get(key = SHOULD_SHOW_RING_PROMO_KEY, default = true),
- transform = { isRingAdded, shouldShowRingPromo -> isRingAdded && shouldShowRingPromo },
- )
- }
-
- override suspend fun setNeverToShowRingPromo() {
- appPreferencesStore.store(key = SHOULD_SHOW_RING_PROMO_KEY, value = false)
- }
-}
\ No newline at end of file
diff --git a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt
index 0ea5bce762..4f04ee135b 100644
--- a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt
+++ b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt
@@ -2,14 +2,12 @@ package com.tangem.data.settings.di
import com.tangem.data.settings.DefaultAppRatingRepository
import com.tangem.data.settings.DefaultPermissionRepository
-import com.tangem.data.settings.DefaultPromoSettingsRepository
import com.tangem.data.settings.DefaultSettingsRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.PermissionRepository
-import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@@ -44,12 +42,6 @@ internal object SettingsDataModule {
return DefaultAppRatingRepository(appPreferencesStore = appPreferencesStore)
}
- @Provides
- @Singleton
- fun providePromoSettingsSettingsRepository(appPreferencesStore: AppPreferencesStore): PromoSettingsRepository {
- return DefaultPromoSettingsRepository(appPreferencesStore = appPreferencesStore)
- }
-
@Provides
@Singleton
fun providePushPermissionRepository(appPreferencesStore: AppPreferencesStore): PermissionRepository {
diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt
new file mode 100644
index 0000000000..a0fb323d81
--- /dev/null
+++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt
@@ -0,0 +1,70 @@
+package com.tangem.data.visa
+
+import com.tangem.datasource.api.visa.TangemVisaAuthApi
+import com.tangem.domain.visa.model.VisaAuthChallenge
+import com.tangem.domain.visa.model.VisaAuthSession
+import com.tangem.domain.visa.model.VisaAuthSignedChallenge
+import com.tangem.domain.visa.model.VisaAuthTokens
+import com.tangem.domain.visa.repository.VisaAuthRepository
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+internal class DefaultVisaAuthRepository @Inject constructor(
+ private val visaAuthApi: TangemVisaAuthApi,
+ private val dispatchers: CoroutineDispatcherProvider,
+) : VisaAuthRepository {
+
+ override suspend fun getCardAuthChallenge(cardId: String, cardPublicKey: String): VisaAuthChallenge.Card =
+ withContext(dispatchers.io) {
+ val response = visaAuthApi.generateNonceByCard(
+ cardId = cardId,
+ cardPublicKey = cardPublicKey,
+ )
+
+ VisaAuthChallenge.Card(
+ challenge = response.nonce,
+ session = VisaAuthSession(response.sessionId),
+ )
+ }
+
+ override suspend fun getCustomerWalletAuthChallenge(
+ cardId: String,
+ walletPublicKey: String,
+ ): VisaAuthChallenge.Wallet = withContext(dispatchers.io) {
+ val response = visaAuthApi.generateNonceByWalletAddress(
+ customerId = cardId,
+ customerWalletAddress = walletPublicKey,
+ )
+
+ VisaAuthChallenge.Wallet(
+ challenge = response.nonce,
+ session = VisaAuthSession(response.sessionId),
+ )
+ }
+
+ override suspend fun getAccessTokens(signedChallenge: VisaAuthSignedChallenge): VisaAuthTokens =
+ withContext(dispatchers.io) {
+ val response = when (signedChallenge) {
+ is VisaAuthSignedChallenge.ByCardPublicKey -> {
+ visaAuthApi.getAccessToken(
+ sessionId = signedChallenge.challenge.session.sessionId,
+ signature = signedChallenge.signature,
+ salt = signedChallenge.salt,
+ )
+ }
+ is VisaAuthSignedChallenge.ByWallet -> {
+ visaAuthApi.getAccessToken(
+ sessionId = signedChallenge.challenge.session.sessionId,
+ signature = signedChallenge.signature,
+ salt = null,
+ )
+ }
+ }
+
+ VisaAuthTokens(
+ accessToken = response.accessToken,
+ refreshToken = response.refreshToken,
+ )
+ }
+}
\ No newline at end of file
diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt
index a8587868f9..8299b95fb4 100644
--- a/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt
+++ b/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt
@@ -1,7 +1,10 @@
package com.tangem.data.visa.di
+import com.tangem.data.visa.DefaultVisaAuthRepository
import com.tangem.data.visa.DummyVisaRepository
+import com.tangem.domain.visa.repository.VisaAuthRepository
import com.tangem.domain.visa.repository.VisaRepository
+import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -21,4 +24,13 @@ internal object VisaDataModule {
): VisaRepository {
return implementedVisaRepository.getOrNull() ?: DummyVisaRepository()
}
+}
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal interface VisaDataBindsModule {
+
+ @Binds
+ @Singleton
+ fun bindVisaAuthRepository(repository: DefaultVisaAuthRepository): VisaAuthRepository
}
\ No newline at end of file
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 2839cb01c0..904ee2d1d8 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
@@ -66,7 +67,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/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
index 440e818d8f..7c2147cd08 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
@@ -22,6 +22,7 @@ import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.common.util.hasDerivation
+import com.tangem.domain.demo.BuildConfig
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
@@ -369,7 +370,7 @@ class DefaultWalletManagersFacade(
blockchain = blockchain,
derivationPath = derivationPath,
)
- if (walletManager == null) {
+ if (BuildConfig.DEBUG || walletManager == null) {
walletManager = walletManagerFactory.createWalletManager(
scanResponse = userWallet.scanResponse,
blockchain = blockchain,
diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt
index 2891d6072d..81a3c174d2 100644
--- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt
+++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt
@@ -21,7 +21,7 @@ sealed interface OnrampProviderWithQuote {
val availablePaymentMethods: List,
) : Unavailable
- data class Error(
+ data class AmountError(
override val provider: OnrampProvider,
val quoteError: OnrampQuote.AmountError,
) : Unavailable
diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt
index 1d6c6700d6..be975c49dd 100644
--- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt
+++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt
@@ -40,7 +40,7 @@ class GetOnrampProviderWithQuoteUseCase(
toAmount = matchedQuote.toAmount,
fromAmount = matchedQuote.fromAmount,
)
- is OnrampQuote.AmountError -> Unavailable.Error(
+ is OnrampQuote.AmountError -> Unavailable.AmountError(
provider = matchedQuote.provider,
quoteError = matchedQuote,
)
diff --git a/domain/promo/.gitignore b/domain/promo/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/domain/promo/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/domain/promo/build.gradle.kts b/domain/promo/build.gradle.kts
new file mode 100644
index 0000000000..cefe592d97
--- /dev/null
+++ b/domain/promo/build.gradle.kts
@@ -0,0 +1,12 @@
+plugins {
+ alias(deps.plugins.kotlin.jvm)
+ id("configuration")
+}
+
+dependencies {
+ implementation(projects.domain.promo.models)
+ implementation(projects.domain.wallets.models)
+
+ implementation(deps.kotlin.coroutines)
+ implementation(deps.arrow.core)
+}
\ No newline at end of file
diff --git a/domain/promo/models/.gitignore b/domain/promo/models/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/domain/promo/models/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/domain/promo/models/build.gradle.kts b/domain/promo/models/build.gradle.kts
new file mode 100644
index 0000000000..fe9e75a251
--- /dev/null
+++ b/domain/promo/models/build.gradle.kts
@@ -0,0 +1,9 @@
+plugins {
+ alias(deps.plugins.kotlin.jvm)
+ alias(deps.plugins.kotlin.serialization)
+ id("configuration")
+}
+
+dependencies {
+ implementation(deps.jodatime)
+}
\ No newline at end of file
diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/promo/PromoBanner.kt b/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt
similarity index 92%
rename from domain/tokens/models/src/main/java/com/tangem/domain/promo/PromoBanner.kt
rename to domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt
index 2608085143..42ecbd01d8 100644
--- a/domain/tokens/models/src/main/java/com/tangem/domain/promo/PromoBanner.kt
+++ b/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt
@@ -1,4 +1,4 @@
-package com.tangem.domain.promo
+package com.tangem.domain.promo.models
import org.joda.time.DateTime
diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PromoSettingsRepository.kt b/domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt
similarity index 60%
rename from domain/settings/src/main/java/com/tangem/domain/settings/repositories/PromoSettingsRepository.kt
rename to domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt
index b68a740e09..6350df9b18 100644
--- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/PromoSettingsRepository.kt
+++ b/domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt
@@ -1,9 +1,17 @@
-package com.tangem.domain.settings.repositories
+package com.tangem.domain.promo
+import com.tangem.domain.promo.models.PromoBanner
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
-interface PromoSettingsRepository {
+interface PromoRepository {
+
+ suspend fun getChangellyPromoBanner(): PromoBanner?
+
+ suspend fun getOkxPromoBanner(): PromoBanner?
+
+ suspend fun getRingPromoBanner(): PromoBanner?
+
fun isReadyToShowWalletSwapPromo(): Flow
fun isReadyToShowTokenSwapPromo(): Flow
diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowRingPromoUseCase.kt b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowRingPromoUseCase.kt
similarity index 73%
rename from domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowRingPromoUseCase.kt
rename to domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowRingPromoUseCase.kt
index 67d9174e78..6dfa5d7b2b 100644
--- a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowRingPromoUseCase.kt
+++ b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowRingPromoUseCase.kt
@@ -1,10 +1,9 @@
-package com.tangem.domain.settings
+package com.tangem.domain.promo
-import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
-class ShouldShowRingPromoUseCase(private val promoSettingsRepository: PromoSettingsRepository) {
+class ShouldShowRingPromoUseCase(private val promoSettingsRepository: PromoRepository) {
operator fun invoke(userWalletId: UserWalletId): Flow {
return promoSettingsRepository.isReadyToShowRingPromo(userWalletId)
diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoTokenUseCase.kt b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoTokenUseCase.kt
similarity index 66%
rename from domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoTokenUseCase.kt
rename to domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoTokenUseCase.kt
index 8ede57bcf9..69908277b5 100644
--- a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoTokenUseCase.kt
+++ b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoTokenUseCase.kt
@@ -1,9 +1,8 @@
-package com.tangem.domain.settings
+package com.tangem.domain.promo
-import com.tangem.domain.settings.repositories.PromoSettingsRepository
import kotlinx.coroutines.flow.Flow
-class ShouldShowSwapPromoTokenUseCase(private val promoSettingsRepository: PromoSettingsRepository) {
+class ShouldShowSwapPromoTokenUseCase(private val promoSettingsRepository: PromoRepository) {
operator fun invoke(): Flow = promoSettingsRepository.isReadyToShowTokenSwapPromo()
diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoWalletUseCase.kt b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoWalletUseCase.kt
similarity index 66%
rename from domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoWalletUseCase.kt
rename to domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoWalletUseCase.kt
index 233277f71f..af234c43b2 100644
--- a/domain/settings/src/main/java/com/tangem/domain/settings/ShouldShowSwapPromoWalletUseCase.kt
+++ b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowSwapPromoWalletUseCase.kt
@@ -1,9 +1,8 @@
-package com.tangem.domain.settings
+package com.tangem.domain.promo
-import com.tangem.domain.settings.repositories.PromoSettingsRepository
import kotlinx.coroutines.flow.Flow
-class ShouldShowSwapPromoWalletUseCase(private val promoSettingsRepository: PromoSettingsRepository) {
+class ShouldShowSwapPromoWalletUseCase(private val promoSettingsRepository: PromoRepository) {
operator fun invoke(): Flow = promoSettingsRepository.isReadyToShowWalletSwapPromo()
diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt
index e6975190ee..5be9720ad4 100644
--- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt
+++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt
@@ -1,37 +1,30 @@
package com.tangem.domain.tokens
-import com.tangem.domain.exchange.RampStateManager
-import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.*
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.tokens.model.warnings.HederaWarnings
import com.tangem.domain.tokens.model.warnings.KaspaWarnings
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
-import com.tangem.domain.tokens.repository.*
+import com.tangem.domain.tokens.repository.CurrenciesRepository
+import com.tangem.domain.tokens.repository.CurrencyChecksRepository
+import com.tangem.domain.tokens.repository.NetworksRepository
+import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
-import com.tangem.feature.swap.domain.api.SwapRepository
-import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
-import com.tangem.utils.coroutines.runCatching
-import com.tangem.utils.isNullOrZero
import kotlinx.coroutines.flow.*
import java.math.BigDecimal
-@Suppress("LongParameterList", "LargeClass")
+@Suppress("LongParameterList")
class GetCurrencyWarningsUseCase(
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
private val networksRepository: NetworksRepository,
- private val swapRepository: SwapRepository,
- private val rampStateManager: RampStateManager,
private val stakingRepository: StakingRepository,
- private val promoRepository: PromoRepository,
- private val showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
private val dispatchers: CoroutineDispatcherProvider,
private val currencyChecksRepository: CurrencyChecksRepository,
) {
@@ -78,75 +71,6 @@ class GetCurrencyWarningsUseCase(
}.flowOn(dispatchers.io)
}
- // disabled for now, don't use it directly in combine() to avoid blocking other notifications
- @Suppress("UnusedPrivateMember")
- private suspend fun getSwapPromoNotificationWarning(
- operations: CurrenciesStatusesOperations,
- userWalletId: UserWalletId,
- currencyStatus: CryptoCurrencyStatus,
- ): Flow {
- val currency = currencyStatus.currency
- val cryptoStatuses = operations.getCurrenciesStatusesSync()
- val promoBanner = promoRepository.getOkxPromoBanner()
- return combine(
- flow = showSwapPromoTokenUseCase()
- .conflate()
- .distinctUntilChanged(),
- flow2 = flowOf(rampStateManager.availableForSwap(userWalletId, currency))
- .conflate()
- .distinctUntilChanged(),
- ) { shouldShowSwapPromo, isExchangeable ->
- promoBanner ?: return@combine null
- val showPromoBanner = promoBanner.isActive && shouldShowSwapPromo
- if (showPromoBanner && isExchangeable && currencyStatus.value !is CryptoCurrencyStatus.Unreachable) {
- cryptoStatuses.fold(
- ifLeft = { null },
- ifRight = { cryptoCurrencyStatuses ->
- val pairs = runCatching(dispatchers.io) {
- swapRepository.getPairsOnly(
- LeastTokenInfo(
- contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0",
- network = currency.network.backendId,
- ),
- cryptoCurrencyStatuses.map { it.currency },
- )
- }.getOrNull()?.pairs ?: emptyList()
-
- val filteredCurrencies = cryptoCurrencyStatuses.filterNot {
- it.currency.id == currency.id
- }
- val currencyPairs = pairs.filter {
- it.from.network == currency.network.backendId ||
- it.to.network == currency.network.backendId
- }
- val showPromo = currencyPairs.any { pair ->
- val availablePair = if (currencyStatus.value.amount.isNullOrZero()) {
- filteredCurrencies.filterNot { it.value.amount.isNullOrZero() }
- } else {
- filteredCurrencies
- }
- availablePair
- .any {
- it.currency.network.backendId == pair.to.network ||
- it.currency.network.backendId == pair.from.network
- }
- }
- if (showPromo) {
- CryptoCurrencyWarning.SwapPromo(
- startDateTime = promoBanner.bannerState.timeline.start,
- endDateTime = promoBanner.bannerState.timeline.end,
- )
- } else {
- null
- }
- },
- )
- } else {
- null
- }
- }
- }
-
@Suppress("LongParameterList")
private suspend fun getCoinRelatedWarnings(
userWalletId: UserWalletId,
diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt
deleted file mode 100644
index 39457c1113..0000000000
--- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.tangem.domain.tokens.repository
-
-import com.tangem.domain.promo.PromoBanner
-
-interface PromoRepository {
-
- suspend fun getChangellyPromoBanner(): PromoBanner?
-
- suspend fun getOkxPromoBanner(): PromoBanner?
-
- suspend fun getRingPromoBanner(): PromoBanner?
-}
\ No newline at end of file
diff --git a/domain/visa/build.gradle.kts b/domain/visa/build.gradle.kts
index 8032c2e995..d5f66c4889 100644
--- a/domain/visa/build.gradle.kts
+++ b/domain/visa/build.gradle.kts
@@ -20,4 +20,6 @@ dependencies {
/** Libs - Other */
implementation(deps.jodatime)
implementation(deps.androidx.paging.runtime)
+ implementation(deps.moshi)
+ implementation(deps.moshi.kotlin)
}
\ No newline at end of file
diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthChallenge.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthChallenge.kt
new file mode 100644
index 0000000000..536b7207dc
--- /dev/null
+++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthChallenge.kt
@@ -0,0 +1,31 @@
+package com.tangem.domain.visa.model
+
+sealed class VisaAuthChallenge {
+
+ abstract val session: VisaAuthSession
+
+ data class Card(
+ val challenge: String,
+ override val session: VisaAuthSession,
+ ) : VisaAuthChallenge()
+
+ data class Wallet(
+ val challenge: String,
+ override val session: VisaAuthSession,
+ ) : VisaAuthChallenge()
+}
+
+fun VisaAuthChallenge.Card.toSignedChallenge(signedChallenge: String, salt: String): VisaAuthSignedChallenge {
+ return VisaAuthSignedChallenge.ByCardPublicKey(
+ challenge = this,
+ signature = signedChallenge,
+ salt = salt,
+ )
+}
+
+fun VisaAuthChallenge.Wallet.toSignedChallenge(signedChallenge: String): VisaAuthSignedChallenge {
+ return VisaAuthSignedChallenge.ByWallet(
+ challenge = this,
+ signature = signedChallenge,
+ )
+}
\ No newline at end of file
diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthSession.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthSession.kt
new file mode 100644
index 0000000000..67437218f5
--- /dev/null
+++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthSession.kt
@@ -0,0 +1,4 @@
+package com.tangem.domain.visa.model
+
+@JvmInline
+value class VisaAuthSession(val sessionId: String)
\ No newline at end of file
diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthSignedChallenge.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthSignedChallenge.kt
new file mode 100644
index 0000000000..a16916a887
--- /dev/null
+++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthSignedChallenge.kt
@@ -0,0 +1,15 @@
+package com.tangem.domain.visa.model
+
+sealed class VisaAuthSignedChallenge {
+
+ data class ByCardPublicKey(
+ val challenge: VisaAuthChallenge.Card,
+ val signature: String,
+ val salt: String,
+ ) : VisaAuthSignedChallenge()
+
+ data class ByWallet(
+ val challenge: VisaAuthChallenge.Wallet,
+ val signature: String,
+ ) : VisaAuthSignedChallenge()
+}
\ No newline at end of file
diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthTokens.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthTokens.kt
new file mode 100644
index 0000000000..83b0a5b091
--- /dev/null
+++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthTokens.kt
@@ -0,0 +1,10 @@
+package com.tangem.domain.visa.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class VisaAuthTokens(
+ @Json(name = "accessToken") val accessToken: String,
+ @Json(name = "refreshToken") val refreshToken: String,
+)
\ No newline at end of file
diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaAuthRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaAuthRepository.kt
new file mode 100644
index 0000000000..a280b47b2c
--- /dev/null
+++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaAuthRepository.kt
@@ -0,0 +1,14 @@
+package com.tangem.domain.visa.repository
+
+import com.tangem.domain.visa.model.VisaAuthChallenge
+import com.tangem.domain.visa.model.VisaAuthSignedChallenge
+import com.tangem.domain.visa.model.VisaAuthTokens
+
+interface VisaAuthRepository {
+
+ suspend fun getCardAuthChallenge(cardId: String, cardPublicKey: String): VisaAuthChallenge.Card
+
+ suspend fun getCustomerWalletAuthChallenge(cardId: String, walletPublicKey: String): VisaAuthChallenge.Wallet
+
+ suspend fun getAccessTokens(signedChallenge: VisaAuthSignedChallenge): VisaAuthTokens
+}
\ No newline at end of file
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index edd1284284..f400b2ee7d 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
@@ -42,7 +50,14 @@ platform :android do
desc "Build external and release APKs"
lane :build do |options|
- gradle(task: 'clean')
+ gradle(task: 'clean') # TODO: remove after fully moving from old CI
+
+ launched_from_github_action = options[:launched_from_github_action] || false # TODO: remove after fully moving from old CI
+ if launched_from_github_action
+ FileUtils.cp("../ci_resources/build_ci_gradle.properties", "../gradle.properties")
+ puts File.read("../gradle.properties")
+ end
+
gradle(
task: "bundle",
build_type: "Release",
@@ -79,19 +94,29 @@ platform :android do
desc "Publish internal and external builds to Firebase App Distribution"
lane :publishToFirebase do |options|
+ FileUtils.cp("../ci_resources/build_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(
+ uploaded_release = 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'],
)
+ release_url = uploaded_release[:testingUri]
+
+ # Applying base64 encoding twice to prevent GA bug "Skip output 'output' since it may contain secret."
+ # See https://github.com/orgs/community/discussions/13082#discussioncomment-6776428 for details
+ encoded_release_url = Base64.strict_encode64(Base64.strict_encode64(release_url))
+ sh("echo 'encoded_release_url=#{encoded_release_url}' >> /workspace/github_output.txt")
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..0c2faf9735 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,14 +18,12 @@ 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.res.LocalSnackbarHostState
+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
@@ -48,12 +46,6 @@ internal fun DetailsScreen(
Scaffold(
modifier = modifier,
containerColor = backgroundColor,
- snackbarHost = {
- TangemSnackbarHost(
- modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
- hostState = LocalSnackbarHostState.current,
- )
- },
topBar = {
TangemTopAppBar(
modifier = Modifier.statusBarsPadding(),
@@ -86,7 +78,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 +119,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 7ffb017cfa..55dbe5ca34 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
@@ -153,7 +153,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/component/impl/DefaultAddCustomTokenComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt
index 563c100c5e..10cf6ddcc7 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt
@@ -62,7 +62,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
override fun BottomSheet() {
val config = remember {
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = ::dismiss,
content = initialConfiguration.step.toContentModel(::popBack),
)
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt
index cfac082152..d11eaaaf24 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt
@@ -29,7 +29,7 @@ internal class PreviewAddCustomTokenComponent(
override fun BottomSheet() {
val config by previewConfig.collectAsStateWithLifecycle()
val bottomSheetConfig = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = ::dismiss,
content = config.step.toContentModel(::dismiss),
)
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..e35c8871c7 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
@@ -51,13 +50,12 @@ import com.tangem.core.ui.components.rows.ChainRow
import com.tangem.core.ui.components.rows.ChainRowContainer
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
import com.tangem.core.ui.components.rows.model.ChainRowUM
-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
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.WindowInsetsZero
@@ -100,12 +98,6 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
state = state,
)
},
- snackbarHost = {
- TangemSnackbarHost(
- modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
- hostState = LocalSnackbarHostState.current,
- )
- },
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
if (state is ManageTokensUM.ManageContent) {
@@ -165,7 +157,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 +269,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..1d764d21a2 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
@@ -18,9 +17,8 @@ import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.PrimaryButtonIconEnd
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.res.LocalSnackbarHostState
+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.WindowInsetsZero
@@ -53,14 +51,6 @@ internal fun OnboardingManageTokensContent(state: OnboardingManageTokensUM, modi
state = state,
)
},
- snackbarHost = {
- TangemSnackbarHost(
- modifier = Modifier
- .padding(all = TangemTheme.dimens.spacing16)
- .navigationBarsPadding(),
- hostState = LocalSnackbarHostState.current,
- )
- },
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
FloatingActionButton(
@@ -102,7 +92,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 +107,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 +115,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/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt
index 8ed4841f87..cad8517d8c 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt
@@ -152,7 +152,7 @@ internal class ManageTokensUiManager(
private fun copyContractAddress(source: ManagedCryptoCurrency.SourceNetwork) {
if (source is ManagedCryptoCurrency.SourceNetwork.Default) {
- clipboardManager.setText(text = source.contractAddress)
+ clipboardManager.setText(text = source.contractAddress, isSensitive = false)
showSnackbarMessage(resourceReference(R.string.contract_address_copied_message))
}
}
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt
index 5121d721b4..c6c2a86b49 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt
@@ -205,7 +205,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
body = MarketsTokenDetailsUM.Body.Loading,
triggerPriceChange = consumedEvent(),
bottomSheetConfig = TangemBottomSheetConfig(
- isShow = false,
+ isShown = false,
onDismissRequest = {},
content = TangemBottomSheetConfigContent.Empty,
),
@@ -510,7 +510,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
state.update { stateToUpdate ->
stateToUpdate.copy(
bottomSheetConfig = stateToUpdate.bottomSheetConfig.copy(
- isShow = true,
+ isShown = true,
onDismissRequest = ::hideBottomSheet,
content = content,
),
@@ -521,7 +521,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
private fun hideBottomSheet() {
state.update { stateToUpdate ->
stateToUpdate.copy(
- bottomSheetConfig = stateToUpdate.bottomSheetConfig.copy(isShow = false),
+ bottomSheetConfig = stateToUpdate.bottomSheetConfig.copy(isShown = false),
)
}
}
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt
index 2aa2188b32..6b22d2f6e3 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt
@@ -336,7 +336,7 @@ private fun Preview() {
onSelectedIntervalChange = { },
body = MarketsTokenDetailsUM.Body.Loading,
bottomSheetConfig = TangemBottomSheetConfig(
- isShow = false,
+ isShown = false,
onDismissRequest = {},
content = TangemBottomSheetConfigContent.Empty,
),
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..4c7bd46d6c 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,
@@ -156,7 +152,7 @@ private fun Preview_ExchangesBottomSheet(
config = TangemBottomSheetConfig(
onDismissRequest = {},
content = content,
- isShow = true,
+ isShown = true,
),
)
}
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/details/impl/ui/components/SecurityScoreBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt
index 07019d64e1..391e6b8969 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/SecurityScoreBottomSheet.kt
@@ -181,7 +181,7 @@ private fun SecurityScoreBottomSheetPreview() {
TangemThemePreview {
SecurityScoreBottomSheet(
config = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {},
content = SecurityScorePreviewData.bottomSheetContent,
),
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt
index 79865f0661..ae0a8b3e09 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt
@@ -1,23 +1,26 @@
package com.tangem.features.markets.entry.impl
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
-import com.arkivanov.decompose.router.stack.*
+import com.arkivanov.decompose.router.stack.ChildStack
+import com.arkivanov.decompose.router.stack.StackNavigation
+import com.arkivanov.decompose.router.stack.childStack
+import com.arkivanov.decompose.router.stack.pushNew
import com.arkivanov.decompose.value.Value
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.navigation.Router
-import com.tangem.core.ui.UiDependencies
-import com.tangem.core.ui.message.EventMessageEffect
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarket
+import com.tangem.domain.markets.toSerializableParam
+import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.entry.MarketsEntryComponent
-import com.tangem.features.markets.details.MarketsTokenDetailsComponent
-import com.tangem.domain.markets.toSerializableParam
import com.tangem.features.markets.entry.impl.MarketsEntryChildFactory.Child
import com.tangem.features.markets.entry.impl.ui.EntryBottomSheetContent
import dagger.assisted.Assisted
@@ -28,7 +31,6 @@ import dagger.assisted.AssistedInject
internal class DefaultMarketsEntryComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
private val marketsEntryChildFactory: MarketsEntryChildFactory,
- private val uiDependencies: UiDependencies,
) : MarketsEntryComponent, AppComponentContext by context {
private val stackNavigation = StackNavigation()
@@ -64,10 +66,6 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
stackState = stack.subscribeAsState(),
modifier = modifier,
)
- EventMessageEffect(
- messageHandler = uiDependencies.eventMessageHandler,
- snackbarHostState = uiDependencies.globalSnackbarHostState,
- )
}
@OptIn(ExperimentalDecomposeApi::class)
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt
index a337b02336..9461484567 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt
@@ -50,7 +50,7 @@ internal class AddToPortfolioBSContentUMFactory(
alreadyAddedNetworks: Set?,
): TangemBottomSheetConfig {
return TangemBottomSheetConfig(
- isShow = portfolioUIData.portfolioBSVisibilityModel.addToPortfolioBSVisibility,
+ isShown = portfolioUIData.portfolioBSVisibilityModel.addToPortfolioBSVisibility,
onDismissRequest = { onAddToPortfolioVisibilityChange(false) },
content = if (selectedWallet != null && alreadyAddedNetworks != null) {
AddToPortfolioBSContentUM(
@@ -107,7 +107,7 @@ internal class AddToPortfolioBSContentUMFactory(
selectedWalletId: UserWalletId,
): TangemBottomSheetConfig {
return TangemBottomSheetConfig(
- isShow = isShow,
+ isShown = isShow,
onDismissRequest = { onWalletSelectorVisibilityChange(false) },
content = WalletSelectorBSContentUM(
userWallets = portfolioData.walletsWithCurrencies
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt
index ce9e2b7841..cce4273617 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt
@@ -101,10 +101,10 @@ internal class TokenActionsHandler @AssistedInject constructor(
updateTokenReceiveBSConfig {
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {
updateTokenReceiveBSConfig {
- it.copy(isShow = false)
+ it.copy(isShown = false)
}
},
content = TokenReceiveBottomSheetConfig(
@@ -130,7 +130,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
.toImmutableList()
val defaultAddress = addresses.firstOrNull()?.value ?: return
- clipboardManager.setText(text = defaultAddress)
+ clipboardManager.setText(text = defaultAddress, isSensitive = true)
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.wallet_notification_address_copied)))
}
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..1f0e080b17 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,
)
@@ -295,7 +295,7 @@ private fun Preview(
TangemThemePreview {
AddToPortfolioBottomSheet(
config = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
content = content,
onDismissRequest = {},
),
@@ -370,7 +370,7 @@ private fun PreviewContentTestOnDevice(
AddToPortfolioBottomSheet(
config = TangemBottomSheetConfig(
- isShow = isShow,
+ isShown = isShow,
content = contentState,
onDismissRequest = { isShow = false },
),
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/TokenActionsBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/TokenActionsBottomSheet.kt
index 611315a24b..8a09c1758a 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/TokenActionsBottomSheet.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/TokenActionsBottomSheet.kt
@@ -72,7 +72,7 @@ private fun Preview() {
Box(Modifier.background(TangemTheme.colors.background.secondary)) {
TokenActionsBottomSheet(
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {},
content = TokenActionsBSContentUM(
title = "Wallet 1",
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WalletSelectorBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WalletSelectorBottomSheet.kt
index 22ba89a8f7..a9fdbdaef9 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WalletSelectorBottomSheet.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/WalletSelectorBottomSheet.kt
@@ -94,7 +94,7 @@ private fun Preview() {
TangemThemePreview {
WalletSelectorBottomSheet(
config = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {},
content = WalletSelectorBSContentUM(
userWallets = persistentListOf(
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/model/statemanager/MarketsListUMStateManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt
index 66801cb9bd..6c2b177d24 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt
@@ -26,8 +26,8 @@ internal class MarketsListUMStateManager(
) {
private var sortByBottomSheetIsShown
- get() = state.value.sortByBottomSheet.isShow
- set(value) = state.update { it.copy(sortByBottomSheet = it.sortByBottomSheet.copy(isShow = value)) }
+ get() = state.value.sortByBottomSheet.isShown
+ set(value) = state.update { it.copy(sortByBottomSheet = it.sortByBottomSheet.copy(isShown = value)) }
var searchQuery
get() = state.value.searchBar.query
@@ -203,7 +203,7 @@ internal class MarketsListUMStateManager(
onIntervalClick = { selectedInterval = it },
onSortByButtonClick = { sortByBottomSheetIsShown = true },
sortByBottomSheet = TangemBottomSheetConfig(
- isShow = false,
+ isShown = false,
onDismissRequest = { sortByBottomSheetIsShown = false },
content = SortByBottomSheetContentUM(
selectedOption = SortByTypeUM.Rating,
@@ -217,7 +217,7 @@ internal class MarketsListUMStateManager(
it.copy(
selectedSortBy = sortByTypeUM,
sortByBottomSheet = it.sortByBottomSheet.copy(
- isShow = false,
+ isShown = false,
content = (it.sortByBottomSheet.content as SortByBottomSheetContentUM).copy(
selectedOption = sortByTypeUM,
),
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..752717b1bb 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
@@ -62,7 +62,7 @@ internal fun MarketsList(
)
MarketsListSortByBottomSheet(config = state.sortByBottomSheet)
KeyboardEvents(
- isSortByBottomSheetShown = state.sortByBottomSheet.isShow,
+ isSortByBottomSheetShown = state.sortByBottomSheet.isShown,
bottomSheetState = bottomSheetState,
)
}
@@ -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,
@@ -321,7 +321,7 @@ private fun Preview() {
onIntervalClick = {},
onSortByButtonClick = {},
sortByBottomSheet = TangemBottomSheetConfig(
- isShow = false,
+ isShown = false,
onDismissRequest = {},
content = SortByBottomSheetContentUM(selectedOption = SortByTypeUM.Rating) {},
),
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/MarketsListSortByBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListSortByBottomSheet.kt
index 61b289ff7e..9f08d614c4 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListSortByBottomSheet.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListSortByBottomSheet.kt
@@ -72,7 +72,7 @@ private fun Preview() {
Box(Modifier.background(TangemTheme.colors.background.secondary)) {
MarketsListSortByBottomSheet(
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {},
content = SortByBottomSheetContentUM(
selectedOption = SortByTypeUM.Trending,
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/MultiWalletAccessCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/MultiWalletAccessCodeComponent.kt
index 9edd571b93..7beeb89bd9 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/MultiWalletAccessCodeComponent.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/MultiWalletAccessCodeComponent.kt
@@ -51,7 +51,7 @@ class MultiWalletAccessCodeComponent(
val bsConfig = remember(this, showBs) {
TangemBottomSheetConfig(
- isShow = showBs,
+ isShown = showBs,
onDismissRequest = { dismiss() },
content = TangemBottomSheetConfigContent.Empty,
)
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..6116ad29e2 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,10 +72,31 @@ 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()
+ // sets proper artwork state for initial step
+ // (if we start from backup cards, we need to show proper artwork) ([REDACTED_TASK_KEY])
+ modelScope.launch {
+ when (initialStep) {
+ MultiWalletFinalizeUM.Step.Primary -> { /* state is already set */ }
+ MultiWalletFinalizeUM.Step.BackupDevice1 -> {
+ onEvent.emit(MultiWalletFinalizeComponent.Event.OneBackupCardAdded)
+ }
+ MultiWalletFinalizeUM.Step.BackupDevice2 -> {
+ onEvent.emit(MultiWalletFinalizeComponent.Event.OneBackupCardAdded)
+ onEvent.emit(MultiWalletFinalizeComponent.Event.TwoBackupCardsAdded)
+ }
+ }
+ }
+
val batchId = when (initialStep) {
MultiWalletFinalizeUM.Step.Primary -> backupService.primaryCardBatchId
MultiWalletFinalizeUM.Step.BackupDevice1 -> backupService.backupCardsBatchIds.getOrNull(0)
@@ -193,18 +214,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 +240,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/model/builder/ImportSeedPhraseUiStateBuilder.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/ImportSeedPhraseUiStateBuilder.kt
index d4f152b149..bc73e0a862 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/ImportSeedPhraseUiStateBuilder.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/ImportSeedPhraseUiStateBuilder.kt
@@ -157,7 +157,7 @@ internal class ImportSeedPhraseUiStateBuilder(
updateUiState { state ->
state.copy(
infoBottomSheetConfig = TangemBottomSheetConfig.Empty.copy(
- isShow = true,
+ isShown = true,
onDismissRequest = {
updateUiState { it.copy(infoBottomSheetConfig = TangemBottomSheetConfig.Empty) }
},
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/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt
index 56169d7fa1..5562ff3af5 100644
--- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt
+++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt
@@ -69,7 +69,7 @@ class ImportSeedPhraseStateBuilder {
return uiState.copy(
importSeedPhraseState = uiState.importSeedPhraseState.copy(
bottomSheetConfig = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = onDismiss,
content = ShowPassphraseInfoBottomSheetContent(onDismiss),
),
@@ -81,7 +81,7 @@ class ImportSeedPhraseStateBuilder {
return uiState.copy(
importSeedPhraseState = uiState.importSeedPhraseState.copy(
bottomSheetConfig = uiState.importSeedPhraseState.bottomSheetConfig?.copy(
- isShow = false,
+ isShown = false,
),
),
)
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt
index 0a88231cc3..e7f8a4d6dc 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt
@@ -47,7 +47,7 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor(
val bottomSheet by bottomSheetSlot.subscribeAsState()
val bottomSheetConfig = remember(key1 = this) {
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = ::dismiss,
content = TangemBottomSheetConfigContent.Empty,
)
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/PreviewConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/PreviewConfirmResidencyComponent.kt
index adc039dca9..70eecdc9a2 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/PreviewConfirmResidencyComponent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/PreviewConfirmResidencyComponent.kt
@@ -33,7 +33,7 @@ internal class PreviewConfirmResidencyComponent(
val state by previewState.collectAsStateWithLifecycle()
val bottomSheetConfig = remember(key1 = this) {
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = ::dismiss,
content = TangemBottomSheetConfigContent.Empty,
)
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 f53b13e685..5a09dc2d37 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.*
@@ -358,7 +358,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/paymentmethod/DefaultSelectPaymentMethodComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/DefaultSelectPaymentMethodComponent.kt
index 75a8748835..25d8b7cb79 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/DefaultSelectPaymentMethodComponent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/DefaultSelectPaymentMethodComponent.kt
@@ -24,7 +24,7 @@ internal class DefaultSelectPaymentMethodComponent @AssistedInject constructor(
override fun BottomSheet() {
val bottomSheetConfig = remember(key1 = this) {
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = ::dismiss,
content = PaymentMethodsBottomSheetConfig(
selectedMethodId = params.selectedMethodId,
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/DefaultSelectProviderComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/DefaultSelectProviderComponent.kt
index e59e4722e9..d0bcd4a3d0 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/DefaultSelectProviderComponent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/DefaultSelectProviderComponent.kt
@@ -52,7 +52,7 @@ internal class DefaultSelectProviderComponent @AssistedInject constructor(
val bottomSheetConfig = remember(key1 = this) {
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = ::dismiss,
content = TangemBottomSheetConfigContent.Empty,
)
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt
index e85759d361..bc077ed6a3 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt
@@ -76,10 +76,13 @@ internal class SelectProviderModel @Inject constructor(
val filteredEmptyMethods = methods.mapNotNull { method ->
val providers = getOnrampProviderWithQuoteUseCase(method).getOrNull()
if (!providers.isNullOrEmpty()) {
+ val allErrorProviders = providers.all {
+ it is OnrampProviderWithQuote.Unavailable.NotSupportedPaymentMethod
+ }
PaymentProviderUM(
paymentMethod = method,
providers = providers.toProvidersListItems(),
- )
+ ).takeIf { !allErrorProviders }
} else {
null
}
@@ -225,7 +228,7 @@ internal class SelectProviderModel @Inject constructor(
},
)
}
- is OnrampProviderWithQuote.Unavailable.Error -> {
+ is OnrampProviderWithQuote.Unavailable.AmountError -> {
val quoteError = quote.quoteError
val amount = quoteError.error.requiredAmount.format {
crypto(symbol = quoteError.fromAmount.symbol, decimals = quoteError.fromAmount.decimals)
@@ -307,7 +310,7 @@ internal class SelectProviderModel @Inject constructor(
is OnrampProviderWithQuote.Data -> it.toAmount.value
// negative difference to sort both when data and unavailable is present
- is OnrampProviderWithQuote.Unavailable.Error -> {
+ is OnrampProviderWithQuote.Unavailable.AmountError -> {
when (val error = it.quoteError.error) {
is OnrampError.AmountError.TooSmallError -> it.quoteError.fromAmount.value - error.requiredAmount
is OnrampError.AmountError.TooBigError -> error.requiredAmount - it.quoteError.fromAmount.value
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 071e09b4b6..39cc85083b 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,
)
@@ -193,7 +193,7 @@ private fun AvailableProviderItem(state: ProviderListItemUM.Available.Content, m
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
@@ -278,7 +278,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 91b94beb8e..df74ea2d77 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
@@ -88,9 +84,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/DefaultSelectCountryComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt
index 35fc452dc8..fa24e2dc0f 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt
@@ -33,7 +33,7 @@ internal class DefaultSelectCountryComponent @AssistedInject constructor(
val state by model.state.collectAsStateWithLifecycle()
val bottomSheetConfig = remember(key1 = this) {
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = ::dismiss,
content = TangemBottomSheetConfigContent.Empty,
)
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 b74c50c6b1..224e1c4168 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,
)
@@ -185,7 +185,7 @@ private fun UnavailableCountryItem(state: CountryItemState.WithContent.Unavailab
color = TangemTheme.colors.text.disabled,
)
Text(
- text = stringResource(R.string.onramp_country_unavailable),
+ text = stringResourceSafe(R.string.onramp_country_unavailable),
maxLines = 1,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt
index 687df5d015..552c4023d0 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt
@@ -33,7 +33,7 @@ internal class DefaultSelectCurrencyComponent @AssistedInject constructor(
val state by model.state.collectAsStateWithLifecycle()
val bottomSheetConfig = remember(key1 = this) {
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = ::dismiss,
content = TangemBottomSheetConfigContent.Empty,
)
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/QrScanningFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt
index 5118630639..787c7bdfe9 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt
@@ -25,6 +25,8 @@ import com.tangem.feature.qrscanning.viewmodel.QrScanningViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import timber.log.Timber
+import java.io.IOException
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import javax.inject.Inject
@@ -62,8 +64,15 @@ internal class QrScanningFragment : ComposeFragment() {
private val galleryLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
val selectedImage = it ?: Uri.EMPTY
if (selectedImage != Uri.EMPTY) {
- val image = InputImage.fromFilePath(requireContext(), selectedImage)
- analyzer.analyze(image)
+ val mimeType = requireContext().contentResolver.getType(selectedImage)
+ if (mimeType.isImageMimeType()) {
+ try {
+ val image = InputImage.fromFilePath(requireContext(), selectedImage)
+ analyzer.analyze(image)
+ } catch (e: IOException) {
+ Timber.e(e, "Unable to get image $selectedImage from gallery")
+ }
+ }
}
}
@@ -80,7 +89,7 @@ internal class QrScanningFragment : ComposeFragment() {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.launchGalleryEvent
.collect {
- galleryLauncher.launch(it.imageFilter)
+ galleryLauncher.launch(GALLERY_IMAGE_FILTER)
delay(timeMillis = 2000)
}
}
@@ -137,8 +146,13 @@ internal class QrScanningFragment : ComposeFragment() {
}
}
+ private fun String?.isImageMimeType() = this?.startsWith(prefix = "$IMAGE_MIME_TYPE/") == true
+
companion object {
+ private const val IMAGE_MIME_TYPE = "image"
+ private const val GALLERY_IMAGE_FILTER = "$IMAGE_MIME_TYPE/*"
+
fun create() = QrScanningFragment()
}
}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraDeniedBottomSheetConfig.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraDeniedBottomSheetConfig.kt
index 3d23eb1813..22e7790a45 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraDeniedBottomSheetConfig.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraDeniedBottomSheetConfig.kt
@@ -3,6 +3,7 @@ package com.tangem.feature.qrscanning.presentation
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
data class CameraDeniedBottomSheetConfig(
+ val onSettingsClick: () -> Unit,
val onGalleryClick: () -> Unit,
val onCancelClick: () -> Unit,
) : TangemBottomSheetConfigContent
\ No newline at end of file
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..dd1f555379 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
@@ -1,9 +1,6 @@
package com.tangem.feature.qrscanning.presentation
-import android.content.Intent
import android.content.res.Configuration
-import android.net.Uri
-import android.provider.Settings
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
@@ -11,13 +8,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.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
@@ -36,26 +31,19 @@ fun CameraDeniedBottomSheet(config: TangemBottomSheetConfig) {
@Composable
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(
- Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.fromParts("package", context.packageName, null),
- )
- ContextCompat.startActivity(context, intent, null)
- },
+ onItemsClick = content.onSettingsClick,
)
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 +63,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,
)
@@ -95,9 +83,10 @@ private fun Preview_CameraDeniedBottomSheet() {
TangemThemePreview {
CameraDeniedBottomSheet(
config = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {},
content = CameraDeniedBottomSheetConfig(
+ onSettingsClick = {},
onCancelClick = {},
onGalleryClick = {},
),
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt
index 5677a023f8..5dedeffebc 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt
@@ -12,9 +12,10 @@ internal class ShowCameraDeniedBottomSheetTransformer(
override fun transform(prevState: QrScanningState): QrScanningState {
return prevState.copy(
bottomSheetConfig = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = clickIntents::onBackClick,
content = CameraDeniedBottomSheetConfig(
+ onSettingsClick = clickIntents::onSettingsClick,
onCancelClick = clickIntents::onBackClick,
onGalleryClick = clickIntents::onGalleryClicked,
),
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt
index 5c2ab95304..cc5dd3518a 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt
@@ -1,5 +1,6 @@
package com.tangem.feature.qrscanning.viewmodel
+import com.tangem.core.navigation.settings.SettingsManager
import com.tangem.domain.qrscanning.usecases.EmitQrScannedEventUseCase
import com.tangem.feature.qrscanning.presentation.QrScanningStateController
import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomSheetTransformer
@@ -11,32 +12,30 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
-interface QrScanningClickIntents {
+internal interface QrScanningClickIntents {
- val launchGallery: SharedFlow
+ val launchGallery: SharedFlow
fun onBackClick()
fun onQrScanned(qrCode: String)
fun onGalleryClicked()
-}
-@JvmInline
-value class GalleryRequest(
- val imageFilter: String,
-)
+ fun onSettingsClick()
+}
@ViewModelScoped
internal class QrScanningClickIntentsImplementor @Inject constructor(
private val stateHolder: QrScanningStateController,
private val emitQrScannedEventUseCase: EmitQrScannedEventUseCase,
+ private val settingsManager: SettingsManager,
private val dispatcher: CoroutineDispatcherProvider,
) : BaseQrScanningClickIntents(), QrScanningClickIntents {
private var isScanned = false
- override val launchGallery = MutableSharedFlow(
+ override val launchGallery = MutableSharedFlow(
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_LATEST,
)
@@ -56,13 +55,13 @@ internal class QrScanningClickIntentsImplementor @Inject constructor(
}
override fun onGalleryClicked() {
- launchGallery.tryEmit(GalleryRequest(imageFilter = GALLERY_IMAGE_FILTER))
+ launchGallery.tryEmit(Unit)
if (stateHolder.value.bottomSheetConfig != null) {
stateHolder.update(DismissBottomSheetTransformer())
}
}
- companion object {
- private const val GALLERY_IMAGE_FILTER = "image/*"
+ override fun onSettingsClick() {
+ settingsManager.openSettings()
}
}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt
index a504e00e9f..ceff5566d6 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt
@@ -31,7 +31,7 @@ internal class QrScanningViewModel @Inject constructor(
private val network: String? = savedStateHandle[AppRoute.QrScanning.NETWORK_KEY]
val uiState: StateFlow = stateHolder.uiState
- val launchGalleryEvent: SharedFlow = clickIntents.launchGallery
+ val launchGalleryEvent: SharedFlow = clickIntents.launchGallery
init {
// samsung for some reason disables reader mode, and then it works unstable
diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts
index 93d3ad159f..63f2a5e291 100644
--- a/features/referral/presentation/build.gradle.kts
+++ b/features/referral/presentation/build.gradle.kts
@@ -14,6 +14,7 @@ dependencies {
/** Core modules */
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
+ implementation(projects.core.navigation)
implementation(projects.core.res)
implementation(projects.core.utils)
implementation(projects.core.ui)
@@ -37,7 +38,6 @@ dependencies {
/** Other libraries */
implementation(deps.compose.shimmer)
- implementation(deps.compose.accompanist.webView)
implementation(deps.compose.accompanist.systemUiController)
/** DI */
diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt
index dc7b06e5ef..2029309da1 100644
--- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt
+++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt
@@ -50,6 +50,6 @@ internal data class ReferralStateHolder(
data class Analytics(
val onAgreementClicked: () -> Unit,
val onCopyClicked: () -> Unit,
- val onShareClicked: () -> Unit,
+ val onShareClicked: (String) -> Unit,
)
}
\ No newline at end of file
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
deleted file mode 100644
index b1245ee718..0000000000
--- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.tangem.feature.referral.ui
-
-import android.content.res.Configuration
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
-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.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.res.TangemTheme
-import com.tangem.core.ui.res.TangemThemePreview
-import com.tangem.core.ui.webview.applySafeSettings
-import com.tangem.feature.referral.presentation.R
-
-/**
- * Bottom sheet content with referral program terms of use
- *
- * @param url link to the html page
- * @param bottomBarHeight bottom insets
- */
-@Composable
-internal fun AgreementBottomSheetContent(url: String, bottomBarHeight: Dp) {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .verticalScroll(rememberScrollState()),
- ) {
- TangemTopAppBar(title = stringResource(id = R.string.details_referral_title))
- AgreementHtmlView(url = url)
- Spacer(modifier = Modifier.height(bottomBarHeight))
- }
-}
-
-@Composable
-private fun AgreementHtmlView(url: String) {
- val state = rememberWebViewState(url)
- val isInPreviewMode = LocalInspectionMode.current
- WebView(
- state = state,
- modifier = Modifier.background(TangemTheme.colors.background.primary),
- captureBackPresses = false,
- onCreated = {
- if (!isInPreviewMode) it.applySafeSettings()
- },
- )
-}
-
-@Preview
-@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
-@Composable
-private fun Preview_AgreementBottomSheet() {
- TangemThemePreview {
- AgreementBottomSheetContent(url = "https://tangem.com/en/", 50.dp)
- }
-}
\ No newline at end of file
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..12ebf8162c 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
@@ -1,7 +1,5 @@
package com.tangem.feature.referral.ui
-import android.content.Context
-import android.content.Intent
import android.content.res.Configuration
import androidx.compose.animation.*
import androidx.compose.foundation.background
@@ -18,15 +16,15 @@ 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
@@ -44,7 +42,7 @@ internal fun ParticipateBottomBlock(
snackbarHostState: SnackbarHostState,
onAgreementClick: () -> Unit,
onCopyClick: () -> Unit,
- onShareClick: () -> Unit,
+ onShareClick: (String) -> Unit,
) {
Column(
modifier = Modifier
@@ -98,10 +96,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 +120,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 +199,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 +259,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,
@@ -281,7 +279,7 @@ private fun AdditionalButtons(
shareLink: String,
snackbarHostState: SnackbarHostState,
onCopyClick: () -> Unit,
- onShareClick: () -> Unit,
+ onShareClick: (String) -> Unit,
) {
val clipboardManager = LocalClipboardManager.current
val hapticFeedback = LocalHapticFeedback.current
@@ -294,7 +292,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 +301,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,12 +311,11 @@ 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()
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
- context.shareText(context.getString(R.string.referral_share_link, shareLink))
+ onShareClick(context.getString(R.string.referral_share_link, shareLink))
},
modifier = Modifier.weight(1f),
)
@@ -341,16 +338,6 @@ private fun calculateOverallItemsCount(expectedAwards: ExpectedAwards?, isExpand
return counterItems + awardItems
}
-private fun Context.shareText(text: String) {
- val sendIntent: Intent = Intent().apply {
- action = Intent.ACTION_SEND
- putExtra(Intent.EXTRA_TEXT, text)
- type = "text/plain"
- }
- val shareIntent = Intent.createChooser(sendIntent, null)
- startActivity(this, shareIntent, null)
-}
-
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@@ -434,5 +421,5 @@ private data class ParticipateBottomBlockData(
val onAgreementClick: () -> Unit = {},
val onShowCopySnackbar: () -> Unit = {},
val onCopyClick: () -> Unit = {},
- val onShareClick: () -> Unit = {},
+ val onShareClick: (String) -> Unit = {},
)
\ No newline at end of file
diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralBottomSheet.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralBottomSheet.kt
deleted file mode 100644
index 0a3d43118a..0000000000
--- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralBottomSheet.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.tangem.feature.referral.ui
-
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ModalBottomSheet
-import androidx.compose.material3.SheetState
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalDensity
-import com.tangem.core.ui.res.TangemTheme
-import com.tangem.core.ui.utils.WindowInsetsZero
-import com.tangem.feature.referral.models.ReferralStateHolder
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-internal fun ReferralBottomSheet(
- sheetState: SheetState,
- isVisible: Boolean,
- onDismissRequest: () -> Unit,
- config: ReferralStateHolder.ReferralInfoState,
-) {
- val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
-
- if (isVisible) {
- ModalBottomSheet(
- modifier = Modifier.statusBarsPadding(),
- onDismissRequest = onDismissRequest,
- sheetState = sheetState,
- shape = RoundedCornerShape(
- topStart = TangemTheme.dimens.radius16,
- topEnd = TangemTheme.dimens.radius16,
- ),
- contentWindowInsets = { WindowInsetsZero },
- containerColor = TangemTheme.colors.background.primary,
- ) {
- AgreementBottomSheetContent(
- url = when (config) {
- is ReferralStateHolder.ReferralInfoState.NonParticipantContent -> config.url
- is ReferralStateHolder.ReferralInfoState.ParticipantContent -> config.url
- is ReferralStateHolder.ReferralInfoState.Loading -> ""
- },
- bottomBarHeight = bottomBarHeight,
- )
- }
- }
-}
\ No newline at end of file
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 a3a69e7bd8..f5941be08d 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
@@ -10,18 +10,21 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
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
@@ -29,6 +32,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
@@ -43,14 +47,9 @@ import kotlinx.coroutines.launch
* Referral program screen for participant and non-participant
*
* @param stateHolder state holder
- * @param modifier modifier
*/
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun ReferralScreen(stateHolder: ReferralStateHolder) {
- var isBottomSheetVisible by remember { mutableStateOf(value = false) }
- val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
-
val snackbarHostState = remember(::SnackbarHostState)
BackHandler(onBack = stateHolder.headerState.onBackClicked)
@@ -59,7 +58,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,
)
},
@@ -83,10 +82,7 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) {
ReferralContent(
stateHolder = stateHolder,
snackbarHostState = snackbarHostState,
- onAgreementClick = {
- stateHolder.analytics.onAgreementClicked.invoke()
- isBottomSheetVisible = true
- },
+ onAgreementClick = stateHolder.analytics.onAgreementClicked,
modifier = Modifier.padding(it),
)
}
@@ -100,7 +96,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,
)
@@ -110,13 +106,6 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) {
}
}
}
-
- ReferralBottomSheet(
- sheetState = sheetState,
- isVisible = isBottomSheetVisible,
- onDismissRequest = { isBottomSheetVisible = false },
- config = stateHolder.referralInfoState,
- )
}
@Composable
@@ -156,7 +145,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,
@@ -273,7 +262,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,
@@ -288,7 +277,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
@@ -318,20 +307,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,
@@ -374,9 +363,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/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt
index 236d0c93db..a0a455ee28 100644
--- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt
+++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt
@@ -10,6 +10,8 @@ import androidx.lifecycle.viewModelScope
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
import com.tangem.core.analytics.api.AnalyticsEventHandler
+import com.tangem.core.navigation.share.ShareManager
+import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.referral.analytics.ReferralEvents
import com.tangem.feature.referral.domain.ReferralInteractor
@@ -34,6 +36,8 @@ internal class ReferralViewModel @Inject constructor(
private val referralInteractor: ReferralInteractor,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
+ private val shareManager: ShareManager,
+ private val urlOpener: UrlOpener,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
@@ -119,14 +123,18 @@ internal class ReferralViewModel @Inject constructor(
private fun onAgreementClicked() {
analyticsEventHandler.send(ReferralEvents.ClickTaC)
+
+ lastReferralData?.tosLink?.let(urlOpener::openUrl)
}
private fun onCopyClicked() {
analyticsEventHandler.send(ReferralEvents.ClickCopy)
}
- private fun onShareClicked() {
+ private fun onShareClicked(text: String) {
analyticsEventHandler.send(ReferralEvents.ClickShare)
+
+ shareManager.shareText(text = text)
}
private fun ReferralData.convertToReferralInfoState(): ReferralInfoState = when (this) {
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/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt
index 7e17a6e883..ca5adcd00a 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt
@@ -59,13 +59,14 @@ internal object SendClickIntentsStub : SendClickIntents {
override fun onExploreClick() {}
- override fun onShareClick() {}
+ override fun onShareClick(txUrl: String) {}
override fun onAmountReduceByClick(
reduceAmountBy: BigDecimal,
reduceAmountByDiff: BigDecimal,
notification: Class,
- ) {}
+ ) {
+ }
override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class) {}
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..f1a3d33ffc 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
@@ -69,7 +68,7 @@ internal fun SendNavigationButtons(
SendDoneButtons(
txUrl = sendState.txUrl,
onExploreClick = uiState.clickIntents::onExploreClick,
- onShareClick = uiState.clickIntents::onShareClick,
+ onShareClick = { uiState.clickIntents.onShareClick(it) },
isVisible = isSentState,
)
SendNavigationButton(
@@ -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/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
index 57247c99ba..e77536f76c 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
@@ -55,7 +55,7 @@ internal interface SendClickIntents : AmountScreenClickIntents {
fun onExploreClick()
- fun onShareClick()
+ fun onShareClick(txUrl: String)
fun onAmountReduceByClick(
reduceAmountBy: BigDecimal,
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
index 8b0778f2cd..ec9e878040 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
@@ -15,6 +15,7 @@ import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
+import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
@@ -106,6 +107,7 @@ internal class SendViewModel @Inject constructor(
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
private val getCardInfoUseCase: GetCardInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
+ private val shareManager: ShareManager,
@DelayedWork private val coroutineScope: CoroutineScope,
validateTransactionUseCase: ValidateTransactionUseCase,
getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
@@ -862,8 +864,9 @@ internal class SendViewModel @Inject constructor(
innerRouter.openUrl(sendState.txUrl)
}
- override fun onShareClick() {
+ override fun onShareClick(txUrl: String) {
analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked)
+ shareManager.shareText(txUrl)
}
override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class) {
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEvent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEvent.kt
index a1b9cce6d9..7e7700595e 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEvent.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEvent.kt
@@ -10,6 +10,4 @@ internal sealed class StakingEvent {
data class ShowSnackBar(val text: TextReference) : StakingEvent()
data class ShowAlert(val alert: AlertUM) : StakingEvent()
-
- data class ShowShareDialog(val txUrl: String) : StakingEvent()
}
\ No newline at end of file
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEventFactory.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEventFactory.kt
index 85c5470976..75bcbd3efd 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEventFactory.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/events/StakingEventFactory.kt
@@ -41,9 +41,4 @@ internal class StakingEventFactory(
)
stateController.updateEvent(alert)
}
-
- fun createShareDialog(txUrl: String) {
- val event = StakingEvent.ShowShareDialog(txUrl)
- stateController.updateEvent(event)
- }
}
\ No newline at end of file
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowActionSelectorBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowActionSelectorBottomSheetTransformer.kt
index 2c927652c7..c70861712b 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowActionSelectorBottomSheetTransformer.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowActionSelectorBottomSheetTransformer.kt
@@ -17,7 +17,7 @@ internal class ShowActionSelectorBottomSheetTransformer(
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
bottomSheetConfig = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
content = StakingActionSelectionBottomSheetConfig(
title = resourceReference(R.string.common_choose_action),
actions = pendingActions,
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowInfoBottomSheetStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowInfoBottomSheetStateTransformer.kt
index f0e0ade2cc..9f1c669b3e 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowInfoBottomSheetStateTransformer.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ShowInfoBottomSheetStateTransformer.kt
@@ -17,7 +17,7 @@ internal class ShowInfoBottomSheetStateTransformer(
return prevState.copy(
bottomSheetConfig = TangemBottomSheetConfig(
onDismissRequest = onDismiss,
- isShow = true,
+ isShown = true,
content = when (infoType) {
InfoType.ANNUAL_PERCENTAGE_RATE -> StakingInfoBottomSheetConfig(
title = resourceReference(R.string.staking_details_annual_percentage_rate),
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetApprovalBottomSheetInProgressTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetApprovalBottomSheetInProgressTransformer.kt
index 085061c2b8..5ceb9ac4d3 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetApprovalBottomSheetInProgressTransformer.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetApprovalBottomSheetInProgressTransformer.kt
@@ -13,7 +13,7 @@ internal class SetApprovalBottomSheetInProgressTransformer(
return prevState.copy(
bottomSheetConfig = prevState.bottomSheetConfig?.copy(
onDismissRequest = onDismiss,
- isShow = true,
+ isShown = true,
content = approvalBottomSheetConfig?.let { config ->
config.copy(
data = config.data.copy(
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt
index 8350e74f74..be9aac4f36 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt
@@ -45,7 +45,7 @@ internal class ShowApprovalBottomSheetTransformer(
)
return prevState.copy(
bottomSheetConfig = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = onDismiss,
content = GiveTxPermissionBottomSheetConfig(
data = GiveTxPermissionState.ReadyForRequest(
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..96234a843e 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,20 +4,18 @@ 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.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
@Composable
internal fun StakingEventEffect(event: StateEvent, snackbarHostState: SnackbarHostState) {
- val context = LocalContext.current
val resources = LocalContext.current.resources
var alertConfig by remember { mutableStateOf(value = null) }
@@ -40,9 +38,6 @@ internal fun StakingEventEffect(event: StateEvent, snackbarHostSta
is StakingEvent.ShowAlert -> {
alertConfig = value.alert
}
- is StakingEvent.ShowShareDialog -> {
- context.shareText(value.txUrl)
- }
}
},
)
@@ -64,7 +59,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/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/StakingActionSelectorBottomSheet.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/StakingActionSelectorBottomSheet.kt
index 8f4434e496..64c371054c 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/StakingActionSelectorBottomSheet.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/StakingActionSelectorBottomSheet.kt
@@ -67,7 +67,7 @@ private fun Preview_StakingActionSelectorBottomSheet() {
TangemThemePreview {
StakingActionSelectorBottomSheet(
config = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {},
content = StakingActionSelectionBottomSheetConfig(
title = resourceReference(R.string.common_select_action),
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/StakingInfoBottomSheet.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/StakingInfoBottomSheet.kt
index 6e0b80bd18..af0ceabcc0 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/StakingInfoBottomSheet.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/bottomsheet/StakingInfoBottomSheet.kt
@@ -52,7 +52,7 @@ private fun Preview_StakingInfoBottomSheet() {
TangemThemePreview {
StakingInfoBottomSheet(
config = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {},
content = StakingInfoBottomSheetConfig(
title = stringReference("Title"),
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt
index adadecbac9..58f5125717 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt
@@ -17,6 +17,7 @@ import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomS
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.ParamsInterceptorHolder
+import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@@ -119,6 +120,7 @@ internal class StakingViewModel @Inject constructor(
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val getActionsUseCase: GetActionsUseCase,
private val paramsInterceptorHolder: ParamsInterceptorHolder,
+ private val shareManager: ShareManager,
@DelayedWork private val coroutineScope: CoroutineScope,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
@@ -765,7 +767,7 @@ internal class StakingViewModel @Inject constructor(
analyticsEventHandler.send(StakingAnalyticsEvent.ButtonShare)
if (txUrl != null) {
vibratorHapticManager.performOneTime(TangemHapticEffect.OneTime.Click)
- stakingEventFactory.createShareDialog(txUrl = txUrl)
+ shareManager.shareText(txUrl)
}
}
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..98c167b26c 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
@@ -93,7 +96,15 @@ sealed class TxFeeState {
data class MultipleFeeState(
val normalFee: TxFee,
val priorityFee: TxFee,
- ) : TxFeeState()
+ ) : TxFeeState() {
+
+ fun getFeeByType(feeType: FeeType): TxFee {
+ return when (feeType) {
+ FeeType.NORMAL -> normalFee
+ FeeType.PRIORITY -> priorityFee
+ }
+ }
+ }
data class SingleFeeState(
val fee: TxFee,
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..0eef2da3aa 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
@@ -65,12 +67,13 @@ interface SwapInteractor {
fee: TxFee,
): SwapTransactionState
- suspend fun updateQuotesStateWithSelectedFee(
- state: SwapState.QuotesLoadedState,
- selectedFee: FeeType,
- fromToken: CryptoCurrencyStatus,
- amountToSwap: String,
- ): SwapState.QuotesLoadedState
+ // suspend fun updateQuotesStateWithSelectedFee(
+ // state: SwapState.QuotesLoadedState,
+ // selectedFee: FeeType,
+ // fromToken: CryptoCurrencyStatus,
+ // amountToSwap: String,
+ // reduceBalanceBy: BigDecimal,
+ // ): SwapState.QuotesLoadedState
/**
* Returns token in wallet balance
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..1cf81e5c50 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,7 +292,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
toToken = toToken,
provider = provider,
amount = amount,
+ reduceBalanceBy = reduceBalanceBy,
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
+ selectedFee = selectedFee,
)
}
}
@@ -359,6 +362,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
txFee = TxFeeState.Empty,
transactionFee = null,
includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex
+ selectedFee = selectedFee,
)
}
}
@@ -369,16 +373,20 @@ internal class SwapInteractorImpl @AssistedInject constructor(
toToken: CryptoCurrencyStatus,
provider: SwapProvider,
amount: SwapAmount,
+ reduceBalanceBy: BigDecimal,
isBalanceWithoutFeeEnough: Boolean,
+ selectedFee: FeeType,
): Pair {
return provider to loadCexQuoteData(
networkId = networkId,
amount = amount,
+ reduceBalanceBy = reduceBalanceBy,
fromTokenStatus = fromToken,
toTokenStatus = toToken,
isAllowedToSpend = true,
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
provider = provider,
+ selectedFee = selectedFee,
)
}
@@ -386,111 +394,71 @@ 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
-
+ selectedFee: FeeType,
+ includeFeeInAmount: IncludeFeeInAmount,
+ ): CryptoCurrencyCheck {
val fee = when (feeState) {
TxFeeState.Empty -> BigDecimal.ZERO
- is TxFeeState.MultipleFeeState -> feeState.priorityFee.feeValue
+ is TxFeeState.MultipleFeeState -> feeState.getFeeByType(selectedFee).feeValue
is TxFeeState.SingleFeeState -> feeState.fee.feeValue
}
+ val balanceAfterTransaction = getCoinBalanceAfterTransaction(fromTokenStatus, amount, includeFeeInAmount, 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))
- }
+ return currencyCheck
}
- private fun manageReduceAmountWarning(
- warnings: MutableList,
+ private suspend fun getCoinBalanceAfterTransaction(
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()))
+ includeFeeInAmount: IncludeFeeInAmount,
+ fee: BigDecimal,
+ ): BigDecimal? {
+ return when (fromTokenStatus.currency) {
+ is CryptoCurrency.Coin -> {
+ val statusValue = fromTokenStatus.value as? CryptoCurrencyStatus.Loaded
+ when (includeFeeInAmount) {
+ is IncludeFeeInAmount.Included -> {
+ statusValue?.let { it.amount - includeFeeInAmount.amountSubtractFee.value - fee }
+ }
+ is IncludeFeeInAmount.Excluded -> {
+ statusValue?.let { it.amount - amount.value - fee }
+ }
+ else -> null
+ }
+ }
+ is CryptoCurrency.Token -> {
+ val feePaidCurrency = getFeePaidCurrency(
+ currency = fromTokenStatus.currency,
+ )
+ when (feePaidCurrency) {
+ FeePaidCurrency.Coin -> {
+ val nativeBalance = userWalletManager.getNativeTokenBalance(
+ networkId = fromTokenStatus.currency.network.backendId,
+ derivationPath = fromTokenStatus.currency.network.derivationPath.value,
+ )
+ nativeBalance?.let { it.value - fee }
+ }
+ else -> null // it doesnt matter for this fun
+ }
+ }
}
}
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 +466,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(
@@ -616,43 +538,46 @@ internal class SwapInteractorImpl @AssistedInject constructor(
}
}
- override suspend fun updateQuotesStateWithSelectedFee(
- state: SwapState.QuotesLoadedState,
- selectedFee: FeeType,
- fromToken: CryptoCurrencyStatus,
- amountToSwap: String,
- ): SwapState.QuotesLoadedState {
- val amountDecimal = toBigDecimalOrNull(amountToSwap)
- if (amountDecimal == null || amountDecimal.signum() == 0) {
- return state
- }
- val amount = SwapAmount(amountDecimal, fromToken.currency.decimals)
- val includeFeeInAmount = getIncludeFeeInAmount(
- networkId = fromToken.currency.network.backendId,
- txFee = state.txFee,
- amount = amount,
- fromToken = fromToken.currency,
- )
- val fee = when (val txFee = state.txFee) {
- TxFeeState.Empty -> BigDecimal.ZERO
- is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeIncludeOtherNativeFee
- is TxFeeState.SingleFeeState -> txFee.fee.feeIncludeOtherNativeFee
- }
- val feeState = getFeeState(
- fee = fee,
- spendAmount = amount,
- networkId = fromToken.currency.network.backendId,
- fromTokenStatus = fromToken,
- )
- return state.copy(
- permissionState = PermissionDataState.Empty,
- preparedSwapConfigState = state.preparedSwapConfigState.copy(
- feeState = feeState,
- isBalanceEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough,
- includeFeeInAmount = includeFeeInAmount,
- ),
- )
- }
+ // override suspend fun updateQuotesStateWithSelectedFee(
+ // state: SwapState.QuotesLoadedState,
+ // selectedFee: FeeType,
+ // fromToken: CryptoCurrencyStatus,
+ // amountToSwap: String,
+ // reduceBalanceBy: BigDecimal,
+ // ): SwapState.QuotesLoadedState {
+ // val amountDecimal = toBigDecimalOrNull(amountToSwap)
+ // if (amountDecimal == null || amountDecimal.signum() == 0) {
+ // return state
+ // }
+ // val amount = SwapAmount(amountDecimal, fromToken.currency.decimals)
+ // val includeFeeInAmount = getIncludeFeeInAmount(
+ // networkId = fromToken.currency.network.backendId,
+ // txFee = state.txFee,
+ // amount = amount,
+ // reduceBalanceBy = reduceBalanceBy,
+ // fromToken = fromToken.currency,
+ // selectedFee = selectedFee,
+ // )
+ // val fee = when (val txFee = state.txFee) {
+ // TxFeeState.Empty -> BigDecimal.ZERO
+ // is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeIncludeOtherNativeFee
+ // is TxFeeState.SingleFeeState -> txFee.fee.feeIncludeOtherNativeFee
+ // }
+ // val feeState = getFeeState(
+ // fee = fee,
+ // spendAmount = amount,
+ // networkId = fromToken.currency.network.backendId,
+ // fromTokenStatus = fromToken,
+ // )
+ // return state.copy(
+ // permissionState = PermissionDataState.Empty,
+ // preparedSwapConfigState = state.preparedSwapConfigState.copy(
+ // feeState = feeState,
+ // isBalanceEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough,
+ // includeFeeInAmount = includeFeeInAmount,
+ // ),
+ // )
+ // }
private suspend fun onSwapDex(
provider: SwapProvider,
@@ -1018,6 +943,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
PolygonZkEVM,
PolygonZkEVMTestnet,
Radiant,
+ Fact0rn,
Base,
BaseTestnet,
Moonriver,
@@ -1050,6 +976,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
Chiliz,
ChilizTestnet,
Clore,
+ VanarChain,
+ VanarChainTestnet,
-> Fee.Common(feeAmount)
// endregion
}
@@ -1150,11 +1078,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
private suspend fun loadCexQuoteData(
networkId: String,
amount: SwapAmount,
+ reduceBalanceBy: BigDecimal,
fromTokenStatus: CryptoCurrencyStatus,
toTokenStatus: CryptoCurrencyStatus,
provider: SwapProvider,
isAllowedToSpend: Boolean,
isBalanceWithoutFeeEnough: Boolean,
+ selectedFee: FeeType,
): SwapState {
val fromToken = fromTokenStatus.currency
val toToken = toTokenStatus.currency
@@ -1175,7 +1105,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
networkId = networkId,
txFee = txFee,
amount = amount,
+ reduceBalanceBy = reduceBalanceBy,
fromToken = fromToken,
+ selectedFee = selectedFee,
)
val amountToRequest = if (includeFeeInAmount is IncludeFeeInAmount.Included) {
includeFeeInAmount.amountSubtractFee
@@ -1206,8 +1138,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
isAllowedToSpend = isAllowedToSpend,
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
txFee = txFee,
- transactionFee = txFeeResult?.getOrNull(),
+ transactionFee = txFeeResult.getOrNull(),
includeFeeInAmount = includeFeeInAmount,
+ selectedFee = selectedFee,
)
}
}
@@ -1225,6 +1158,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
txFee: TxFeeState,
transactionFee: TransactionFee?,
includeFeeInAmount: IncludeFeeInAmount,
+ selectedFee: FeeType,
): SwapState {
return quoteDataModel.fold(
ifRight = { quoteModel ->
@@ -1238,12 +1172,20 @@ 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,
+ selectedFee = selectedFee,
+ includeFeeInAmount = includeFeeInAmount,
),
+ validationResult = manageTransactionValidationWarnings(
+ fromToken = fromToken,
+ amount = amount,
+ feeState = txFee,
+ userWalletId = userWalletId,
+ ),
+ minAdaValue = (transactionFee?.normal as? Fee.CardanoToken)?.minAdaValue,
)
when (provider.type) {
@@ -1321,11 +1263,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
networkId: String,
txFee: TxFeeState,
amount: SwapAmount,
+ reduceBalanceBy: BigDecimal,
fromToken: CryptoCurrency,
+ selectedFee: FeeType,
): IncludeFeeInAmount {
val feeValue = when (txFee) {
TxFeeState.Empty -> BigDecimal.ZERO
- is TxFeeState.MultipleFeeState -> txFee.priorityFee.feeIncludeOtherNativeFee
+ is TxFeeState.MultipleFeeState -> txFee.getFeeByType(selectedFee).feeIncludeOtherNativeFee
is TxFeeState.SingleFeeState -> txFee.fee.feeIncludeOtherNativeFee
}
val feePaidCurrency = getFeePaidCurrency(
@@ -1340,13 +1284,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 +1306,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,
),
)
@@ -1450,6 +1402,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
is ProxyFees.MultipleFees -> feeData.proxyFeesToFeeState(fromToken.currency, otherNativeFee)
is ProxyFees.SingleFee -> feeData.proxyFeesToFeeState(fromToken.currency, otherNativeFee)
}
+ val includeFeeInAmount = IncludeFeeInAmount.Excluded // exclude for dex
val feeByPriority = selectFeeByType(feeType = selectedFee, txFeeState = txFeeState)
val feeToCheckFunds = feeByPriority + (otherNativeFee ?: BigDecimal.ZERO)
val isBalanceIncludeFeeEnough = isBalanceEnough(fromToken, amount, feeToCheckFunds)
@@ -1464,7 +1417,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
isBalanceEnough = isBalanceIncludeFeeEnough,
feeState = feeState,
hasOutgoingTransaction = hasOutgoingTransaction(fromToken),
- includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex
+ includeFeeInAmount = includeFeeInAmount,
)
val swapState = updateBalances(
networkId = networkId,
@@ -1478,11 +1431,18 @@ 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
+ selectedFee = selectedFee,
+ includeFeeInAmount = includeFeeInAmount,
+ ),
+ validationResult = manageTransactionValidationWarnings(
+ fromToken = fromToken,
+ amount = amount,
+ feeState = txFeeState,
+ userWalletId = userWalletId,
),
preparedSwapConfigState = preparedSwapConfigState,
)
@@ -1578,6 +1538,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapDataModel = swapData,
txFee = txFeeState,
swapProvider = provider,
+ minAdaValue = null,
)
}
@@ -1600,20 +1561,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
amount = amount,
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
- ).mapFeeToForceSingleFeeIfNeeded(cryptoCurrency)
- }
-
- private fun Either.mapFeeToForceSingleFeeIfNeeded(
- cryptoCurrency: CryptoCurrency,
- ): Either {
- return this.map {
- val blockchain = Blockchain.fromNetworkId(cryptoCurrency.network.backendId)
- if (it is TransactionFee.Choosable && forceSingleFeeBlockchains.contains(blockchain)) {
- TransactionFee.Single(normal = it.normal)
- } else {
- it
- }
- }
+ )
}
@Suppress("LongParameterList", "LongMethod")
@@ -2227,17 +2175,6 @@ internal class SwapInteractorImpl @AssistedInject constructor(
private val minDemoFee = "0.0001".toBigDecimal()
private val normalDemoFee = "0.0002".toBigDecimal()
private val priorityDemoFee = "0.0003".toBigDecimal()
-
- private val forceSingleFeeBlockchains = listOf(
- Bitcoin, BitcoinTestnet,
- BitcoinCash, BitcoinCashTestnet,
- Litecoin,
- Dogecoin,
- Dash,
- Kaspa,
- Ravencoin, RavencoinTestnet,
- Ducatus,
- )
}
@AssistedFactory
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/models/states/events/SwapEvent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/events/SwapEvent.kt
index 1741d69029..a00fed2610 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/events/SwapEvent.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/events/SwapEvent.kt
@@ -6,6 +6,4 @@ import com.tangem.common.ui.alerts.models.AlertUM
@Immutable
internal sealed class SwapEvent {
data class ShowAlert(val alert: AlertUM) : SwapEvent()
-
- data class ShowShareDialog(val txUrl: String) : SwapEvent()
}
\ 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..401f98e20f
--- /dev/null
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/presentation/SwapNotificationsFactory.kt
@@ -0,0 +1,352 @@
+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 {
+ maybeAddRentExemptionError(quoteModel)
+ maybeAddDomainWarnings(quoteModel, feeCryptoCurrencyStatus, selectedFeeType)
+ maybeAddNeedReserveToCreateAccountWarning(quoteModel)
+ maybeAddPermissionNeededWarning(quoteModel, fromToken, providerName)
+ maybeAddNetworkFeeCoverageWarning(quoteModel, selectedFeeType)
+ maybeAddUnableCoverFeeWarning(quoteModel, fromToken)
+ maybeAddTransactionInProgressWarning(quoteModel)
+ }
+ return warnings.toPersistentList()
+ }
+
+ private fun MutableList.maybeAddRentExemptionError(quoteModel: SwapState.QuotesLoadedState) {
+ quoteModel.currencyCheck?.rentWarning?.let {
+ add(NotificationUM.Solana.RentInfo(it))
+ }
+ }
+
+ 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..13dc36fbac 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)) {
@@ -174,7 +170,7 @@ private fun Preview_ChooseFeeBottomSheet() {
TangemThemePreview {
ChooseFeeBottomSheet(
config = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {},
content = content,
),
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..44fae2d1f8 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
@@ -134,7 +134,7 @@ private fun Preview_ChooseProviderBottomSheet() {
TangemThemePreview {
ChooseProviderBottomSheet(
TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = {},
content = content,
),
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..58a9aabddf 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,15 @@ 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 +22,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 +65,10 @@ internal class StateBuilder(
appCurrencyProvider = appCurrencyProvider,
)
+ private val notificationsFactory by lazy(LazyThreadSafetyMode.NONE) {
+ SwapNotificationsFactory(actions)
+ }
+
fun createInitialLoadingState(
initialCurrencyFrom: CryptoCurrency,
initialCurrencyTo: CryptoCurrency?,
@@ -110,6 +119,7 @@ internal class StateBuilder(
providerState = ProviderState.Empty(),
shouldShowMaxAmount = false,
priceImpact = PriceImpact.Empty(),
+ isInsufficientFunds = false,
)
}
@@ -141,18 +151,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 +210,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 +234,7 @@ internal class StateBuilder(
uiStateHolder: SwapStateHolder,
quoteModel: SwapState.QuotesLoadedState,
fromToken: CryptoCurrency,
+ feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
swapProvider: SwapProvider,
bestRatedProviderId: String,
isNeedBestRateBadge: Boolean,
@@ -243,9 +243,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 +305,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 +316,7 @@ internal class StateBuilder(
),
fee = feeState,
swapButton = SwapButton(
- enabled = getSwapButtonEnabled(quoteModel),
+ enabled = getSwapButtonEnabled(notifications),
onClick = actions.onSwapClick,
),
changeCardsButtonState = if (isReverseSwapPossible) {
@@ -363,281 +365,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 +391,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 +436,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 +490,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 +528,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 +688,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 +726,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 +878,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 +936,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) {
@@ -1292,7 +945,7 @@ internal class StateBuilder(
)
return uiState.copy(
bottomSheetConfig = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = onDismiss,
content = config,
),
@@ -1303,7 +956,7 @@ internal class StateBuilder(
fun dismissBottomSheet(uiState: SwapStateHolder): SwapStateHolder {
return uiState.copy(
- bottomSheetConfig = uiState.bottomSheetConfig?.copy(isShow = false),
+ bottomSheetConfig = uiState.bottomSheetConfig?.copy(isShown = false),
)
}
@@ -1327,7 +980,7 @@ internal class StateBuilder(
)
return uiState.copy(
bottomSheetConfig = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = onDismiss,
content = config,
),
@@ -1368,21 +1021,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,
@@ -1405,7 +1043,7 @@ internal class StateBuilder(
)
return uiState.copy(
bottomSheetConfig = TangemBottomSheetConfig(
- isShow = true,
+ isShown = true,
onDismissRequest = onDismiss,
content = config,
),
@@ -1420,21 +1058,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 +1106,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 +1219,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 +1234,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..3a7455b5b0 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
@@ -1,22 +1,19 @@
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
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
@Composable
internal fun SwapEventEffect(event: StateEvent) {
- val context = LocalContext.current
var alertConfig by remember { mutableStateOf(value = null) }
val keyboardController = LocalSoftwareKeyboardController.current
@@ -35,9 +32,6 @@ internal fun SwapEventEffect(event: StateEvent) {
is SwapEvent.ShowAlert -> {
alertConfig = value.alert
}
- is SwapEvent.ShowShareDialog -> {
- context.shareText(value.txUrl)
- }
}
},
)
@@ -53,7 +47,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 13de36639d..a43e7c530c 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 = {
@@ -310,6 +309,7 @@ internal class SwapViewModel @Inject constructor(
fromToken = fromCurrencyStatus,
toToken = toCurrencyStatus,
amount = lastAmount.value,
+ reduceBalanceBy = lastReducedBalanceBy.value,
toProvidersList = findSwapProviders(fromCurrencyStatus, toCurrencyStatus),
)
}
@@ -327,6 +327,7 @@ internal class SwapViewModel @Inject constructor(
fromToken: CryptoCurrencyStatus,
toToken: CryptoCurrencyStatus,
amount: String,
+ reduceBalanceBy: BigDecimal,
toProvidersList: List,
isSilent: Boolean = false,
) {
@@ -345,6 +346,7 @@ internal class SwapViewModel @Inject constructor(
fromToken = fromToken,
toToken = toToken,
amount = amount,
+ reduceBalanceBy = reduceBalanceBy,
toProvidersList = toProvidersList,
),
)
@@ -360,6 +362,7 @@ internal class SwapViewModel @Inject constructor(
toToken = toCurrency,
amount = amount,
isSilent = isSilent,
+ reduceBalanceBy = dataState.reduceBalanceBy,
toProvidersList = findSwapProviders(fromCurrency, toCurrency),
)
}
@@ -369,6 +372,7 @@ internal class SwapViewModel @Inject constructor(
fromToken: CryptoCurrencyStatus,
toToken: CryptoCurrencyStatus,
amount: String,
+ reduceBalanceBy: BigDecimal,
toProvidersList: List,
): PeriodicTask