Updated on 2026-08-14
This commit is contained in:
commit
e9e45f7550
44 changed files with 358 additions and 296 deletions
|
|
@ -1,10 +1,7 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.usecase.ArchiveCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.usecase.RecoverCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.usecase.*
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -44,4 +41,12 @@ internal object AccountDomainModule {
|
|||
): RecoverCryptoPortfolioUseCase {
|
||||
return RecoverCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetUnoccupiedAccountIndexUseCase(
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
): GetUnoccupiedAccountIndexUseCase {
|
||||
return GetUnoccupiedAccountIndexUseCase(crudRepository = accountsCRUDRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +60,7 @@ class AmountStateConverter(
|
|||
return AmountState.Data(
|
||||
title = value.title,
|
||||
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
|
||||
availableBalanceShort = stringReference(crypto),
|
||||
tokenName = stringReference(status.currency.name),
|
||||
tokenIconState = iconStateConverter.convert(status),
|
||||
amountTextField = amountFieldConverter.convert(value.value),
|
||||
|
|
@ -130,6 +131,7 @@ class AmountStateConverterV2(
|
|||
} else {
|
||||
resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat))
|
||||
},
|
||||
availableBalanceShort = stringReference(crypto),
|
||||
tokenName = stringReference(cryptoCurrencyStatus.currency.name),
|
||||
tokenIconState = iconStateConverter.convert(cryptoCurrencyStatus.currency),
|
||||
amountTextField = amountFieldConverter.convert(value.value),
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class AmountBoundaryUpdateTransformer(
|
|||
|
||||
return prevState.copy(
|
||||
availableBalance = availableBalance,
|
||||
availableBalanceShort = stringReference(crypto),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,8 @@ sealed class AmountState {
|
|||
/**
|
||||
* @param isPrimaryButtonEnabled indicates if next state button enabled
|
||||
* @param title title
|
||||
* @param availableBalance user crypto currency balance
|
||||
* @param availableBalance user crypto currency balance with fiat balance
|
||||
* @param availableBalanceShort user crypto currency balance without fiat balance
|
||||
* @param tokenIconState crypto currency icon state
|
||||
* @param segmentedButtonConfig currency switcher config
|
||||
* @param selectedButton selected currency index
|
||||
|
|
@ -33,6 +34,7 @@ sealed class AmountState {
|
|||
override val isRedesignEnabled: Boolean,
|
||||
val title: TextReference,
|
||||
val availableBalance: TextReference,
|
||||
val availableBalanceShort: TextReference,
|
||||
val tokenName: TextReference,
|
||||
val tokenIconState: CurrencyIconState,
|
||||
val segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ object AmountStatePreviewData {
|
|||
val amountState = AmountState.Data(
|
||||
isPrimaryButtonEnabled = false,
|
||||
title = stringReference("Family Wallet"),
|
||||
availableBalance = stringReference("2 130,88 USDT (2 129,92 \$)"),
|
||||
availableBalance = stringReference("2 130,88 USDT • 2 129,92 \$)"),
|
||||
availableBalanceShort = stringReference("2 130,88 USDT"),
|
||||
tokenIconState = CurrencyIconState.Loading,
|
||||
segmentedButtonConfig = persistentListOf(
|
||||
AmountSegmentedButtonsConfig(
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ fun AmountBlockV2(
|
|||
|
||||
AmountBlockV2(
|
||||
title = amountState.title,
|
||||
balance = amountState.availableBalance,
|
||||
balance = amountState.availableBalanceShort,
|
||||
currencyTitle = currencyTitle,
|
||||
currencyIconState = amountState.tokenIconState,
|
||||
firstAmount = firstAmount,
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ import okio.IOException
|
|||
* Switch api environment [Interceptor]
|
||||
*
|
||||
* @property id api config id [ApiConfig.ID]
|
||||
* @property baseUrls base urls for all api config environments
|
||||
* @property apiConfigsManager api configs manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SwitchEnvironmentInterceptor(
|
||||
private val id: ApiConfig.ID,
|
||||
private val baseUrls: Set<String>,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
) : Interceptor {
|
||||
|
||||
|
|
@ -39,10 +41,13 @@ internal class SwitchEnvironmentInterceptor(
|
|||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
private fun HttpUrl.adjustBaseUrl(url: String): HttpUrl {
|
||||
return this.newBuilder()
|
||||
.host(host = url.toHttpUrl().host)
|
||||
.build()
|
||||
private fun HttpUrl.adjustBaseUrl(newBaseUrl: String): HttpUrl {
|
||||
val currentUrl = this.toString()
|
||||
val currentBaseUrl = baseUrls.first { currentUrl.contains(it) }
|
||||
|
||||
return currentUrl
|
||||
.replace(oldValue = currentBaseUrl, newValue = newBaseUrl)
|
||||
.toHttpUrl()
|
||||
}
|
||||
|
||||
private fun Request.Builder.addHeaders(headers: Map<String, ProviderSuspend<String>>): Request.Builder {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.datasource.BuildConfig
|
|||
import com.tangem.datasource.api.common.SwitchEnvironmentInterceptor
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||
|
|
@ -42,6 +43,7 @@ import javax.inject.Singleton
|
|||
*/
|
||||
@Singleton
|
||||
internal class RetrofitApiBuilder @Inject constructor(
|
||||
private val apiConfigs: ApiConfigs,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
@NetworkMoshi private val moshi: Moshi,
|
||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
|
|
@ -49,6 +51,8 @@ internal class RetrofitApiBuilder @Inject constructor(
|
|||
private val appLogsStore: AppLogsStore,
|
||||
) {
|
||||
|
||||
private val configsBaseUrls: Map<ApiConfig.ID, Set<String>> = getConfigsBaseUrls()
|
||||
|
||||
/**
|
||||
* Builds a Retrofit API instance for the specified API configuration ID
|
||||
*
|
||||
|
|
@ -95,13 +99,26 @@ internal class RetrofitApiBuilder @Inject constructor(
|
|||
val writeTimeoutSeconds: Long? = null,
|
||||
)
|
||||
|
||||
private fun getConfigsBaseUrls(): Map<ApiConfig.ID, Set<String>> {
|
||||
return apiConfigs.associate { config ->
|
||||
val allBaseUrls = config.environmentConfigs.mapTo(hashSetOf(), ApiEnvironmentConfig::baseUrl)
|
||||
|
||||
config.id to allBaseUrls
|
||||
}
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.applyApiConfig(
|
||||
apiConfigId: ApiConfig.ID,
|
||||
environmentConfig: ApiEnvironmentConfig,
|
||||
): OkHttpClient.Builder {
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED || BuildConfig.BUILD_TYPE == MOCKED_BUILD_TYPE) {
|
||||
addInterceptor(
|
||||
interceptor = SwitchEnvironmentInterceptor(id = apiConfigId, apiConfigsManager = apiConfigsManager),
|
||||
interceptor = SwitchEnvironmentInterceptor(
|
||||
id = apiConfigId,
|
||||
baseUrls = configsBaseUrls[apiConfigId]
|
||||
?: error("Base URLs for ApiConfig with id [$apiConfigId] not found"),
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
val headers = environmentConfig.headers
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@
|
|||
<string name="common_access_denied">Zugang verweigert</string>
|
||||
<string name="common_add_to_portfolio">Zum Portfolio hinzufügen</string>
|
||||
<string name="common_add_token">Token hinzufügen</string>
|
||||
<string name="common_address">Vertragsadresse</string>
|
||||
<string name="common_all">Alle</string>
|
||||
<string name="common_allow">Erlauben</string>
|
||||
<string name="common_amount">Betrag</string>
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@
|
|||
<string name="common_access_denied">Acceso denegado</string>
|
||||
<string name="common_add_to_portfolio">Añadir al portafolio</string>
|
||||
<string name="common_add_token">Agregar token</string>
|
||||
<string name="common_address">Dirección</string>
|
||||
<string name="common_all">Todos</string>
|
||||
<string name="common_allow">Autorizar</string>
|
||||
<string name="common_amount">Montante</string>
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@
|
|||
<string name="common_access_denied">Accès refusé</string>
|
||||
<string name="common_add_to_portfolio">Ajouter au portfolio</string>
|
||||
<string name="common_add_token">Ajouter un jeton</string>
|
||||
<string name="common_address">Adresse</string>
|
||||
<string name="common_all">Tous</string>
|
||||
<string name="common_allow">Permettre</string>
|
||||
<string name="common_amount">Montant</string>
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@
|
|||
<string name="common_access_denied">アクセスが拒否されました</string>
|
||||
<string name="common_add_to_portfolio">ポートフォリオに追加</string>
|
||||
<string name="common_add_token">トークンを追加</string>
|
||||
<string name="common_address">アドレス</string>
|
||||
<string name="common_all">すべて</string>
|
||||
<string name="common_allow">許可する</string>
|
||||
<string name="common_amount">金額</string>
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@
|
|||
<string name="common_access_denied">Доступ запрещен</string>
|
||||
<string name="common_add_to_portfolio">Добавить в портфель</string>
|
||||
<string name="common_add_token">Добавить токен</string>
|
||||
<string name="common_address">Адрес</string>
|
||||
<string name="common_all">Все</string>
|
||||
<string name="common_allow">Разрешить</string>
|
||||
<string name="common_amount">Сумма</string>
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@
|
|||
<string name="common_access_denied">Доступ заборонено</string>
|
||||
<string name="common_add_to_portfolio">Додати у портфель</string>
|
||||
<string name="common_add_token">Додати токен</string>
|
||||
<string name="common_address">Адреса</string>
|
||||
<string name="common_all">Усе</string>
|
||||
<string name="common_allow">Дозволити</string>
|
||||
<string name="common_amount">Сума</string>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<string name="account_details_archive_action">Archive</string>
|
||||
<string name="account_details_archive_description">You are archiving this account, but you can always get it back.</string>
|
||||
<string name="account_details_title">Account</string>
|
||||
<string name="account_form_account_index">Account #%s — used for address derivation.</string>
|
||||
<string name="account_form_create_button">Add account</string>
|
||||
<string name="account_form_edit_button">Save</string>
|
||||
<string name="account_form_name">Account name</string>
|
||||
|
|
@ -151,6 +152,7 @@
|
|||
<string name="common_access_denied">Access denied</string>
|
||||
<string name="common_add_to_portfolio">Add to portfolio</string>
|
||||
<string name="common_add_token">Add token</string>
|
||||
<string name="common_address">Address</string>
|
||||
<string name="common_all">All</string>
|
||||
<string name="common_allow">Allow</string>
|
||||
<string name="common_amount">Amount</string>
|
||||
|
|
@ -1001,6 +1003,8 @@
|
|||
<string name="send_with_swap_confirm_title">Swap and send</string>
|
||||
<string name="send_with_swap_convert_token_alert_message">Proceed with conversion? This will clear your previous data.</string>
|
||||
<string name="send_with_swap_convert_token_alert_title">Confirm Conversion</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_message">Sending any other currency will result in its irreversible loss.</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_title">Select the correct recipient network</string>
|
||||
<string name="send_with_swap_notification_text">Send any token, and we’ll convert it on the way. Your recipient gets exactly what they need—seamlessly.</string>
|
||||
<string name="send_with_swap_recipient_amount_success_title">Recipient will receive</string>
|
||||
<string name="send_with_swap_recipient_amount_text">To recipient</string>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ private const val DISABLED_ICON_ALPHA = 0.4f
|
|||
fun ProviderChooseCrypto(providerChooseUM: ProviderChooseUM, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
ConstraintLayout(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(14.dp))
|
||||
.selectedBorder(isSelected = providerChooseUM.isSelected)
|
||||
.clickable(
|
||||
enabled = !providerChooseUM.hasError(),
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
package com.tangem.core.ui.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavController
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* The ViewModel is scoped to the parent route Navigation graph
|
||||
* and is provided using the Hilt-generated ViewModel factory
|
||||
*
|
||||
* ```
|
||||
* val navController = rememberNavController()
|
||||
*
|
||||
* navigation(
|
||||
* route = "parent",
|
||||
* startDestination = "parent/1"
|
||||
* ) {
|
||||
* composable("route/1") { entry ->
|
||||
* val viewModel = entry.parentHiltViewModel(navController)
|
||||
* }
|
||||
* composable("route/2") { entry ->
|
||||
* val viewModel = entry.parentHiltViewModel(navController)
|
||||
* }
|
||||
* composable("route/3") { entry ->
|
||||
* val viewModel = entry.parentHiltViewModel(navController)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param navController NavController within the common NavGraph
|
||||
* @throws Exception if there is no parent route
|
||||
*/
|
||||
@Composable
|
||||
inline fun <reified T : ViewModel> NavBackStackEntry.parentHiltViewModel(navController: NavController): T {
|
||||
val viewModelStoreOwner = remember(this) {
|
||||
try {
|
||||
navController.getBackStackEntry(this.destination.parent!!.id)
|
||||
} catch (e: Exception) {
|
||||
Timber.tag("scopedViewModel").e(e, "There is no parent route'")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
return hiltViewModel<T>(viewModelStoreOwner)
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package com.tangem.core.ui.extensions
|
||||
|
||||
import android.R
|
||||
import android.content.Context
|
||||
import android.graphics.Color.*
|
||||
import android.view.WindowManager
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import kotlin.math.sqrt
|
||||
|
||||
@Deprecated("Use only in legacy fragments")
|
||||
fun Fragment.setStatusBarColor(@ColorRes colorResId: Int) {
|
||||
with(requireActivity().window) {
|
||||
clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
statusBarColor = ContextCompat.getColor(requireContext(), colorResId)
|
||||
val view = view ?: return
|
||||
val windowInsetsController = WindowCompat.getInsetsController(this, view)
|
||||
windowInsetsController.isAppearanceLightStatusBars = luminance(requireContext(), colorResId)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO replace by android.graphics.luminance() after bump min API to 24
|
||||
@Suppress("MagicNumber")
|
||||
fun luminance(context: Context, @ColorRes colorRes: Int): Boolean {
|
||||
val color = context.resources.getColor(colorRes, null)
|
||||
if (R.color.transparent == color) return true
|
||||
var rtnValue = false
|
||||
val rgb = intArrayOf(red(color), green(color), blue(color))
|
||||
val brightness = sqrt(
|
||||
rgb[0] * rgb[0] * .241 +
|
||||
rgb[1] * rgb[1] * .691 +
|
||||
rgb[2] * rgb[2] * .068,
|
||||
).toInt()
|
||||
|
||||
// color is light
|
||||
if (brightness >= 200) {
|
||||
rtnValue = true
|
||||
}
|
||||
return rtnValue
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.LocalIndication
|
|||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -72,26 +71,24 @@ fun Modifier.conditionalCompose(
|
|||
fun Modifier.selectedBorder(
|
||||
isSelected: Boolean,
|
||||
width: Dp = 2.5.dp,
|
||||
color: Color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
|
||||
color: Color = TangemTheme.colors.text.accent,
|
||||
radius: Dp = 16.dp,
|
||||
) = conditionalCompose(
|
||||
condition = isSelected,
|
||||
modifier = {
|
||||
border(
|
||||
outsetBorder(
|
||||
width = width,
|
||||
color = color,
|
||||
shape = RoundedCornerShape(radius),
|
||||
color = color.copy(alpha = 0.15f),
|
||||
shape = RoundedCornerShape(radius + 2.dp),
|
||||
)
|
||||
.padding(width)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
shape = RoundedCornerShape(radius - 2.dp),
|
||||
color = color,
|
||||
shape = RoundedCornerShape(radius),
|
||||
)
|
||||
.clip(RoundedCornerShape(radius - 2.dp))
|
||||
.clip(RoundedCornerShape(radius))
|
||||
},
|
||||
otherModifier = {
|
||||
padding(width)
|
||||
.clip(RoundedCornerShape(radius - 2.dp))
|
||||
clip(RoundedCornerShape(radius))
|
||||
},
|
||||
)
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
package com.tangem.core.ui.screen
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* An abstract base class for bottom sheet dialogs that use Compose for UI rendering.
|
||||
* Extends [BottomSheetDialogFragment] and implements [ComposeScreen] interface.
|
||||
*/
|
||||
abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment(), ComposeScreen {
|
||||
|
||||
/**
|
||||
* The initial state of the bottom sheet. Default is [BottomSheetBehavior.STATE_EXPANDED].
|
||||
*/
|
||||
open val initialBottomSheetState = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
||||
/**
|
||||
* The fraction of the screen height that the bottom sheet should take when expanded.
|
||||
* Default is `null`, indicating that the height will be determined by the content.
|
||||
*/
|
||||
@FloatRange(from = 0.0, to = 1.0)
|
||||
open val expandedHeightFraction: Float? = null
|
||||
|
||||
override val screenModifier: Modifier
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = Modifier
|
||||
.fillMaxWidth()
|
||||
.let {
|
||||
if (expandedHeightFraction != null) it.fillMaxHeight(expandedHeightFraction!!) else it
|
||||
}
|
||||
.background(
|
||||
color = TangemTheme.colors.background.primary,
|
||||
shape = TangemTheme.shapes.bottomSheet,
|
||||
)
|
||||
|
||||
override fun getTheme(): Int = R.style.AppTheme_TransparentBottomSheetDialog
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
return createComposeView(
|
||||
context = inflater.context,
|
||||
activity = requireActivity(),
|
||||
overrideSystemBarColors = false,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
|
||||
(dialog as BottomSheetDialog).behavior.apply {
|
||||
state = initialBottomSheetState
|
||||
skipCollapsed = true
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
package com.tangem.core.ui.screen
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.core.ui.R
|
||||
|
||||
/**
|
||||
* An abstract base class for fragments that use Compose for UI rendering.
|
||||
* Extends [Fragment] and implements [ComposeScreen] interface.
|
||||
*/
|
||||
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, requireActivity()).also {
|
||||
it.isTransitionGroup = isTransitionsInflated
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
/*
|
||||
* We need to manually dispatch configuration changes to the Compose view.
|
||||
*
|
||||
|
||||
* `android:configChanges="uiMode"` is set in the manifest.
|
||||
* */
|
||||
view?.dispatchConfigurationChanged(newConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates transitions for the fragment. Override this method to customize
|
||||
* enter and exit transitions for the fragment.
|
||||
*
|
||||
* @return `true` if transitions were inflated; `false` otherwise.
|
||||
*/
|
||||
protected open fun TransitionInflater.inflateTransitions(): Boolean {
|
||||
enterTransition = inflateTransition(R.transition.fade)
|
||||
exitTransition = inflateTransition(R.transition.fade)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -53,6 +53,12 @@ internal class DefaultAccountsCRUDRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getTotalAccountsCount(userWalletId: UserWalletId): Int {
|
||||
val activeAccountsCount = runtimeStore.getSyncOrNull()?.size ?: 1
|
||||
|
||||
return activeAccountsCount + 1
|
||||
}
|
||||
|
||||
override fun getUserWallet(userWalletId: UserWalletId): UserWallet {
|
||||
return userWalletsStore.getSyncStrict(userWalletId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,15 +43,20 @@ interface AccountsCRUDRepository {
|
|||
*
|
||||
* @param accountList the list of accounts to be saved.
|
||||
*/
|
||||
@Throws
|
||||
suspend fun saveAccounts(accountList: AccountList)
|
||||
|
||||
/**
|
||||
* Retrieves the total count of accounts associated with a specific user wallet including archived accounts
|
||||
*
|
||||
* @param userWalletId the unique identifier of the user wallet
|
||||
*/
|
||||
suspend fun getTotalAccountsCount(userWalletId: UserWalletId): Int
|
||||
|
||||
/**
|
||||
* Retrieves a user wallet by its unique identifier
|
||||
*
|
||||
* @param userWalletId the unique identifier of the user wallet
|
||||
* @return the [UserWallet] associated with the given identifier
|
||||
*/
|
||||
@Throws
|
||||
fun getUserWallet(userWalletId: UserWalletId): UserWallet
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.domain.account.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Use case for retrieving the next unoccupied account index
|
||||
*
|
||||
* @property crudRepository repository for performing CRUD operations on accounts
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetUnoccupiedAccountIndexUseCase(
|
||||
private val crudRepository: AccountsCRUDRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Invokes the use case to calculate the next unoccupied account index
|
||||
*
|
||||
* @param userWalletId the unique identifier of the user wallet
|
||||
*/
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<Error, DerivationIndex> = either {
|
||||
val totalAccountsCount = getTotalAccountsCount(userWalletId = userWalletId)
|
||||
|
||||
DerivationIndex(totalAccountsCount + 1).getOrElse {
|
||||
raise(Error.InvalidDerivationIndex(it))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.getTotalAccountsCount(userWalletId: UserWalletId): Int {
|
||||
return catch(
|
||||
block = { crudRepository.getTotalAccountsCount(userWalletId = userWalletId) },
|
||||
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents possible errors that can occur in the use case
|
||||
*/
|
||||
sealed interface Error {
|
||||
|
||||
val tag: String
|
||||
get() = this::class.simpleName ?: "GetUnoccupiedAccountIndexUseCase.Error"
|
||||
|
||||
/** Error indicating that the derivation index is invalid */
|
||||
data class InvalidDerivationIndex(val cause: DerivationIndex.Error) : Error {
|
||||
override fun toString(): String = "$tag: Invalid derivation index: $cause"
|
||||
}
|
||||
|
||||
/** Error indicating that a data operation failed */
|
||||
data class DataOperationFailed(val cause: Throwable) : Error {
|
||||
override fun toString(): String = "$tag: Data operation failed: ${cause.message ?: "Unknown error"}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.domain.account.usecase
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class GetUnoccupiedAccountIndexUseCaseTest {
|
||||
|
||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val useCase = GetUnoccupiedAccountIndexUseCase(crudRepository)
|
||||
private val userWalletId = UserWalletId("011")
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return next unoccupied index when repository returns count`() = runTest {
|
||||
// Arrange
|
||||
coEvery { crudRepository.getTotalAccountsCount(userWalletId) } returns 3
|
||||
|
||||
// Act
|
||||
val actual = useCase(userWalletId = userWalletId)
|
||||
|
||||
// Assert
|
||||
val expected = 4.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerify { crudRepository.getTotalAccountsCount(userWalletId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if repository throws exception`() = runTest {
|
||||
// Arrange
|
||||
val exception = IllegalStateException("Test error")
|
||||
coEvery { crudRepository.getTotalAccountsCount(userWalletId) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = useCase(userWalletId = userWalletId)
|
||||
|
||||
// Assert
|
||||
val expected = GetUnoccupiedAccountIndexUseCase.Error.DataOperationFailed(exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerify { crudRepository.getTotalAccountsCount(userWalletId) }
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ dependencies {
|
|||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.error)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.navigation)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.features.account.createedit
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -9,11 +11,14 @@ import com.tangem.core.res.R
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.utils.showErrorDialog
|
||||
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase
|
||||
import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.account.AccountCreateEditComponent
|
||||
import com.tangem.features.account.common.toDomain
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUM
|
||||
|
|
@ -21,15 +26,20 @@ import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder
|
|||
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateButton
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateColorSelect
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateDerivationIndex
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateIconSelect
|
||||
import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateName
|
||||
import com.tangem.features.account.createedit.error.AccountFeatureError
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
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
|
||||
|
||||
@ModelScoped
|
||||
@Suppress("LongParameterList")
|
||||
internal class AccountCreateEditModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
private val messageSender: UiMessageSender,
|
||||
|
|
@ -37,13 +47,21 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val updateCryptoPortfolioUseCase: UpdateCryptoPortfolioUseCase,
|
||||
private val addCryptoPortfolioUseCase: AddCryptoPortfolioUseCase,
|
||||
private val getUnoccupiedAccountIndexUseCase: GetUnoccupiedAccountIndexUseCase,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<AccountCreateEditComponent.Params>()
|
||||
private val umBuilder = AccountCreateEditUMBuilder(params)
|
||||
|
||||
val uiState: StateFlow<AccountCreateEditUM> get() = _uiState
|
||||
private val _uiState = MutableStateFlow(value = getInitialState())
|
||||
val uiState: StateFlow<AccountCreateEditUM>
|
||||
field = MutableStateFlow(value = getInitialState())
|
||||
|
||||
init {
|
||||
if (params is AccountCreateEditComponent.Params.Create) {
|
||||
updateDerivationInfo(userWalletId = params.userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unsaveChangeDialog() {
|
||||
val secondAction = EventMessageAction(
|
||||
|
|
@ -74,13 +92,16 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
|
||||
private suspend fun createNewCryptoPortfolio(params: AccountCreateEditComponent.Params.Create) {
|
||||
val state = uiState.value
|
||||
val name = AccountName(state.account.name).getOrNull() ?: return
|
||||
val name = AccountName(value = state.account.name).getOrNull() ?: return
|
||||
val icon = state.account.portfolioIcon.toDomain()
|
||||
val index = state.account.derivationInfo.index ?: return
|
||||
val derivationIndex = DerivationIndex(value = index).getOrNull() ?: return
|
||||
|
||||
addCryptoPortfolioUseCase(
|
||||
userWalletId = params.userWalletId,
|
||||
accountName = name,
|
||||
icon = icon,
|
||||
derivationIndex = DerivationIndex.Main, // todo account
|
||||
derivationIndex = derivationIndex,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -100,19 +121,19 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
private fun onCloseClick() = unsaveChangeDialog()
|
||||
|
||||
private fun onIconSelect(icon: CryptoPortfolioIcon.Icon) {
|
||||
_uiState.value = uiState.value
|
||||
uiState.value = uiState.value
|
||||
.updateIconSelect(icon)
|
||||
.validateNewState()
|
||||
}
|
||||
|
||||
private fun onColorSelect(color: CryptoPortfolioIcon.Color) {
|
||||
_uiState.value = uiState.value
|
||||
uiState.value = uiState.value
|
||||
.updateColorSelect(color)
|
||||
.validateNewState()
|
||||
}
|
||||
|
||||
private fun onNameChange(name: String) {
|
||||
_uiState.value = uiState.value
|
||||
uiState.value = uiState.value
|
||||
.updateName(name)
|
||||
.validateNewState()
|
||||
}
|
||||
|
|
@ -140,4 +161,35 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
onCloseClick = ::onCloseClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateDerivationInfo(userWalletId: UserWalletId) {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
getUnoccupiedAccountIndexUseCase(userWalletId = userWalletId)
|
||||
.onRight { derivationIndex ->
|
||||
uiState.update {
|
||||
it.updateDerivationIndex(derivationIndex = derivationIndex.value)
|
||||
}
|
||||
}
|
||||
.onLeft {
|
||||
handleError(
|
||||
error = AccountFeatureError.CreateAccount.UnableToGetDerivationIndex,
|
||||
params = mapOf("userWalletId" to userWalletId.stringValue),
|
||||
)
|
||||
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleError(error: AccountFeatureError, params: Map<String, String> = mapOf()) {
|
||||
val exception = IllegalStateException(error.toString())
|
||||
|
||||
Timber.e(exception)
|
||||
|
||||
analyticsExceptionHandler.sendException(
|
||||
event = ExceptionAnalyticsEvent(exception = exception, params = params),
|
||||
)
|
||||
|
||||
messageSender.showErrorDialog(universalError = error, onDismiss = router::pop)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,11 +17,23 @@ data class AccountCreateEditUM(
|
|||
data class Account(
|
||||
val name: String,
|
||||
val portfolioIcon: CryptoPortfolioIconUM,
|
||||
val derivationInfo: TextReference,
|
||||
val derivationInfo: DerivationInfo,
|
||||
val inputPlaceholder: TextReference,
|
||||
val onNameChange: (String) -> Unit,
|
||||
)
|
||||
|
||||
sealed interface DerivationInfo {
|
||||
val text: TextReference
|
||||
val index: Int?
|
||||
|
||||
data class Content(override val text: TextReference, override val index: Int) : DerivationInfo
|
||||
|
||||
data object Empty : DerivationInfo {
|
||||
override val text: TextReference = TextReference.EMPTY
|
||||
override val index: Int? = null
|
||||
}
|
||||
}
|
||||
|
||||
data class Colors(
|
||||
val selected: CryptoPortfolioIcon.Color,
|
||||
val list: ImmutableList<CryptoPortfolioIcon.Color>,
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ package com.tangem.features.account.createedit.entity
|
|||
import com.tangem.core.res.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.features.account.AccountCreateEditComponent
|
||||
import com.tangem.features.account.common.toUM
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AccountCreateEditUMBuilder @Inject constructor(
|
||||
val params: AccountCreateEditComponent.Params,
|
||||
internal class AccountCreateEditUMBuilder(
|
||||
private val params: AccountCreateEditComponent.Params,
|
||||
) {
|
||||
|
||||
private val accountColors = CryptoPortfolioIcon.Color.entries.toImmutableList()
|
||||
|
|
@ -29,14 +29,16 @@ internal class AccountCreateEditUMBuilder @Inject constructor(
|
|||
is AccountCreateEditComponent.Params.Create -> AccountCreateEditUM.Account(
|
||||
name = "",
|
||||
portfolioIcon = createIcon,
|
||||
derivationInfo = TextReference.EMPTY,
|
||||
derivationInfo = AccountCreateEditUM.DerivationInfo.Empty,
|
||||
inputPlaceholder = resourceReference(R.string.account_form_placeholder_new_account),
|
||||
onNameChange = onNameChange,
|
||||
)
|
||||
is AccountCreateEditComponent.Params.Edit -> AccountCreateEditUM.Account(
|
||||
name = params.account.name.value,
|
||||
portfolioIcon = params.account.portfolioIcon.toUM(),
|
||||
derivationInfo = TextReference.EMPTY, // todo account use Account.CryptoPortfolio.derivationIndex ?
|
||||
derivationInfo = createAccountDerivationInfo(
|
||||
index = (params.account as Account.CryptoPortfolio).derivationIndex.value,
|
||||
),
|
||||
inputPlaceholder = resourceReference(R.string.account_form_placeholder_edit_account),
|
||||
onNameChange = onNameChange,
|
||||
)
|
||||
|
|
@ -113,5 +115,25 @@ internal class AccountCreateEditUMBuilder @Inject constructor(
|
|||
fun AccountCreateEditUM.updateButton(isButtonEnabled: Boolean): AccountCreateEditUM {
|
||||
return this.copy(buttonState = this.buttonState.copy(isButtonEnabled = isButtonEnabled))
|
||||
}
|
||||
|
||||
fun AccountCreateEditUM.updateDerivationIndex(derivationIndex: Int): AccountCreateEditUM {
|
||||
return this.copy(
|
||||
account = this.account.copy(
|
||||
derivationInfo = createAccountDerivationInfo(index = derivationIndex),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createAccountDerivationInfo(index: Int): AccountCreateEditUM.DerivationInfo {
|
||||
val derivationIndexText = if (index.toString().length == 1) "0$index" else "$index"
|
||||
|
||||
return AccountCreateEditUM.DerivationInfo.Content(
|
||||
text = resourceReference(
|
||||
id = R.string.account_form_account_index,
|
||||
formatArgs = wrappedList(derivationIndexText),
|
||||
),
|
||||
index = index,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.features.account.createedit.error
|
||||
|
||||
import com.tangem.core.error.UniversalError
|
||||
|
||||
sealed interface AccountFeatureError : UniversalError {
|
||||
|
||||
val subsystemCode: String
|
||||
val specificErrorCode: String
|
||||
|
||||
override val errorCode: Int
|
||||
get() = "108$subsystemCode$specificErrorCode".toInt()
|
||||
|
||||
sealed interface CreateAccount : AccountFeatureError {
|
||||
|
||||
override val subsystemCode: String get() = "001"
|
||||
|
||||
data object UnableToGetDerivationIndex : CreateAccount {
|
||||
override val specificErrorCode: String = "001"
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface EditAccount : AccountFeatureError {
|
||||
|
||||
override val subsystemCode: String get() = "002"
|
||||
|
||||
data object RequiredCryptoPortfolio : EditAccount {
|
||||
override val specificErrorCode: String = "001"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,10 +32,7 @@ import com.tangem.core.ui.components.SpacerH24
|
|||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.fields.AutoSizeTextField
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
|
|
@ -76,7 +73,7 @@ internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modi
|
|||
SpacerH8()
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
text = state.account.derivationInfo.resolveReference(),
|
||||
text = state.account.derivationInfo.text.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
|
@ -93,7 +90,7 @@ internal fun AccountCreateEditContent(state: AccountCreateEditUM, modifier: Modi
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun AccountSummary(account: AccountCreateEditUM.Account) {
|
||||
private fun AccountSummary(account: Account) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
|
|
@ -126,7 +123,7 @@ private fun AccountSummary(account: AccountCreateEditUM.Account) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun AccountIcon(account: AccountCreateEditUM.Account) {
|
||||
private fun AccountIcon(account: Account) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
|
|
@ -308,7 +305,10 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider<AccountC
|
|||
portfolioIcon = portfolioIcon,
|
||||
inputPlaceholder = resourceReference(R.string.account_form_placeholder_new_account),
|
||||
onNameChange = {},
|
||||
derivationInfo = stringReference("Account #03 — used for address derivation."),
|
||||
derivationInfo = AccountCreateEditUM.DerivationInfo.Content(
|
||||
text = resourceReference(id = R.string.account_form_account_index, formatArgs = wrappedList(1)),
|
||||
index = 1,
|
||||
),
|
||||
),
|
||||
colorsState = AccountCreateEditUM.Colors(
|
||||
selected = portfolioIcon.color,
|
||||
|
|
@ -340,7 +340,10 @@ private class PreviewStateProvider : CollectionPreviewParameterProvider<AccountC
|
|||
name = "Main account",
|
||||
inputPlaceholder = resourceReference(R.string.account_form_placeholder_edit_account),
|
||||
onNameChange = {},
|
||||
derivationInfo = stringReference("Account #03 — used for address derivation."),
|
||||
derivationInfo = AccountCreateEditUM.DerivationInfo.Content(
|
||||
text = resourceReference(id = R.string.account_form_account_index, formatArgs = wrappedList(1)),
|
||||
index = 1,
|
||||
),
|
||||
),
|
||||
colorsState = AccountCreateEditUM.Colors(
|
||||
selected = portfolioIcon.color,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -19,9 +15,9 @@ import com.tangem.core.ui.components.notifications.Notification
|
|||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.components.rows.RowContentContainer
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.outsetBorder
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.selectedBorder
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -112,17 +108,7 @@ private fun SelectableChainRow(
|
|||
RowContentContainer(
|
||||
modifier = modifier
|
||||
.heightIn(min = 48.dp)
|
||||
.outsetBorder(
|
||||
color = if (selected) TangemTheme.colors.icon.accent.copy(alpha = 0.15f) else Color.Transparent,
|
||||
width = 5.dp,
|
||||
shape = RoundedCornerShape(size = 18.dp),
|
||||
)
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = if (selected) TangemTheme.colors.icon.accent else Color.Transparent,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.selectedBorder(selected)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(12.dp),
|
||||
icon = {
|
||||
|
|
@ -163,7 +149,7 @@ private fun Preview() {
|
|||
),
|
||||
),
|
||||
selectedOption = SelectableChainRowUM(
|
||||
event = OnboardingVisaChooseWalletComponent.Params.Event.OtherWallet,
|
||||
event = OnboardingVisaChooseWalletComponent.Params.Event.TangemWallet,
|
||||
icon = R.drawable.ic_tangem_24,
|
||||
text = TextReference.Str("Tangem Wallet"),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ internal fun FeeSelectorModalBottomSheet(
|
|||
FeeSelectorItems(
|
||||
state = state,
|
||||
feeSelectorIntents = feeSelectorIntents,
|
||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 13.dp),
|
||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 12.dp),
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
|
|
@ -141,7 +141,6 @@ private fun FeeSelectorItems(
|
|||
)
|
||||
val itemModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.selectedBorder(isSelected = isSelected)
|
||||
.clickableSingle(onClick = { feeSelectorIntents.onFeeItemSelected(item) })
|
||||
when (item) {
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
currentRoute = model.currentRoute.filterIsInstance<CommonSendRoute.Destination>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
analyticsCategoryName = model.analyticCategoryName,
|
||||
title = resourceReference(R.string.send_recipient_label),
|
||||
title = resourceReference(R.string.common_address),
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.currency,
|
||||
callback = model,
|
||||
|
|
|
|||
|
|
@ -10,4 +10,7 @@ internal enum class EnterAddressSource {
|
|||
|
||||
val isPasted: Boolean
|
||||
get() = this != InputField
|
||||
|
||||
val isAutoNext: Boolean
|
||||
get() = this == RecentAddress || this == MyWallets
|
||||
}
|
||||
|
|
@ -286,8 +286,7 @@ internal class SendDestinationModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun autoNextFromRecipient(type: EnterAddressSource?, isValidAddress: Boolean, isValidMemo: Boolean) {
|
||||
val isRecent = type == EnterAddressSource.RecentAddress
|
||||
if (isRecent && isValidAddress && isValidMemo) {
|
||||
if (type?.isAutoNext == true && isValidAddress && isValidMemo) {
|
||||
saveResult()
|
||||
(params as? SendDestinationComponentParams.DestinationParams)?.callback?.onNextClick()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ class SendConfirmationNotificationsTransformerTest {
|
|||
isRedesignEnabled = false,
|
||||
title = mockk(relaxed = true),
|
||||
availableBalance = mockk(relaxed = true),
|
||||
availableBalanceShort = mockk(relaxed = true),
|
||||
tokenName = mockk(relaxed = true),
|
||||
tokenIconState = mockk(relaxed = true),
|
||||
segmentedButtonConfig = persistentListOf(),
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
isRedesignEnabled = false,
|
||||
title = mockk(relaxed = true),
|
||||
availableBalance = mockk(relaxed = true),
|
||||
availableBalanceShort = mockk(relaxed = true),
|
||||
tokenName = mockk(relaxed = true),
|
||||
tokenIconState = mockk(relaxed = true),
|
||||
segmentedButtonConfig = persistentListOf(),
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@ class TransformersComparisonTest {
|
|||
isRedesignEnabled = false,
|
||||
title = mockk(relaxed = true),
|
||||
availableBalance = mockk(relaxed = true),
|
||||
availableBalanceShort = mockk(relaxed = true),
|
||||
tokenName = mockk(relaxed = true),
|
||||
tokenIconState = mockk(relaxed = true),
|
||||
segmentedButtonConfig = persistentListOf(),
|
||||
|
|
|
|||
|
|
@ -72,14 +72,13 @@ internal fun SwapAmountBlockContent(
|
|||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
extraContent = {
|
||||
SwapPriceImpact(amountFieldUM = amountUM.primaryAmount, onInfoClick = onInfoClick)
|
||||
},
|
||||
extraContent = { SwapPriceImpact(amountFieldUM = amountUM.primaryAmount, onInfoClick = onInfoClick) },
|
||||
)
|
||||
AmountBlockV2(
|
||||
amountState = (amountUM.secondaryAmount.amountField as? AmountState.Data)?.copy(
|
||||
title = resourceReference(R.string.send_with_swap_recipient_amount_title),
|
||||
availableBalance = TextReference.EMPTY,
|
||||
availableBalanceShort = TextReference.EMPTY,
|
||||
) ?: amountUM.secondaryAmount.amountField,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = false,
|
||||
|
|
|
|||
|
|
@ -5,14 +5,12 @@ import androidx.compose.animation.AnimatedVisibility
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -65,7 +63,7 @@ internal fun SwapChooseProviderContent(
|
|||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier.padding(horizontal = 13.dp),
|
||||
modifier = modifier.padding(horizontal = 12.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.onramp_choose_provider_title_hint),
|
||||
|
|
@ -89,7 +87,6 @@ internal fun SwapChooseProviderContent(
|
|||
SwapProviderItem(
|
||||
state = provider.swapProviderState,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(14.dp))
|
||||
.selectedBorder(isSelected = provider.swapProviderState.isSelected)
|
||||
.clickable(
|
||||
enabled = provider.quote !is SwapQuoteUM.Error,
|
||||
|
|
|
|||
|
|
@ -96,13 +96,13 @@ internal object SwapChooseProviderContentPreview {
|
|||
),
|
||||
quote = quote2,
|
||||
swapProviderState = SwapProviderState.Content(
|
||||
name = provider1.name,
|
||||
type = provider1.type.typeName,
|
||||
name = provider2.name,
|
||||
type = provider2.type.typeName,
|
||||
iconUrl = "",
|
||||
subtitle = stringReference("1800 POL"),
|
||||
additionalBadge = SwapProviderState.AdditionalBadge.BestTrade,
|
||||
diffPercent = SwapQuoteUM.Content.DifferencePercent.Best,
|
||||
isSelected = true,
|
||||
isSelected = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
|
|||
currentRoute = model.currentRoute.filterIsInstance<DestinationRoute>(),
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
analyticsCategoryName = model.analyticCategoryName,
|
||||
title = resourceReference(R.string.send_recipient_label),
|
||||
title = resourceReference(R.string.common_address),
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = secondaryCryptoCurrency,
|
||||
callback = model,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ internal fun WcAddressItem(address: String, modifier: Modifier = Modifier) {
|
|||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing8),
|
||||
text = stringResourceSafe(R.string.wc_common_address),
|
||||
text = stringResourceSafe(R.string.common_address),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue