String>) {
class StakeKit(stakeKitAuthProvider: StakeKitAuthProvider) : RequestHeader(
"X-API-KEY" to { stakeKitAuthProvider.getApiKey() },
+ "accept" to { "application/json" },
)
}
\ No newline at end of file
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt
index b5df451866..74fba5a48e 100644
--- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt
@@ -1,5 +1,6 @@
package com.tangem.core.decompose.di
+import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import dagger.BindsInstance
@@ -37,6 +38,14 @@ interface DecomposeComponent {
*/
fun uiMessageSender(@BindsInstance uiMessageSender: UiMessageSender): Builder
+ /**
+ * Sets the parameters container for the component.
+ *
+ * @param container The parameters container to set.
+ * @return The builder instance.
+ */
+ fun paramsContainer(@BindsInstance container: ParamsContainer): Builder
+
/**
* Builds the Decompose component.
*
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/factory/ComponentFactory.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/factory/ComponentFactory.kt
new file mode 100644
index 0000000000..20c1159324
--- /dev/null
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/factory/ComponentFactory.kt
@@ -0,0 +1,8 @@
+package com.tangem.core.decompose.factory
+
+import com.tangem.core.decompose.context.AppComponentContext
+
+interface ComponentFactory {
+
+ fun create(context: AppComponentContext, params: P): C
+}
\ No newline at end of file
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt
index 58cad4cf7a..286678b4c3 100644
--- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt
@@ -22,24 +22,43 @@ interface ModelsEntryPoint {
}
/**
- * Gets or creates a component's [Model].
+ * Gets or creates a component's [Model] with no parameters.
*/
-inline fun AppComponentContext.getOrCreateModel(): M {
- val modelKey = "model_${M::class.simpleName}"
+inline fun AppComponentContext.getOrCreateModel(): M = getOrCreateModel(params = null)
+/**
+ * Gets or creates a component's [Model].
+ *
+ * Be careful with objects you pass in parameters as they will be used in [Model] lifecycle.
+ * If you pass a object with different lifecycle than the [Model] then you can face memory leaks.
+ *
+ * @param params The parameters to store in the [ParamsContainer],
+
+ */
+inline fun AppComponentContext.getOrCreateModel(params: P?): M {
val entryPoint = instanceKeeper.getOrCreateSimple(key = "modelsEntryPoint") {
val hiltComponent = hiltComponentBuilder
.router(router)
.uiMessageSender(messageSender)
+ .let { builder ->
+ if (params != null) {
+ val container = MutableParamsContainer(params)
+
+ builder.paramsContainer(container)
+ } else {
+ builder
+ }
+ }
.build()
EntryPoints.get(hiltComponent, ModelsEntryPoint::class.java)
}
+ val modelKey = "model_${M::class.simpleName}"
val model = instanceKeeper.getOrCreate(modelKey) {
requireNotNull(entryPoint.models()[M::class.java]?.get()) {
"Model ${M::class.simpleName} is not provided"
- }
+ } as M
}
val isModelExist = tags.getOrElse(modelKey) { false } as Boolean
@@ -47,5 +66,5 @@ inline fun AppComponentContext.getOrCreateModel(): M {
tags[modelKey] = true
}
- return model as M
+ return model
}
\ No newline at end of file
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ParamsContainer.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ParamsContainer.kt
new file mode 100644
index 0000000000..910764d661
--- /dev/null
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ParamsContainer.kt
@@ -0,0 +1,45 @@
+package com.tangem.core.decompose.model
+
+/**
+ * Lazy container for [Model] params.
+ *
+ * This contrainer can be accessed by DI because it's provided via [com.tangem.core.decompose.di.DecomposeComponent].
+ * */
+interface ParamsContainer {
+
+ /** Returns stored value if it is of type [T], otherwise returns null. */
+ fun get(): T?
+
+ /** Returns stored value if it is of type [T], otherwise throws an exception. */
+ fun require(): T
+}
+
+/**
+ * Mutable implementation of [ParamsContainer].
+ *
+ * ***You should not use this class directly in other modules, use immutable [ParamsContainer] instead.***
+ * */
+class MutableParamsContainer private constructor() : ParamsContainer {
+
+ private var value: Any? = null
+
+ /** Stores [value] inside a container, replaces any previous stored value. */
+ fun set(value: Any) {
+ this.value = value
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun get(): T? = value as? T
+
+ override fun require(): T = get() ?: error("Contrainer is empty or contains a value of a different type.")
+
+ companion object {
+
+ /** Creates a new [MutableParamsContainer] and stores [value] inside it. */
+ operator fun invoke(value: T): MutableParamsContainer {
+ return MutableParamsContainer().apply {
+ set(value)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt
index 50e3840442..86c43706a6 100644
--- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt
@@ -6,6 +6,7 @@ import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.popWhile
import com.arkivanov.decompose.router.stack.pushNew
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
+import kotlin.reflect.KClass
internal class DefaultRouter(
private val navigationProvider: AppNavigationProvider,
@@ -19,6 +20,15 @@ internal class DefaultRouter(
navigation.pushNew(route, onComplete)
}
+ override fun replaceAll(vararg routes: Route, onComplete: (isSuccess: Boolean) -> Unit) {
+ val newRoutes = routes.toList()
+
+ navigation.navigate(
+ transformer = { newRoutes },
+ onComplete = { newStack, _ -> onComplete(newStack.size == newRoutes.size) },
+ )
+ }
+
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
navigation.pop(onComplete)
}
@@ -29,4 +39,11 @@ internal class DefaultRouter(
onComplete = onComplete,
)
}
+
+ override fun popTo(routeClass: KClass, onComplete: (isSuccess: Boolean) -> Unit) {
+ navigation.popWhile(
+ predicate = { it::class != routeClass },
+ onComplete = onComplete,
+ )
+ }
}
\ No newline at end of file
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DummyRouter.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DummyRouter.kt
new file mode 100644
index 0000000000..77e7a8f59d
--- /dev/null
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DummyRouter.kt
@@ -0,0 +1,26 @@
+package com.tangem.core.decompose.navigation
+
+import kotlin.reflect.KClass
+
+class DummyRouter : Router {
+
+ override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
+ onComplete(true)
+ }
+
+ override fun replaceAll(vararg routes: Route, onComplete: (isSuccess: Boolean) -> Unit) {
+ onComplete(true)
+ }
+
+ override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
+ onComplete(true)
+ }
+
+ override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
+ onComplete(true)
+ }
+
+ override fun popTo(routeClass: KClass, onComplete: (isSuccess: Boolean) -> Unit) {
+ onComplete(true)
+ }
+}
\ No newline at end of file
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Router.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Router.kt
index 3a9b7d93ca..da7f859b8f 100644
--- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Router.kt
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/Router.kt
@@ -1,8 +1,10 @@
package com.tangem.core.decompose.navigation
+import kotlin.reflect.KClass
+
/**
* Interface for a router in the application.
- * It provides methods for navigating through the application.
+ * It provides methods for navigating through the application by stack.
*/
interface Router {
@@ -14,6 +16,14 @@ interface Router {
*/
fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit = {})
+ /**
+ * Replaces ***all*** routes in the navigation stack with the specified [routes].
+ *
+ * @param routes The routes to replace the current stack with.
+ * @param onComplete The callback to be invoked when the operation is complete.
+ */
+ fun replaceAll(vararg routes: Route, onComplete: (isSuccess: Boolean) -> Unit = {})
+
/**
* Pops the top route from the navigation stack.
*
@@ -22,10 +32,18 @@ interface Router {
fun pop(onComplete: (isSuccess: Boolean) -> Unit = {})
/**
- * Pops routes from the navigation stack until the specified route is found.
+ * Pops routes from the navigation stack until the specified [route] is found.
*
* @param route The route to pop to.
* @param onComplete The callback to be invoked when the operation is complete.
*/
fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit = {})
+
+ /**
+ * Pops routes from the navigation stack until the ***first*** specified [routeClass] is found.
+ *
+ * @param routeClass The route class to pop to.
+ * @param onComplete The callback to be invoked when the operation is complete.
+ */
+ fun popTo(routeClass: KClass, onComplete: (isSuccess: Boolean) -> Unit = {})
}
\ No newline at end of file
diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/RouterExt.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/RouterExt.kt
new file mode 100644
index 0000000000..777cc78199
--- /dev/null
+++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/RouterExt.kt
@@ -0,0 +1,11 @@
+package com.tangem.core.decompose.navigation
+
+/**
+ * Pops routes from the navigation stack until the specified route [R] is found.
+ *
+ * @param R The route to pop to.
+ * @param onComplete The callback to be invoked when the operation is complete.
+ */
+inline fun Router.popTo(noinline onComplete: (isSuccess: Boolean) -> Unit = {}) {
+ popTo(R::class, onComplete)
+}
\ No newline at end of file
diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
index a4472cf056..1bb02865e0 100644
--- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
+++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
@@ -42,5 +42,9 @@
{
"name": "DETAILS_REDESIGN_ENABLED",
"version": "undefined"
+ },
+ {
+ "name": "PUSH_NOTIFICATIONS_ENABLED",
+ "version": "undefined"
}
]
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationAction.kt b/core/navigation/src/main/java/com/tangem/core/navigation/NavigationAction.kt
deleted file mode 100644
index 676958ef92..0000000000
--- a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationAction.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.tangem.core.navigation
-
-import android.net.Uri
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import org.rekotlin.Action
-import java.lang.ref.WeakReference
-
-sealed class NavigationAction : Action {
-
- data class NavigateTo(
- val screen: AppScreen,
- val fragmentShareTransition: FragmentShareTransition? = null,
- val addToBackstack: Boolean = true,
- val bundle: Bundle? = null,
- ) : NavigationAction()
-
- data class PopBackTo(val screen: AppScreen? = null, val inclusive: Boolean = false) : NavigationAction()
-
- data class OpenUrl(val url: String) : NavigationAction()
-
- data class OpenDocument(val url: Uri) : NavigationAction()
-
- object OpenBiometricsSettings : NavigationAction()
-
- data class OpenDialog(val stateDialog: StateDialog) : NavigationAction()
-
- data class Share(val data: String) : NavigationAction()
-
- data class ActivityCreated(val activity: WeakReference) : NavigationAction()
-
- data class ActivityDestroyed(val activity: WeakReference) : NavigationAction()
-}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt b/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt
deleted file mode 100644
index a0a6d64f25..0000000000
--- a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.tangem.core.navigation
-
-import androidx.appcompat.app.AppCompatActivity
-import org.rekotlin.StateType
-import java.lang.ref.WeakReference
-
-data class NavigationState(
- val backStack: List = emptyList(),
- val activity: WeakReference? = null,
-) : StateType
-
-enum class AppScreen(val isDialogFragment: Boolean = false) {
- Home,
- Disclaimer,
- OnboardingNote,
- OnboardingWallet,
- OnboardingTwins,
- OnboardingOther,
- Wallet,
- WalletDetails,
- Send(isDialogFragment = true),
- Details,
- DetailsSecurity,
- CardSettings,
- AppSettings,
- ResetToFactory,
- AccessCodeRecovery,
- ManageTokens,
- AddCustomToken,
- WalletConnectSessions,
- QrScanning,
- ReferralProgram,
- Swap,
- Welcome,
- SaveWallet(isDialogFragment = true),
- AppCurrencySelector,
- ModalNotification(isDialogFragment = true),
-}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/ReduxNavController.kt b/core/navigation/src/main/java/com/tangem/core/navigation/ReduxNavController.kt
deleted file mode 100644
index 20e37f3958..0000000000
--- a/core/navigation/src/main/java/com/tangem/core/navigation/ReduxNavController.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.tangem.core.navigation
-
-/**
- * Navigation controller that based on redux actions
- *
-[REDACTED_AUTHOR]
- */
-interface ReduxNavController {
-
- /** Navigate by [action] */
- fun navigate(action: NavigationAction)
-
- fun popBackStack(screen: AppScreen? = null)
-
- fun getBackStack(): List
-}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/FragmentShareTransition.kt b/core/navigation/src/main/java/com/tangem/core/navigation/ShareElement.kt
similarity index 74%
rename from core/navigation/src/main/java/com/tangem/core/navigation/FragmentShareTransition.kt
rename to core/navigation/src/main/java/com/tangem/core/navigation/ShareElement.kt
index 2cb2132eec..b3fd049583 100644
--- a/core/navigation/src/main/java/com/tangem/core/navigation/FragmentShareTransition.kt
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/ShareElement.kt
@@ -1,18 +1,8 @@
package com.tangem.core.navigation
import android.view.View
-import androidx.transition.TransitionSet
import java.lang.ref.WeakReference
-/**
-[REDACTED_AUTHOR]
- */
-data class FragmentShareTransition(
- val shareElements: List,
- val enterTransitionSet: TransitionSet,
- val exitTransitionSet: TransitionSet,
-)
-
/**
* For ease of use, the name is used as transitionName\name into the FragmentTransaction.addSharedElement
*/
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt b/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt
deleted file mode 100644
index 75aa1f0579..0000000000
--- a/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.tangem.core.navigation
-
-interface StateDialog {
-
- data class ScanFailsDialog(val source: ScanFailsSource) : StateDialog
-
- enum class ScanFailsSource {
- MAIN, SIGN_IN, SETTINGS, INTRO;
- }
-}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/feedback/DummyFeedbackManager.kt b/core/navigation/src/main/java/com/tangem/core/navigation/feedback/DummyFeedbackManager.kt
new file mode 100644
index 0000000000..2f92f40ad9
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/feedback/DummyFeedbackManager.kt
@@ -0,0 +1,8 @@
+package com.tangem.core.navigation.feedback
+
+class DummyFeedbackManager : FeedbackManager {
+
+ override fun sendEmail(type: FeedbackType) {
+ /* no-op */
+ }
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/feedback/FeedbackManager.kt b/core/navigation/src/main/java/com/tangem/core/navigation/feedback/FeedbackManager.kt
new file mode 100644
index 0000000000..a76d2704e6
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/feedback/FeedbackManager.kt
@@ -0,0 +1,6 @@
+package com.tangem.core.navigation.feedback
+
+interface FeedbackManager {
+
+ fun sendEmail(type: FeedbackType)
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/feedback/FeedbackType.kt b/core/navigation/src/main/java/com/tangem/core/navigation/feedback/FeedbackType.kt
new file mode 100644
index 0000000000..b1470f5e4d
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/feedback/FeedbackType.kt
@@ -0,0 +1,13 @@
+package com.tangem.core.navigation.feedback
+
+sealed class FeedbackType {
+ data object RateCanBeBetter : FeedbackType()
+
+ data object ScanFails : FeedbackType()
+
+ data class SendTransactionFailed(val error: String) : FeedbackType()
+
+ data object Feedback : FeedbackType()
+
+ data object Support : FeedbackType()
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/finisher/AppFinisher.kt b/core/navigation/src/main/java/com/tangem/core/navigation/finisher/AppFinisher.kt
new file mode 100644
index 0000000000..54785819e1
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/finisher/AppFinisher.kt
@@ -0,0 +1,8 @@
+package com.tangem.core.navigation.finisher
+
+interface AppFinisher {
+
+ fun finish()
+
+ fun restart()
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/settings/DummySettingsManager.kt b/core/navigation/src/main/java/com/tangem/core/navigation/settings/DummySettingsManager.kt
new file mode 100644
index 0000000000..25c4706fb9
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/settings/DummySettingsManager.kt
@@ -0,0 +1,5 @@
+package com.tangem.core.navigation.settings
+
+class DummySettingsManager : SettingsManager {
+ override fun openSettings() { /* no-op */ }
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/settings/SettingsManager.kt b/core/navigation/src/main/java/com/tangem/core/navigation/settings/SettingsManager.kt
new file mode 100644
index 0000000000..4be904c9cb
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/settings/SettingsManager.kt
@@ -0,0 +1,5 @@
+package com.tangem.core.navigation.settings
+
+interface SettingsManager {
+ fun openSettings()
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/share/DummyShareManager.kt b/core/navigation/src/main/java/com/tangem/core/navigation/share/DummyShareManager.kt
new file mode 100644
index 0000000000..20769b6995
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/share/DummyShareManager.kt
@@ -0,0 +1,8 @@
+package com.tangem.core.navigation.share
+
+class DummyShareManager : ShareManager {
+
+ override fun shareText(text: String) {
+ /* no-op */
+ }
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/share/ShareManager.kt b/core/navigation/src/main/java/com/tangem/core/navigation/share/ShareManager.kt
new file mode 100644
index 0000000000..81146f78f4
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/share/ShareManager.kt
@@ -0,0 +1,6 @@
+package com.tangem.core.navigation.share
+
+interface ShareManager {
+
+ fun shareText(text: String)
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/url/DummyUrlOpener.kt b/core/navigation/src/main/java/com/tangem/core/navigation/url/DummyUrlOpener.kt
new file mode 100644
index 0000000000..cff4946b40
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/url/DummyUrlOpener.kt
@@ -0,0 +1,8 @@
+package com.tangem.core.navigation.url
+
+class DummyUrlOpener : UrlOpener {
+
+ override fun openUrl(url: String) {
+ /* no-op */
+ }
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/url/UrlOpener.kt b/core/navigation/src/main/java/com/tangem/core/navigation/url/UrlOpener.kt
new file mode 100644
index 0000000000..d78ba1b1eb
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/url/UrlOpener.kt
@@ -0,0 +1,6 @@
+package com.tangem.core.navigation.url
+
+interface UrlOpener {
+
+ fun openUrl(url: String)
+}
\ No newline at end of file
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 0c2f9e38e2..8c1d366c83 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -1,9 +1,12 @@
+ Выберите сеть
Добавить токен
Валюты
Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств.
+ Как сканировать
Обратиться в поддержку
+ Попробовать снова
Эта функция недоступна в демонстрационном режиме
Причина: %s
Не могу отправить транзакцию
@@ -81,6 +84,7 @@
Копировать
Скопировать адрес
Создать
+ Свое
Удалить
Отключено
Готово
@@ -92,7 +96,6 @@
Обозреватель
Комиссия
Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s
- Свое
Быстро
По рынку
Медленно
@@ -117,6 +120,7 @@
Отклонить
Перезагрузить
Переименовать
+ Сохранить
Сохранить изменения
Искать
Поиск токенов
@@ -128,6 +132,7 @@
Поделиться
Подписать
Подписать и отправить
+ Стейкинг
Начать
Отправить
Успешно
@@ -169,6 +174,7 @@
Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить
Остерегайтесь мошеннических токенов, они могут ничего не стоить
Токены могут быть созданы кем угодно
+ Купить кошелек Tangem
Чат
Код доступа
Перед сканированием карты вам нужно будет ввести правильный код доступа.
@@ -186,6 +192,7 @@
Скрывать балансы жестом переворота
Эмитент
Подписано
+ Отправить отзыв
Подробности
Проверьте подключение с интернетом или переключитесь на другую сеть
Условия использования
@@ -295,7 +302,9 @@
- %1$d из %2$d кошельков
- %1$d из %2$d кошельков
+ Удалить
например Bitcoin
+ Ваш портфель был обновлен
Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление.
Голосовать
Выберите кошелек
@@ -535,9 +544,12 @@
Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время
Неверный адрес
Транзакция отправлена
+ Подготовьтесь к сканированию карты, которую вы хотите настроить.
Забыть кошелек
Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова.
Имя
+ Доступно
+ Стейкинг %s
Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.
Революционный аппаратный кошелек
До трех карт с одним кошельком
@@ -579,9 +591,10 @@
У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства.
У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства.
В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями.
- Продажа средств станет доступной после завершения транзакции(-ий) в сети %s
+ Продажа средств станет доступной после завершения транзакции(-ий) в сети %s
Отправка средств станет доступной после завершения транзакции(-ий) в сети %s
В данный момент продажа %s недоступна. Следите за нашими обновлениями.
+ В данный момент стейкинг монеты %s недоступен. Следите за нашими обновлениями.
Сгенерировать XPUB
Скрыть
Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 0e761dde1b..38c7f41345 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -1,9 +1,12 @@
+ Choose network
Add custom token
Manage tokens
Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds.
+ How to scan
Request support
+ Try again
This feature is disabled in Demo mode
Reason: %s
Can\'t send a transaction
@@ -64,6 +67,8 @@
Not enough ADA
Accept
Access denied
+ All
+ Allow
Apply
Approval
Attention
@@ -80,6 +85,7 @@
Copy
Copy address
Create
+ Custom
Delete
Disabled
Done
@@ -91,7 +97,6 @@
Explorer
Fee
Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s
- Custom
Fast
Market
Slow
@@ -111,11 +116,13 @@
Primary Card
Passphrase
Paste
+ %1$s-%2$s
Read more
Receive
Reject
Reload
Rename
+ Save
Save changes
Search
Search tokens
@@ -127,6 +134,8 @@
Share
Sign
Sign and send
+ Stake
+ Staking
Start
Submit
Success
@@ -168,6 +177,7 @@
Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing.
Be aware of adding scam tokens, they can cost nothing
Note that tokens can be created by anyone
+ Buy Tangem Wallet
Chat
Access code
You will have to submit the correct access code before scanning the card
@@ -185,6 +195,7 @@
Flip-to-Hide Balances
Issuer
Signed
+ Send feedback
Details
Check your internet connection or switch to a different network
Terms of service
@@ -294,11 +305,20 @@
- %1$d of %2$d wallet
- %1$d of %2$d wallets
+ Remove
e.g. BTC I trust, hodl I must
+ Your portfolio has been updated
The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it.
Upvote
Choose wallet
The wallet doesn\'t support more than one network
+ To start buying, exchanging or receiving this asset, add this token to at least 1 network
+ This asset is not available
+ Add to portfolio
+ My portfolio
+ Market
+ Select wallet
+ Sort By
You have to set up a single access code to protect all your cards
Protect
You can set up an individual access code on each card later
@@ -528,9 +548,31 @@
Invalid address
%1$s (%2$s)
Transaction sent
+ Prepare to scan card you want to setup.
Forget wallet
This will remove the wallet from the application. The wallet itself can be added again.
Name
+ APR
+ APY
+ Available
+ Average Reward Rate
+ %s est. profit
+ Market rating
+ Metrics
+ Minimum Requirement
+ No rewards to claim
+ On stake
+ Reward claiming
+ Reward schedule
+ Rewards to claim: %s
+ Staking %s
+ Unbonding Period
+ Warmup period
+ Native staking
+ Staking allow you to earn %1s. Your staking rewards arrive every ~%2s days.
+ Earn staking rewards
+ Rewards
+ Validator
Store your crypto assets secure while keeping private keys contained in your card
Revolutionary Hardware Wallet
Up to 3 physical cards to one wallet
@@ -575,6 +617,7 @@
Selling funds will be available once the pending transaction(s) in network %s is complete
Sending funds will be available once the pending transaction(s) in network %s is complete
Selling %s is not available at the moment. Please check our updates.
+ Staking %s is not available at the moment. Please check our updates.
Generate XPUB
Hide
You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.
@@ -609,6 +652,9 @@
This action is irreversible. You will not have access to the old wallet.
Tap the twin card with number %s and do not remove until the end of the operation
Use %s or scan a card to have an access to your wallet
+ Stay up to date with the latest features and news
+ Be able to get important market notifications
+ Would you like to use
Push-notifications?
Add new wallet
Are you sure you want to delete this wallet?
An error has occurred, please scan your card to log in
diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts
index e7c5bfc4e7..cb8f7f15ee 100644
--- a/core/ui/build.gradle.kts
+++ b/core/ui/build.gradle.kts
@@ -9,9 +9,6 @@ android {
}
dependencies {
- /** Project - Common */
- implementation(projects.common)
-
/** Project - Domain */
implementation(projects.domain.tokens.models)
implementation(projects.domain.appTheme.models)
@@ -25,6 +22,12 @@ dependencies {
implementation(deps.androidx.fragment.ktx)
implementation(deps.androidx.paging.runtime)
implementation(deps.androidx.palette)
+ implementation(deps.androidx.windowManager) {
+ exclude(
+ deps.kotlin.coroutines.android.get().module.group,
+ deps.kotlin.coroutines.android.get().module.name
+ )
+ }
/** Compose */
implementation(deps.compose.constraintLayout)
@@ -40,11 +43,12 @@ dependencies {
/** Other libraries */
implementation(deps.compose.accompanist.systemUiController)
+ implementation(deps.compose.accompanist.permission)
implementation(deps.material)
implementation(deps.compose.shimmer)
implementation(deps.kotlin.immutable.collections)
implementation(deps.zxing.qrCore)
- implementation(deps.jodatime)
+ api(deps.jodatime)
implementation(deps.timber)
implementation(deps.markdown)
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt b/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt
index 62e19d4af6..49a90c5af6 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt
@@ -1,11 +1,19 @@
package com.tangem.core.ui
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.runtime.Stable
import com.tangem.core.ui.haptic.HapticManager
+import com.tangem.core.ui.message.EventMessageHandler
import com.tangem.core.ui.theme.AppThemeModeHolder
+@Stable
interface UiDependencies {
val hapticManager: HapticManager
val appThemeModeHolder: AppThemeModeHolder
+
+ val globalSnackbarHostState: SnackbarHostState
+
+ val eventMessageHandler: EventMessageHandler
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/BottomFade.kt b/core/ui/src/main/java/com/tangem/core/ui/components/BottomFade.kt
new file mode 100644
index 0000000000..fbb4ae896b
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/BottomFade.kt
@@ -0,0 +1,33 @@
+package com.tangem.core.ui.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import com.tangem.core.ui.res.TangemTheme
+
+/**
+ * A composable that draws a fade effect at the bottom of the screen. Used on screens with a list of repeating
+ * elements and floating button at the bottom of the screen.
+ */
+@Composable
+fun BottomFade(modifier: Modifier = Modifier, backgroundColor: Color = TangemTheme.colors.background.secondary) {
+ val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
+
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(TangemTheme.dimens.size100 + bottomBarHeight)
+ .background(
+ brush = Brush.verticalGradient(
+ colors = listOf(
+ Color.Transparent,
+ backgroundColor,
+ ),
+ ),
+ ),
+ )
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/BoxWithGradient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/BoxWithGradient.kt
new file mode 100644
index 0000000000..b51d1c0f08
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/BoxWithGradient.kt
@@ -0,0 +1,41 @@
+package com.tangem.core.ui.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.res.TangemColorPalette
+import com.tangem.core.ui.res.TangemTheme
+
+@Composable
+fun BoxWithGradient(
+ modifier: Modifier = Modifier,
+ gradient: Brush = BottomGradient,
+ content: @Composable BoxScope.() -> Unit,
+) {
+ val bottomInsetsPx = WindowInsets.navigationBars.getBottom(LocalDensity.current)
+
+ Box(modifier = modifier.fillMaxSize()) {
+ content()
+ Box(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ .height(TangemTheme.dimens.size164 + bottomInsetsPx.dp)
+ .background(gradient),
+ )
+ }
+}
+
+private val BottomGradient: Brush = Brush.verticalGradient(
+ colors = listOf(
+ TangemColorPalette.Black.copy(alpha = 0f),
+ TangemColorPalette.Black.copy(alpha = 0.75f),
+ TangemColorPalette.Black.copy(alpha = 0.95f),
+ TangemColorPalette.Black,
+ ),
+)
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt
index bb17fcf7b6..7ef56f015e 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt
@@ -86,6 +86,7 @@ fun PrimaryButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
size: TangemButtonSize = TangemButtonSize.Default,
+ colors: ButtonColors = TangemButtonsDefaults.primaryButtonColors,
showProgress: Boolean = false,
enabled: Boolean = true,
) {
@@ -94,7 +95,7 @@ fun PrimaryButton(
text = text,
icon = TangemButtonIconPosition.None,
onClick = onClick,
- colors = TangemButtonsDefaults.primaryButtonColors,
+ colors = colors,
enabled = enabled,
showProgress = showProgress,
size = size,
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt
index 18ca78091a..97a3b692cc 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/Dialogs.kt
@@ -27,8 +27,8 @@ import com.tangem.core.ui.R
import com.tangem.core.ui.components.SelctorDialogParamsProvider.SelectorDialogParams
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.components.fields.SimpleDialogTextField
-import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@@ -100,7 +100,7 @@ fun TextInputDialog(
confirmButton: DialogButton,
onDismissDialog: () -> Unit,
onValueChange: (TextFieldValue) -> Unit,
- textFieldParams: AdditionalTextInputDialogParams,
+ textFieldParams: AdditionalTextInputDialogParams = remember { AdditionalTextInputDialogParams() },
title: String? = null,
dismissButton: DialogButton? = null,
isDismissable: Boolean = true,
@@ -128,7 +128,7 @@ fun TextInputDialog(
confirmButton: DialogButton,
onDismissDialog: () -> Unit,
onValueChange: (String) -> Unit,
- textFieldParams: AdditionalTextInputDialogParams,
+ textFieldParams: AdditionalTextInputDialogParams = remember { AdditionalTextInputDialogParams() },
title: String? = null,
dismissButton: DialogButton? = null,
isDismissable: Boolean = true,
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsEffect.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsEffect.kt
deleted file mode 100644
index ea843b7a7a..0000000000
--- a/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsEffect.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.tangem.core.ui.components
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import com.google.accompanist.systemuicontroller.SystemUiController
-import com.google.accompanist.systemuicontroller.rememberSystemUiController
-
-@Composable
-fun SystemBarsEffect(block: SystemUiController.() -> Unit) {
- val systemUiController = rememberSystemUiController()
- SideEffect { block(systemUiController) }
-}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsUtils.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsUtils.kt
new file mode 100644
index 0000000000..0e40554581
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsUtils.kt
@@ -0,0 +1,61 @@
+package com.tangem.core.ui.components
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
+import com.google.accompanist.systemuicontroller.rememberSystemUiController
+import com.tangem.core.ui.res.LocalIsInDarkTheme
+
+/**
+ * Provides the ability to set a scrim for 3-button navigation
+ *
+ * Automatically makes navigation bar transparent when the composable is disposed.
+ *
+ * Usage example: [com.tangem.feature.tokendetails.presentation.TokenDetailsFragment]
+ */
+@Composable
+fun NavigationBar3ButtonsScrim() {
+ val systemUiController = rememberSystemUiController()
+ val isDarkTheme = LocalIsInDarkTheme.current
+ SideEffect {
+ systemUiController.isNavigationBarContrastEnforced = true
+ }
+ LaunchedEffect(systemUiController.isNavigationBarContrastEnforced) {
+ if (systemUiController.isNavigationBarContrastEnforced.not()) {
+ systemUiController.isNavigationBarContrastEnforced = true
+ }
+ }
+ DisposableEffect(isDarkTheme) {
+ onDispose {
+ systemUiController.isNavigationBarContrastEnforced = false
+ }
+ }
+}
+
+/**
+ * Provides the ability to set dark/light icons in cases where the darkness of the screen differs from that
+ * provided in [TangemTheme].
+ *
+ * Automatically returns the icon colors to their original state when the composable is disposed.
+ *
+ * Usage example: [com.tangem.feature.qrscanning.QrScanningFragment]
+ */
+@Composable
+fun SystemBarsIconsDisposable(darkIcons: Boolean, isNavigationBarContrastEnforced: Boolean = false) {
+ val systemUiController = rememberSystemUiController()
+
+ SideEffect {
+ systemUiController.systemBarsDarkContentEnabled = darkIcons
+ systemUiController.isNavigationBarContrastEnforced = isNavigationBarContrastEnforced
+ }
+
+ val isDarkTheme = LocalIsInDarkTheme.current
+
+ DisposableEffect(isDarkTheme) {
+ onDispose {
+ systemUiController.systemBarsDarkContentEnabled = !isDarkTheme
+ systemUiController.isNavigationBarContrastEnforced = false
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt
index 685358db13..07f48d4787 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt
@@ -1,23 +1,57 @@
package com.tangem.core.ui.components.appbar
import android.content.res.Configuration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.Text
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.appbar.models.AdditionalButton
-import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+
+/**
+ * App bar with title and two additional buttons
+ *
+ * @param startButton button information attached to the left edge
+ * @param endButton button information attached to the right edge
+ *
+ * @see
+ * Figma Component
+ */
+@Composable
+fun AppBarWithAdditionalButtons(
+ text: TextReference,
+ modifier: Modifier = Modifier,
+ startButton: AdditionalButton? = null,
+ endButton: AdditionalButton? = null,
+ textColor: Color = TangemTheme.colors.text.primary1,
+ iconColor: Color = TangemTheme.colors.icon.primary1,
+) {
+ AppBarWithAdditionalButtons(
+ text = text.resolveReference(),
+ startButton = startButton,
+ endButton = endButton,
+ modifier = modifier,
+ textColor = textColor,
+ iconColor = iconColor,
+ )
+}
/**
* App bar with title and two additional buttons
@@ -31,43 +65,63 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun AppBarWithAdditionalButtons(
text: String,
+ modifier: Modifier = Modifier,
startButton: AdditionalButton? = null,
endButton: AdditionalButton? = null,
+ textColor: Color = TangemTheme.colors.text.primary1,
+ iconColor: Color = TangemTheme.colors.icon.primary1,
) {
Box(
- modifier = Modifier
+ modifier = modifier
.fillMaxWidth()
- .heightIn(TangemTheme.dimens.size56)
.padding(all = TangemTheme.dimens.spacing16),
) {
if (startButton != null) {
- IconButton(modifier = Modifier.align(Alignment.CenterStart), onClick = startButton.onIconClicked) {
- Icon(
- painter = painterResource(id = startButton.iconRes),
- contentDescription = null,
- modifier = Modifier.size(TangemTheme.dimens.size24),
- tint = TangemTheme.colors.icon.primary1,
- )
- }
+ Icon(
+ painter = painterResource(id = startButton.iconRes),
+ contentDescription = null,
+ tint = iconColor,
+ modifier = Modifier
+ .size(TangemTheme.dimens.size24)
+ .align(Alignment.CenterStart)
+ .clickable(
+ onClick = startButton.onIconClicked,
+ role = Role.Button,
+ interactionSource = remember { MutableInteractionSource() },
+ indication = androidx.compose.material.ripple.rememberRipple(
+ bounded = false,
+ radius = TangemTheme.dimens.size24 / 2,
+ ),
+ ),
+ )
}
Text(
text = text,
modifier = Modifier.align(Alignment.Center),
- color = TangemTheme.colors.text.primary1,
+ color = textColor,
maxLines = 1,
style = TangemTheme.typography.subtitle1,
)
if (endButton != null) {
- IconButton(modifier = Modifier.align(Alignment.CenterEnd), onClick = endButton.onIconClicked) {
- Icon(
- painter = painterResource(id = endButton.iconRes),
- contentDescription = null,
- modifier = Modifier.size(TangemTheme.dimens.size24),
- tint = TangemTheme.colors.icon.primary1,
- )
- }
+ Icon(
+ painter = painterResource(id = endButton.iconRes),
+ contentDescription = null,
+ tint = iconColor,
+ modifier = Modifier
+ .size(TangemTheme.dimens.size24)
+ .align(Alignment.CenterEnd)
+ .clickable(
+ onClick = endButton.onIconClicked,
+ role = Role.Button,
+ interactionSource = remember { MutableInteractionSource() },
+ indication = androidx.compose.material.ripple.rememberRipple(
+ bounded = false,
+ radius = TangemTheme.dimens.size24 / 2,
+ ),
+ ),
+ )
}
}
}
@@ -87,6 +141,7 @@ private fun Preview_AppBarWithAdditionalButtons() {
iconRes = R.drawable.ic_more_vertical_24,
onIconClicked = {},
),
+ modifier = Modifier.background(TangemTheme.colors.background.secondary),
)
}
}
@@ -102,6 +157,7 @@ private fun Preview_AppBarWithOnlyStartButtons() {
iconRes = R.drawable.ic_scan_24,
onIconClicked = {},
),
+ modifier = Modifier.background(TangemTheme.colors.background.secondary),
)
}
}
@@ -117,6 +173,7 @@ private fun Preview_AppBarWithOnlyEndButtons() {
iconRes = R.drawable.ic_more_vertical_24,
onIconClicked = {},
),
+ modifier = Modifier.background(TangemTheme.colors.background.secondary),
)
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/TopAppBarMedium.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/TopAppBarMedium.kt
new file mode 100644
index 0000000000..d50a8f5c75
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/TopAppBarMedium.kt
@@ -0,0 +1,75 @@
+package com.tangem.core.ui.components.appbar.models
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextOverflow
+import com.tangem.core.ui.R
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.res.TangemTheme
+
+private const val COLLAPSED_APP_BAR_THRESHOLD = 0.4f
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TopAppBarMedium(
+ title: TextReference,
+ scrollBehavior: TopAppBarScrollBehavior,
+ onBackClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ navigationIconResId: Int = R.drawable.ic_back_24,
+ colors: TopAppBarColors = TangemTopAppBarColors,
+) {
+ MediumTopAppBar(
+ modifier = modifier,
+ scrollBehavior = scrollBehavior,
+ colors = colors,
+ title = {
+ val collapsedStyle = TangemTheme.typography.subtitle1
+ val expandedStyle = TangemTheme.typography.h1
+ val style by remember(scrollBehavior.state.collapsedFraction) {
+ derivedStateOf {
+ if (scrollBehavior.state.collapsedFraction >= COLLAPSED_APP_BAR_THRESHOLD) {
+ collapsedStyle
+ } else {
+ expandedStyle
+ }
+ }
+ }
+
+ Text(
+ text = title.resolveReference(),
+ style = style,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ },
+ navigationIcon = {
+ IconButton(
+ modifier = Modifier.size(TangemTheme.dimens.size32),
+ onClick = onBackClick,
+ ) {
+ Icon(
+ modifier = Modifier.size(TangemTheme.dimens.size24),
+ painter = painterResource(id = navigationIconResId),
+ contentDescription = null,
+ )
+ }
+ },
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+internal val TangemTopAppBarColors: TopAppBarColors
+ @Composable
+ @ReadOnlyComposable
+ get() = TopAppBarColors(
+ containerColor = TangemTheme.colors.background.secondary,
+ scrolledContainerColor = TangemTheme.colors.background.secondary,
+ navigationIconContentColor = TangemTheme.colors.icon.primary1,
+ titleContentColor = TangemTheme.colors.text.primary1,
+ actionIconContentColor = TangemTheme.colors.icon.primary1,
+ )
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/radiobutton/TangemRadioButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/radiobutton/TangemRadioButton.kt
new file mode 100644
index 0000000000..777062e8bd
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/radiobutton/TangemRadioButton.kt
@@ -0,0 +1,85 @@
+package com.tangem.core.ui.components.atoms.radiobutton
+
+import android.content.res.Configuration
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import com.tangem.core.ui.R
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+
+/**
+ * [Radio button](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=101-119&t=FH84ljLBk1vmGAei-4)
+ *
+ * @param isSelected Whether the radio button is selected
+ * @param onClick Called when the user clicks the button
+ * @param modifier Modifier to be applied to the button
+ * @param isEnabled Whether the button click is enabled
+ */
+@Composable
+fun TangemRadioButton(
+ isSelected: Boolean,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ isEnabled: Boolean = true,
+) {
+ Box(
+ modifier = modifier.clickable(
+ enabled = isEnabled,
+ interactionSource = remember { MutableInteractionSource() },
+ indication = rememberRipple(bounded = false, radius = TangemTheme.dimens.size16),
+ onClick = onClick,
+ ),
+ ) {
+ val color = TangemTheme.colors.stroke.secondary
+ val radius = with(LocalDensity.current) { TangemTheme.dimens.size9.toPx() }
+ val width = with(LocalDensity.current) { TangemTheme.dimens.size2.toPx() }
+ Canvas(
+ modifier = Modifier
+ .size(TangemTheme.dimens.size24)
+ .padding(TangemTheme.dimens.spacing2),
+ ) {
+ drawCircle(
+ color = color,
+ radius = radius,
+ style = Stroke(width),
+ )
+ }
+ AnimatedVisibility(
+ visible = isSelected,
+ label = "Radio button animation",
+ modifier = modifier
+ .size(TangemTheme.dimens.size24),
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_check_circle_24),
+ contentDescription = null,
+ tint = TangemTheme.colors.control.checked,
+ )
+ }
+ }
+}
+
+// region Preview
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun TangemRadioButton_Preview() {
+ var isSelected by remember { mutableStateOf(false) }
+ TangemThemePreview {
+ TangemRadioButton(isSelected, onClick = { isSelected = !isSelected })
+ }
+}
+// endregion
\ No newline at end of file
diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/BlockCard.kt b/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt
similarity index 85%
rename from features/details/impl/src/main/kotlin/com/tangem/features/details/ui/BlockCard.kt
rename to core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt
index e11a41c41e..24f64babdc 100644
--- a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/BlockCard.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt
@@ -1,4 +1,4 @@
-package com.tangem.features.details.ui
+package com.tangem.core.ui.components.block
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.Card
@@ -9,9 +9,10 @@ import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.TangemTheme
@Composable
-internal fun BlockCard(
+fun BlockCard(
modifier: Modifier = Modifier,
enabled: Boolean = true,
+ colors: CardColors = TangemBlockCardColors,
onClick: () -> Unit = {},
content: @Composable ColumnScope.() -> Unit = {},
) {
@@ -19,13 +20,13 @@ internal fun BlockCard(
modifier = modifier,
onClick = onClick,
shape = TangemTheme.shapes.roundedCornersXMedium,
- colors = BlockColors,
+ colors = colors,
enabled = enabled,
content = content,
)
}
-private val BlockColors: CardColors
+val TangemBlockCardColors: CardColors
@Composable
@ReadOnlyComposable
get() = CardColors(
diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/BlockItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockItem.kt
similarity index 61%
rename from features/details/impl/src/main/kotlin/com/tangem/features/details/ui/BlockItem.kt
rename to core/ui/src/main/java/com/tangem/core/ui/components/block/BlockItem.kt
index 80d99b3b20..d729408a97 100644
--- a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/BlockItem.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockItem.kt
@@ -1,4 +1,4 @@
-package com.tangem.features.details.ui
+package com.tangem.core.ui.components.block
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
@@ -11,12 +11,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
+import com.tangem.core.ui.components.block.model.BlockUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.features.details.entity.DetailsItemUM
@Composable
-internal fun BlockItem(model: DetailsItemUM.Basic.Item, modifier: Modifier = Modifier) {
+fun BlockItem(model: BlockUM, modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
onClick = model.onClick,
@@ -29,14 +29,22 @@ internal fun BlockItem(model: DetailsItemUM.Basic.Item, modifier: Modifier = Mod
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = model.iconRes),
- tint = TangemTheme.colors.icon.secondary,
+ tint = when (model.accentType) {
+ BlockUM.AccentType.NONE -> TangemTheme.colors.icon.secondary
+ BlockUM.AccentType.ACCENT -> TangemTheme.colors.text.accent
+ BlockUM.AccentType.WARNING -> TangemTheme.colors.text.warning
+ },
contentDescription = null,
)
Text(
- text = model.title.resolveReference(),
+ text = model.text.resolveReference(),
style = TangemTheme.typography.subtitle1,
- color = TangemTheme.colors.text.primary1,
+ color = when (model.accentType) {
+ BlockUM.AccentType.NONE -> TangemTheme.colors.text.primary1
+ BlockUM.AccentType.ACCENT -> TangemTheme.colors.text.accent
+ BlockUM.AccentType.WARNING -> TangemTheme.colors.text.warning
+ },
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/block/model/BlockUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/block/model/BlockUM.kt
new file mode 100644
index 0000000000..df7e04af33
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/block/model/BlockUM.kt
@@ -0,0 +1,16 @@
+package com.tangem.core.ui.components.block.model
+
+import androidx.annotation.DrawableRes
+import com.tangem.core.ui.extensions.TextReference
+
+data class BlockUM(
+ val text: TextReference,
+ @DrawableRes val iconRes: Int,
+ val onClick: () -> Unit,
+ val accentType: AccentType = AccentType.NONE,
+) {
+
+ enum class AccentType {
+ NONE, ACCENT, WARNING,
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt
index 3f934a0f9b..ec10ddfa0a 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt
@@ -1,13 +1,14 @@
package com.tangem.core.ui.components.bottomsheets
-import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ModalBottomSheet
-import androidx.compose.material3.SheetState
-import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.utils.WindowInsetsZero
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
@@ -15,6 +16,7 @@ import kotlinx.coroutines.launch
* Tangem bottom sheet with custom draggable header and config
*
* @param config data model containing logic and ui models
+ * @param
* @param content custom bottom sheet content
*/
@OptIn(ExperimentalMaterial3Api::class)
@@ -22,20 +24,33 @@ import kotlinx.coroutines.launch
inline fun TangemBottomSheet(
config: TangemBottomSheetConfig,
containerColor: Color = TangemTheme.colors.background.primary,
+ addBottomInsets: Boolean = true,
crossinline content: @Composable ColumnScope.(T) -> Unit,
) {
var isVisible by remember { mutableStateOf(value = config.isShow) }
-
+ val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
if (isVisible && config.content is T) {
ModalBottomSheet(
+ // FIXME temporary solution to fix height of the bottom sheet
+ modifier = Modifier.sizeIn(maxHeight = LocalWindowSize.current.height - statusBarHeight),
onDismissRequest = config.onDismissRequest,
sheetState = sheetState,
containerColor = containerColor,
shape = TangemTheme.shapes.bottomSheetLarge,
+ windowInsets = WindowInsetsZero,
dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) },
) {
- content(config.content)
+ if (addBottomInsets) {
+ Column(
+ // FIXME temporary solution to fix height of the bottom sheet
+ modifier = Modifier.navigationBarsPadding(),
+ ) {
+ content(config.content)
+ }
+ } else {
+ content(config.content)
+ }
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt
index 0bfeb822a6..1101935641 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt
@@ -8,10 +8,7 @@ import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.*
@@ -21,8 +18,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
-import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
@@ -42,7 +40,7 @@ import kotlinx.collections.immutable.persistentListOf
*/
@Composable
inline fun SegmentedButtons(
- config: PersistentList,
+ config: ImmutableList,
crossinline onClick: (T) -> Unit,
modifier: Modifier = Modifier,
color: Color = TangemTheme.colors.background.tertiary,
@@ -51,7 +49,7 @@ inline fun SegmentedButtons(
showIndication: Boolean = true,
initialSelectedItem: T? = null,
isEnabled: Boolean = true,
- crossinline buttonContent: @Composable (T) -> Unit,
+ crossinline buttonContent: @Composable BoxScope.(T) -> Unit,
) {
if (config.isEmpty() || config.size == 1) return
@@ -97,7 +95,7 @@ inline fun SegmentedButtons(
onClick(config[index])
},
) {
- buttonContent.invoke(config[index])
+ buttonContent.invoke(this, config[index])
}
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt
index 3f711de362..0a17c85b13 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowBestRate.kt
@@ -5,7 +5,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
@@ -14,18 +13,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
-import coil.compose.SubcomposeAsyncImage
-import coil.request.ImageRequest
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerWMax
-import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
+import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemThemePreview
@@ -63,7 +59,7 @@ fun InputRowBestRate(
modifier = Modifier
.padding(TangemTheme.dimens.spacing12),
) {
- InnerIcon(imageUrl = imageUrl)
+ InputRowAsyncImage(imageUrl = imageUrl, modifier = Modifier.size(TangemTheme.dimens.spacing40))
Column(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing12),
@@ -131,29 +127,6 @@ private fun InnerTitle(title: TextReference, titleExtra: TextReference, showTag:
}
}
-@Composable
-private fun InnerIcon(imageUrl: String) {
- SubcomposeAsyncImage(
- modifier = Modifier.size(TangemTheme.dimens.size40),
- model = ImageRequest.Builder(context = LocalContext.current)
- .data(imageUrl)
- .crossfade(enable = true)
- .allowHardware(enable = false)
- .build(),
- loading = { LoadingIcon() },
- error = {
- Box(
- modifier = Modifier
- .background(
- color = TangemTheme.colors.background.tertiary,
- shape = CircleShape,
- ),
- )
- },
- contentDescription = null,
- )
-}
-
//region preview
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt
new file mode 100644
index 0000000000..51f7b29ac6
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt
@@ -0,0 +1,54 @@
+package com.tangem.core.ui.components.inputrow
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resolveAnnotatedReference
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.res.TangemTheme
+
+@Composable
+internal fun InputRowImageBase(
+ subtitle: TextReference,
+ caption: TextReference,
+ imageUrl: String,
+ modifier: Modifier = Modifier,
+ subtitleColor: Color = TangemTheme.colors.text.primary1,
+ captionColor: Color = TangemTheme.colors.text.tertiary,
+ extraContent: @Composable RowScope.() -> Unit = {},
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier,
+ ) {
+ InputRowAsyncImage(
+ imageUrl = imageUrl,
+ modifier = Modifier
+ .size(TangemTheme.dimens.spacing36)
+ .padding(vertical = TangemTheme.dimens.size1),
+ )
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .padding(start = TangemTheme.dimens.spacing12),
+ ) {
+ Text(
+ text = subtitle.resolveReference(),
+ style = TangemTheme.typography.subtitle2,
+ color = subtitleColor,
+ )
+ Text(
+ text = caption.resolveAnnotatedReference(),
+ style = TangemTheme.typography.caption2,
+ color = captionColor,
+ modifier = Modifier.padding(top = TangemTheme.dimens.spacing2),
+ )
+ }
+ extraContent()
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageChevron.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageChevron.kt
new file mode 100644
index 0000000000..9c8fcff521
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageChevron.kt
@@ -0,0 +1,46 @@
+package com.tangem.core.ui.components.inputrow
+
+import androidx.compose.material.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import com.tangem.core.ui.R
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.res.TangemTheme
+
+/**
+ * Input row component with selector
+ * [Input Row Image](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2100-842&t=hoBXmDX8NeLrp4p6-4)
+ *
+ * @param subtitle subtitle text
+ * @param caption caption text
+ * @param imageUrl icon to load
+ * @param modifier modifier
+ * @param subtitleColor subtitle text color
+ * @param captionColor caption text color
+ */
+@Composable
+fun InputRowImageChevron(
+ subtitle: TextReference,
+ caption: TextReference,
+ imageUrl: String,
+ modifier: Modifier = Modifier,
+ subtitleColor: Color = TangemTheme.colors.text.primary1,
+ captionColor: Color = TangemTheme.colors.text.tertiary,
+) {
+ InputRowImageBase(
+ subtitle = subtitle,
+ caption = caption,
+ imageUrl = imageUrl,
+ modifier = modifier,
+ subtitleColor = subtitleColor,
+ captionColor = captionColor,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_chevron_right_24),
+ tint = TangemTheme.colors.icon.informative,
+ contentDescription = null,
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt
new file mode 100644
index 0000000000..9e672eb56b
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt
@@ -0,0 +1,131 @@
+package com.tangem.core.ui.components.inputrow
+
+import android.content.res.Configuration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import com.tangem.core.ui.R
+import com.tangem.core.ui.components.atoms.radiobutton.TangemRadioButton
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.annotatedReference
+import com.tangem.core.ui.extensions.combinedReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.core.ui.utils.BigDecimalFormatter
+import java.math.BigDecimal
+
+/**
+ * Input row component with selector
+ * [Input Row Image](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2772-905&t=FH84ljLBk1vmGAei-4)
+ *
+ * @param subtitle subtitle text
+ * @param caption caption text
+ * @param imageUrl icon to load
+ * @param onSelect callback when selected
+ * @param modifier modifier
+ * @param subtitleColor subtitle text color
+ * @param captionColor caption text color
+ * @param isSelected true if selected
+ */
+@Composable
+fun InputRowImageSelector(
+ subtitle: TextReference,
+ caption: TextReference,
+ imageUrl: String,
+ onSelect: () -> Unit,
+ modifier: Modifier = Modifier,
+ subtitleColor: Color = TangemTheme.colors.text.primary1,
+ captionColor: Color = TangemTheme.colors.text.tertiary,
+ isSelected: Boolean = false,
+) {
+ InputRowImageBase(
+ subtitle = subtitle,
+ caption = caption,
+ imageUrl = imageUrl,
+ subtitleColor = subtitleColor,
+ captionColor = captionColor,
+ modifier = modifier
+ .clickable(
+ onClick = onSelect,
+ interactionSource = remember { MutableInteractionSource() },
+ indication = rememberRipple(),
+ )
+ .padding(TangemTheme.dimens.spacing12),
+ ) {
+ TangemRadioButton(isSelected = isSelected, isEnabled = false, onClick = onSelect)
+ }
+}
+
+//region preview
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun InputRowImageSelectorPreview(
+ @PreviewParameter(InputRowImageSelectorPreviewDataProvider::class) data: InputRowImageSelectorPreviewData,
+) {
+ TangemThemePreview {
+ InputRowImageSelector(
+ modifier = Modifier.background(TangemTheme.colors.background.action),
+ subtitle = data.subtitle,
+ caption = combinedReference(
+ resourceReference(R.string.staking_details_apr),
+ annotatedReference(
+ buildAnnotatedString {
+ append(" ")
+ withStyle(style = SpanStyle(color = TangemTheme.colors.text.accent)) {
+ append(
+ BigDecimalFormatter.formatPercent(BigDecimal.ZERO, true),
+ )
+ }
+ },
+ ),
+ ),
+ imageUrl = "",
+ isSelected = false,
+ onSelect = {},
+ )
+ }
+}
+
+private data class InputRowImageSelectorPreviewData(
+ val subtitle: TextReference,
+ val caption: TextReference,
+ val showDivider: Boolean,
+ val actionIconRes: Int?,
+ val isSelected: Boolean,
+)
+
+private class InputRowImageSelectorPreviewDataProvider :
+ PreviewParameterProvider {
+ override val values: Sequence
+ get() = sequenceOf(
+ InputRowImageSelectorPreviewData(
+ subtitle = TextReference.Str("subtitle"),
+ caption = TextReference.Str("caption"),
+ actionIconRes = null,
+ showDivider = false,
+ isSelected = false,
+ ),
+ InputRowImageSelectorPreviewData(
+ subtitle = TextReference.Str("subtitle"),
+ caption = TextReference.Str("caption"),
+ actionIconRes = R.drawable.ic_chevron_right_24,
+ showDivider = true,
+ isSelected = true,
+ ),
+ )
+}
+//endregion
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt
new file mode 100644
index 0000000000..90bf7e86b8
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/inner/InputRowAsyncImage.kt
@@ -0,0 +1,41 @@
+package com.tangem.core.ui.components.inputrow.inner
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import coil.compose.SubcomposeAsyncImage
+import coil.request.ImageRequest
+import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon
+import com.tangem.core.ui.res.TangemTheme
+
+/**
+ * Loads image by url for icon in the input row
+ *
+ * @param imageUrl url of the image
+ * @param modifier modifier
+ */
+@Composable
+internal fun InputRowAsyncImage(imageUrl: String, modifier: Modifier = Modifier) {
+ SubcomposeAsyncImage(
+ modifier = modifier,
+ model = ImageRequest.Builder(context = LocalContext.current)
+ .data(imageUrl)
+ .crossfade(enable = true)
+ .allowHardware(enable = false)
+ .build(),
+ loading = { LoadingIcon() },
+ error = {
+ Box(
+ modifier = Modifier
+ .background(
+ color = TangemTheme.colors.background.tertiary,
+ shape = CircleShape,
+ ),
+ )
+ },
+ contentDescription = null,
+ )
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/TravalaNotificationWithBackground.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/TravalaNotificationWithBackground.kt
index 9e6ba3db1d..6fc2f27ebd 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/TravalaNotificationWithBackground.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/TravalaNotificationWithBackground.kt
@@ -36,6 +36,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemColorPalette.White
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
/**
* Travala notification with image background
@@ -192,7 +193,7 @@ private fun formatSubtitle(subtitle: String): AnnotatedString {
@Preview
@Composable
private fun TravalaNotificationWithBackgroundPreview() {
- TangemTheme {
+ TangemThemePreview {
TravalaNotificationWithBackground(
config = NotificationConfig(
title = resourceReference(
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/RoundableCornersRow.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/RoundableCornersRow.kt
new file mode 100644
index 0000000000..e83444c298
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/RoundableCornersRow.kt
@@ -0,0 +1,147 @@
+package com.tangem.core.ui.components.rows
+
+import android.content.res.Configuration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Icon
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.TextStyle
+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.core.ui.R
+
+@Suppress("LongParameterList")
+@Composable
+fun RoundableCornersRow(
+ startText: String,
+ startTextColor: Color,
+ startTextStyle: TextStyle,
+ endText: String,
+ endTextColor: Color,
+ endTextStyle: TextStyle,
+ cornersToRound: CornersToRound,
+ iconResId: Int? = null,
+) {
+ Surface(
+ shape = cornersToRound.getShape(),
+ color = TangemTheme.colors.background.primary,
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(TangemTheme.dimens.size48)
+ .padding(
+ horizontal = TangemTheme.dimens.spacing16,
+ vertical = TangemTheme.dimens.spacing12,
+ ),
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ text = startText,
+ color = startTextColor,
+ maxLines = 1,
+ style = startTextStyle,
+ )
+ if (iconResId != null) {
+ Icon(
+ modifier = Modifier
+ .padding(TangemTheme.dimens.spacing4)
+ .size(TangemTheme.dimens.size16),
+ painter = painterResource(id = R.drawable.ic_alert_24),
+ contentDescription = null,
+ tint = TangemTheme.colors.text.tertiary,
+ )
+ }
+ Spacer(modifier = Modifier.weight(1f))
+ Text(
+ text = endText,
+ color = endTextColor,
+ maxLines = 1,
+ style = endTextStyle,
+ )
+ }
+ }
+}
+
+enum class CornersToRound {
+
+ ALL_4,
+ TOP_2,
+ BOTTOM_2,
+ ZERO,
+ ;
+
+ @Suppress("TopLevelComposableFunctions")
+ @Composable
+ fun getShape(): RoundedCornerShape {
+ val radius = TangemTheme.dimens.radius12
+ return when (this) {
+ ALL_4 -> RoundedCornerShape(radius)
+ TOP_2 -> RoundedCornerShape(topStart = radius, topEnd = radius)
+ BOTTOM_2 -> RoundedCornerShape(bottomStart = radius, bottomEnd = radius)
+ ZERO -> RoundedCornerShape(0.dp)
+ }
+ }
+}
+
+@Preview(widthDp = 360, showBackground = true)
+@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview_RoundableCornersRow(
+ @PreviewParameter(RoundableCornersRowDataProvider::class) previewData: RoundableCornersRowPreviewData,
+) {
+ TangemThemePreview {
+ Box(modifier = Modifier.background(color = TangemTheme.colors.icon.attention)) {
+ RoundableCornersRow(
+ startText = previewData.startText,
+ startTextColor = TangemTheme.colors.text.tertiary,
+ startTextStyle = TangemTheme.typography.subtitle2,
+ endText = previewData.endText,
+ endTextColor = TangemTheme.colors.text.primary1,
+ endTextStyle = TangemTheme.typography.subtitle2,
+ cornersToRound = previewData.cornersToRound,
+ iconResId = previewData.iconResId,
+ )
+ }
+ }
+}
+
+private data class RoundableCornersRowPreviewData(
+ val startText: String,
+ val endText: String,
+ val cornersToRound: CornersToRound,
+ val iconResId: Int? = null,
+)
+
+private class RoundableCornersRowDataProvider :
+ PreviewParameterProvider {
+
+ override val values: Sequence
+ get() = sequenceOf(
+ getPreviewData(cornersToRound = CornersToRound.ZERO),
+ getPreviewData(cornersToRound = CornersToRound.TOP_2),
+ getPreviewData(cornersToRound = CornersToRound.BOTTOM_2),
+ getPreviewData(cornersToRound = CornersToRound.ZERO, iconResId = R.drawable.ic_alert_24),
+ getPreviewData(cornersToRound = CornersToRound.TOP_2, iconResId = R.drawable.ic_alert_24),
+ getPreviewData(cornersToRound = CornersToRound.BOTTOM_2, iconResId = R.drawable.ic_alert_24),
+ )
+
+ private fun getPreviewData(cornersToRound: CornersToRound, iconResId: Int? = null) = RoundableCornersRowPreviewData(
+ startText = "startText",
+ endText = "endText",
+ cornersToRound = cornersToRound,
+ iconResId = iconResId,
+ )
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt
index c1e06e7270..5eb6b5f287 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/SelectorRowItem.kt
@@ -25,6 +25,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.utils.Strings
@Composable
fun SelectorRowItem(
@@ -127,7 +128,7 @@ private fun RowScope.SelectorValueContent(
)
if (postDot != null) {
Text(
- text = "•",
+ text = Strings.DOT,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt
new file mode 100644
index 0000000000..8112024b54
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt
@@ -0,0 +1,143 @@
+package com.tangem.core.ui.components.showcase
+
+import android.content.res.Configuration
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.tangem.core.ui.R
+import com.tangem.core.ui.components.PrimaryButton
+import com.tangem.core.ui.components.SecondaryButton
+import com.tangem.core.ui.components.showcase.model.ShowcaseButtonModel
+import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+
+/**
+ * @param headerIconRes big header icon
+ * @param headerText header text
+ * @param showcaseItems list of bullet points
+ * @param primaryButton primary button
+ * @param secondaryButton secondary button
+ * @param modifier compose modifier
+ *
+ * @see Figma component
+ */
+@Composable
+fun Showcase(
+ @DrawableRes headerIconRes: Int,
+ headerText: TextReference,
+ showcaseItems: ImmutableList,
+ primaryButton: ShowcaseButtonModel,
+ secondaryButton: ShowcaseButtonModel,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier.fillMaxSize(),
+ ) {
+ ShowcaseContent(
+ headerIconRes = headerIconRes,
+ headerText = headerText,
+ showcaseItems = showcaseItems,
+ modifier = Modifier
+ .weight(1f)
+ .align(Alignment.CenterHorizontally),
+ )
+ ShowcaseButtons(
+ primaryButtonText = primaryButton.buttonText,
+ onPrimaryClick = primaryButton.onClick,
+ secondaryButtonText = secondaryButton.buttonText,
+ onSecondaryClick = secondaryButton.onClick,
+ )
+ }
+}
+
+@Composable
+private fun ShowcaseButtons(
+ primaryButtonText: TextReference,
+ secondaryButtonText: TextReference,
+ onPrimaryClick: () -> Unit,
+ onSecondaryClick: () -> Unit,
+ hint: TextReference? = null,
+) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ PrimaryButton(
+ text = primaryButtonText.resolveReference(),
+ onClick = onPrimaryClick,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ start = TangemTheme.dimens.spacing16,
+ end = TangemTheme.dimens.spacing16,
+ ),
+ )
+ SecondaryButton(
+ text = secondaryButtonText.resolveReference(),
+ onClick = onSecondaryClick,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ start = TangemTheme.dimens.spacing16,
+ end = TangemTheme.dimens.spacing16,
+ top = TangemTheme.dimens.spacing12,
+ bottom = TangemTheme.dimens.spacing16,
+ ),
+ )
+ hint?.let {
+ Text(
+ text = it.resolveReference(),
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.secondary,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ start = TangemTheme.dimens.spacing16,
+ end = TangemTheme.dimens.spacing16,
+ bottom = TangemTheme.dimens.spacing12,
+ ),
+ )
+ }
+ }
+}
+
+// region Preview
+@Preview(showBackground = true, widthDp = 360, heightDp = 720)
+@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Showcase_Preview() {
+ TangemThemePreview {
+ Showcase(
+ headerIconRes = R.drawable.ic_notifications_unread_24,
+ headerText = resourceReference(R.string.user_push_notification_agreement_header),
+ showcaseItems = persistentListOf(
+ ShowcaseItemModel(
+ R.drawable.ic_rocket_launch_24,
+ resourceReference(R.string.user_push_notification_agreement_argument_one),
+ ),
+ ShowcaseItemModel(
+ R.drawable.ic_storefront_24,
+ resourceReference(R.string.user_push_notification_agreement_argument_two),
+ ),
+ ),
+ primaryButton = ShowcaseButtonModel(resourceReference(R.string.common_allow), {}),
+ secondaryButton = ShowcaseButtonModel(resourceReference(R.string.common_later), {}),
+ modifier = Modifier.background(TangemTheme.colors.background.primary),
+ )
+ }
+}
+// endregion
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt
new file mode 100644
index 0000000000..336172a208
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt
@@ -0,0 +1,71 @@
+package com.tangem.core.ui.components.showcase
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.res.TangemTheme
+import kotlinx.collections.immutable.ImmutableList
+
+@Composable
+fun ShowcaseContent(
+ @DrawableRes headerIconRes: Int,
+ headerText: TextReference,
+ showcaseItems: ImmutableList,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ modifier = modifier,
+ ) {
+ Icon(
+ painter = painterResource(id = headerIconRes),
+ contentDescription = null,
+ tint = TangemTheme.colors.icon.primary1,
+ modifier = Modifier
+ .align(Alignment.CenterHorizontally)
+ .size(TangemTheme.dimens.size56),
+ )
+ Text(
+ text = headerText.resolveReference(),
+ style = TangemTheme.typography.h2,
+ color = TangemTheme.colors.text.primary1,
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .align(Alignment.CenterHorizontally)
+ .padding(
+ start = TangemTheme.dimens.spacing34,
+ end = TangemTheme.dimens.spacing34,
+ top = TangemTheme.dimens.spacing28,
+ ),
+ )
+ Column(
+ modifier = Modifier
+ .padding(
+ start = TangemTheme.dimens.spacing34,
+ end = TangemTheme.dimens.spacing34,
+ top = TangemTheme.dimens.spacing28,
+ ),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ repeat(showcaseItems.size) { index ->
+ ShowcaseItem(
+ iconRes = showcaseItems[index].iconRes,
+ text = showcaseItems[index].text,
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt
new file mode 100644
index 0000000000..824144dbf5
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt
@@ -0,0 +1,30 @@
+package com.tangem.core.ui.components.showcase
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.res.TangemTheme
+
+@Composable
+internal fun ShowcaseItem(@DrawableRes iconRes: Int, text: TextReference) {
+ Row {
+ Icon(
+ painter = painterResource(id = iconRes),
+ contentDescription = null,
+ tint = TangemTheme.colors.icon.primary1,
+ )
+ Text(
+ text = text.resolveReference(),
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.secondary,
+ modifier = Modifier.padding(start = TangemTheme.dimens.spacing20),
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseButtonModel.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseButtonModel.kt
new file mode 100644
index 0000000000..602465c864
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseButtonModel.kt
@@ -0,0 +1,8 @@
+package com.tangem.core.ui.components.showcase.model
+
+import com.tangem.core.ui.extensions.TextReference
+
+data class ShowcaseButtonModel(
+ val buttonText: TextReference,
+ val onClick: () -> Unit,
+)
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt
new file mode 100644
index 0000000000..f9865aab9e
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt
@@ -0,0 +1,9 @@
+package com.tangem.core.ui.components.showcase.model
+
+import androidx.annotation.DrawableRes
+import com.tangem.core.ui.extensions.TextReference
+
+data class ShowcaseItemModel(
+ @DrawableRes val iconRes: Int,
+ val text: TextReference,
+)
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbar.kt
index 35cc8f6903..eea8a09bae 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbar.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbar.kt
@@ -19,6 +19,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
/**
* Snackbar to inform the user about copying text to the clipboard
@@ -100,7 +101,7 @@ private fun MessageText(text: TextReference, modifier: Modifier = Modifier) {
private fun Preview_CopiedTextSnackbar(
@PreviewParameter(CopiedTextSnackbarDataProvider::class) message: TextReference,
) {
- TangemTheme(isDark = false) {
+ TangemThemePreview {
CopiedTextSnackbar(message = message)
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
index e43769432c..3b99a455ce 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
@@ -22,7 +22,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
-import com.tangem.common.Strings
import com.tangem.core.ui.R
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
@@ -35,6 +34,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.utils.Strings
import java.util.UUID
/**
diff --git a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableContentComponent.kt b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableContentComponent.kt
new file mode 100644
index 0000000000..4f8144398d
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableContentComponent.kt
@@ -0,0 +1,13 @@
+package com.tangem.core.ui.decompose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.Modifier
+
+@Stable
+fun interface ComposableContentComponent {
+
+ @Composable
+ @Suppress("TopLevelComposableFunctions") // TODO: Remove this check
+ fun Content(modifier: Modifier)
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableDialogComponent.kt b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableDialogComponent.kt
new file mode 100644
index 0000000000..a827872ee3
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableDialogComponent.kt
@@ -0,0 +1,14 @@
+package com.tangem.core.ui.decompose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+
+@Stable
+interface ComposableDialogComponent {
+
+ val doOnDismiss: () -> Unit
+
+ @Composable
+ @Suppress("TopLevelComposableFunctions") // TODO: Remove this check
+ fun Dialog()
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt
index 2f22994f64..386f69b66b 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt
@@ -11,6 +11,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import org.intellij.markdown.MarkdownElementTypes
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
/**
* Utility class for creating text as [String] or [StringRes].
@@ -46,6 +48,13 @@ sealed interface TextReference {
*/
data class Str(val value: String) : TextReference
+ /**
+ * Annotated text
+ *
+ * @property value annotated string
+ */
+ data class Annotated(val value: AnnotatedString) : TextReference
+
/**
* Combined reference. It concatenates all [refs].
*
@@ -81,6 +90,16 @@ fun stringReference(value: String): TextReference {
return TextReference.Str(value)
}
+/**
+ * Creates a [TextReference] using an annotated string value.
+ *
+ * @param value The annotated string value.
+ * @return A [TextReference] representing the provided annotated string value.
+ */
+fun annotatedReference(value: AnnotatedString): TextReference {
+ return TextReference.Annotated(value)
+}
+
/**
* Creates a [TextReference] using a plural string resource ID with count and optional format arguments.
*
@@ -131,6 +150,7 @@ fun TextReference.resolveReference(): String {
}
is TextReference.PluralRes -> pluralStringResource(id, count, *formatArgs.toTypedArray())
is TextReference.Str -> value
+ is TextReference.Annotated -> value.text
is TextReference.Combined -> {
buildString {
refs.forEach {
@@ -153,6 +173,7 @@ fun TextReference.resolveReference(resources: Resources): String {
}
is TextReference.PluralRes -> resources.getQuantityString(id, count, *formatArgs.toTypedArray())
is TextReference.Str -> value
+ is TextReference.Annotated -> value.text
is TextReference.Combined -> {
buildString {
refs.forEach {
@@ -178,9 +199,10 @@ fun TextReference.resolveAnnotatedReference(): AnnotatedString {
pluralStringResource(id, count, *formatArgs.toTypedArray()),
)
is TextReference.Str -> formatAnnotated(value)
+ is TextReference.Annotated -> value
is TextReference.Combined -> buildAnnotatedString {
refs.forEach {
- append(formatAnnotated(it.resolveReference()))
+ append(it.resolveAnnotatedReference())
}
}
}
@@ -193,10 +215,21 @@ operator fun TextReference.plus(ref: TextReference): TextReference {
is TextReference.PluralRes,
is TextReference.Res,
is TextReference.Str,
+ is TextReference.Annotated,
-> TextReference.Combined(refs = wrappedList(this, ref))
}
}
+@Suppress("NOTHING_TO_INLINE")
+@OptIn(ExperimentalContracts::class)
+inline fun TextReference?.isNullOrEmpty(): Boolean {
+ contract {
+ returns(false) implies (this@isNullOrEmpty != null)
+ }
+
+ return this == null || this == TextReference.EMPTY
+}
+
@Composable
private fun formatAnnotated(rawString: String): AnnotatedString {
val markdownDescriptor = rememberMarkdownParser()
diff --git a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageHandler.kt b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageHandler.kt
index 0f4d117dab..782cd2a84f 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageHandler.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageHandler.kt
@@ -1,5 +1,6 @@
package com.tangem.core.ui.message
+import androidx.compose.runtime.Stable
import com.tangem.core.decompose.ui.UiMessage
import com.tangem.core.decompose.ui.UiMessageHandler
import com.tangem.core.ui.event.StateEvent
@@ -11,6 +12,7 @@ import kotlinx.coroutines.flow.StateFlow
/**
* Message handler that is used to show or remove an [EventMessage] in the UI.
*/
+@Stable
class EventMessageHandler(
private val events: MutableStateFlow> = MutableStateFlow(consumedEvent()),
) : UiMessageHandler, StateFlow> by events {
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt
index 451207be18..5ff9fc7e8d 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt
@@ -83,7 +83,6 @@ class TangemColors internal constructor(
warning: Color,
attention: Color,
accent: Color = TangemColorPalette.Azure,
- constant: Color = TangemColorPalette.White,
) {
var primary1 by mutableStateOf(primary1)
private set
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
index 55321fa0b2..2ad32a8f6f 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
@@ -47,6 +47,7 @@ data class TangemDimens internal constructor(
val size5: Dp = 5.dp,
val size7: Dp = 7.dp,
val size8: Dp = 8.dp,
+ val size9: Dp = 9.dp,
val size10: Dp = 10.dp,
val size11: Dp = 11.dp,
val size12: Dp = 12.dp,
@@ -81,8 +82,10 @@ data class TangemDimens internal constructor(
val size90: Dp = 90.dp,
val size93: Dp = 93.dp,
val size96: Dp = 96.dp,
+ val size100: Dp = 100.dp,
val size102: Dp = 102.dp,
val size108: Dp = 108.dp,
+ val size110: Dp = 110.dp,
val size116: Dp = 116.dp,
val size120: Dp = 120.dp,
val size142: Dp = 142.dp,
@@ -99,9 +102,11 @@ data class TangemDimens internal constructor(
val spacing2: Dp = 2.dp,
val spacing3: Dp = 3.dp,
val spacing4: Dp = 4.dp,
+ val spacing5: Dp = 5.dp,
val spacing6: Dp = 6.dp,
val spacing8: Dp = 8.dp,
val spacing10: Dp = 10.dp,
+ val spacing11: Dp = 11.dp,
val spacing12: Dp = 12.dp,
val spacing14: Dp = 14.dp,
val spacing15: Dp = 15.dp,
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
index 4f3287a86d..58552b1353 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
@@ -3,19 +3,22 @@ package com.tangem.core.ui.res
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
+import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.*
+import androidx.compose.ui.graphics.Color
+import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.haptic.MockHapticManager
-
-// TODO: use isSystemInDarkTheme() for automatic color detection
-internal const val IS_SYSTEM_IN_DARK_THEME: Boolean = false
+import com.tangem.core.ui.windowsize.WindowSize
@Composable
fun TangemTheme(
isDark: Boolean = false,
+ windowSize: WindowSize,
typography: TangemTypography = TangemTheme.typography,
dimens: TangemDimens = TangemTheme.dimens,
hapticManager: HapticManager = MockHapticManager,
+ snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
content: @Composable () -> Unit,
) {
val themeColors = if (isDark) darkThemeColors() else lightThemeColors()
@@ -23,6 +26,16 @@ fun TangemTheme(
.also { it.update(themeColors) }
val shapes = remember { TangemShapes(dimens) }
+ val systemUiController = rememberSystemUiController()
+
+ SideEffect {
+ systemUiController.setSystemBarsColor(
+ color = Color.Transparent,
+ darkIcons = !isDark,
+ isNavigationBarContrastEnforced = false,
+ )
+ }
+
MaterialTheme(
colors = materialThemeColors(colors = themeColors, isDark = isDark),
) {
@@ -33,6 +46,8 @@ fun TangemTheme(
LocalTangemShapes provides shapes,
LocalIsInDarkTheme provides isDark,
LocalHapticManager provides hapticManager,
+ LocalSnackbarHostState provides snackbarHostState,
+ LocalWindowSize provides windowSize,
) {
ProvideTextStyle(
value = TangemTheme.typography.body1,
@@ -204,4 +219,12 @@ val LocalIsInDarkTheme = staticCompositionLocalOf { false }
val LocalHapticManager = staticCompositionLocalOf {
error("No HapticManager provided")
+}
+
+val LocalSnackbarHostState = staticCompositionLocalOf {
+ error("No SnackbarHostState provided")
+}
+
+val LocalWindowSize = staticCompositionLocalOf {
+ error("No WindowSize provided")
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt
index 2a9e0caf65..48e3237b47 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt
@@ -1,7 +1,9 @@
package com.tangem.core.ui.res
import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.runtime.Composable
+import com.tangem.core.ui.windowsize.rememberWindowSizePreview
@Composable
fun TangemThemePreview(
@@ -12,10 +14,13 @@ fun TangemThemePreview(
) {
val isDarkTheme = isDark ?: isSystemInDarkTheme()
- TangemTheme(
- isDark = isDarkTheme,
- typography = typography,
- dimens = dimens,
- content = content,
- )
+ BoxWithConstraints {
+ TangemTheme(
+ isDark = isDarkTheme,
+ typography = typography,
+ dimens = dimens,
+ windowSize = rememberWindowSizePreview(maxWidth, maxHeight),
+ content = content,
+ )
+ }
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeActivity.kt b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeActivity.kt
index fe4f115dcd..9d4acd5fc0 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeActivity.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeActivity.kt
@@ -12,6 +12,6 @@ abstract class ComposeActivity : ComponentActivity(), ComposeScreen {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(createComposeView(context = this))
+ setContentView(createComposeView(context = this, activity = this))
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeBottomSheetFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeBottomSheetFragment.kt
index b437a39cd5..75dc4e8a98 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeBottomSheetFragment.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeBottomSheetFragment.kt
@@ -52,7 +52,7 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment(), Compose
override fun getTheme(): Int = R.style.AppTheme_TransparentBottomSheetDialog
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- return createComposeView(inflater.context)
+ return createComposeView(inflater.context, requireActivity())
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
diff --git a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeFragment.kt
index fb811871af..51d450ed02 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeFragment.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeFragment.kt
@@ -17,7 +17,7 @@ abstract class ComposeFragment : Fragment(), ComposeScreen {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val isTransitionsInflated = TransitionInflater.from(requireContext()).inflateTransitions()
- return createComposeView(inflater.context).also {
+ return createComposeView(inflater.context, requireActivity()).also {
it.isTransitionGroup = isTransitionsInflated
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt
index 4c2faae280..d6b1869ec5 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt
@@ -1,5 +1,6 @@
package com.tangem.core.ui.screen
+import android.app.Activity
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize
@@ -10,6 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.windowsize.rememberWindowSize
import com.tangem.domain.apptheme.model.AppThemeMode
/**
@@ -50,14 +52,17 @@ internal interface ComposeScreen {
* @param context The context.
* @return A [ComposeView] instance with the defined screen content.
*/
-internal fun ComposeScreen.createComposeView(context: Context): ComposeView {
+internal fun ComposeScreen.createComposeView(context: Context, activity: Activity): ComposeView {
return ComposeView(context).apply {
setContent {
val appThemeMode by uiDependencies.appThemeModeHolder.appThemeMode
+ val windowSize = rememberWindowSize(activity = activity)
TangemTheme(
isDark = shouldUseDarkTheme(appThemeMode),
+ windowSize = windowSize,
hapticManager = uiDependencies.hapticManager,
+ snackbarHostState = uiDependencies.globalSnackbarHostState,
) {
ScreenContent(modifier = screenModifier)
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt
new file mode 100644
index 0000000000..575485aae9
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/utils/RequestPushPermission.kt
@@ -0,0 +1,52 @@
+package com.tangem.core.ui.utils
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.isGranted
+import com.google.accompanist.permissions.rememberPermissionState
+import com.google.accompanist.permissions.shouldShowRationale
+
+/**
+ * Returns push permission requester.
+ * Handles granting permission from app settings.
+ */
+@Suppress("LongParameterList")
+@OptIn(ExperimentalPermissionsApi::class)
+@Composable
+fun requestPushPermission(
+ isFirstTimeAsking: Boolean,
+ pushPermission: String?,
+ isClicked: MutableState,
+ onAllow: () -> Unit,
+ onDeny: () -> Unit,
+ onOpenSettings: () -> Unit,
+): () -> Unit {
+ val permissionState = pushPermission?.let { permission ->
+ val tempPermissionState = rememberPermissionState(permission = permission)
+ rememberPermissionState(permission = permission) {
+ when {
+ it -> onAllow()
+ !tempPermissionState.status.shouldShowRationale && !isFirstTimeAsking -> onOpenSettings()
+ else -> onDeny()
+ }
+ }
+ }
+
+ // Check if user granted permission and close bottom sheet
+ LaunchedEffect(key1 = permissionState?.status, isClicked) {
+ if (!isClicked.value) return@LaunchedEffect
+ if (permissionState?.status?.isGranted == true) {
+ onAllow()
+ } else {
+ onDeny()
+ }
+ }
+
+ return if (permissionState == null) {
+ onOpenSettings
+ } else {
+ permissionState::launchPermissionRequest
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/WindowInsetsZero.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/WindowInsetsZero.kt
new file mode 100644
index 0000000000..a17ac5f1ef
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/utils/WindowInsetsZero.kt
@@ -0,0 +1,12 @@
+package com.tangem.core.ui.utils
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+
+object WindowInsetsZero : WindowInsets {
+ override fun getBottom(density: Density): Int = 0
+ override fun getLeft(density: Density, layoutDirection: LayoutDirection): Int = 0
+ override fun getRight(density: Density, layoutDirection: LayoutDirection): Int = 0
+ override fun getTop(density: Density): Int = 0
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/windowsize/WindowSize.kt b/core/ui/src/main/java/com/tangem/core/ui/windowsize/WindowSize.kt
new file mode 100644
index 0000000000..a8928b86f8
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/windowsize/WindowSize.kt
@@ -0,0 +1,99 @@
+package com.tangem.core.ui.windowsize
+
+import android.app.Activity
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.window.layout.WindowMetricsCalculator
+
+/**
+ * The preferred way to determine the window size is because it provides the actual size of the window as opposed to
+ * [LocalConfiguration.current.screenHeightDp]
+ *
+ * @property width actual window width
+ * @property height actual window height
+ * @property widthSizeType window size type based on width
+ * @property heightSizeType window size type based on height
+ * @property smallestSize smallest size of width and height
+ */
+data class WindowSize(
+ val width: Dp,
+ val height: Dp,
+) {
+ val widthSizeType: WindowSizeType = getWidthWindowSizeType(width)
+ val heightSizeType: WindowSizeType = getHeightWindowSizeType(height)
+ val smallestSize: Dp = minOf(width, height)
+
+ fun widthAtLeast(type: WindowSizeType): Boolean = this.widthSizeType.ordinal >= type.ordinal
+
+ fun widthAtLeast(value: Dp): Boolean = this.width >= value
+
+ fun heightAtLeast(type: WindowSizeType): Boolean = this.heightSizeType.ordinal >= type.ordinal
+
+ fun heightAtLeast(value: Dp): Boolean = this.height >= value
+
+ fun widthAtMost(type: WindowSizeType): Boolean = this.widthSizeType.ordinal <= type.ordinal
+
+ fun widthAtMost(value: Dp): Boolean = this.width <= value
+
+ fun heightAtMost(type: WindowSizeType): Boolean = this.heightSizeType.ordinal <= type.ordinal
+
+ fun heightAtMost(value: Dp): Boolean = this.height <= value
+
+ private fun getWidthWindowSizeType(windowDp: Dp): WindowSizeType = when {
+ windowDp < 300.dp -> WindowSizeType.ExtraSmall
+ windowDp < 380.dp -> WindowSizeType.Small
+ windowDp < 540.dp -> WindowSizeType.Normal
+ windowDp < 700.dp -> WindowSizeType.Large
+ else -> WindowSizeType.ExtraLarge
+ }
+
+ private fun getHeightWindowSizeType(heightDp: Dp): WindowSizeType = when {
+ heightDp < 480.dp -> WindowSizeType.ExtraSmall
+ heightDp < 640.dp -> WindowSizeType.Small
+ heightDp < 860.dp -> WindowSizeType.Normal
+ heightDp < 1100.dp -> WindowSizeType.Large
+ else -> WindowSizeType.ExtraLarge
+ }
+}
+
+enum class WindowSizeType {
+ ExtraSmall, Small, Normal, Large, ExtraLarge
+}
+
+@Composable
+fun rememberWindowSize(activity: Activity): WindowSize {
+ val windowBoundsSize = rememberWindowBoundsSize(activity)
+ val windowDpSize = with(LocalDensity.current) {
+ windowBoundsSize.toDpSize()
+ }
+
+ return WindowSize(
+ width = windowDpSize.width,
+ height = windowDpSize.height,
+ )
+}
+
+@Composable
+internal fun rememberWindowSizePreview(width: Dp, height: Dp): WindowSize {
+ return remember(width, height) {
+ WindowSize(
+ width = width,
+ height = height,
+ )
+ }
+}
+
+@Composable
+private fun rememberWindowBoundsSize(activity: Activity): Size {
+ val configuration = LocalConfiguration.current
+ val windowMetrics = remember(configuration) {
+ WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity)
+ }
+ return windowMetrics.bounds.toComposeRect().size
+}
\ No newline at end of file
diff --git a/core/ui/src/main/res/drawable/ic_check_circle_24.xml b/core/ui/src/main/res/drawable/ic_check_circle_24.xml
new file mode 100644
index 0000000000..21b193accf
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_check_circle_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_notifications_unread_24.xml b/core/ui/src/main/res/drawable/ic_notifications_unread_24.xml
new file mode 100644
index 0000000000..c50e451c69
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_notifications_unread_24.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_rocket_launch_24.xml b/core/ui/src/main/res/drawable/ic_rocket_launch_24.xml
new file mode 100644
index 0000000000..a7b572f5f0
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_rocket_launch_24.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_storefront_24.xml b/core/ui/src/main/res/drawable/ic_storefront_24.xml
new file mode 100644
index 0000000000..7f17e6c496
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_storefront_24.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/src/main/java/com/tangem/common/Strings.kt b/core/utils/src/main/java/com/tangem/utils/Strings.kt
similarity index 55%
rename from common/src/main/java/com/tangem/common/Strings.kt
rename to core/utils/src/main/java/com/tangem/utils/Strings.kt
index 943b0de614..f6cc72a41a 100644
--- a/common/src/main/java/com/tangem/common/Strings.kt
+++ b/core/utils/src/main/java/com/tangem/utils/Strings.kt
@@ -1,6 +1,7 @@
-package com.tangem.common
+package com.tangem.utils
object Strings {
const val STARS = "\u2217\u2217\u2217"
+ const val DOT = "•"
}
\ No newline at end of file
diff --git a/core/utils/src/main/java/com/tangem/utils/transformer/Transformer.kt b/core/utils/src/main/java/com/tangem/utils/transformer/Transformer.kt
new file mode 100644
index 0000000000..9c271d0920
--- /dev/null
+++ b/core/utils/src/main/java/com/tangem/utils/transformer/Transformer.kt
@@ -0,0 +1,8 @@
+package com.tangem.utils.transformer
+
+/**
+ * Transforms state to updated state.
+ */
+interface Transformer {
+ fun transform(prevState: S): S
+}
\ No newline at end of file
diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultPermissionRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultPermissionRepository.kt
new file mode 100644
index 0000000000..b73d5d069f
--- /dev/null
+++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultPermissionRepository.kt
@@ -0,0 +1,69 @@
+package com.tangem.data.settings
+
+import android.os.SystemClock
+import com.tangem.datasource.local.preferences.AppPreferencesStore
+import com.tangem.datasource.local.preferences.PreferencesKeys
+import com.tangem.datasource.local.preferences.PreferencesKeys.getIsFirstTimeAskingPermission
+import com.tangem.datasource.local.preferences.PreferencesKeys.getPermissionDaysCount
+import com.tangem.datasource.local.preferences.PreferencesKeys.getPermissionLaunchCount
+import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowInitialPermissionScreen
+import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowPermission
+import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
+import com.tangem.datasource.local.preferences.utils.store
+import com.tangem.domain.settings.repositories.PermissionRepository
+
+internal class DefaultPermissionRepository(
+ private val appPreferencesStore: AppPreferencesStore,
+) : PermissionRepository {
+
+ override suspend fun shouldInitiallyShowPermissionScreen(permission: String): Boolean {
+ val key = getShouldShowInitialPermissionScreen(permission)
+ val initialPermissionScreen = appPreferencesStore.getSyncOrDefault(key = key, default = true)
+ if (initialPermissionScreen) appPreferencesStore.store(key = key, value = false)
+ return initialPermissionScreen
+ }
+
+ override suspend fun isFirstTimeAskingPermission(permission: String): Boolean =
+ appPreferencesStore.getSyncOrDefault(
+ key = getIsFirstTimeAskingPermission(permission),
+ default = true,
+ )
+
+ override suspend fun setFirstTimeAskingPermission(permission: String, value: Boolean) {
+ appPreferencesStore.store(
+ key = getIsFirstTimeAskingPermission(permission),
+ value = value,
+ )
+ }
+
+ override suspend fun shouldAskPermission(permission: String): Boolean {
+ val shouldAskPermission = appPreferencesStore.getSyncOrDefault(getShouldShowPermission(permission), true)
+ val delayedLaunches = appPreferencesStore.getSyncOrDefault(getPermissionLaunchCount(permission), 0)
+ val delayedDays = appPreferencesStore.getSyncOrDefault(getPermissionDaysCount(permission), 0)
+ val currentLaunchCounter = appPreferencesStore.getSyncOrDefault(PreferencesKeys.APP_LAUNCH_COUNT_KEY, 0)
+
+ val nowMillis = SystemClock.elapsedRealtime()
+ val isDaysDelayed = delayedDays < nowMillis
+ val isLaunchesDelayed = delayedLaunches < currentLaunchCounter
+ return shouldAskPermission && isDaysDelayed && isLaunchesDelayed
+ }
+
+ override suspend fun neverAskPermission(permission: String) {
+ appPreferencesStore.store(key = getShouldShowPermission(permission), value = false)
+ }
+
+ override suspend fun delayPermissionAsking(permission: String) {
+ appPreferencesStore.editData {
+ val appLaunchCounter = it.getOrDefault(PreferencesKeys.APP_LAUNCH_COUNT_KEY, 0)
+ val nowMillis = SystemClock.elapsedRealtime()
+
+ it[getPermissionLaunchCount(permission)] = appLaunchCounter + DELAY_LAUNCH_COUNT
+ it[getPermissionDaysCount(permission)] = nowMillis + DELAY_DAYS_COUNT
+ }
+ }
+
+ private companion object {
+ const val DELAY_LAUNCH_COUNT = 5
+ const val DELAY_DAYS_COUNT = 3L * 24 * 3600 * 1000 // 3 days in millis
+ }
+}
\ No newline at end of file
diff --git a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt
index 4f6bf8e17b..da47f1f604 100644
--- a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt
+++ b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt
@@ -1,12 +1,14 @@
package com.tangem.data.settings.di
import com.tangem.data.settings.DefaultAppRatingRepository
-import com.tangem.data.settings.DefaultSettingsRepository
import com.tangem.data.settings.DefaultPromoSettingsRepository
+import com.tangem.data.settings.DefaultPermissionRepository
+import com.tangem.data.settings.DefaultSettingsRepository
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.settings.repositories.AppRatingRepository
-import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository
+import com.tangem.domain.settings.repositories.PermissionRepository
+import com.tangem.domain.settings.repositories.SettingsRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -34,4 +36,10 @@ internal object SettingsDataModule {
fun providePromoSettingsSettingsRepository(appPreferencesStore: AppPreferencesStore): PromoSettingsRepository {
return DefaultPromoSettingsRepository(appPreferencesStore = appPreferencesStore)
}
+
+ @Provides
+ @Singleton
+ fun providePushPermissionRepository(appPreferencesStore: AppPreferencesStore): PermissionRepository {
+ return DefaultPermissionRepository(appPreferencesStore = appPreferencesStore)
+ }
}
\ No newline at end of file
diff --git a/data/staking/build.gradle.kts b/data/staking/build.gradle.kts
index 83a8cccc87..186d53da6e 100644
--- a/data/staking/build.gradle.kts
+++ b/data/staking/build.gradle.kts
@@ -14,8 +14,8 @@ dependencies {
implementation(projects.core.datasource)
implementation(projects.core.utils)
+ implementation(projects.domain.tokens.models)
implementation(projects.domain.staking)
- implementation(projects.features.staking.api)
// region DI
@@ -29,6 +29,7 @@ dependencies {
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
+ implementation(projects.libs.blockchainSdk)
implementation(deps.tangem.blockchain) {
exclude(module = "joda-time")
}
diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt
index a60a8c5141..f0e5a33941 100644
--- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt
+++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt
@@ -1,29 +1,61 @@
package com.tangem.data.staking
import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchainsdk.utils.toCoinId
+import com.tangem.data.staking.converters.StakingNetworkTypeConverter
+import com.tangem.data.staking.converters.TokenConverter
+import com.tangem.data.staking.converters.YieldConverter
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.stakekit.StakeKitApi
+import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
+import com.tangem.domain.staking.model.Yield
import com.tangem.domain.staking.repositories.StakingRepository
-import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
+import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
internal class DefaultStakingRepository(
private val stakeKitApi: StakeKitApi,
- private val stakingFeatureToggles: StakingFeatureToggles,
+ private val stakingYieldsStore: StakingYieldsStore,
private val dispatchers: CoroutineDispatcherProvider,
) : StakingRepository {
- override fun getStakingAvailability(blockchainId: String): StakingAvailability {
- if (!stakingFeatureToggles.isStakingEnabled) {
- return StakingAvailability.Unavailable
- }
+ private val stakingNetworkTypeConverter = StakingNetworkTypeConverter()
- return integrationIdMap[Blockchain.fromId(blockchainId)]?.let {
- StakingAvailability.Available(it)
- } ?: StakingAvailability.Unavailable
+ private val tokenConverter = TokenConverter(
+ stakingNetworkTypeConverter = stakingNetworkTypeConverter,
+ )
+ private val yieldConverter = YieldConverter(
+ tokenConverter = tokenConverter,
+ )
+
+ override fun isStakingSupported(currencyId: String): Boolean {
+ return integrationIds.contains(currencyId)
+ }
+
+ override suspend fun fetchEnabledYields() {
+ withContext(dispatchers.io) {
+ val stakingTokensWithYields = stakeKitApi.getMultipleYields().getOrThrow()
+
+ stakingYieldsStore.store(stakingTokensWithYields.data)
+ }
+ }
+
+ override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield {
+ return withContext(dispatchers.io) {
+ val yields = getEnabledYields() ?: error("No yields found")
+ val rawCurrencyId = cryptoCurrencyId.rawCurrencyId ?: error("Staking custom tokens is not available")
+
+ val prefetchedYield = findPrefetchedYield(
+ yields = yields,
+ currencyId = rawCurrencyId,
+ symbol = symbol,
+ )
+
+ prefetchedYield ?: error("Staking is unavailable")
+ }
}
override suspend fun getEntryInfo(integrationId: String): StakingEntryInfo {
@@ -38,31 +70,53 @@ internal class DefaultStakingRepository(
}
}
- companion object {
- private const val SOLANA_INTEGRATION_ID = "solana-sol-native-multivalidator-staking"
- private const val COSMOS_INTEGRATION_ID = "cosmos-atom-native-staking"
- private const val POLKADOT_INTEGRATION_ID = "polkadot-dot-validator-staking"
- private const val ETHEREUM_INTEGRATION_ID = "ethereum-matic-native-staking"
- private const val AVALANCHE_INTEGRATION_ID = "avalanche-avax-native-staking"
- private const val TRON_INTEGRATION_ID = "tron-trx-native-staking"
- private const val CRONOS_INTEGRATION_ID = "cronos-cro-native-staking"
- private const val BINANCE_INTEGRATION_ID = "binance-bnb-native-staking"
- private const val KAVA_INTEGRATION_ID = "kava-kava-native-staking"
- private const val NEAR_INTEGRATION_ID = "near-near-native-staking"
- private const val TEZOS_INTEGRATION_ID = "tezos-xtz-native-staking"
+ override suspend fun getStakingAvailabilityForActions(
+ cryptoCurrencyId: CryptoCurrency.ID,
+ symbol: String,
+ ): StakingAvailability {
+ val rawCurrencyId = cryptoCurrencyId.rawCurrencyId ?: return StakingAvailability.Unavailable
- private val integrationIdMap = mapOf(
- Blockchain.Solana to SOLANA_INTEGRATION_ID,
- Blockchain.Cosmos to COSMOS_INTEGRATION_ID,
- Blockchain.Polkadot to POLKADOT_INTEGRATION_ID,
- Blockchain.Polygon to ETHEREUM_INTEGRATION_ID,
- Blockchain.Avalanche to AVALANCHE_INTEGRATION_ID,
- Blockchain.Tron to TRON_INTEGRATION_ID,
- Blockchain.Cronos to CRONOS_INTEGRATION_ID,
- Blockchain.Binance to BINANCE_INTEGRATION_ID,
- Blockchain.Kava to KAVA_INTEGRATION_ID,
- Blockchain.Near to NEAR_INTEGRATION_ID,
- Blockchain.Tezos to TEZOS_INTEGRATION_ID,
+ return withContext(dispatchers.io) {
+ val yields = getEnabledYields() ?: return@withContext StakingAvailability.Unavailable
+
+ val prefetchedYield = findPrefetchedYield(yields, rawCurrencyId, symbol)
+ val isSupported = isStakingSupported(rawCurrencyId)
+
+ when {
+ prefetchedYield != null && isSupported -> {
+ StakingAvailability.Available(prefetchedYield.id)
+ }
+ prefetchedYield == null && isSupported -> {
+ StakingAvailability.TemporaryDisabled
+ }
+ else -> StakingAvailability.Unavailable
+ }
+ }
+ }
+
+ private fun findPrefetchedYield(yields: List, currencyId: String, symbol: String): Yield? {
+ return yields
+ .find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol }
+ }
+
+ private suspend fun getEnabledYields(): List? {
+ val yields = stakingYieldsStore.getSyncOrNull() ?: return null
+ return yields.map { yieldConverter.convert(it) }
+ }
+
+ companion object {
+ private val integrationIds = setOf(
+ Blockchain.Solana.toCoinId(),
+ Blockchain.Cosmos.toCoinId(),
+ Blockchain.Polkadot.toCoinId(),
+ Blockchain.Polygon.toCoinId(),
+ Blockchain.Avalanche.toCoinId(),
+ Blockchain.Tron.toCoinId(),
+ Blockchain.Cronos.toCoinId(),
+ Blockchain.Binance.toCoinId(),
+ Blockchain.Kava.toCoinId(),
+ Blockchain.Near.toCoinId(),
+ Blockchain.Tezos.toCoinId(),
)
}
}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/StakingNetworkTypeConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/StakingNetworkTypeConverter.kt
new file mode 100644
index 0000000000..f7cd02c340
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/StakingNetworkTypeConverter.kt
@@ -0,0 +1,81 @@
+package com.tangem.data.staking.converters
+
+import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
+import com.tangem.domain.staking.model.NetworkType
+import com.tangem.utils.converter.Converter
+
+@Suppress("CyclomaticComplexMethod", "LongMethod")
+class StakingNetworkTypeConverter : Converter {
+
+ override fun convert(value: NetworkTypeDTO): NetworkType {
+ return when (value) {
+ NetworkTypeDTO.AVALANCHE_C -> NetworkType.AVALANCHE_C
+ NetworkTypeDTO.AVALANCHE_ATOMIC -> NetworkType.AVALANCHE_ATOMIC
+ NetworkTypeDTO.AVALANCHE_P -> NetworkType.AVALANCHE_P
+ NetworkTypeDTO.ARBITRUM -> NetworkType.ARBITRUM
+ NetworkTypeDTO.BINANCE -> NetworkType.BINANCE
+ NetworkTypeDTO.CELO -> NetworkType.CELO
+ NetworkTypeDTO.ETHEREUM -> NetworkType.ETHEREUM
+ NetworkTypeDTO.ETHEREUM_GOERLI -> NetworkType.ETHEREUM_GOERLI
+ NetworkTypeDTO.ETHEREUM_HOLESKY -> NetworkType.ETHEREUM_HOLESKY
+ NetworkTypeDTO.FANTOM -> NetworkType.FANTOM
+ NetworkTypeDTO.HARMONY -> NetworkType.HARMONY
+ NetworkTypeDTO.OPTIMISM -> NetworkType.OPTIMISM
+ NetworkTypeDTO.POLYGON -> NetworkType.POLYGON
+ NetworkTypeDTO.GNOSIS -> NetworkType.GNOSIS
+ NetworkTypeDTO.MOONRIVER -> NetworkType.MOONRIVER
+ NetworkTypeDTO.OKC -> NetworkType.OKC
+ NetworkTypeDTO.ZKSYNC -> NetworkType.ZKSYNC
+ NetworkTypeDTO.VICTION -> NetworkType.VICTION
+ NetworkTypeDTO.AGORIC -> NetworkType.AGORIC
+ NetworkTypeDTO.AKASH -> NetworkType.AKASH
+ NetworkTypeDTO.AXELAR -> NetworkType.AXELAR
+ NetworkTypeDTO.BAND_PROTOCOL -> NetworkType.BAND_PROTOCOL
+ NetworkTypeDTO.BITSONG -> NetworkType.BITSONG
+ NetworkTypeDTO.CANTO -> NetworkType.CANTO
+ NetworkTypeDTO.CHIHUAHUA -> NetworkType.CHIHUAHUA
+ NetworkTypeDTO.COMDEX -> NetworkType.COMDEX
+ NetworkTypeDTO.COREUM -> NetworkType.COREUM
+ NetworkTypeDTO.COSMOS -> NetworkType.COSMOS
+ NetworkTypeDTO.CRESCENT -> NetworkType.CRESCENT
+ NetworkTypeDTO.CRONOS -> NetworkType.CRONOS
+ NetworkTypeDTO.CUDOS -> NetworkType.CUDOS
+ NetworkTypeDTO.DESMOS -> NetworkType.DESMOS
+ NetworkTypeDTO.DYDX -> NetworkType.DYDX
+ NetworkTypeDTO.EVMOS -> NetworkType.EVMOS
+ NetworkTypeDTO.FETCH_AI -> NetworkType.FETCH_AI
+ NetworkTypeDTO.GRAVITY_BRIDGE -> NetworkType.GRAVITY_BRIDGE
+ NetworkTypeDTO.INJECTIVE -> NetworkType.INJECTIVE
+ NetworkTypeDTO.IRISNET -> NetworkType.IRISNET
+ NetworkTypeDTO.JUNO -> NetworkType.JUNO
+ NetworkTypeDTO.KAVA -> NetworkType.KAVA
+ NetworkTypeDTO.KI_NETWORK -> NetworkType.KI_NETWORK
+ NetworkTypeDTO.MARS_PROTOCOL -> NetworkType.MARS_PROTOCOL
+ NetworkTypeDTO.NYM -> NetworkType.NYM
+ NetworkTypeDTO.OKEX_CHAIN -> NetworkType.OKEX_CHAIN
+ NetworkTypeDTO.ONOMY -> NetworkType.ONOMY
+ NetworkTypeDTO.OSMOSIS -> NetworkType.OSMOSIS
+ NetworkTypeDTO.PERSISTENCE -> NetworkType.PERSISTENCE
+ NetworkTypeDTO.QUICKSILVER -> NetworkType.QUICKSILVER
+ NetworkTypeDTO.REGEN -> NetworkType.REGEN
+ NetworkTypeDTO.SECRET -> NetworkType.SECRET
+ NetworkTypeDTO.SENTINEL -> NetworkType.SENTINEL
+ NetworkTypeDTO.SOMMELIER -> NetworkType.SOMMELIER
+ NetworkTypeDTO.STAFI -> NetworkType.STAFI
+ NetworkTypeDTO.STARGAZE -> NetworkType.STARGAZE
+ NetworkTypeDTO.STRIDE -> NetworkType.STRIDE
+ NetworkTypeDTO.TERITORI -> NetworkType.TERITORI
+ NetworkTypeDTO.TGRADE -> NetworkType.TGRADE
+ NetworkTypeDTO.UMEE -> NetworkType.UMEE
+ NetworkTypeDTO.POLKADOT -> NetworkType.POLKADOT
+ NetworkTypeDTO.KUSAMA -> NetworkType.KUSAMA
+ NetworkTypeDTO.WESTEND -> NetworkType.WESTEND
+ NetworkTypeDTO.BINANCEBEACON -> NetworkType.BINANCEBEACON
+ NetworkTypeDTO.NEAR -> NetworkType.NEAR
+ NetworkTypeDTO.SOLANA -> NetworkType.SOLANA
+ NetworkTypeDTO.TEZOS -> NetworkType.TEZOS
+ NetworkTypeDTO.TRON -> NetworkType.TRON
+ else -> NetworkType.UNKNOWN
+ }
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/StakingTokenConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/StakingTokenConverter.kt
new file mode 100644
index 0000000000..06cbb55869
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/StakingTokenConverter.kt
@@ -0,0 +1,22 @@
+package com.tangem.data.staking.converters
+
+import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYieldDTO
+import com.tangem.domain.staking.model.StakingToken
+import com.tangem.domain.staking.model.StakingTokenWithYield
+import com.tangem.utils.converter.Converter
+
+class StakingTokenConverter : Converter {
+
+ override fun convert(value: TokenWithYieldDTO): StakingTokenWithYield {
+ return StakingTokenWithYield(
+ token = StakingToken(
+ name = value.token.name,
+ symbol = value.token.symbol,
+ decimals = value.token.decimals,
+ contractAddress = value.token.address,
+ coinGeckoId = value.token.coinGeckoId,
+ ),
+ availableYieldIds = value.availableYieldIds,
+ )
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/TokenConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/TokenConverter.kt
new file mode 100644
index 0000000000..480d4574bc
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/TokenConverter.kt
@@ -0,0 +1,23 @@
+package com.tangem.data.staking.converters
+
+import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
+import com.tangem.domain.staking.model.Token
+import com.tangem.utils.converter.Converter
+
+class TokenConverter(
+ private val stakingNetworkTypeConverter: StakingNetworkTypeConverter,
+) : Converter {
+
+ override fun convert(value: TokenDTO): Token {
+ return Token(
+ name = value.name,
+ network = stakingNetworkTypeConverter.convert(value.network),
+ symbol = value.symbol,
+ decimals = value.decimals,
+ address = value.address,
+ coinGeckoId = value.coinGeckoId,
+ logoURI = value.logoURI,
+ isPoints = value.isPoints,
+ )
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt
new file mode 100644
index 0000000000..8dd5a5536f
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt
@@ -0,0 +1,121 @@
+package com.tangem.data.staking.converters
+
+import com.tangem.datasource.api.stakekit.models.response.model.AddressArgumentDTO
+import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
+import com.tangem.domain.staking.model.*
+import com.tangem.utils.converter.Converter
+
+class YieldConverter(
+ private val tokenConverter: TokenConverter,
+) : Converter {
+
+ override fun convert(value: YieldDTO): Yield {
+ return Yield(
+ id = value.id,
+ token = tokenConverter.convert(value.token),
+ tokens = value.tokens.map { tokenConverter.convert(it) },
+ args = convertArgs(value.args),
+ status = convertStatus(value.status),
+ apy = value.apy,
+ rewardRate = value.rewardRate,
+ rewardType = convertRewardType(value.rewardType),
+ metadata = convertMetadata(value.metadata),
+ validators = value.validators.map { convertValidator(it) },
+ isAvailable = value.isAvailable,
+ )
+ }
+
+ private fun convertArgs(argsDTO: YieldDTO.ArgsDTO): Yield.Args {
+ return Yield.Args(
+ enter = convertEnter(argsDTO.enter),
+ exit = argsDTO.exit?.let { convertEnter(it) },
+ )
+ }
+
+ private fun convertEnter(enterDTO: YieldDTO.ArgsDTO.Enter): Yield.Args.Enter {
+ return Yield.Args.Enter(
+ addresses = convertAddresses(enterDTO.addresses),
+ args = enterDTO.args.mapValues { convertAddressArgument(it.value) },
+ )
+ }
+
+ private fun convertAddresses(addressesDTO: YieldDTO.ArgsDTO.Enter.Addresses): Yield.Args.Enter.Addresses {
+ return Yield.Args.Enter.Addresses(
+ address = convertAddressArgument(addressesDTO.address),
+ additionalAddresses = addressesDTO.additionalAddresses?.mapValues { convertAddressArgument(it.value) },
+ )
+ }
+
+ private fun convertAddressArgument(addressArgumentDTO: AddressArgumentDTO): AddressArgument {
+ return AddressArgument(
+ required = addressArgumentDTO.required,
+ network = addressArgumentDTO.network,
+ minimum = addressArgumentDTO.minimum,
+ maximum = addressArgumentDTO.maximum,
+ )
+ }
+
+ private fun convertStatus(statusDTO: YieldDTO.StatusDTO): Yield.Status {
+ return Yield.Status(
+ enter = statusDTO.enter,
+ exit = statusDTO.exit,
+ )
+ }
+
+ private fun convertMetadata(metadataDTO: YieldDTO.MetadataDTO): Yield.Metadata {
+ return Yield.Metadata(
+ name = metadataDTO.name,
+ logoUri = metadataDTO.logoUri,
+ description = metadataDTO.description,
+ documentation = metadataDTO.documentation,
+ gasFeeToken = tokenConverter.convert(metadataDTO.gasFeeTokenDTO),
+ token = tokenConverter.convert(metadataDTO.tokenDTO),
+ tokens = metadataDTO.tokensDTO.map { tokenConverter.convert(it) },
+ type = metadataDTO.type,
+ rewardSchedule = metadataDTO.rewardSchedule,
+ cooldownPeriod = convertPeriod(metadataDTO.cooldownPeriod),
+ warmupPeriod = convertPeriod(metadataDTO.warmupPeriod),
+ rewardClaiming = metadataDTO.rewardClaiming,
+ defaultValidator = metadataDTO.defaultValidator,
+ minimumStake = metadataDTO.minimumStake,
+ supportsMultipleValidators = metadataDTO.supportsMultipleValidators,
+ revshare = convertEnabled(metadataDTO.revshare),
+ fee = convertEnabled(metadataDTO.fee),
+ )
+ }
+
+ private fun convertPeriod(periodDTO: YieldDTO.MetadataDTO.PeriodDTO): Yield.Metadata.Period {
+ return Yield.Metadata.Period(
+ days = periodDTO.days,
+ )
+ }
+
+ private fun convertEnabled(enabledDTO: YieldDTO.MetadataDTO.EnabledDTO): Yield.Metadata.Enabled {
+ return Yield.Metadata.Enabled(
+ enabled = enabledDTO.enabled,
+ )
+ }
+
+ private fun convertValidator(validatorDTO: YieldDTO.ValidatorDTO): Yield.Validator {
+ return Yield.Validator(
+ address = validatorDTO.address,
+ status = validatorDTO.status,
+ name = validatorDTO.name,
+ image = validatorDTO.image,
+ website = validatorDTO.website,
+ apr = validatorDTO.apr,
+ commission = validatorDTO.commission,
+ stakedBalance = validatorDTO.stakedBalance,
+ votingPower = validatorDTO.votingPower,
+ preferred = validatorDTO.preferred,
+ )
+ }
+
+ private fun convertRewardType(rewardTypeDTO: YieldDTO.RewardTypeDTO): Yield.RewardType {
+ return when (rewardTypeDTO) {
+ YieldDTO.RewardTypeDTO.APY -> Yield.RewardType.APY
+ YieldDTO.RewardTypeDTO.APR -> Yield.RewardType.APR
+ else -> Yield.RewardType.UNKNOWN
+ }
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt
new file mode 100644
index 0000000000..360ebbbbf6
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt
@@ -0,0 +1,19 @@
+package com.tangem.data.staking.converters.action
+
+import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
+import com.tangem.domain.staking.model.action.StakingActionStatus
+import com.tangem.utils.converter.Converter
+
+class ActionStatusConverter : Converter {
+ override fun convert(value: StakingActionStatusDTO): StakingActionStatus {
+ return when (value) {
+ StakingActionStatusDTO.CANCELED -> StakingActionStatus.CANCELED
+ StakingActionStatusDTO.CREATED -> StakingActionStatus.CREATED
+ StakingActionStatusDTO.WAITING_FOR_NEXT -> StakingActionStatus.WAITING_FOR_NEXT
+ StakingActionStatusDTO.PROCESSING -> StakingActionStatus.PROCESSING
+ StakingActionStatusDTO.FAILED -> StakingActionStatus.FAILED
+ StakingActionStatusDTO.SUCCESS -> StakingActionStatus.SUCCESS
+ else -> StakingActionStatus.UNKNOWN
+ }
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt
new file mode 100644
index 0000000000..888d805305
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt
@@ -0,0 +1,28 @@
+package com.tangem.data.staking.converters.action
+
+import com.tangem.data.staking.converters.transaction.StakingTransactionConverter
+import com.tangem.datasource.api.stakekit.models.response.EnterActionResponse
+import com.tangem.domain.staking.model.action.EnterAction
+import com.tangem.utils.converter.Converter
+
+class EnterActionResponseConverter(
+ private val actionStatusConverter: ActionStatusConverter,
+ private val stakingActionTypeConverter: StakingActionTypeConverter,
+ private val transactionConverter: StakingTransactionConverter,
+) : Converter {
+
+ override fun convert(value: EnterActionResponse): EnterAction {
+ return EnterAction(
+ id = value.id,
+ integrationId = value.integrationId,
+ status = actionStatusConverter.convert(value.status),
+ type = stakingActionTypeConverter.convert(value.type),
+ currentStepIndex = value.currentStepIndex,
+ amount = value.amount,
+ validatorAddress = value.validatorAddress,
+ validatorAddresses = value.validatorAddresses,
+ transactions = value.transactions?.map(transactionConverter::convert),
+ createdAt = value.createdAt,
+ )
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/action/StakingActionTypeConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/action/StakingActionTypeConverter.kt
new file mode 100644
index 0000000000..941020c57d
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/action/StakingActionTypeConverter.kt
@@ -0,0 +1,30 @@
+package com.tangem.data.staking.converters.action
+
+import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
+import com.tangem.domain.staking.model.action.StakingActionType
+import com.tangem.utils.converter.Converter
+
+@Suppress("CyclomaticComplexMethod")
+class StakingActionTypeConverter : Converter {
+
+ override fun convert(value: StakingActionTypeDTO): StakingActionType {
+ return when (value) {
+ StakingActionTypeDTO.STAKE -> StakingActionType.STAKE
+ StakingActionTypeDTO.UNSTAKE -> StakingActionType.UNSTAKE
+ StakingActionTypeDTO.CLAIM_REWARDS -> StakingActionType.CLAIM_REWARDS
+ StakingActionTypeDTO.RESTAKE_REWARDS -> StakingActionType.RESTAKE_REWARDS
+ StakingActionTypeDTO.WITHDRAW -> StakingActionType.WITHDRAW
+ StakingActionTypeDTO.RESTAKE -> StakingActionType.RESTAKE
+ StakingActionTypeDTO.CLAIM_UNSTAKED -> StakingActionType.CLAIM_UNSTAKED
+ StakingActionTypeDTO.UNLOCK_LOCKED -> StakingActionType.UNLOCK_LOCKED
+ StakingActionTypeDTO.STAKE_LOCKED -> StakingActionType.STAKE_LOCKED
+ StakingActionTypeDTO.VOTE -> StakingActionType.VOTE
+ StakingActionTypeDTO.REVOKE -> StakingActionType.REVOKE
+ StakingActionTypeDTO.VOTE_LOCKED -> StakingActionType.VOTE_LOCKED
+ StakingActionTypeDTO.REVOTE -> StakingActionType.REVOTE
+ StakingActionTypeDTO.REBOND -> StakingActionType.REBOND
+ StakingActionTypeDTO.MIGRATE -> StakingActionType.MIGRATE
+ StakingActionTypeDTO.UNKNOWN -> StakingActionType.UNKNOWN
+ }
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/GasEstimateConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/GasEstimateConverter.kt
new file mode 100644
index 0000000000..3219d87359
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/GasEstimateConverter.kt
@@ -0,0 +1,19 @@
+package com.tangem.data.staking.converters.transaction
+
+import com.tangem.data.staking.converters.TokenConverter
+import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingGasEstimateDTO
+import com.tangem.domain.staking.model.transaction.StakingGasEstimate
+import com.tangem.utils.converter.Converter
+
+class GasEstimateConverter(
+ private val tokenConverter: TokenConverter,
+) : Converter {
+
+ override fun convert(value: StakingGasEstimateDTO): StakingGasEstimate {
+ return StakingGasEstimate(
+ amount = value.amount,
+ token = tokenConverter.convert(value.token),
+ gasLimit = value.gasLimit,
+ )
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/StakingTransactionConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/StakingTransactionConverter.kt
new file mode 100644
index 0000000000..1ad9ca3198
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/StakingTransactionConverter.kt
@@ -0,0 +1,33 @@
+package com.tangem.data.staking.converters.transaction
+
+import com.tangem.data.staking.converters.StakingNetworkTypeConverter
+import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionDTO
+import com.tangem.domain.staking.model.transaction.StakingTransaction
+import com.tangem.utils.converter.Converter
+
+class StakingTransactionConverter(
+ private val networkTypeConverter: StakingNetworkTypeConverter,
+ private val transactionStatusConverter: StakingTransactionStatusConverter,
+ private val transactionTypeConverter: StakingTransactionTypeConverter,
+ private val gasEstimateConverter: GasEstimateConverter,
+) : Converter {
+
+ override fun convert(value: StakingTransactionDTO): StakingTransaction {
+ return StakingTransaction(
+ id = value.id,
+ network = networkTypeConverter.convert(value.network),
+ status = transactionStatusConverter.convert(value.status),
+ type = transactionTypeConverter.convert(value.type),
+ hash = value.hash,
+ signedTransaction = value.signedTransaction,
+ unsignedTransaction = value.unsignedTransaction,
+ stepIndex = value.stepIndex,
+ error = value.error,
+ gasEstimate = value.gasEstimate?.let { gasEstimateConverter.convert(it) },
+ stakeId = value.stakeId,
+ explorerUrl = value.explorerUrl,
+ ledgerHwAppId = value.ledgerHwAppId,
+ isMessage = value.isMessage,
+ )
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/StakingTransactionStatusConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/StakingTransactionStatusConverter.kt
new file mode 100644
index 0000000000..9569f61534
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/StakingTransactionStatusConverter.kt
@@ -0,0 +1,24 @@
+package com.tangem.data.staking.converters.transaction
+
+import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionStatusDTO
+import com.tangem.domain.staking.model.transaction.StakingTransactionStatus
+import com.tangem.utils.converter.Converter
+
+class StakingTransactionStatusConverter : Converter {
+
+ override fun convert(value: StakingTransactionStatusDTO): StakingTransactionStatus {
+ return when (value) {
+ StakingTransactionStatusDTO.NOT_FOUND -> StakingTransactionStatus.NOT_FOUND
+ StakingTransactionStatusDTO.CREATED -> StakingTransactionStatus.CREATED
+ StakingTransactionStatusDTO.BLOCKED -> StakingTransactionStatus.BLOCKED
+ StakingTransactionStatusDTO.WAITING_FOR_SIGNATURE -> StakingTransactionStatus.WAITING_FOR_SIGNATURE
+ StakingTransactionStatusDTO.SIGNED -> StakingTransactionStatus.SIGNED
+ StakingTransactionStatusDTO.BROADCASTED -> StakingTransactionStatus.BROADCASTED
+ StakingTransactionStatusDTO.PENDING -> StakingTransactionStatus.PENDING
+ StakingTransactionStatusDTO.CONFIRMED -> StakingTransactionStatus.CONFIRMED
+ StakingTransactionStatusDTO.FAILED -> StakingTransactionStatus.FAILED
+ StakingTransactionStatusDTO.SKIPPED -> StakingTransactionStatus.SKIPPED
+ StakingTransactionStatusDTO.UNKNOWN -> StakingTransactionStatus.UNKNOWN
+ }
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/StakingTransactionTypeConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/StakingTransactionTypeConverter.kt
new file mode 100644
index 0000000000..15e156f691
--- /dev/null
+++ b/data/staking/src/main/java/com/tangem/data/staking/converters/transaction/StakingTransactionTypeConverter.kt
@@ -0,0 +1,54 @@
+package com.tangem.data.staking.converters.transaction
+
+import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionTypeDTO
+import com.tangem.domain.staking.model.transaction.StakingTransactionType
+import com.tangem.utils.converter.Converter
+
+@Suppress("CyclomaticComplexMethod")
+class StakingTransactionTypeConverter : Converter {
+
+ override fun convert(value: StakingTransactionTypeDTO): StakingTransactionType {
+ return when (value) {
+ StakingTransactionTypeDTO.SWAP -> StakingTransactionType.SWAP
+ StakingTransactionTypeDTO.DEPOSIT -> StakingTransactionType.DEPOSIT
+ StakingTransactionTypeDTO.APPROVAL -> StakingTransactionType.APPROVAL
+ StakingTransactionTypeDTO.STAKE -> StakingTransactionType.STAKE
+ StakingTransactionTypeDTO.CLAIM_UNSTAKED -> StakingTransactionType.CLAIM_UNSTAKED
+ StakingTransactionTypeDTO.CLAIM_REWARDS -> StakingTransactionType.CLAIM_REWARDS
+ StakingTransactionTypeDTO.RESTAKE_REWARDS -> StakingTransactionType.RESTAKE_REWARDS
+ StakingTransactionTypeDTO.UNSTAKE -> StakingTransactionType.UNSTAKE
+ StakingTransactionTypeDTO.SPLIT -> StakingTransactionType.SPLIT
+ StakingTransactionTypeDTO.MERGE -> StakingTransactionType.MERGE
+ StakingTransactionTypeDTO.LOCK -> StakingTransactionType.LOCK
+ StakingTransactionTypeDTO.UNLOCK -> StakingTransactionType.UNLOCK
+ StakingTransactionTypeDTO.SUPPLY -> StakingTransactionType.SUPPLY
+ StakingTransactionTypeDTO.BRIDGE -> StakingTransactionType.BRIDGE
+ StakingTransactionTypeDTO.VOTE -> StakingTransactionType.VOTE
+ StakingTransactionTypeDTO.REVOKE -> StakingTransactionType.REVOKE
+ StakingTransactionTypeDTO.RESTAKE -> StakingTransactionType.RESTAKE
+ StakingTransactionTypeDTO.REBOND -> StakingTransactionType.REBOND
+ StakingTransactionTypeDTO.WITHDRAW -> StakingTransactionType.WITHDRAW
+ StakingTransactionTypeDTO.CREATE_ACCOUNT -> StakingTransactionType.CREATE_ACCOUNT
+ StakingTransactionTypeDTO.REVEAL -> StakingTransactionType.REVEAL
+ StakingTransactionTypeDTO.MIGRATE -> StakingTransactionType.MIGRATE
+ StakingTransactionTypeDTO.UTXO_P_TO_C_IMPORT -> StakingTransactionType.UTXO_P_TO_C_IMPORT
+ StakingTransactionTypeDTO.UTXO_C_TO_P_IMPORT -> StakingTransactionType.UTXO_C_TO_P_IMPORT
+ StakingTransactionTypeDTO.UNFREEZE_LEGACY -> StakingTransactionType.UNFREEZE_LEGACY
+ StakingTransactionTypeDTO.UNFREEZE_LEGACY_BANDWIDTH -> StakingTransactionType.UNFREEZE_LEGACY_BANDWIDTH
+ StakingTransactionTypeDTO.UNFREEZE_LEGACY_ENERGY -> StakingTransactionType.UNFREEZE_LEGACY_ENERGY
+ StakingTransactionTypeDTO.UNFREEZE_BANDWIDTH -> StakingTransactionType.UNFREEZE_BANDWIDTH
+ StakingTransactionTypeDTO.UNFREEZE_ENERGY -> StakingTransactionType.UNFREEZE_ENERGY
+ StakingTransactionTypeDTO.FREEZE_BANDWIDTH -> StakingTransactionType.FREEZE_BANDWIDTH
+ StakingTransactionTypeDTO.FREEZE_ENERGY -> StakingTransactionType.FREEZE_ENERGY
+ StakingTransactionTypeDTO.UNDELEGATE_BANDWIDTH -> StakingTransactionType.UNDELEGATE_BANDWIDTH
+ StakingTransactionTypeDTO.UNDELEGATE_ENERGY -> StakingTransactionType.UNDELEGATE_ENERGY
+ StakingTransactionTypeDTO.P2P_NODE_REQUEST -> StakingTransactionType.P2P_NODE_REQUEST
+ StakingTransactionTypeDTO.LUGANODES_PROVISION -> StakingTransactionType.LUGANODES_PROVISION
+ StakingTransactionTypeDTO.LUGANODES_EXIT_REQUEST -> StakingTransactionType.LUGANODES_EXIT_REQUEST
+ StakingTransactionTypeDTO.INFSTONES_PROVISION -> StakingTransactionType.INFSTONES_PROVISION
+ StakingTransactionTypeDTO.INFSTONES_EXIT_REQUEST -> StakingTransactionType.INFSTONES_EXIT_REQUEST
+ StakingTransactionTypeDTO.INFSTONES_CLAIM_REQUEST -> StakingTransactionType.INFSTONES_CLAIM_REQUEST
+ StakingTransactionTypeDTO.UNKNOWN -> StakingTransactionType.UNKNOWN
+ }
+ }
+}
\ No newline at end of file
diff --git a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt
index 0f5076bb01..0b47b925d1 100644
--- a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt
+++ b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt
@@ -2,8 +2,8 @@ package com.tangem.data.staking.di
import com.tangem.data.staking.DefaultStakingRepository
import com.tangem.datasource.api.stakekit.StakeKitApi
+import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.repositories.StakingRepository
-import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@@ -19,13 +19,13 @@ internal object StakingDataModule {
@Singleton
fun provideStakingRepository(
stakeKitApi: StakeKitApi,
- stakingFeatureToggles: StakingFeatureToggles,
- coroutineDispatcherProvider: CoroutineDispatcherProvider,
+ stakingTokenStore: StakingYieldsStore,
+ dispatchers: CoroutineDispatcherProvider,
): StakingRepository {
return DefaultStakingRepository(
stakeKitApi = stakeKitApi,
- stakingFeatureToggles = stakingFeatureToggles,
- dispatchers = coroutineDispatcherProvider,
+ stakingYieldsStore = stakingTokenStore,
+ dispatchers = dispatchers,
)
}
}
\ No newline at end of file
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt
index 062692cd06..5705476197 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt
@@ -7,7 +7,7 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.network.NetworksStatusesStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.quote.QuotesStore
-import com.tangem.datasource.local.token.AssetsStore
+import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.repository.*
@@ -31,7 +31,7 @@ internal object TokensDataModule {
userTokensStore: UserTokensStore,
userWalletsStore: UserWalletsStore,
walletManagersFacade: WalletManagersFacade,
- assetsStore: AssetsStore,
+ expressAssetsStore: ExpressAssetsStore,
cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider,
): CurrenciesRepository {
@@ -41,7 +41,7 @@ internal object TokensDataModule {
userTokensStore = userTokensStore,
walletManagersFacade = walletManagersFacade,
userWalletsStore = userWalletsStore,
- assetsStore = assetsStore,
+ expressAssetsStore = expressAssetsStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
)
@@ -87,8 +87,8 @@ internal object TokensDataModule {
@Provides
@Singleton
- fun provideDefaultMarketCoinsRepository(assetsStore: AssetsStore): MarketCryptoCurrencyRepository {
- return DefaultMarketCryptoCurrencyRepository(assetsStore)
+ fun provideDefaultMarketCoinsRepository(expressAssetsStore: ExpressAssetsStore): MarketCryptoCurrencyRepository {
+ return DefaultMarketCryptoCurrencyRepository(expressAssetsStore)
}
@Provides
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt
index 89bb9aca17..4c9d33c1d9 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt
@@ -15,7 +15,7 @@ import com.tangem.datasource.api.express.models.request.AssetsRequestBody
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
-import com.tangem.datasource.local.token.AssetsStore
+import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.util.derivationStyleProvider
@@ -46,7 +46,7 @@ internal class DefaultCurrenciesRepository(
private val userTokensStore: UserTokensStore,
private val userWalletsStore: UserWalletsStore,
private val walletManagersFacade: WalletManagersFacade,
- private val assetsStore: AssetsStore,
+ private val expressAssetsStore: ExpressAssetsStore,
private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider,
) : CurrenciesRepository {
@@ -61,6 +61,18 @@ internal class DefaultCurrenciesRepository(
private val isMultiCurrencyWalletCurrenciesFetching = MutableStateFlow(
value = emptyMap(),
)
+ private val parallelTransactionsEnabledBlockchains = setOf(
+ Blockchain.Ethereum,
+ Blockchain.EthereumTestnet,
+ Blockchain.Polygon,
+ Blockchain.PolygonTestnet,
+ Blockchain.Arbitrum,
+ Blockchain.ArbitrumTestnet,
+ Blockchain.Binance,
+ Blockchain.BinanceTestnet,
+ Blockchain.Tron,
+ Blockchain.TronTestnet,
+ )
override suspend fun saveTokens(
userWalletId: UserWalletId,
@@ -366,18 +378,19 @@ internal class DefaultCurrenciesRepository(
}
}
- override fun hasPendingTransactions(
+ override fun isSendBlockedByPendingTransactions(
cryptoCurrencyStatus: CryptoCurrencyStatus,
coinStatus: CryptoCurrencyStatus?,
): Boolean {
val blockchain = Blockchain.fromId(cryptoCurrencyStatus.currency.network.id.value)
val isBitcoinBlockchain = blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet
-
- return if (cryptoCurrencyStatus.currency is CryptoCurrency.Coin && isBitcoinBlockchain) {
- val outgoingTransactions = cryptoCurrencyStatus.value.pendingTransactions.filter { it.isOutgoing }
- outgoingTransactions.isNotEmpty()
- } else {
- coinStatus?.value?.hasCurrentNetworkTransactions == true
+ return when {
+ cryptoCurrencyStatus.currency is CryptoCurrency.Coin && isBitcoinBlockchain -> {
+ val outgoingTransactions = cryptoCurrencyStatus.value.pendingTransactions.filter { it.isOutgoing }
+ outgoingTransactions.isNotEmpty()
+ }
+ parallelTransactionsEnabledBlockchains.contains(blockchain) -> false
+ else -> coinStatus?.value?.hasCurrentNetworkTransactions == true
}
}
@@ -493,7 +506,7 @@ internal class DefaultCurrenciesRepository(
),
)
- assetsStore.store(userWalletId, response.getOrThrow())
+ expressAssetsStore.store(userWalletId, response.getOrThrow())
}
} catch (e: Throwable) {
Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}")
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt
index a210d96273..a3c7834ed9 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt
@@ -1,13 +1,13 @@
package com.tangem.data.tokens.repository
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
-import com.tangem.datasource.local.token.AssetsStore
+import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
import com.tangem.domain.wallets.models.UserWalletId
class DefaultMarketCryptoCurrencyRepository(
- private val assetsStore: AssetsStore,
+ private val expressAssetsStore: ExpressAssetsStore,
) : MarketCryptoCurrencyRepository {
override suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
@@ -17,7 +17,7 @@ class DefaultMarketCryptoCurrencyRepository(
private suspend fun getExchangeableFlag(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
val contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE
- return assetsStore.getSyncOrNull(userWalletId)?.find {
+ return expressAssetsStore.getSyncOrNull(userWalletId)?.find {
it.network == cryptoCurrency.network.backendId &&
it.contractAddress.equals(contractAddress, ignoreCase = true)
}?.exchangeAvailable ?: false
diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt
index a817781b53..e558b2def0 100644
--- a/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt
+++ b/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt
@@ -10,10 +10,8 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.data.common.cache.CacheRegistry
-import com.tangem.data.visa.utils.VisaConfig
-import com.tangem.data.visa.utils.VisaCurrencyFactory
-import com.tangem.data.visa.utils.VisaTxDetailsFactory
-import com.tangem.data.visa.utils.VisaTxHistoryPagingSource
+import com.tangem.data.visa.config.VisaLibLoader
+import com.tangem.data.visa.utils.*
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.userwallet.UserWalletsStore
@@ -24,8 +22,6 @@ import com.tangem.domain.visa.model.VisaTxHistoryItem
import com.tangem.domain.visa.repository.VisaRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
-import com.tangem.lib.visa.VisaContractInfoProvider
-import com.tangem.lib.visa.api.VisaApi
import com.tangem.lib.visa.model.VisaTxHistoryResponse
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
@@ -35,9 +31,8 @@ import kotlinx.coroutines.withContext
import java.math.BigDecimal
internal class DefaultVisaRepository(
- private val visaContractInfoProvider: VisaContractInfoProvider,
+ private val visaLibLoader: VisaLibLoader,
private val tangemTechApi: TangemTechApi,
- private val visaApi: VisaApi,
private val cacheRegistry: CacheRegistry,
private val userWalletsStore: UserWalletsStore,
private val dispatchers: CoroutineDispatcherProvider,
@@ -76,9 +71,11 @@ internal class DefaultVisaRepository(
}
private suspend fun fetchVisaCurrency(address: String) {
+ val contractInfoProvider = visaLibLoader.getOrCreateProvider()
+
parZip(
dispatchers.io,
- { visaContractInfoProvider.getContractInfo(address) },
+ { contractInfoProvider.getContractInfo(address) },
{ getFiatRate() },
{ contractInfo, fiatRate ->
fetchedCurrencies.update { value ->
@@ -97,6 +94,7 @@ internal class DefaultVisaRepository(
): Flow> {
val userWallet = findVisaUserWallet(userWalletId)
val cardPubKey = getCardPubKey(userWallet)
+ val api = visaLibLoader.getOrCreateApi()
val pager = Pager(
config = PagingConfig(
pageSize = pageSize,
@@ -110,7 +108,7 @@ internal class DefaultVisaRepository(
isRefresh = isRefresh,
),
cacheRegistry = cacheRegistry,
- visaApi = visaApi,
+ visaApi = api,
fetchedItems = fetchedHistoryItems,
dispatchers = dispatchers,
)
@@ -137,7 +135,7 @@ internal class DefaultVisaRepository(
}
private suspend fun makeAddress(userWalletId: UserWalletId): String {
- if (IS_DEMO_MODE_ENABLED) return DEMO_ADDRESS
+ if (VisaConstants.IS_DEMO_MODE_ENABLED) return getDemoAddress()
val userWallet = findVisaUserWallet(userWalletId)
val walletAddresses = makeWalletAddresses(userWallet)
@@ -149,13 +147,13 @@ internal class DefaultVisaRepository(
}
private suspend fun getFiatRate(): BigDecimal? {
- val fiatCurrencyId = VisaConfig.fiatCurrency.code.lowercase()
+ val fiatCurrencyId = VisaConstants.fiatCurrency.code.lowercase()
val quotes = tangemTechApi.getQuotes(
currencyId = fiatCurrencyId,
- coinIds = VisaConfig.TOKEN_ID,
+ coinIds = VisaConstants.TOKEN_ID,
).getOrThrow()
- return quotes.quotes[VisaConfig.TOKEN_ID]?.price
+ return quotes.quotes[VisaConstants.TOKEN_ID]?.price
}
private fun makeWalletAddresses(userWallet: UserWallet): Set {
@@ -165,7 +163,7 @@ internal class DefaultVisaRepository(
}
private fun getCardPubKey(userWallet: UserWallet): String {
- if (IS_DEMO_MODE_ENABLED) return DEMO_PUBLIC_KEY
+ if (VisaConstants.IS_DEMO_MODE_ENABLED) return getDemoPublicKey()
val cardWallet = userWallet.scanResponse.card.wallets.firstOrNull {
it.curve == EllipticCurve.Secp256k1
@@ -189,12 +187,4 @@ internal class DefaultVisaRepository(
private fun getVisaCurrencyKey(address: String): String {
return "visa_currency_$address"
}
-
- private companion object {
- // Must be `false` in production
- const val IS_DEMO_MODE_ENABLED = false
-
- const val DEMO_ADDRESS = "0x40d8194b7168723ece51fa34d16825c60ba03dfa"
- const val DEMO_PUBLIC_KEY = "02C2BBA0DA1E066EA968C1EB129499F6DEBC5FD82D70D61DCAF691CDB69AF5D8B9"
- }
}
\ No newline at end of file
diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaConfig.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaConfig.kt
new file mode 100644
index 0000000000..8c5dba275a
--- /dev/null
+++ b/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaConfig.kt
@@ -0,0 +1,25 @@
+package com.tangem.data.visa.config
+
+import com.squareup.moshi.Json
+
+internal data class VisaConfig(
+ @Json(name = "testnet")
+ val testnet: Addresses,
+ @Json(name = "mainnet")
+ val mainnet: Addresses,
+ @Json(name = "txHistoryAPIAdditionalHeaders")
+ val header: Header,
+) {
+
+ data class Addresses(
+ @Json(name = "paymentAccountRegistry")
+ val paymentAccountRegistry: String,
+ @Json(name = "bridgeProcessor")
+ val bridgeProcessor: String,
+ )
+
+ data class Header(
+ @Json(name = "x-asn")
+ val xAsn: String,
+ )
+}
\ No newline at end of file
diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt
new file mode 100644
index 0000000000..e5062cf662
--- /dev/null
+++ b/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt
@@ -0,0 +1,86 @@
+package com.tangem.data.visa.config
+
+import com.squareup.moshi.Moshi
+import com.tangem.data.visa.BuildConfig
+import com.tangem.data.visa.utils.VisaConstants
+import com.tangem.datasource.asset.loader.AssetLoader
+import com.tangem.datasource.di.NetworkMoshi
+import com.tangem.lib.visa.VisaContractInfoProvider
+import com.tangem.lib.visa.api.VisaApi
+import com.tangem.lib.visa.api.VisaApiBuilder
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import javax.inject.Inject
+
+internal class VisaLibLoader @Inject constructor(
+ private val assetLoader: AssetLoader,
+ @NetworkMoshi private val moshi: Moshi,
+ private val dispatchers: CoroutineDispatcherProvider,
+) {
+
+ private val createMutex = Mutex()
+
+ private var config: VisaConfig? = null
+
+ private var provider: VisaContractInfoProvider? = null
+ private var api: VisaApi? = null
+
+ suspend fun getOrCreateProvider(): VisaContractInfoProvider = provider ?: createProvider()
+
+ suspend fun getOrCreateApi(): VisaApi = api ?: createApi()
+
+ private suspend fun createProvider(): VisaContractInfoProvider = createMutex.withLock {
+ val config = getOrLoadConfig()
+
+ provider = VisaContractInfoProvider.Builder(
+ useTestnetRpc = VisaConstants.USE_TEST_ENV,
+ bridgeProcessorAddress = if (VisaConstants.USE_TEST_ENV) {
+ config.testnet.bridgeProcessor
+ } else {
+ config.mainnet.bridgeProcessor
+ },
+ paymentAccountRegistryAddress = if (VisaConstants.USE_TEST_ENV) {
+ config.testnet.paymentAccountRegistry
+ } else {
+ config.mainnet.paymentAccountRegistry
+ },
+ isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
+ dispatchers = dispatchers,
+ ).build()
+
+ return requireNotNull(provider) {
+ "Visa provider is not created"
+ }
+ }
+
+ private suspend fun createApi(): VisaApi = createMutex.withLock {
+ val config = getOrLoadConfig()
+
+ api = VisaApiBuilder(
+ useDevApi = VisaConstants.USE_TEST_ENV,
+ isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
+ moshi = moshi,
+ headers = mapOf(
+ X_ASN_HEADER_NAME to config.header.xAsn,
+ ),
+ ).build()
+
+ return requireNotNull(api) {
+ "Visa API is not created"
+ }
+ }
+
+ private suspend fun getOrLoadConfig(): VisaConfig {
+ config = assetLoader.load(VISA_CONFIG_FILE_NAME)
+
+ return requireNotNull(config) {
+ "Visa config is not found"
+ }
+ }
+
+ companion object {
+ private const val VISA_CONFIG_FILE_NAME = "tangem-app-config/visa_config"
+ private const val X_ASN_HEADER_NAME = "x-asn"
+ }
+}
\ No newline at end of file
diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt
index a3022a444a..60d487838e 100644
--- a/data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt
+++ b/data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt
@@ -1,15 +1,11 @@
package com.tangem.data.visa.di
-import com.squareup.moshi.Moshi
import com.tangem.data.common.cache.CacheRegistry
-import com.tangem.data.visa.BuildConfig
import com.tangem.data.visa.DefaultVisaRepository
+import com.tangem.data.visa.config.VisaLibLoader
import com.tangem.datasource.api.tangemTech.TangemTechApi
-import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.visa.repository.VisaRepository
-import com.tangem.lib.visa.VisaContractInfoProvider
-import com.tangem.lib.visa.api.VisaApiBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@@ -25,29 +21,16 @@ internal object ImplementedVisaDataModule {
@Singleton
@ImplementedVisaRepository
fun provideVisaRepository(
- @NetworkMoshi moshi: Moshi,
+ visaLibLoader: VisaLibLoader,
tangemTechApi: TangemTechApi,
cacheRegistry: CacheRegistry,
userWalletsStore: UserWalletsStore,
dispatchers: CoroutineDispatcherProvider,
- ): VisaRepository {
- val contractInfoProvider = VisaContractInfoProvider.Builder(
- isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
- dispatchers = dispatchers,
- ).build()
- val visaApi = VisaApiBuilder(
- useDevApi = true,
- isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
- moshi = moshi,
- ).build()
-
- return DefaultVisaRepository(
- contractInfoProvider,
- tangemTechApi,
- visaApi,
- cacheRegistry,
- userWalletsStore,
- dispatchers,
- )
- }
+ ): VisaRepository = DefaultVisaRepository(
+ visaLibLoader,
+ tangemTechApi,
+ cacheRegistry,
+ userWalletsStore,
+ dispatchers,
+ )
}
\ No newline at end of file
diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt
index b664ad4320..bcc9aeb311 100644
--- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt
+++ b/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt
@@ -7,9 +7,9 @@ import java.util.Currency
internal fun findCurrencyByNumericCode(code: Int): Currency {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Currency.getAvailableCurrencies().firstOrNull { it.numericCode == code }
- ?: Currency.getInstance(VisaConfig.fiatCurrency.code)
+ ?: Currency.getInstance(VisaConstants.fiatCurrency.code)
} else {
Timber.w("Unable to get currency by numeric code on API level ${Build.VERSION.SDK_INT}")
- Currency.getInstance(VisaConfig.fiatCurrency.code)
+ Currency.getInstance(VisaConstants.fiatCurrency.code)
}
}
\ No newline at end of file
diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConfig.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConfig.kt
deleted file mode 100644
index 6b3e3f3f8b..0000000000
--- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConfig.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.tangem.data.visa.utils
-
-import com.tangem.domain.appcurrency.model.AppCurrency
-
-internal object VisaConfig {
-
- const val NETWORK_NAME = "Polygon PoS"
-
- const val TOKEN_ID = "tether"
-
- val fiatCurrency = AppCurrency(
- code = "EUR",
- name = "Euro",
- symbol = "€",
- )
-}
\ No newline at end of file
diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConstants.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConstants.kt
new file mode 100644
index 0000000000..27cd336588
--- /dev/null
+++ b/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConstants.kt
@@ -0,0 +1,44 @@
+package com.tangem.data.visa.utils
+
+import com.tangem.domain.appcurrency.model.AppCurrency
+
+internal object VisaConstants {
+
+ const val NETWORK_NAME = "Polygon PoS"
+
+ const val TOKEN_ID = "tether"
+
+ val fiatCurrency = AppCurrency(
+ code = "EUR",
+ name = "Euro",
+ symbol = "€",
+ )
+
+ /*
+ * Must be `false` in production
+ * Don't forget to change CardTypesResolver.isVisaWallet
+ * */
+ const val IS_DEMO_MODE_ENABLED = false
+
+ const val USE_TEST_ENV = true
+
+ const val DEMO_TESTNET_ADDRESS = "0x51d034eb1563d0d2e66379ef37756d3c14936c44"
+ const val DEMO_TESTNET_PUBLIC_KEY = "03FA1122B809079F79C4E0F657FE11337FEC88C3FB3C6341B2CE2E4F5D9241DD86"
+
+ const val DEMO_MAINNET_ADDRESS = "0x927e3ef2b3d85bacf9e520379f64f6627d323fcd"
+ const val DEMO_MAINNET_PUBLIC_KEY = "02AC61CD57B8011BEE8BB489FB744845CC113AD379132C56015EE70528B6A88E92"
+}
+
+internal fun getDemoAddress(): String {
+ return if (VisaConstants.USE_TEST_ENV) {
+ VisaConstants.DEMO_TESTNET_ADDRESS
+ } else {
+ VisaConstants.DEMO_MAINNET_ADDRESS
+ }
+}
+
+internal fun getDemoPublicKey(): String {
+ return if (VisaConstants.USE_TEST_ENV) {
+ VisaConstants.DEMO_TESTNET_PUBLIC_KEY
+ } else VisaConstants.DEMO_MAINNET_PUBLIC_KEY
+}
\ No newline at end of file
diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt
index 3c9f55e37f..6398dcec84 100644
--- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt
+++ b/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt
@@ -21,10 +21,10 @@ internal class VisaCurrencyFactory {
return VisaCurrency(
symbol = contractInfo.token.symbol,
- networkName = VisaConfig.NETWORK_NAME,
+ networkName = VisaConstants.NETWORK_NAME,
decimals = contractInfo.token.decimals,
fiatRate = fiatRate,
- fiatCurrency = VisaConfig.fiatCurrency,
+ fiatCurrency = VisaConstants.fiatCurrency,
balances = with(contractInfo) {
VisaCurrency.Balances(
total = balances.total,
diff --git a/domain/core/build.gradle.kts b/domain/core/build.gradle.kts
index 63325166f7..46d2670d68 100644
--- a/domain/core/build.gradle.kts
+++ b/domain/core/build.gradle.kts
@@ -1,5 +1,6 @@
plugins {
alias(deps.plugins.kotlin.jvm)
+ alias(deps.plugins.kotlin.serialization)
id("configuration")
}
@@ -7,4 +8,6 @@ dependencies {
api(deps.kotlin.coroutines)
api(deps.arrow.core)
api(deps.arrow.fx)
+
+ implementation(deps.kotlin.serialization)
}
\ No newline at end of file
diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt
index 25029586ad..0812a3d523 100644
--- a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt
+++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt
@@ -96,4 +96,15 @@ sealed class Lce {
ifContent = ::identity,
ifError = { null },
)
+
+ /**
+ * Returns the error of this [Lce] if it's a [Lce.Error], `null` otherwise.
+ *
+ * @return The error of this [Lce] or `null`.
+ */
+ fun errorOrNull(): E? = fold(
+ ifLoading = { null },
+ ifContent = { null },
+ ifError = ::identity,
+ )
}
\ No newline at end of file
diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt
index 90dbc6d172..2040b0774a 100644
--- a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt
+++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt
@@ -2,8 +2,10 @@ package com.tangem.domain.core.lce
import arrow.atomic.Atomic
import arrow.core.raise.Raise
+import arrow.core.raise.RaiseDSL
import arrow.core.raise.recover
import com.tangem.domain.core.utils.lceContent
+import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading
import kotlin.experimental.ExperimentalTypeInference
@@ -18,18 +20,55 @@ class LceRaise