Updated on 2026-08-14
|
|
@ -273,6 +273,8 @@ dependencies {
|
|||
implementation(projects.features.welcome.impl)
|
||||
implementation(projects.features.createWalletSelection.api)
|
||||
implementation(projects.features.createWalletSelection.impl)
|
||||
implementation(projects.features.createWalletStart.api)
|
||||
implementation(projects.features.createWalletStart.impl)
|
||||
implementation(projects.features.home.api)
|
||||
implementation(projects.features.home.impl)
|
||||
implementation(projects.features.account.api)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.features.account.AccountCreateEditComponent
|
|||
import com.tangem.features.account.AccountDetailsComponent
|
||||
import com.tangem.features.account.ArchivedAccountListComponent
|
||||
import com.tangem.features.createwalletselection.CreateWalletSelectionComponent
|
||||
import com.tangem.features.createwalletstart.CreateWalletStartComponent
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
||||
import com.tangem.features.home.api.HomeComponent
|
||||
|
|
@ -99,6 +100,7 @@ internal class ChildFactory @Inject constructor(
|
|||
private val usedeskComponentFactory: UsedeskComponent.Factory,
|
||||
private val chooseManagedTokensComponentFactory: ChooseManagedTokensComponent.Factory,
|
||||
private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory,
|
||||
private val createWalletStartComponentFactory: CreateWalletStartComponent.Factory,
|
||||
private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory,
|
||||
private val upgradeWalletComponentFactory: UpgradeWalletComponent.Factory,
|
||||
private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory,
|
||||
|
|
@ -476,6 +478,19 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = chooseManagedTokensComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.CreateWalletStart -> {
|
||||
val mode = when (route.mode) {
|
||||
AppRoute.CreateWalletStart.Mode.ColdWallet -> CreateWalletStartComponent.Mode.ColdWallet
|
||||
AppRoute.CreateWalletStart.Mode.HotWallet -> CreateWalletStartComponent.Mode.HotWallet
|
||||
}
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = CreateWalletStartComponent.Params(
|
||||
mode = mode,
|
||||
),
|
||||
componentFactory = createWalletStartComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.CreateWalletSelection -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
|
|
|
|||
|
|
@ -311,6 +311,16 @@ sealed class AppRoute(val path: String) : Route {
|
|||
@Serializable
|
||||
object CreateWalletSelection : AppRoute(path = "/create_wallet_selection")
|
||||
|
||||
@Serializable
|
||||
data class CreateWalletStart(
|
||||
val mode: Mode,
|
||||
) : AppRoute(path = "/create_wallet_start") {
|
||||
enum class Mode {
|
||||
ColdWallet,
|
||||
HotWallet,
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
object CreateMobileWallet : AppRoute(path = "/create_mobile_wallet")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,54 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import com.google.accompanist.systemuicontroller.SystemUiController
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.tangem.core.ui.res.LocalIsInDarkTheme
|
||||
|
||||
val LocalSystemBarsIconsController = staticCompositionLocalOf<SystemBarsIconsController> {
|
||||
error("No SystemBarsIconsController provided")
|
||||
}
|
||||
|
||||
class SystemBarsIconsController(private val systemUiController: SystemUiController) {
|
||||
private var count by mutableIntStateOf(0)
|
||||
|
||||
fun setIcons(darkIcons: Boolean, isNavigationBarContrastEnforced: Boolean) {
|
||||
if (count == 0) {
|
||||
systemUiController.systemBarsDarkContentEnabled = darkIcons
|
||||
systemUiController.isNavigationBarContrastEnforced = isNavigationBarContrastEnforced
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
fun restoreIcons(isDarkTheme: Boolean) {
|
||||
count--
|
||||
if (count == 0) {
|
||||
systemUiController.systemBarsDarkContentEnabled = !isDarkTheme
|
||||
systemUiController.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ProvideSystemBarsIconsController(content: @Composable () -> Unit) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val controller = remember(systemUiController) { SystemBarsIconsController(systemUiController) }
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalSystemBarsIconsController provides controller,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the ability to set a scrim for 3-button navigation
|
||||
*
|
||||
|
|
@ -43,19 +85,16 @@ fun NavigationBar3ButtonsScrim() {
|
|||
*/
|
||||
@Composable
|
||||
fun SystemBarsIconsDisposable(darkIcons: Boolean, isNavigationBarContrastEnforced: Boolean = false) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val controller = LocalSystemBarsIconsController.current
|
||||
val isDarkTheme = LocalIsInDarkTheme.current
|
||||
|
||||
SideEffect {
|
||||
systemUiController.systemBarsDarkContentEnabled = darkIcons
|
||||
systemUiController.isNavigationBarContrastEnforced = isNavigationBarContrastEnforced
|
||||
controller.setIcons(darkIcons, isNavigationBarContrastEnforced)
|
||||
}
|
||||
|
||||
val isDarkTheme = LocalIsInDarkTheme.current
|
||||
|
||||
DisposableEffect(isDarkTheme) {
|
||||
onDispose {
|
||||
systemUiController.systemBarsDarkContentEnabled = !isDarkTheme
|
||||
systemUiController.isNavigationBarContrastEnforced = false
|
||||
controller.restoreIcons(isDarkTheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.platform.LocalView
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.LocalSystemBarsIconsController
|
||||
import com.tangem.core.ui.components.SystemBarsIconsController
|
||||
import com.tangem.core.ui.components.TangemShimmer
|
||||
import com.tangem.core.ui.components.text.BladeAnimation
|
||||
import com.tangem.core.ui.components.text.rememberBladeAnimation
|
||||
|
|
@ -65,6 +67,8 @@ fun TangemTheme(
|
|||
val themeColors = if (isDark) darkThemeColors() else lightThemeColors()
|
||||
val rememberedColors = remember { themeColors }
|
||||
.also { it.update(themeColors) }
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val systemBarsIconsController = remember(systemUiController) { SystemBarsIconsController(systemUiController) }
|
||||
|
||||
val shapes = remember { TangemShapes(dimens) }
|
||||
|
||||
|
|
@ -103,6 +107,7 @@ fun TangemTheme(
|
|||
LocalEventMessageHandler provides eventMessageHandler,
|
||||
LocalWindowSize provides windowSize,
|
||||
LocalBladeAnimation provides rememberBladeAnimation(),
|
||||
LocalSystemBarsIconsController provides systemBarsIconsController,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalTangemShimmer provides TangemShimmer,
|
||||
|
|
@ -119,6 +124,14 @@ fun TangemTheme(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ForceDarkTheme(content: @Composable () -> Unit) {
|
||||
CompositionLocalProvider(
|
||||
LocalTangemColors provides darkThemeColors(),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
object TangemTheme {
|
||||
val colors: TangemColors
|
||||
@Composable
|
||||
|
|
|
|||
BIN
core/ui/src/main/res/drawable-hdpi/img_hardware_wallet.webp
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
core/ui/src/main/res/drawable-hdpi/img_mobile_wallet.webp
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
core/ui/src/main/res/drawable-mdpi/img_hardware_wallet.webp
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
core/ui/src/main/res/drawable-mdpi/img_mobile_wallet.webp
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
core/ui/src/main/res/drawable-xhdpi/img_hardware_wallet.webp
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
core/ui/src/main/res/drawable-xhdpi/img_mobile_wallet.webp
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
core/ui/src/main/res/drawable-xxhdpi/img_hardware_wallet.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
core/ui/src/main/res/drawable-xxhdpi/img_mobile_wallet.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
core/ui/src/main/res/drawable-xxxhdpi/img_hardware_wallet.webp
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
core/ui/src/main/res/drawable-xxxhdpi/img_mobile_wallet.webp
Normal file
|
After Width: | Height: | Size: 128 KiB |
9
core/ui/src/main/res/drawable/ic_chevron_right_18x24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="19dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="19"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12.632,12.113C12.632,12.331 12.552,12.511 12.382,12.682L8.287,16.687C8.148,16.825 7.984,16.889 7.787,16.889C7.388,16.889 7.063,16.575 7.063,16.176C7.063,15.979 7.149,15.798 7.292,15.655L10.93,12.118L7.292,8.57C7.149,8.427 7.063,8.251 7.063,8.049C7.063,7.65 7.388,7.331 7.787,7.331C7.984,7.331 8.148,7.4 8.287,7.538L12.382,11.543C12.558,11.714 12.632,11.894 12.632,12.113Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
9
core/ui/src/main/res/drawable/ic_flash_16.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="17dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="17"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M10.49,1.932C10.866,2.174 11.072,2.631 10.988,3.121L10.323,7.004C10.312,7.067 10.355,7.127 10.418,7.138L12.198,7.443C12.706,7.53 12.997,7.933 13.079,8.318C13.162,8.702 13.063,9.161 12.713,9.481L7.814,13.946C7.422,14.304 6.898,14.317 6.51,14.067C6.135,13.825 5.928,13.368 6.012,12.878L6.677,8.995C6.688,8.932 6.646,8.872 6.582,8.861L4.803,8.556C4.295,8.469 4.003,8.066 3.921,7.681C3.839,7.297 3.937,6.838 4.288,6.519L9.186,2.053C9.579,1.695 10.102,1.682 10.49,1.932Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
</vector>
|
||||
10
core/ui/src/main/res/drawable/ic_shield_check_16.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M7.973,1.085C8.088,1.085 8.207,1.096 8.331,1.118C8.455,1.14 8.572,1.167 8.682,1.198C8.829,1.247 9.036,1.316 9.302,1.405C9.573,1.494 9.871,1.596 10.197,1.712C10.523,1.828 10.849,1.943 11.175,2.059C11.505,2.17 11.806,2.275 12.077,2.373C12.348,2.471 12.559,2.551 12.711,2.613C13.046,2.746 13.293,2.905 13.454,3.087C13.615,3.269 13.695,3.556 13.695,3.948V8.912C13.695,9.508 13.606,10.035 13.427,10.493C13.248,10.947 12.956,11.372 12.552,11.768C12.149,12.164 11.609,12.562 10.934,12.962C10.264,13.367 9.435,13.816 8.448,14.31C8.361,14.355 8.274,14.385 8.187,14.403C8.104,14.426 8.033,14.436 7.973,14.436C7.913,14.436 7.84,14.425 7.753,14.403C7.67,14.385 7.585,14.355 7.498,14.31C6.529,13.79 5.708,13.324 5.033,12.915C4.363,12.51 3.823,12.117 3.414,11.734C3.006,11.347 2.71,10.929 2.526,10.48C2.343,10.031 2.251,9.508 2.251,8.912V3.948C2.251,3.556 2.331,3.269 2.492,3.087C2.653,2.905 2.9,2.746 3.235,2.613C3.387,2.555 3.598,2.477 3.869,2.379C4.14,2.277 4.438,2.168 4.764,2.052C5.09,1.937 5.416,1.823 5.742,1.712C6.072,1.596 6.371,1.494 6.637,1.405C6.908,1.316 7.117,1.247 7.264,1.198C7.379,1.167 7.496,1.14 7.615,1.118C7.739,1.096 7.858,1.085 7.973,1.085ZM11.402,5.142C11.057,4.855 10.545,4.902 10.258,5.246L7.505,8.554L6.188,7.237C5.871,6.92 5.357,6.92 5.04,7.237C4.723,7.555 4.723,8.069 5.04,8.386L6.986,10.332C7.147,10.494 7.369,10.58 7.597,10.569C7.825,10.559 8.039,10.453 8.185,10.278L11.507,6.285C11.794,5.941 11.747,5.429 11.402,5.142Z"
|
||||
android:fillColor="#0099FF"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
12
core/ui/src/main/res/drawable/ic_sparkles_16.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="17dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="17"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M10.313,1.505C10.524,1.505 10.708,1.652 10.755,1.858L11.08,3.303C11.318,4.358 12.142,5.182 13.196,5.42L14.642,5.745C14.848,5.792 14.995,5.975 14.995,6.187C14.995,6.399 14.848,6.583 14.642,6.629L13.196,6.955C12.142,7.193 11.318,8.016 11.08,9.071L10.755,10.516C10.708,10.723 10.524,10.87 10.313,10.87C10.101,10.87 9.917,10.723 9.87,10.516L9.545,9.071C9.307,8.016 8.484,7.193 7.429,6.955L5.984,6.629C5.777,6.583 5.63,6.399 5.63,6.187C5.63,5.975 5.777,5.792 5.984,5.745L7.429,5.42C8.484,5.182 9.307,4.358 9.545,3.303L9.87,1.858C9.917,1.652 10.101,1.505 10.313,1.505Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
<path
|
||||
android:pathData="M5.479,7.547C5.691,7.547 5.874,7.694 5.921,7.9L6.154,8.933C6.312,9.637 6.863,10.188 7.567,10.346L8.6,10.579C8.806,10.625 8.953,10.809 8.953,11.021C8.953,11.233 8.806,11.416 8.6,11.463L7.567,11.696C6.863,11.854 6.312,12.405 6.154,13.109L5.921,14.142C5.874,14.348 5.691,14.495 5.479,14.495C5.267,14.495 5.083,14.348 5.037,14.142L4.804,13.109C4.646,12.405 4.095,11.854 3.391,11.696L2.358,11.463C2.152,11.416 2.005,11.233 2.005,11.021C2.005,10.809 2.152,10.625 2.358,10.579L3.391,10.346C4.095,10.188 4.646,9.637 4.804,8.933L5.037,7.9C5.083,7.694 5.267,7.547 5.479,7.547Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
</vector>
|
||||
15
core/ui/src/main/res/drawable/ic_stack_fill_new_16.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M12.94,10.526C13.241,10.375 13.608,10.497 13.758,10.798C13.909,11.099 13.786,11.465 13.485,11.615L8.459,14.129C8.422,14.148 8.3,14.215 8.16,14.241C8.054,14.261 7.945,14.261 7.839,14.241C7.7,14.215 7.578,14.148 7.541,14.129L2.515,11.615C2.214,11.465 2.091,11.099 2.241,10.798C2.392,10.497 2.758,10.375 3.059,10.526L8,12.995L12.94,10.526Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
<path
|
||||
android:pathData="M12.94,7.455C13.241,7.305 13.608,7.427 13.758,7.727C13.909,8.028 13.786,8.394 13.485,8.545L8.459,11.059C8.422,11.077 8.299,11.144 8.16,11.171C8.054,11.191 7.945,11.191 7.839,11.171C7.7,11.144 7.578,11.077 7.541,11.059L2.515,8.545C2.214,8.394 2.091,8.028 2.241,7.727C2.392,7.427 2.758,7.305 3.059,7.455L8,9.925L12.94,7.455Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
<path
|
||||
android:pathData="M8.08,1.749L8.16,1.76L8.261,1.786C8.355,1.817 8.431,1.857 8.459,1.871L13.485,4.385C13.692,4.488 13.822,4.7 13.823,4.93C13.823,5.161 13.691,5.372 13.485,5.475L8.459,7.989C8.422,8.008 8.299,8.074 8.16,8.1C8.054,8.12 7.945,8.12 7.839,8.1C7.7,8.074 7.578,8.008 7.541,7.989L2.514,5.475C2.308,5.372 2.177,5.161 2.177,4.93C2.177,4.7 2.308,4.488 2.514,4.385L7.541,1.871C7.578,1.853 7.7,1.786 7.839,1.76L7.92,1.749C7.973,1.744 8.026,1.744 8.08,1.749Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
</vector>
|
||||
1
features/create-wallet-start/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
21
features/create-wallet-start/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.createwalletstart.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.createwalletstart
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface CreateWalletStartComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val mode: Mode,
|
||||
)
|
||||
|
||||
enum class Mode {
|
||||
ColdWallet,
|
||||
HotWallet,
|
||||
}
|
||||
|
||||
interface Factory : ComponentFactory<Params, CreateWalletStartComponent>
|
||||
}
|
||||
1
features/create-wallet-start/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
72
features/create-wallet-start/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.createwalletstart.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Api */
|
||||
implementation(projects.features.createWalletStart.api)
|
||||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.datasource)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(projects.libs.tangemSdkApi)
|
||||
implementation(tangemDeps.card.core)
|
||||
implementation(tangemDeps.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
implementation(deps.lifecycle.runtime.ktx)
|
||||
|
||||
/** Compose libraries */
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.animation)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.lottie.compose)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.androidx.datastore)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.firebase.crashlytics)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
package com.tangem.features.createwalletstart
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic.SignedIn
|
||||
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
|
||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.features.createwalletstart.entity.CreateWalletStartUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val HIDE_PROGRESS_DELAY = 400L
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class CreateWalletStartModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val appRouter: AppRouter,
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
|
||||
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<CreateWalletStartComponent.Params>()
|
||||
|
||||
internal val uiState: StateFlow<CreateWalletStartUM>
|
||||
field = MutableStateFlow(
|
||||
when (params.mode) {
|
||||
CreateWalletStartComponent.Mode.ColdWallet -> CreateWalletStartUM(
|
||||
title = resourceReference(R.string.common_tangem_wallet),
|
||||
description = resourceReference(R.string.welcome_create_wallet_hardware_description),
|
||||
featureItems = persistentListOf(
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_shield_check_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_class),
|
||||
),
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_flash_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_delivery),
|
||||
),
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_sparkles_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_use),
|
||||
),
|
||||
),
|
||||
imageResId = R.drawable.img_hardware_wallet,
|
||||
showScanSecondaryButton = true,
|
||||
onPrimaryButtonClick = ::onBuyClick,
|
||||
primaryButtonText = resourceReference(R.string.details_buy_wallet),
|
||||
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_mobile_title),
|
||||
otherMethodDescription = resourceReference(R.string.welcome_create_wallet_mobile_description),
|
||||
otherMethodClick = ::onStartWithMobileWalletClick,
|
||||
onBackClick = { router.pop() },
|
||||
onScanClick = ::onScanClick,
|
||||
isScanInProgress = false,
|
||||
)
|
||||
CreateWalletStartComponent.Mode.HotWallet -> CreateWalletStartUM(
|
||||
title = resourceReference(R.string.hw_mobile_wallet),
|
||||
description = resourceReference(R.string.welcome_create_wallet_mobile_description_full),
|
||||
featureItems = persistentListOf(
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_shield_check_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_seamless),
|
||||
),
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_flash_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_one_tap),
|
||||
),
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_stack_fill_new_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_assets),
|
||||
),
|
||||
),
|
||||
imageResId = R.drawable.img_mobile_wallet,
|
||||
showScanSecondaryButton = false,
|
||||
onPrimaryButtonClick = ::onStartWithMobileWalletClick,
|
||||
primaryButtonText = resourceReference(R.string.welcome_create_wallet_mobile_title),
|
||||
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_use_hardware_title),
|
||||
otherMethodDescription = resourceReference(R.string.welcome_create_wallet_use_hardware_description),
|
||||
otherMethodClick = ::onBuyClick,
|
||||
onBackClick = { router.pop() },
|
||||
onScanClick = ::onScanClick,
|
||||
isScanInProgress = false,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
private fun onScanClick() {
|
||||
scanCard()
|
||||
}
|
||||
|
||||
private fun onStartWithMobileWalletClick() {
|
||||
router.push(AppRoute.CreateMobileWallet)
|
||||
}
|
||||
|
||||
private fun onBuyClick() {
|
||||
modelScope.launch {
|
||||
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
modelScope.launch {
|
||||
setLoading(true)
|
||||
|
||||
val shouldSaveAccessCodes = settingsRepository.shouldSaveAccessCodes()
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
val analyticsSource = AnalyticsParam.ScreensSources.Intro
|
||||
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = analyticsSource,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (!showProgress) {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
} else {
|
||||
setLoading(true)
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
handleScanError(error)
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
proceedWithScanResponse(scanResponse)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
|
||||
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build()
|
||||
|
||||
if (userWallet == null) {
|
||||
Timber.e("User wallet not created")
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
saveWalletUseCase(userWallet = userWallet).fold(
|
||||
ifLeft = {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
setLoading(false)
|
||||
when (it) {
|
||||
is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet")
|
||||
is SaveWalletError.WalletAlreadySaved -> {
|
||||
userWalletsListRepository.unlock(
|
||||
userWalletId = userWallet.walletId,
|
||||
unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse),
|
||||
).onRight {
|
||||
appRouter.replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ifRight = {
|
||||
setLoading(false)
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
appRouter.replaceAll(AppRoute.Wallet)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
||||
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
|
||||
if (currency != null) {
|
||||
analyticsEventHandler.send(
|
||||
SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = SignInType.Card,
|
||||
walletsCount = userWalletsListRepository.userWalletsSync().size.toString(),
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLoading(isLoading: Boolean) {
|
||||
uiState.update { it.copy(isScanInProgress = isLoading) }
|
||||
}
|
||||
|
||||
private fun handleScanError(error: TangemError) {
|
||||
when (error) {
|
||||
is TangemSdkError.NfcFeatureIsUnavailable -> handleNfcFeatureUnavailable()
|
||||
is TangemSdkError -> Timber.e(error, "Scan error occurred")
|
||||
else -> Timber.e(error, "Error happened")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNfcFeatureUnavailable() {
|
||||
uiMessageSender.send(
|
||||
message = DialogMessage(
|
||||
message = resourceReference(R.string.nfc_error_unavailable),
|
||||
title = resourceReference(id = R.string.common_error),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.features.createwalletstart
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.SystemBarsIconsDisposable
|
||||
import com.tangem.core.ui.res.ForceDarkTheme
|
||||
import com.tangem.features.createwalletstart.ui.CreateWalletStartContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultCreateWalletStartComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: CreateWalletStartComponent.Params,
|
||||
) : CreateWalletStartComponent, AppComponentContext by context {
|
||||
|
||||
private val model: CreateWalletStartModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
SystemBarsIconsDisposable(darkIcons = false)
|
||||
ForceDarkTheme {
|
||||
CreateWalletStartContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : CreateWalletStartComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: CreateWalletStartComponent.Params,
|
||||
): DefaultCreateWalletStartComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.features.createwalletstart.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.createwalletstart.CreateWalletStartComponent
|
||||
import com.tangem.features.createwalletstart.CreateWalletStartModel
|
||||
import com.tangem.features.createwalletstart.DefaultCreateWalletStartComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CreateWalletStartModule
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface CreateWalletStartModuleBinds {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindCreateWalletStartComponentFactory(
|
||||
impl: DefaultCreateWalletStartComponent.Factory,
|
||||
): CreateWalletStartComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(CreateWalletStartModel::class)
|
||||
fun bindCreateWalletStartModel(model: CreateWalletStartModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.features.createwalletstart.entity
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class CreateWalletStartUM(
|
||||
val title: TextReference,
|
||||
val description: TextReference,
|
||||
val featureItems: ImmutableList<FeatureItem>,
|
||||
val imageResId: Int,
|
||||
val isScanInProgress: Boolean,
|
||||
val showScanSecondaryButton: Boolean,
|
||||
val primaryButtonText: TextReference,
|
||||
val onPrimaryButtonClick: () -> Unit,
|
||||
val otherMethodDescription: TextReference,
|
||||
val otherMethodTitle: TextReference,
|
||||
val otherMethodClick: () -> Unit,
|
||||
val onScanClick: () -> Unit,
|
||||
val onBackClick: () -> Unit,
|
||||
) {
|
||||
data class FeatureItem(
|
||||
val iconResId: Int,
|
||||
val text: TextReference,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,486 @@
|
|||
package com.tangem.features.createwalletstart.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathEffect
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.bottomFade
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.createwalletstart.entity.CreateWalletStartUM
|
||||
import com.tangem.features.createwalletstart.impl.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlin.math.max
|
||||
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
internal fun CreateWalletStartContent(state: CreateWalletStartUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
listOf(
|
||||
TangemColorPalette.Dark6,
|
||||
TangemColorPalette.Black,
|
||||
),
|
||||
),
|
||||
)
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
TopAppBar(
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = Color.Transparent,
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = state.onBackClick) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_back_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
title = { },
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.bottomFade(height = 24.dp),
|
||||
) {
|
||||
AdaptiveScrollableContent(
|
||||
topContent = {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 32.dp,
|
||||
top = 16.dp,
|
||||
end = 32.dp,
|
||||
),
|
||||
text = state.title.resolveReference(),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 32.dp,
|
||||
top = 8.dp,
|
||||
end = 32.dp,
|
||||
),
|
||||
text = state.description.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 24.dp,
|
||||
top = 16.dp,
|
||||
end = 24.dp,
|
||||
),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
state.featureItems.forEach {
|
||||
FeatureItem(
|
||||
iconResId = it.iconResId,
|
||||
text = it.text,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
imageContent = {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(
|
||||
vertical = 12.dp,
|
||||
horizontal = 16.dp,
|
||||
),
|
||||
painter = painterResource(id = state.imageResId),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
},
|
||||
bottomContent = {
|
||||
if (state.showScanSecondaryButton) {
|
||||
SecondaryButtonIconEnd(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = stringResourceSafe(R.string.welcome_unlock_card),
|
||||
onClick = state.onScanClick,
|
||||
showProgress = state.isScanInProgress,
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
)
|
||||
}
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 8.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
text = state.primaryButtonText.resolveReference(),
|
||||
onClick = state.onPrimaryButtonClick,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 24.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
DashedGradientLine(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(16.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.welcome_create_wallet_other_method),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
DashedGradientLine(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(16.dp)
|
||||
.scale(scaleX = -1f, scaleY = 1f),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
),
|
||||
text = state.otherMethodDescription.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.clickable { state.otherMethodClick() }
|
||||
.padding(
|
||||
horizontal = 16.dp,
|
||||
vertical = 12.dp,
|
||||
),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = state.otherMethodTitle.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_18x24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
minImageHeight = 160.dp,
|
||||
)
|
||||
}
|
||||
if (!state.showScanSecondaryButton) {
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 24.dp,
|
||||
end = 16.dp,
|
||||
bottom = 8.dp,
|
||||
),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.welcome_create_wallet_already_have),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Spacer(modifier = Modifier.size(4.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
) { state.onScanClick() },
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.wallet_create_scan_title),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Spacer(modifier = Modifier.size(2.dp))
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp),
|
||||
painter = painterResource(id = R.drawable.ic_tangem_24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnusedBoxWithConstraintsScope")
|
||||
@Composable
|
||||
private fun AdaptiveScrollableContent(
|
||||
minImageHeight: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
topContent: @Composable () -> Unit,
|
||||
imageContent: @Composable () -> Unit,
|
||||
bottomContent: @Composable () -> Unit,
|
||||
) {
|
||||
BoxWithConstraints(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val viewportHeight = maxHeight
|
||||
val minImageHeightPx = with(density) { minImageHeight.roundToPx() }
|
||||
val viewportHeightPx = with(density) { viewportHeight.roundToPx() }
|
||||
Layout(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
topContent()
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
imageContent()
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
bottomContent()
|
||||
}
|
||||
},
|
||||
) { measurables, constraints ->
|
||||
val topPlaceable = measurables[0].measure(
|
||||
constraints.copy(minHeight = 0, maxHeight = androidx.compose.ui.unit.Constraints.Infinity),
|
||||
)
|
||||
val bottomPlaceable = measurables[2].measure(
|
||||
constraints.copy(minHeight = 0, maxHeight = androidx.compose.ui.unit.Constraints.Infinity),
|
||||
)
|
||||
val imageIntrinsicHeight = measurables[1].maxIntrinsicHeight(constraints.maxWidth)
|
||||
val availableHeightForImage = max(0, viewportHeightPx - topPlaceable.height - bottomPlaceable.height)
|
||||
val targetImageHeight = when {
|
||||
imageIntrinsicHeight < minImageHeightPx -> minImageHeightPx
|
||||
imageIntrinsicHeight > availableHeightForImage -> max(minImageHeightPx, availableHeightForImage)
|
||||
else -> imageIntrinsicHeight
|
||||
}
|
||||
val imagePlaceable = measurables[1].measure(
|
||||
constraints.copy(
|
||||
minHeight = targetImageHeight,
|
||||
maxHeight = targetImageHeight,
|
||||
),
|
||||
)
|
||||
val totalContentHeight = topPlaceable.height + imagePlaceable.height + bottomPlaceable.height
|
||||
layout(constraints.maxWidth, totalContentHeight) {
|
||||
var yOffset = 0
|
||||
topPlaceable.placeRelative(0, yOffset)
|
||||
yOffset += topPlaceable.height
|
||||
imagePlaceable.placeRelative(0, yOffset)
|
||||
yOffset += imagePlaceable.height
|
||||
bottomPlaceable.placeRelative(0, yOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DashedGradientLine(modifier: Modifier = Modifier) {
|
||||
val density = LocalDensity.current
|
||||
|
||||
val strokeColor = TangemTheme.colors.stroke.primary
|
||||
|
||||
Canvas(modifier = modifier) {
|
||||
val strokePx = with(density) { 4.dp.toPx() }
|
||||
val dashPx = with(density) { 4.dp.toPx() }
|
||||
val gapPx = with(density) { 8.dp.toPx() }
|
||||
|
||||
val width = size.width
|
||||
val centerY = size.height / 2
|
||||
|
||||
val brush = Brush.linearGradient(
|
||||
colors = listOf(strokeColor.copy(alpha = 0f), strokeColor),
|
||||
start = Offset(0f, 0f),
|
||||
end = Offset(width, 0f),
|
||||
)
|
||||
|
||||
val pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashPx, gapPx), 0f)
|
||||
|
||||
drawLine(
|
||||
brush = brush,
|
||||
start = Offset(0f, centerY),
|
||||
end = Offset(width, centerY),
|
||||
strokeWidth = strokePx,
|
||||
pathEffect = pathEffect,
|
||||
cap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeatureItem(@DrawableRes iconResId: Int, text: TextReference) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.padding(horizontal = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp),
|
||||
painter = painterResource(iconResId),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateWalletStartStateProvider : CollectionPreviewParameterProvider<CreateWalletStartUM>(
|
||||
collection = listOf(
|
||||
CreateWalletStartUM(
|
||||
title = resourceReference(R.string.common_tangem_wallet),
|
||||
description = resourceReference(R.string.welcome_create_wallet_hardware_description),
|
||||
featureItems = persistentListOf(
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_shield_check_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_class),
|
||||
),
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_flash_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_delivery),
|
||||
),
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_sparkles_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_use),
|
||||
),
|
||||
),
|
||||
imageResId = R.drawable.img_hardware_wallet,
|
||||
showScanSecondaryButton = true,
|
||||
onPrimaryButtonClick = { },
|
||||
primaryButtonText = resourceReference(R.string.details_buy_wallet),
|
||||
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_mobile_title),
|
||||
otherMethodDescription = resourceReference(
|
||||
R.string.welcome_create_wallet_mobile_description,
|
||||
),
|
||||
otherMethodClick = { },
|
||||
onBackClick = { },
|
||||
onScanClick = { },
|
||||
isScanInProgress = false,
|
||||
),
|
||||
CreateWalletStartUM(
|
||||
title = resourceReference(R.string.hw_mobile_wallet),
|
||||
description = resourceReference(R.string.welcome_create_wallet_mobile_description_full),
|
||||
featureItems = persistentListOf(
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_shield_check_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_seamless),
|
||||
),
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_flash_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_one_tap),
|
||||
),
|
||||
CreateWalletStartUM.FeatureItem(
|
||||
iconResId = R.drawable.ic_stack_fill_new_16,
|
||||
text = resourceReference(R.string.welcome_create_wallet_feature_assets),
|
||||
),
|
||||
),
|
||||
imageResId = R.drawable.img_mobile_wallet,
|
||||
showScanSecondaryButton = false,
|
||||
onPrimaryButtonClick = { },
|
||||
primaryButtonText = resourceReference(R.string.welcome_create_wallet_mobile_title),
|
||||
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_use_hardware_title),
|
||||
otherMethodDescription = resourceReference(
|
||||
R.string.welcome_create_wallet_use_hardware_description,
|
||||
),
|
||||
otherMethodClick = { },
|
||||
onBackClick = { },
|
||||
onScanClick = { },
|
||||
isScanInProgress = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 560, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 840, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewCreateWalletStartContent(
|
||||
@PreviewParameter(CreateWalletStartStateProvider::class) param: CreateWalletStartUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
CreateWalletStartContent(
|
||||
state = param,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -279,6 +279,9 @@ include(":features:tangempay:onboarding:impl")
|
|||
include(":features:create-wallet-selection:api")
|
||||
include(":features:create-wallet-selection:impl")
|
||||
|
||||
include(":features:create-wallet-start:api")
|
||||
include(":features:create-wallet-start:impl")
|
||||
|
||||
include(":features:welcome:api")
|
||||
include(":features:welcome:impl")
|
||||
|
||||
|
|
|
|||