Updated on 2026-08-14

This commit is contained in:
Tangem 2025-01-09 20:37:33 +03:00
commit 8e42589a00
456 changed files with 6580 additions and 3738 deletions

44
Dockerfile Normal file
View file

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

View file

@ -75,6 +75,7 @@ dependencies {
implementation(projects.domain.markets) implementation(projects.domain.markets)
implementation(projects.domain.manageTokens) implementation(projects.domain.manageTokens)
implementation(projects.domain.onramp) implementation(projects.domain.onramp)
implementation(projects.domain.promo)
implementation(projects.common) implementation(projects.common)
implementation(projects.common.routing) implementation(projects.common.routing)
@ -166,6 +167,7 @@ dependencies {
implementation(deps.androidx.browser) implementation(deps.androidx.browser)
implementation(deps.androidx.paging.runtime) implementation(deps.androidx.paging.runtime)
implementation(deps.androidx.swipeRefreshLayout) implementation(deps.androidx.swipeRefreshLayout)
implementation(deps.androidx.fragment.compose)
implementation(deps.lifecycle.runtime.ktx) implementation(deps.lifecycle.runtime.ktx)
implementation(deps.lifecycle.common.java8) implementation(deps.lifecycle.common.java8)
implementation(deps.lifecycle.viewModel.ktx) implementation(deps.lifecycle.viewModel.ktx)
@ -226,11 +228,11 @@ dependencies {
implementation(deps.xmlShimmer) implementation(deps.xmlShimmer)
implementation(deps.viewBindingDelegate) implementation(deps.viewBindingDelegate)
implementation(deps.armadillo) implementation(deps.armadillo)
implementation(deps.mviCore.watcher)
implementation(deps.kotlin.serialization) implementation(deps.kotlin.serialization)
implementation(deps.reownCore) implementation(deps.reownCore)
implementation(deps.reownWeb3) implementation(deps.reownWeb3)
implementation(deps.prettyLogger) implementation(deps.prettyLogger)
implementation(deps.decompose.ext.compose)
/** Testing libraries */ /** Testing libraries */
testImplementation(deps.test.coroutine) testImplementation(deps.test.coroutine)

View file

@ -10,11 +10,6 @@
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" /> <uses-permission android:name="com.google.android.gms.permission.AD_ID" />
<uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera" />

@ -1 +1 @@
Subproject commit f1aa2f2cbf374ecba5da753d9f51c9df81749ef2 Subproject commit 1f1c301f4e0286cf6ab4cd8ee9323c85de3dfaab

View file

@ -759,6 +759,16 @@
} }
] ]
}, },
{
"id": "vanar-chain",
"name": "Vanar Chain",
"symbol": "VANRY",
"networks": [
{
"networkId": "vanar-chain/test"
}
]
},
{ {
"id": "casper-network", "id": "casper-network",
"name": "Casper", "name": "Casper",

View file

@ -10,6 +10,7 @@ import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage 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.features.onramp.OnrampFeatureToggles
import com.tangem.tap.common.log.TangemAppLoggerInitializer import com.tangem.tap.common.log.TangemAppLoggerInitializer
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.AppStateHolder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.EntryPoint import dagger.hilt.EntryPoint
@ -113,8 +113,6 @@ interface ApplicationEntryPoint {
fun getTransactionSignerFactory(): TransactionSignerFactory fun getTransactionSignerFactory(): TransactionSignerFactory
fun getHomeFeatureToggles(): HomeFeatureToggles
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
fun getOnrampFeatureToggles(): OnrampFeatureToggles fun getOnrampFeatureToggles(): OnrampFeatureToggles
@ -128,4 +126,6 @@ interface ApplicationEntryPoint {
fun getExcludedBlockchains(): ExcludedBlockchains fun getExcludedBlockchains(): ExcludedBlockchains
fun getAppLogsStore(): AppLogsStore fun getAppLogsStore(): AppLogsStore
fun getClipboardManager(): ClipboardManager
} }

View file

@ -8,7 +8,6 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.message.EventMessageEffect
import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.utils.Provider import com.tangem.utils.Provider
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -33,11 +32,6 @@ internal class DecomposeFragment : ComposeFragment() {
@Composable @Composable
override fun ScreenContent(modifier: Modifier) { override fun ScreenContent(modifier: Modifier) {
component.Content(modifier) component.Content(modifier)
EventMessageEffect(
messageHandler = uiDependencies.eventMessageHandler,
snackbarHostState = uiDependencies.globalSnackbarHostState,
)
} }
private class ComponentBuilder<C : ComposableContentComponent, P : Any, F : ComponentFactory<P, C>>( private class ComponentBuilder<C : ComposableContentComponent, P : Any, F : ComponentFactory<P, C>>(

View file

@ -7,17 +7,20 @@ import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.SystemBarStyle import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode 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.Color
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.Lifecycle 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.decompose.di.RootAppComponentContext
import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.navigation.email.EmailSender 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.event.StateEvent
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference 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.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.data.card.sdk.CardSdkOwner import com.tangem.data.card.sdk.CardSdkOwner
import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.card.ScanCardUseCase 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.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphAction 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.configurator.AppRouterConfig
import com.tangem.tap.routing.toggle.RoutingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import com.tangem.wallet.R import com.tangem.wallet.R
@ -209,6 +217,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject @Inject
lateinit var dispatchers: CoroutineDispatcherProvider lateinit var dispatchers: CoroutineDispatcherProvider
@Inject
internal lateinit var routingFeatureToggles: RoutingFeatureToggles
@Inject
internal lateinit var uiDependencies: UiDependencies
internal val viewModel: MainViewModel by viewModels() internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode> private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
@ -255,8 +269,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
installActivityDependencies() installActivityDependencies()
observeAppThemeModeUpdates() observeAppThemeModeUpdates()
setContentView(R.layout.activity_main) if (routingFeatureToggles.isNavigationRefactoringEnabled) {
installRouting() setRootContent()
} else {
setContentView(R.layout.activity_main)
installRouting()
installEventMessageEffect()
}
initContent() initContent()
observeStateUpdates() observeStateUpdates()
@ -271,6 +291,43 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
lifecycle.addObserver(WindowObscurationObserver) 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() { private fun installRouting() {
val routingComponent = routingComponentFactory.create( val routingComponent = routingComponentFactory.create(
context = rootComponentContext, context = rootComponentContext,
@ -297,6 +354,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
is RoutingComponent.Child.LegacyIntent -> { is RoutingComponent.Child.LegacyIntent -> {
startActivity(child.intent) startActivity(child.intent)
} }
is RoutingComponent.Child.ComposableComponent -> error("Unsupported child: $child")
} }
} }
} }
@ -400,8 +458,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
override fun onResume() { override fun onResume() {
super.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) navigateToInitialScreenIfNeeded(intent)
} }
@ -481,22 +542,22 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
} }
} }
override fun showSnackbar( override fun showSnackbar(@StringRes text: Int, length: Int, @StringRes buttonTitle: Int?, action: (() -> Unit)?) {
@StringRes text: Int, showSnackbar(
length: Int, getString(text),
@StringRes buttonTitle: Int?, length,
action: View.OnClickListener?, buttonTitle?.let(::getString),
) { action = { action?.invoke() },
showSnackbar(getString(text), length, buttonTitle?.let(::getString), action) )
} }
override fun showSnackbar( override fun showSnackbar(text: TextReference, length: Int, buttonTitle: TextReference?, action: (() -> Unit)?) {
text: TextReference, showSnackbar(
length: Int, text = text.resolveReference(resources),
buttonTitle: TextReference?, length = length,
action: View.OnClickListener?, buttonTitle = buttonTitle?.resolveReference(resources),
) { action = { action?.invoke() },
showSnackbar(text.resolveReference(resources), length, buttonTitle?.resolveReference(resources), action) )
} }
override fun dismissSnackbar() { override fun dismissSnackbar() {
@ -531,7 +592,13 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
return if (result) super.dispatchTouchEvent(event) else false 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 if (snackbar != null) return
snackbar = Snackbar.make(binding.fragmentContainer, text, length).apply { snackbar = Snackbar.make(binding.fragmentContainer, text, length).apply {
@ -542,12 +609,13 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
setTextColor(textColor) setTextColor(textColor)
if (buttonTitle != null && action != null) { if (buttonTitle != null && action != null) {
setAction(buttonTitle, action) setAction(buttonTitle, { action() })
} }
addCallback( addCallback(
object : BaseTransientBottomBar.BaseCallback<Snackbar>() { object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
onDismiss()
snackbar = null snackbar = null
removeCallback(this) removeCallback(this)
} }

View file

@ -20,6 +20,7 @@ import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.Basic.TransactionSent.WalletForm import com.tangem.core.analytics.models.Basic.TransactionSent.WalletForm
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
import com.tangem.core.configtoggle.feature.FeatureTogglesManager 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.MoshiConverter
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import com.tangem.datasource.connection.NetworkConnectionManager 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.common.redux.appReducer
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.tasks.product.DerivationsFinder 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.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -179,9 +179,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val transactionSignerFactory: TransactionSignerFactory private val transactionSignerFactory: TransactionSignerFactory
get() = entryPoint.getTransactionSignerFactory() get() = entryPoint.getTransactionSignerFactory()
private val homeFeatureToggles: HomeFeatureToggles
get() = entryPoint.getHomeFeatureToggles()
private val getUserCountryUseCase: GetUserCountryUseCase private val getUserCountryUseCase: GetUserCountryUseCase
get() = entryPoint.getGetUserCountryCodeUseCase() get() = entryPoint.getGetUserCountryCodeUseCase()
@ -202,6 +199,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val appLogsStore: AppLogsStore private val appLogsStore: AppLogsStore
get() = entryPoint.getAppLogsStore() get() = entryPoint.getAppLogsStore()
private val clipboardManager: ClipboardManager
get() = entryPoint.getClipboardManager()
// endregion // endregion
override fun onCreate() { override fun onCreate() {
@ -292,7 +292,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
shareManager = shareManager, shareManager = shareManager,
appRouter = appRouter, appRouter = appRouter,
transactionSignerFactory = transactionSignerFactory, transactionSignerFactory = transactionSignerFactory,
homeFeatureToggles = homeFeatureToggles,
getUserCountryUseCase = getUserCountryUseCase, getUserCountryUseCase = getUserCountryUseCase,
onrampFeatureToggles = onrampFeatureToggles, onrampFeatureToggles = onrampFeatureToggles,
environmentConfigStorage = environmentConfigStorage, environmentConfigStorage = environmentConfigStorage,
@ -300,6 +299,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
onboardingRepository = onboardingRepository, onboardingRepository = onboardingRepository,
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,
appPreferencesStore = appPreferencesStore, appPreferencesStore = appPreferencesStore,
clipboardManager = clipboardManager,
), ),
), ),
) )

View file

@ -1,6 +1,5 @@
package com.tangem.tap.common package com.tangem.tap.common
import android.view.View
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
@ -11,14 +10,14 @@ interface SnackbarHandler {
@StringRes text: Int, @StringRes text: Int,
length: Int = Snackbar.LENGTH_INDEFINITE, length: Int = Snackbar.LENGTH_INDEFINITE,
@StringRes buttonTitle: Int? = null, @StringRes buttonTitle: Int? = null,
action: View.OnClickListener? = null, action: (() -> Unit)? = null,
) )
fun showSnackbar( fun showSnackbar(
text: TextReference, text: TextReference,
length: Int = Snackbar.LENGTH_INDEFINITE, length: Int = Snackbar.LENGTH_INDEFINITE,
buttonTitle: TextReference? = null, buttonTitle: TextReference? = null,
action: View.OnClickListener? = null, action: (() -> Unit)? = null,
) )
fun dismissSnackbar() fun dismissSnackbar()

View file

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

View file

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

View file

@ -1,12 +1,52 @@
package com.tangem.tap.common.clipboard package com.tangem.tap.common.clipboard
import android.content.ClipData 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 com.tangem.core.ui.clipboard.ClipboardManager
import timber.log.Timber
import android.content.ClipboardManager as AndroidClipboardManager import android.content.ClipboardManager as AndroidClipboardManager
internal class DefaultClipboardManager(private val clipboardManager: AndroidClipboardManager) : ClipboardManager { internal class DefaultClipboardManager(private val clipboardManager: AndroidClipboardManager) : ClipboardManager {
override fun setText(label: String, text: String) { override fun setText(text: String, isSensitive: Boolean, label: String) {
clipboardManager.setPrimaryClip(ClipData.newPlainText(label, text)) 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"
}
} }
} }

View file

@ -4,8 +4,10 @@ import com.tangem.core.ui.clipboard.ClipboardManager
import timber.log.Timber import timber.log.Timber
internal object MockClipboardManager : ClipboardManager { 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") Timber.w("Clipboard Manager not available")
} }
override fun getText(default: String?): String? = null
} }

View file

@ -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
* <queries>
* <intent>
* <action android:name="android.intent.action.SENDTO" />
* <data android:scheme="mailto" />
* </intent>
* </queries>
*/
@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)
}
}

View file

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

View file

@ -2,26 +2,19 @@
package com.tangem.tap.common.extensions 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.Context
import android.content.ContextWrapper
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.View 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.core.content.ContextCompat
import androidx.fragment.app.Fragment
fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? { fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? {
return ContextCompat.getDrawable(this, drawableResId) return ContextCompat.getDrawable(this, drawableResId)
} }
@ColorInt
fun Fragment.getColor(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(requireContext(), colorRes)
}
@ColorInt @ColorInt
fun Context.getColorCompat(@ColorRes colorRes: Int): Int { fun Context.getColorCompat(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(this, colorRes) return ContextCompat.getColor(this, colorRes)
@ -40,10 +33,6 @@ fun View.getString(@StringRes id: Int, vararg formatArgs: String): String {
return context.getString(id, *formatArgs) 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) { fun View.show(show: Boolean, invokeBeforeStateChanged: (() -> Unit)? = null) {
return if (show) this.show(invokeBeforeStateChanged) else this.hide(invokeBeforeStateChanged) return if (show) this.show(invokeBeforeStateChanged) else this.hide(invokeBeforeStateChanged)
} }
@ -62,25 +51,6 @@ fun View.hide(invokeBeforeStateChanged: (() -> Unit)? = null) {
this.visibility = View.GONE 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 { fun View.getString(resId: Int, vararg formatArgs: Any?): String {
return context.getString(resId, *formatArgs) return context.getString(resId, *formatArgs)
} }

View file

@ -1,8 +1,5 @@
package com.tangem.tap.common.redux 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.GlobalMiddleware
import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.legacy.LegacyMiddleware 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.features.welcome.redux.WelcomeState
import com.tangem.tap.proxy.redux.DaggerGraphMiddleware import com.tangem.tap.proxy.redux.DaggerGraphMiddleware
import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import org.rekotlin.Middleware import org.rekotlin.Middleware
import org.rekotlin.StateType import org.rekotlin.StateType
@ -46,18 +42,6 @@ data class AppState(
val daggerGraphState: DaggerGraphState = DaggerGraphState(), val daggerGraphState: DaggerGraphState = DaggerGraphState(),
) : StateType { ) : 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 { companion object {
fun getMiddleware(): List<Middleware<AppState>> { fun getMiddleware(): List<Middleware<AppState>> {
return listOf( return listOf(

View file

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

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@ package com.tangem.tap.common.ui
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.Toast
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.core.analytics.Analytics 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.extensions.*
import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.domain.model.WalletAddressData import com.tangem.tap.domain.model.WalletAddressData
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.wallet.R import com.tangem.wallet.R
import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
@ -51,7 +53,12 @@ internal class AddressInfoBottomSheetDialog(
tvAddress.text = data.address tvAddress.text = data.address
btnFlCopyAddress.setOnClickListener { btnFlCopyAddress.setOnClickListener {
Analytics.send(Token.Receive.ButtonCopyAddress()) 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 { btnFlShare.setOnClickListener {
Analytics.send(Token.Receive.ButtonShareAddress()) Analytics.send(Token.Receive.ButtonShareAddress())

View file

@ -68,5 +68,6 @@ internal class AndroidEmailSender : EmailSender {
} }
return builder.intent return builder.intent
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
} }
} }

View file

@ -6,8 +6,9 @@ import com.arkivanov.decompose.defaultComponentContext
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.DefaultAppComponentContext import com.tangem.core.decompose.context.DefaultAppComponentContext
import com.tangem.core.decompose.di.DecomposeComponent 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.decompose.di.RootAppComponentContext
import com.tangem.core.ui.UiDependencies import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -27,13 +28,13 @@ internal object RootAppComponentContextModule {
@ActivityContext context: Context, @ActivityContext context: Context,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
componentBuilder: DecomposeComponent.Builder, componentBuilder: DecomposeComponent.Builder,
uiDependencies: UiDependencies, @GlobalUiMessageSender messageSender: UiMessageSender,
): AppComponentContext { ): AppComponentContext {
return DefaultAppComponentContext( return DefaultAppComponentContext(
componentContext = (context as AppCompatActivity).defaultComponentContext(), componentContext = (context as AppCompatActivity).defaultComponentContext(),
messageHandler = uiDependencies.eventMessageHandler,
dispatchers = dispatchers, dispatchers = dispatchers,
hiltComponentBuilder = componentBuilder, hiltComponentBuilder = componentBuilder,
messageSender = messageSender,
) )
} }
} }

View file

@ -1,6 +1,9 @@
package com.tangem.tap.di package com.tangem.tap.di
import androidx.compose.material3.SnackbarHostState 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.UiDependencies
import com.tangem.core.ui.haptic.VibratorHapticManager import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.core.ui.message.EventMessageHandler import com.tangem.core.ui.message.EventMessageHandler
@ -28,4 +31,11 @@ internal object UiDependenciesModule {
override val eventMessageHandler: EventMessageHandler = EventMessageHandler() override val eventMessageHandler: EventMessageHandler = EventMessageHandler()
} }
} }
@Provides
@Singleton
@GlobalUiMessageSender
fun provideUiMessageSender(uiDependencies: UiDependencies): UiMessageSender {
return DefaultUiMessageSender(uiDependencies.eventMessageHandler)
}
} }

View file

@ -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)
}
}

View file

@ -8,7 +8,6 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.settings.* import com.tangem.domain.settings.*
import com.tangem.domain.settings.repositories.AppRatingRepository import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.PermissionRepository 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.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
@ -117,30 +116,6 @@ internal object SettingsDomainModule {
return IsWalletsScrollPreviewEnabled(settingsRepository = settingsRepository) 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 @Provides
@Singleton @Singleton
fun provideDeleteDeprecatedLogsUseCase(settingsRepository: SettingsRepository): DeleteDeprecatedLogsUseCase { fun provideDeleteDeprecatedLogsUseCase(settingsRepository: SettingsRepository): DeleteDeprecatedLogsUseCase {

View file

@ -1,12 +1,10 @@
package com.tangem.tap.di.domain package com.tangem.tap.di.domain
import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.* import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.repository.* import com.tangem.domain.tokens.repository.*
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -118,13 +116,9 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository, quotesRepository: QuotesRepository,
networksRepository: NetworksRepository, networksRepository: NetworksRepository,
swapRepository: SwapRepository,
currencyChecksRepository: CurrencyChecksRepository, currencyChecksRepository: CurrencyChecksRepository,
showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
promoRepository: PromoRepository,
stakingRepository: StakingRepository, stakingRepository: StakingRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
rampStateManager: RampStateManager,
): GetCurrencyWarningsUseCase { ): GetCurrencyWarningsUseCase {
return GetCurrencyWarningsUseCase( return GetCurrencyWarningsUseCase(
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
@ -132,12 +126,8 @@ internal object TokensDomainModule {
quotesRepository = quotesRepository, quotesRepository = quotesRepository,
networksRepository = networksRepository, networksRepository = networksRepository,
currencyChecksRepository = currencyChecksRepository, currencyChecksRepository = currencyChecksRepository,
swapRepository = swapRepository,
showSwapPromoTokenUseCase = showSwapPromoTokenUseCase,
promoRepository = promoRepository,
stakingRepository = stakingRepository, stakingRepository = stakingRepository,
dispatchers = dispatchers, dispatchers = dispatchers,
rampStateManager = rampStateManager,
) )
} }

View file

@ -1,7 +1,7 @@
package com.tangem.tap.di.routing package com.tangem.tap.di.routing
import com.tangem.tap.routing.RoutingComponent import com.tangem.tap.routing.component.RoutingComponent
import com.tangem.tap.routing.impl.DefaultRoutingComponent import com.tangem.tap.routing.component.impl.DefaultRoutingComponent
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.hilt.InstallIn import dagger.hilt.InstallIn

View file

@ -15,6 +15,7 @@ import com.tangem.common.services.secure.SecureStorage
import com.tangem.common.usersCode.UserCodeRepository import com.tangem.common.usersCode.UserCodeRepository
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.res.getStringSafe
import com.tangem.crypto.bip39.DefaultMnemonic import com.tangem.crypto.bip39.DefaultMnemonic
import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey 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.CreateProductWalletTaskResponse
import com.tangem.sdk.api.TangemSdkManager import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.derivationsFinder 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.CreateFirstTwinWalletTask
import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask
import com.tangem.tap.domain.twins.FinalizeTwinTask import com.tangem.tap.domain.twins.FinalizeTwinTask
@ -122,7 +126,7 @@ class DefaultTangemSdkManager(
messageRes: Int?, messageRes: Int?,
allowsRequestAccessCodeFromRepository: Boolean, allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<ScanResponse> { ): CompletionResult<ScanResponse> {
val message = Message(resources.getString(messageRes ?: R.string.initial_message_scan_header)) val message = Message(resources.getStringSafe(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain( return runTaskAsyncReturnOnMain(
runnable = ScanProductTask( runnable = ScanProductTask(
card = null, card = null,
@ -154,9 +158,9 @@ class DefaultTangemSdkManager(
), ),
cardId = scanResponse.card.cardId, cardId = scanResponse.card.cardId,
initialMessage = if (scanResponse.cardTypesResolver.isRing()) { 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 { } 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, iconScanRes = if (scanResponse.cardTypesResolver.isRing()) R.drawable.img_hand_scan_ring else null,
preflightReadFilter = null, preflightReadFilter = null,
@ -194,9 +198,9 @@ class DefaultTangemSdkManager(
), ),
cardId = scanResponse.card.cardId, cardId = scanResponse.card.cardId,
initialMessage = if (scanResponse.cardTypesResolver.isRing()) { 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 { } else {
Message(resources.getString(R.string.initial_message_create_wallet_body)) Message(resources.getStringSafe(R.string.initial_message_create_wallet_body))
}, },
preflightReadFilter = null, preflightReadFilter = null,
) )
@ -243,7 +247,7 @@ class DefaultTangemSdkManager(
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository, allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
), ),
cardId = cardId, 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( return runTaskAsyncReturnOnMain(
runnable = ResetBackupCardTask(userWalletId), runnable = ResetBackupCardTask(userWalletId),
initialMessage = Message( initialMessage = Message(
resources.getString( resources.getStringSafe(
R.string.initial_message_reset_backup_card_header, R.string.initial_message_reset_backup_card_header,
cardNumber.toString(), cardNumber.toString(),
), ),
@ -281,7 +285,7 @@ class DefaultTangemSdkManager(
return runTaskAsyncReturnOnMain( return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changePasscode(null), SetUserCodeCommand.changePasscode(null),
cardId, 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( return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changeAccessCode(null), SetUserCodeCommand.changeAccessCode(null),
cardId, 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( return runTaskAsyncReturnOnMain(
SetUserCodeCommand.resetUserCodes(), SetUserCodeCommand.resetUserCodes(),
cardId, 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( return runTaskAsyncReturnOnMain(
SetUserCodeRecoveryAllowedTask(enabled), SetUserCodeRecoveryAllowedTask(enabled),
cardId, 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( return runTaskAsyncReturnOnMain(
runnable = ScanTask(allowRequestAccessCodeFromRepository), runnable = ScanTask(allowRequestAccessCodeFromRepository),
cardId = cardId, 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) } .map { CardDTO(it) }
} }
@ -372,7 +376,9 @@ class DefaultTangemSdkManager(
@Deprecated("TangemSdkManager shouldn't returns a string from resources") @Deprecated("TangemSdkManager shouldn't returns a string from resources")
override fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String { 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) { override fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) {

View file

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

View file

@ -59,7 +59,6 @@ class WalletConnectMiddleware {
is WalletConnectAction.StartWalletConnect -> { is WalletConnectAction.StartWalletConnect -> {
val uri = action.copiedUri val uri = action.copiedUri
if (uri != null && isWalletConnectUri(uri)) { if (uri != null && isWalletConnectUri(uri)) {
// TODO check
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri)) store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
} else { } else {
store.dispatchNavigationAction { push(AppRoute.QrScanning(SourceType.WALLET_CONNECT)) } store.dispatchNavigationAction { push(AppRoute.QrScanning(SourceType.WALLET_CONNECT)) }

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ package com.tangem.tap.features.details.ui.cardsettings
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable 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.redux.SecurityOption
import com.tangem.tap.features.details.ui.securitymode.toTitleRes import com.tangem.tap.features.details.ui.securitymode.toTitleRes
import com.tangem.wallet.R import com.tangem.wallet.R
@ -77,7 +77,7 @@ internal sealed interface TextReference {
@ReadOnlyComposable @ReadOnlyComposable
internal fun TextReference.resolveReference(): String { internal fun TextReference.resolveReference(): String {
return when (this) { 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 is TextReference.Str -> this.value
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -5,21 +5,19 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource 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.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.analytics.events.Settings 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.tap.features.details.ui.common.SettingsScreensScaffold
import com.tangem.wallet.R import com.tangem.wallet.R
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -30,8 +28,6 @@ internal fun WalletConnectScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val context = LocalContext.current
SettingsScreensScaffold( SettingsScreensScaffold(
modifier = modifier, modifier = modifier,
content = { content = {
@ -46,7 +42,7 @@ internal fun WalletConnectScreen(
AddSessionFab( AddSessionFab(
onAddSession = { onAddSession = {
Analytics.send(Settings.ButtonStartWalletConnectSession()) 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) { private fun AddSessionFab(onAddSession: () -> Unit, modifier: Modifier = Modifier) {
FloatingActionButton( FloatingActionButton(
onClick = onAddSession, onClick = onAddSession,
backgroundColor = TangemTheme.colors.button.primary, containerColor = TangemTheme.colors.button.primary,
contentColor = TangemTheme.colors.icon.primary2, contentColor = TangemTheme.colors.icon.primary2,
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
modifier = modifier, modifier = modifier,
@ -96,7 +92,7 @@ private fun EmptyScreen(state: WalletConnectScreenState) {
) )
Spacer(modifier = Modifier.size(24.dp)) Spacer(modifier = Modifier.size(24.dp))
Text( Text(
text = stringResource(id = R.string.wallet_connect_subtitle), text = stringResourceSafe(id = R.string.wallet_connect_subtitle),
style = TangemTheme.typography.body2, style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary, color = TangemTheme.colors.text.tertiary,
) )

View file

@ -6,7 +6,7 @@ internal data class WalletConnectScreenState(
val sessions: ImmutableList<WcSessionForScreen>, val sessions: ImmutableList<WcSessionForScreen>,
val isLoading: Boolean = false, val isLoading: Boolean = false,
val onRemoveSession: (String) -> Unit = {}, val onRemoveSession: (String) -> Unit = {},
val onAddSession: (String?) -> Unit = {}, val onAddSession: () -> Unit = {},
) )
data class WcSessionForScreen( data class WcSessionForScreen(

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.details.ui.walletconnect
import androidx.lifecycle.* import androidx.lifecycle.*
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
@ -17,6 +18,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class WalletConnectViewModel @Inject constructor( internal class WalletConnectViewModel @Inject constructor(
private val listenToQrScanningUseCase: ListenToQrScanningUseCase, private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
private val clipboardManager: ClipboardManager,
) : ViewModel(), DefaultLifecycleObserver { ) : ViewModel(), DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) { override fun onCreate(owner: LifecycleOwner) {
@ -35,7 +37,11 @@ internal class WalletConnectViewModel @Inject constructor(
sessions.toImmutableList(), sessions.toImmutableList(),
isLoading = state.loading, isLoading = state.loading,
onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, sessions) }, onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, sessions) },
onAddSession = { copiedUri -> store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) }, onAddSession = {
store.dispatch(
WalletConnectAction.StartWalletConnect(copiedUri = clipboardManager.getText()),
)
},
) )
} }

View file

@ -57,6 +57,7 @@ internal fun StoriesScreen(
} }
} }
// todo refactor [REDACTED_TASK_KEY]
StoriesScreenContent( StoriesScreenContent(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@ -75,6 +76,7 @@ internal fun StoriesScreen(
) )
} }
@Deprecated("Use StoriesContainer from core/ui")
@Suppress("LongMethod") @Suppress("LongMethod")
@Composable @Composable
private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: Modifier = Modifier) { private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: Modifier = Modifier) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import org.rekotlin.StateType import org.rekotlin.StateType
// todo refactor [REDACTED_TASK_KEY]
data class HomeState( data class HomeState(
val scanInProgress: Boolean = false, val scanInProgress: Boolean = false,
val stories: ImmutableList<Stories> = Stories.entries.toImmutableList(), val stories: ImmutableList<Stories> = Stories.entries.toImmutableList(),

View file

@ -35,7 +35,7 @@ internal class MainScreenStateHolder(private val intents: MainIntents) {
stateFlowInternal.update { state -> stateFlowInternal.update { state ->
state.copy( state.copy(
modalNotification = TangemBottomSheetConfig( modalNotification = TangemBottomSheetConfig(
isShow = true, isShown = true,
onDismissRequest = intents::onDismissBottomSheet, onDismissRequest = intents::onDismissBottomSheet,
content = notificationsFactory.createBalancesAreHiddenModalNotification(), content = notificationsFactory.createBalancesAreHiddenModalNotification(),
), ),
@ -45,7 +45,7 @@ internal class MainScreenStateHolder(private val intents: MainIntents) {
fun updateWithoutModalNotification() { fun updateWithoutModalNotification() {
stateFlowInternal.update { state -> stateFlowInternal.update { state ->
state.copy(modalNotification = state.modalNotification?.copy(isShow = false)) state.copy(modalNotification = state.modalNotification?.copy(isShown = false))
} }
} }

View file

@ -18,7 +18,6 @@ import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.staking.FetchStakingTokensUseCase import com.tangem.domain.staking.FetchStakingTokensUseCase
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.tap.common.extensions.setContext 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.tap.features.main.model.MainScreenState
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -41,7 +40,6 @@ internal class MainViewModel @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase, private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
private val apiConfigsManager: ApiConfigsManager, private val apiConfigsManager: ApiConfigsManager,
homeFeatureToggles: HomeFeatureToggles,
private val fetchUserCountryUseCase: FetchUserCountryUseCase, private val fetchUserCountryUseCase: FetchUserCountryUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel(), MainIntents { ) : ViewModel(), MainIntents {
@ -63,11 +61,9 @@ internal class MainViewModel @Inject constructor(
viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() } viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() }
if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) { viewModelScope.launch {
viewModelScope.launch { fetchUserCountryUseCase().onLeft {
fetchUserCountryUseCase().onLeft { Timber.e("Unable to fetch the user country code $it")
Timber.e("Unable to fetch the user country code $it")
}
} }
} }
@ -129,7 +125,7 @@ internal class MainViewModel @Inject constructor(
it.isBalanceHidingNotificationEnabled && it.isBalanceHidden it.isBalanceHidingNotificationEnabled && it.isBalanceHidden
} }
.onEach { .onEach {
if (state.value.modalNotification?.isShow != true && !it.isUpdateFromToast) { if (state.value.modalNotification?.isShown != true && !it.isUpdateFromToast) {
listenToFlipsUseCase.changeUpdateEnabled(false) listenToFlipsUseCase.changeUpdateEnabled(false)
stateHolder.updateWithHiddenBalancesNotification() stateHolder.updateWithHiddenBalancesNotification()
router.push(AppRoute.ModalNotification) router.push(AppRoute.ModalNotification)

View file

@ -50,7 +50,7 @@ internal class ModalNotificationBottomSheetFragment : ComposeBottomSheetFragment
} }
LaunchedEffect(notification) { LaunchedEffect(notification) {
if (!notification.isShow) { if (!notification.isShown) {
dismiss() dismiss()
} }
} }

View file

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

View file

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

View file

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

View file

@ -37,7 +37,6 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.di.RootAppComponentContext import com.tangem.core.decompose.di.RootAppComponentContext
import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.extensions.setStatusBarColor 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.res.TangemTheme
import com.tangem.core.ui.windowsize.rememberWindowSize import com.tangem.core.ui.windowsize.rememberWindowSize
import com.tangem.datasource.utils.isNullOrEmpty import com.tangem.datasource.utils.isNullOrEmpty
@ -301,10 +300,6 @@ class OnboardingWalletFragment :
windowSize = rememberWindowSize(activity = requireActivity()), windowSize = rememberWindowSize(activity = requireActivity()),
) { ) {
onboardingManageTokensComponent?.Content(modifier = Modifier.fillMaxSize()) onboardingManageTokensComponent?.Content(modifier = Modifier.fillMaxSize())
EventMessageEffect(
messageHandler = uiDependencies.eventMessageHandler,
snackbarHostState = uiDependencies.globalSnackbarHostState,
)
} }
} }
} }

View file

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

View file

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

View file

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

View file

@ -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
}
}

View file

@ -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<Params, WelcomeComponent>
}

View file

@ -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,
)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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<WelcomeState> {
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<WelcomeScreenState> = 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)
}
}

View file

@ -1,13 +1,15 @@
package com.tangem.tap.features.welcome.ui 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.details.ui.cardsettings.TextReference
import com.tangem.tap.features.welcome.ui.model.WarningModel import com.tangem.tap.features.welcome.ui.model.WarningModel
@Immutable
internal data class WelcomeScreenState( internal data class WelcomeScreenState(
val onPopBack: () -> Unit = {},
val showUnlockWithBiometricsProgress: Boolean = false, val showUnlockWithBiometricsProgress: Boolean = false,
val showUnlockWithCardProgress: Boolean = false, val showUnlockWithCardProgress: Boolean = false,
val warning: WarningModel? = null, val warning: WarningModel? = null,
val error: TextReference? = null, val error: TextReference? = null,
val onUnlockClick: () -> Unit = {},
val onScanCardClick: () -> Unit = {},
val onCloseError: () -> Unit = {},
) )

View file

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

View file

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

View file

@ -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<WelcomeComponent> {
override val values: Sequence<WelcomeComponent>
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

View file

@ -9,10 +9,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource 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.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.* 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.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.wallet.R import com.tangem.wallet.R
@ -24,8 +24,12 @@ internal fun WelcomeScreenContent(
showScanCardProgress: Boolean, showScanCardProgress: Boolean,
onUnlockClick: () -> Unit, onUnlockClick: () -> Unit,
onScanCardClick: () -> Unit, onScanCardClick: () -> Unit,
modifier: Modifier = Modifier,
) { ) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
) {
SpacerHMax() SpacerHMax()
Icon( Icon(
modifier = Modifier.size(TangemTheme.dimens.size96), modifier = Modifier.size(TangemTheme.dimens.size96),
@ -38,7 +42,7 @@ internal fun WelcomeScreenContent(
modifier = Modifier modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16) .padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(), .fillMaxWidth(),
text = stringResource(R.string.welcome_unlock_title), text = stringResourceSafe(R.string.welcome_unlock_title),
style = TangemTheme.typography.h2, style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1, color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
@ -48,9 +52,9 @@ internal fun WelcomeScreenContent(
modifier = Modifier modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing44) .padding(horizontal = TangemTheme.dimens.spacing44)
.fillMaxWidth(), .fillMaxWidth(),
text = stringResource( text = stringResourceSafe(
id = R.string.welcome_unlock_description, id = R.string.welcome_unlock_description,
stringResource(id = R.string.common_biometric_authentication), stringResourceSafe(id = R.string.common_biometric_authentication),
), ),
style = TangemTheme.typography.body1, style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary, color = TangemTheme.colors.text.secondary,
@ -61,9 +65,9 @@ internal fun WelcomeScreenContent(
modifier = Modifier modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16) .padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(), .fillMaxWidth(),
text = stringResource( text = stringResourceSafe(
id = R.string.welcome_unlock, id = R.string.welcome_unlock,
stringResource(id = R.string.common_biometrics), stringResourceSafe(id = R.string.common_biometrics),
), ),
showProgress = showUnlockProgress, showProgress = showUnlockProgress,
onClick = onUnlockClick, onClick = onUnlockClick,
@ -73,7 +77,7 @@ internal fun WelcomeScreenContent(
modifier = Modifier modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16) .padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(), .fillMaxWidth(),
text = stringResource(R.string.welcome_unlock_card), text = stringResourceSafe(R.string.welcome_unlock_card),
showProgress = showScanCardProgress, showProgress = showScanCardProgress,
iconResId = R.drawable.ic_tangem_24, iconResId = R.drawable.ic_tangem_24,
onClick = onScanCardClick, onClick = onScanCardClick,

View file

@ -109,6 +109,7 @@ internal val Blockchain.mercuryoNetwork: String?
Blockchain.PolygonZkEVM -> null Blockchain.PolygonZkEVM -> null
Blockchain.PolygonZkEVMTestnet -> null Blockchain.PolygonZkEVMTestnet -> null
Blockchain.Radiant -> null Blockchain.Radiant -> null
Blockchain.Fact0rn -> null
Blockchain.BaseTestnet -> null Blockchain.BaseTestnet -> null
Blockchain.Moonriver -> null Blockchain.Moonriver -> null
Blockchain.MoonriverTestnet -> null Blockchain.MoonriverTestnet -> null
@ -140,6 +141,8 @@ internal val Blockchain.mercuryoNetwork: String?
Blockchain.CoreTestnet -> null Blockchain.CoreTestnet -> null
Blockchain.Chiliz -> null Blockchain.Chiliz -> null
Blockchain.ChilizTestnet -> null Blockchain.ChilizTestnet -> null
Blockchain.VanarChain -> null
Blockchain.VanarChainTestnet -> null
Blockchain.Unknown -> null Blockchain.Unknown -> null
Blockchain.Xodex -> null Blockchain.Xodex -> null
Blockchain.Canxium -> null Blockchain.Canxium -> null

View file

@ -114,6 +114,7 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
PolygonZkEVM -> null PolygonZkEVM -> null
PolygonZkEVMTestnet -> null PolygonZkEVMTestnet -> null
Radiant -> null Radiant -> null
Fact0rn -> null
BaseTestnet -> null BaseTestnet -> null
Moonriver -> null Moonriver -> null
MoonriverTestnet -> null MoonriverTestnet -> null
@ -145,4 +146,6 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
Xodex -> null Xodex -> null
Canxium -> null Canxium -> null
Clore -> null Clore -> null
VanarChain -> null
VanarChainTestnet -> null
} }

View file

@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage 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.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.AppStateHolder
import org.rekotlin.StateType import org.rekotlin.StateType
@ -69,7 +69,6 @@ data class DaggerGraphState(
val appRouter: AppRouter? = null, val appRouter: AppRouter? = null,
val pushNotificationsRouter: PushNotificationsRouter? = null, val pushNotificationsRouter: PushNotificationsRouter? = null,
val transactionSignerFactory: TransactionSignerFactory? = null, val transactionSignerFactory: TransactionSignerFactory? = null,
val homeFeatureToggles: HomeFeatureToggles? = null,
val getUserCountryUseCase: GetUserCountryUseCase? = null, val getUserCountryUseCase: GetUserCountryUseCase? = null,
val onrampFeatureToggles: OnrampFeatureToggles? = null, val onrampFeatureToggles: OnrampFeatureToggles? = null,
val environmentConfigStorage: EnvironmentConfigStorage? = null, val environmentConfigStorage: EnvironmentConfigStorage? = null,
@ -77,4 +76,5 @@ data class DaggerGraphState(
val onboardingRepository: OnboardingRepository? = null, val onboardingRepository: OnboardingRepository? = null,
val excludedBlockchains: ExcludedBlockchains? = null, val excludedBlockchains: ExcludedBlockchains? = null,
val appPreferencesStore: AppPreferencesStore? = null, val appPreferencesStore: AppPreferencesStore? = null,
val clipboardManager: ClipboardManager? = null,
) : StateType ) : StateType

View file

@ -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<AppRoute, RoutingComponent.Child>,
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()
}
}

View file

@ -1,22 +1,28 @@
package com.tangem.tap.routing package com.tangem.tap.routing.component
import android.content.Intent import android.content.Intent
import androidx.compose.runtime.Immutable
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
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.Value
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.utils.Provider import com.tangem.utils.Provider
internal interface RoutingComponent { internal interface RoutingComponent : ComposableContentComponent {
// TODO: Remove after full navigation refactoring
val router: Router val router: Router
// TODO: Remove after full navigation refactoring
val stack: Value<ChildStack<AppRoute, Child>> val stack: Value<ChildStack<AppRoute, Child>>
@Immutable
sealed class Child { sealed class Child {
// TODO: Remove and find initial intent in RoutingComponent: [REDACTED_JIRA]
data object Initial : Child() data object Initial : Child()
data class LegacyFragment( data class LegacyFragment(
@ -25,6 +31,10 @@ internal interface RoutingComponent {
) : Child() ) : Child()
data class LegacyIntent(val intent: Intent) : Child() data class LegacyIntent(val intent: Intent) : Child()
data class ComposableComponent(
val component: ComposableContentComponent,
) : Child()
} }
interface Factory { interface Factory {

View file

@ -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<AppRoute, Child>> = 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
}
}

View file

@ -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<AppRoute, Child>> = 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
}
}

View file

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

View file

@ -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.twins.ui.OnboardingTwinsFragment
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment 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.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 com.tangem.utils.Provider
import dagger.hilt.android.scopes.ActivityScoped import dagger.hilt.android.scopes.ActivityScoped
import java.util.WeakHashMap import java.util.WeakHashMap
import javax.inject.Inject import javax.inject.Inject
@ActivityScoped @ActivityScoped
@Suppress("LongParameterList") @Suppress("LongParameterList", "LargeClass")
internal class ChildFactory @Inject constructor( internal class ChildFactory @Inject constructor(
private val detailsComponentFactory: DetailsComponent.Factory, private val detailsComponentFactory: DetailsComponent.Factory,
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory, private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
@ -54,6 +56,7 @@ internal class ChildFactory @Inject constructor(
private val sellCryptoComponentFactory: SellCryptoComponent.Factory, private val sellCryptoComponentFactory: SellCryptoComponent.Factory,
private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory, private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory,
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory, private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
private val welcomeComponentFactory: WelcomeComponent.Factory,
private val sendRouter: SendRouter, private val sendRouter: SendRouter,
private val tokenDetailsRouter: TokenDetailsRouter, private val tokenDetailsRouter: TokenDetailsRouter,
private val walletRouter: WalletRouter, private val walletRouter: WalletRouter,
@ -61,12 +64,174 @@ internal class ChildFactory @Inject constructor(
private val stakingRouter: StakingRouter, private val stakingRouter: StakingRouter,
private val testerRouter: TesterRouter, private val testerRouter: TesterRouter,
private val pushNotificationRouter: PushNotificationsRouter, private val pushNotificationRouter: PushNotificationsRouter,
private val routingFeatureToggles: RoutingFeatureToggles,
) { ) {
@Suppress("LongMethod", "CyclomaticComplexMethod")
fun createChild(route: AppRoute, contextFactory: (route: AppRoute) -> AppComponentContext): Child { 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) 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) { return when (route) {
is AppRoute.Initial -> { is AppRoute.Initial -> {
Child.Initial Child.Initial
@ -248,6 +413,7 @@ internal class ChildFactory @Inject constructor(
) )
} }
} }
// endregion
} }
fun doOnDestroy() { fun doOnDestroy() {

View file

@ -7,7 +7,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.tap.DecomposeFragment 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 import com.tangem.utils.Provider
internal fun AppRoute.asFragmentChild(fragmentProvider: Provider<Fragment>): Child { internal fun AppRoute.asFragmentChild(fragmentProvider: Provider<Fragment>): Child {
@ -37,4 +37,14 @@ internal fun <C : ComposableContentComponent, P : Any, F : ComponentFactory<P, C
} }
return Child.LegacyFragment(path, fragmentProvider) return Child.LegacyFragment(path, fragmentProvider)
}
internal fun <C : ComposableContentComponent, P : Any, F : ComponentFactory<P, C>> createComponentChild(
contextProvider: Provider<AppComponentContext>,
params: P,
componentFactory: F,
): Child {
val context = contextProvider()
return Child.ComposableComponent(componentFactory.create(context, params))
} }

View file

@ -6,4 +6,10 @@
android:background="@color/background_primary" android:background="@color/background_primary"
android:filterTouchesWhenObscured="true"> android:filterTouchesWhenObscured="true">
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -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

View file

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,7 +1,28 @@
plugins { plugins {
alias(deps.plugins.kotlin.jvm) alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("configuration") id("configuration")
} }
android {
namespace = "com.tangem.common"
}
dependencies { dependencies {
implementation(projects.core.utils) 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)
} }

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