Updated on 2026-08-14
This commit is contained in:
commit
7453e8d179
49 changed files with 1320 additions and 408 deletions
|
|
@ -7,7 +7,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory
|
||||
|
|
@ -20,12 +20,12 @@ internal fun SettingsAlertDialog(dialog: Dialog.Alert) {
|
|||
title = dialog.title.resolveReference(),
|
||||
message = dialog.description.resolveReference(),
|
||||
isDismissable = false,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = dialog.confirmText.resolveReference(),
|
||||
warning = true,
|
||||
onClick = dialog.onConfirm,
|
||||
),
|
||||
dismissButton = DialogButton(
|
||||
dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = dialog.onDismiss,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.components.SelectorDialog
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -21,7 +21,7 @@ internal fun SettingsSelectorDialog(dialog: Dialog.Selector) {
|
|||
title = dialog.title.resolveReference(),
|
||||
selectedItemIndex = dialog.selectedItemIndex,
|
||||
items = dialog.items.map { it.resolveReference() }.toImmutableList(),
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(R.string.common_cancel),
|
||||
onClick = dialog.onDismiss,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -190,11 +190,11 @@ private fun CommonResetDialog(dialog: ResetCardScreenState.Dialog) {
|
|||
BasicDialog(
|
||||
title = stringResource(dialog.titleResId),
|
||||
message = stringResource(dialog.messageResId),
|
||||
dismissButton = DialogButton(
|
||||
dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = dialog.onDismiss,
|
||||
),
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.card_settings_action_sheet_reset),
|
||||
warning = true,
|
||||
onClick = dialog.onConfirmClick,
|
||||
|
|
@ -208,7 +208,7 @@ private fun CompletedResetDialog(dialog: ResetCardDialog) {
|
|||
BasicDialog(
|
||||
title = stringResource(id = dialog.titleResId),
|
||||
message = stringResource(id = dialog.messageResId),
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = dialog.onConfirmClick,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ internal class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
|||
viewModel.onSearchClick()
|
||||
} else {
|
||||
Analytics.send(IntroductionProcess.ButtonTokensList())
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(readOnlyContent = true)) }
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens()) }
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ internal class HomeViewModel @Inject constructor(
|
|||
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList())
|
||||
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(readOnlyContent = true)) }
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens()) }
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
|
||||
|
||||
@Composable
|
||||
|
|
@ -19,11 +19,11 @@ fun EnrollBiometricsDialogContent(dialog: EnrollBiometricsDialog) {
|
|||
BasicDialog(
|
||||
title = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_title),
|
||||
message = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_description),
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(R.string.common_enable),
|
||||
onClick = dialog.onEnroll,
|
||||
),
|
||||
dismissButton = DialogButton(
|
||||
dismissButton = DialogButtonUM(
|
||||
onClick = dialog.onCancel,
|
||||
),
|
||||
onDismissDialog = dialog.onCancel,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -27,7 +27,7 @@ internal fun WarningDialog(warning: WarningModel?) {
|
|||
},
|
||||
),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
|
|
@ -38,7 +38,7 @@ internal fun WarningDialog(warning: WarningModel?) {
|
|||
title = stringResource(id = R.string.common_attention),
|
||||
message = stringResource(id = R.string.key_invalidated_warning_description),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
|
|
@ -50,7 +50,7 @@ internal fun WarningDialog(warning: WarningModel?) {
|
|||
message = stringResource(id = R.string.biometric_unavailable_warning),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
isDismissable = false,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -126,13 +126,7 @@ internal class ChildFactory @Inject constructor(
|
|||
if (manageTokensToggles.isFeatureEnabled) {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = ManageTokensComponent.Params(
|
||||
mode = if (route.readOnlyContent) {
|
||||
ManageTokensComponent.Mode.READ_ONLY
|
||||
} else {
|
||||
ManageTokensComponent.Mode.MANAGE
|
||||
},
|
||||
),
|
||||
params = ManageTokensComponent.Params(route.userWalletId),
|
||||
componentFactory = manageTokensComponentFactory,
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -177,8 +177,8 @@ sealed class AppRoute(val path: String) : Route {
|
|||
|
||||
@Serializable
|
||||
data class ManageTokens(
|
||||
val readOnlyContent: Boolean,
|
||||
) : AppRoute(path = "/manage_tokens/$readOnlyContent"), RouteBundleParams {
|
||||
val userWalletId: UserWalletId? = null,
|
||||
) : AppRoute(path = "/manage_tokens/$userWalletId"), RouteBundleParams {
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ fun GiveTxPermissionBottomSheet(config: TangemBottomSheetConfig) {
|
|||
BasicDialog(
|
||||
message = content.data.dialogText.resolveReference(),
|
||||
title = stringResource(id = R.string.common_approve),
|
||||
confirmButton = DialogButton { isPermissionAlertShow = false },
|
||||
confirmButton = DialogButtonUM { isPermissionAlertShow = false },
|
||||
onDismissDialog = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
@Composable
|
||||
fun BasicDialog(
|
||||
message: String,
|
||||
confirmButton: DialogButton,
|
||||
confirmButton: DialogButtonUM,
|
||||
onDismissDialog: () -> Unit,
|
||||
title: String? = null,
|
||||
dismissButton: DialogButton? = null,
|
||||
dismissButton: DialogButtonUM? = null,
|
||||
isDismissable: Boolean = true,
|
||||
) {
|
||||
TangemDialog(
|
||||
|
|
@ -72,7 +72,7 @@ fun BasicDialog(
|
|||
fun SimpleOkDialog(message: String, onDismissDialog: () -> Unit) {
|
||||
TangemDialog(
|
||||
type = DialogType.Message(message),
|
||||
confirmButton = DialogButton(onClick = onDismissDialog),
|
||||
confirmButton = DialogButtonUM(onClick = onDismissDialog),
|
||||
onDismissDialog = onDismissDialog,
|
||||
title = null,
|
||||
dismissButton = null,
|
||||
|
|
@ -97,12 +97,12 @@ fun SimpleOkDialog(message: String, onDismissDialog: () -> Unit) {
|
|||
@Composable
|
||||
fun TextInputDialog(
|
||||
fieldValue: TextFieldValue,
|
||||
confirmButton: DialogButton,
|
||||
confirmButton: DialogButtonUM,
|
||||
onDismissDialog: () -> Unit,
|
||||
onValueChange: (TextFieldValue) -> Unit,
|
||||
textFieldParams: AdditionalTextInputDialogParams = remember { AdditionalTextInputDialogParams() },
|
||||
textFieldParams: AdditionalTextInputDialogUM = remember { AdditionalTextInputDialogUM() },
|
||||
title: String? = null,
|
||||
dismissButton: DialogButton? = null,
|
||||
dismissButton: DialogButtonUM? = null,
|
||||
isDismissable: Boolean = true,
|
||||
) {
|
||||
TangemDialog(
|
||||
|
|
@ -125,12 +125,12 @@ fun TextInputDialog(
|
|||
@Composable
|
||||
fun TextInputDialog(
|
||||
fieldValue: String,
|
||||
confirmButton: DialogButton,
|
||||
confirmButton: DialogButtonUM,
|
||||
onDismissDialog: () -> Unit,
|
||||
onValueChange: (String) -> Unit,
|
||||
textFieldParams: AdditionalTextInputDialogParams = remember { AdditionalTextInputDialogParams() },
|
||||
textFieldParams: AdditionalTextInputDialogUM = remember { AdditionalTextInputDialogUM() },
|
||||
title: String? = null,
|
||||
dismissButton: DialogButton? = null,
|
||||
dismissButton: DialogButtonUM? = null,
|
||||
isDismissable: Boolean = true,
|
||||
) {
|
||||
TangemDialog(
|
||||
|
|
@ -154,7 +154,7 @@ fun TextInputDialog(
|
|||
fun SelectorDialog(
|
||||
selectedItemIndex: Int,
|
||||
items: ImmutableList<String>,
|
||||
confirmButton: DialogButton,
|
||||
confirmButton: DialogButtonUM,
|
||||
onSelect: (index: Int) -> Unit,
|
||||
onDismissDialog: () -> Unit,
|
||||
title: String? = null,
|
||||
|
|
@ -180,7 +180,7 @@ fun SelectorDialog(
|
|||
* @param enabled If false button will be disabled
|
||||
* @param onClick Button click callback
|
||||
*/
|
||||
data class DialogButton(
|
||||
data class DialogButtonUM(
|
||||
val title: String? = null,
|
||||
val warning: Boolean = false,
|
||||
val enabled: Boolean = true,
|
||||
|
|
@ -190,7 +190,7 @@ data class DialogButton(
|
|||
/**
|
||||
* Additional params for dialog text field
|
||||
*/
|
||||
data class AdditionalTextInputDialogParams(
|
||||
data class AdditionalTextInputDialogUM(
|
||||
val label: String? = null,
|
||||
val placeholder: String? = null,
|
||||
val caption: String? = null,
|
||||
|
|
@ -203,10 +203,10 @@ data class AdditionalTextInputDialogParams(
|
|||
@Composable
|
||||
private fun TangemDialog(
|
||||
type: DialogType,
|
||||
confirmButton: DialogButton,
|
||||
confirmButton: DialogButtonUM,
|
||||
onDismissDialog: () -> Unit,
|
||||
title: String? = null,
|
||||
dismissButton: DialogButton? = null,
|
||||
dismissButton: DialogButtonUM? = null,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
) {
|
||||
Dialog(properties = properties, onDismissRequest = onDismissDialog) {
|
||||
|
|
@ -304,7 +304,11 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun DialogButtons(confirmButton: DialogButton, dismissButton: DialogButton?, modifier: Modifier = Modifier) {
|
||||
private fun DialogButtons(
|
||||
confirmButton: DialogButtonUM,
|
||||
dismissButton: DialogButtonUM?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
|
|
@ -413,13 +417,13 @@ private sealed class DialogType {
|
|||
data class TextInput(
|
||||
val value: TextFieldValue,
|
||||
val onValueChange: (TextFieldValue) -> Unit,
|
||||
val params: AdditionalTextInputDialogParams = AdditionalTextInputDialogParams(),
|
||||
val params: AdditionalTextInputDialogUM = AdditionalTextInputDialogUM(),
|
||||
) : DialogType()
|
||||
|
||||
data class SimpleTextInput(
|
||||
val value: String,
|
||||
val onValueChange: (String) -> Unit,
|
||||
val params: AdditionalTextInputDialogParams = AdditionalTextInputDialogParams(),
|
||||
val params: AdditionalTextInputDialogUM = AdditionalTextInputDialogUM(),
|
||||
) : DialogType()
|
||||
|
||||
data class Selector(
|
||||
|
|
@ -445,8 +449,8 @@ private fun BasicDialogPreview() {
|
|||
message = "All protected passwords will be deleted from the secure storage, you must enter the wallet " +
|
||||
"password to work with the app",
|
||||
title = "Attention",
|
||||
confirmButton = DialogButton {},
|
||||
dismissButton = DialogButton {},
|
||||
confirmButton = DialogButtonUM {},
|
||||
dismissButton = DialogButtonUM {},
|
||||
onDismissDialog = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -478,8 +482,8 @@ private fun WarningBasicDialogSample(modifier: Modifier = Modifier) {
|
|||
message = "All protected passwords will be deleted from the secure storage, you must enter the wallet " +
|
||||
"password to work with the app",
|
||||
title = "Attention",
|
||||
confirmButton = DialogButton(warning = true) {},
|
||||
dismissButton = DialogButton {},
|
||||
confirmButton = DialogButtonUM(warning = true) {},
|
||||
dismissButton = DialogButtonUM {},
|
||||
onDismissDialog = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -502,10 +506,10 @@ private fun TextInputDialogSample(modifier: Modifier = Modifier) {
|
|||
TextInputDialog(
|
||||
fieldValue = TextFieldValue(text = ""),
|
||||
title = "Rename Wallet",
|
||||
confirmButton = DialogButton {},
|
||||
confirmButton = DialogButtonUM {},
|
||||
onDismissDialog = {},
|
||||
onValueChange = {},
|
||||
textFieldParams = AdditionalTextInputDialogParams(
|
||||
textFieldParams = AdditionalTextInputDialogUM(
|
||||
label = "Wallet name",
|
||||
),
|
||||
)
|
||||
|
|
@ -530,7 +534,7 @@ private fun SelectorDialogPreview(@PreviewParameter(SelctorDialogParamsProvider:
|
|||
title = param.title,
|
||||
items = param.items,
|
||||
selectedItemIndex = param.selectedItemIndex,
|
||||
confirmButton = DialogButton(title = "Cancel", onClick = {}),
|
||||
confirmButton = DialogButtonUM(title = "Cancel", onClick = {}),
|
||||
onSelect = {},
|
||||
onDismissDialog = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ sealed class CurrencyIconState {
|
|||
* Represents a token icon.
|
||||
*
|
||||
* @property url The URL where the token icon can be fetched from. May be `null` if not found.
|
||||
* @property topBadgeIconResId The drawable resource ID for the network badge.
|
||||
* @property topBadgeIconResId The drawable resource ID for the network badge. May be `null`.
|
||||
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
||||
* @property showCustomBadge Specifies whether to show the custom token badge.
|
||||
* @property fallbackTint The color to be used for tinting the fallback icon.
|
||||
|
|
@ -46,7 +46,7 @@ sealed class CurrencyIconState {
|
|||
*/
|
||||
data class TokenIcon(
|
||||
val url: String?,
|
||||
@DrawableRes override val topBadgeIconResId: Int,
|
||||
@DrawableRes override val topBadgeIconResId: Int?,
|
||||
override val isGrayscale: Boolean,
|
||||
override val showCustomBadge: Boolean,
|
||||
val fallbackTint: Color,
|
||||
|
|
@ -81,4 +81,28 @@ sealed class CurrencyIconState {
|
|||
override val showCustomBadge: Boolean = false
|
||||
override val topBadgeIconResId: Int? = null
|
||||
}
|
||||
|
||||
fun copySealed(
|
||||
isGrayscale: Boolean = this.isGrayscale,
|
||||
showCustomBadge: Boolean = this.showCustomBadge,
|
||||
topBadgeIconResId: Int? = this.topBadgeIconResId,
|
||||
): CurrencyIconState = when (this) {
|
||||
is CoinIcon -> copy(
|
||||
isGrayscale = isGrayscale,
|
||||
showCustomBadge = showCustomBadge,
|
||||
)
|
||||
is CustomTokenIcon -> copy(
|
||||
isGrayscale = isGrayscale,
|
||||
showCustomBadge = showCustomBadge,
|
||||
topBadgeIconResId = topBadgeIconResId ?: this.topBadgeIconResId,
|
||||
)
|
||||
is TokenIcon -> copy(
|
||||
isGrayscale = isGrayscale,
|
||||
showCustomBadge = showCustomBadge,
|
||||
topBadgeIconResId = topBadgeIconResId,
|
||||
)
|
||||
is Loading,
|
||||
is Locked,
|
||||
-> this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.core.ui.components.list
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.*
|
||||
|
||||
@Composable
|
||||
fun InfiniteListHandler(listState: LazyListState, onLoadMore: () -> Boolean, buffer: Int = 2) {
|
||||
val loadMore by remember {
|
||||
derivedStateOf {
|
||||
val layoutInfo = listState.layoutInfo
|
||||
val totalItemsNumber = layoutInfo.totalItemsCount
|
||||
val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1
|
||||
|
||||
lastVisibleItemIndex > totalItemsNumber - buffer
|
||||
}
|
||||
}
|
||||
|
||||
val totalItemsCount by remember { derivedStateOf { listState.layoutInfo.totalItemsCount } }
|
||||
var emitted by remember(totalItemsCount) { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(loadMore) {
|
||||
if (loadMore && !emitted) {
|
||||
emitted = onLoadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
14
core/ui/src/main/res/drawable/ic_tether_24.xml
Normal file
14
core/ui/src/main/res/drawable/ic_tether_24.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
|
||||
<path
|
||||
android:fillColor="#1E1E1E"
|
||||
android:pathData="M8.82,17.87C9.04,16.02 9.74,14.31 10.8,12.89C10.41,13.08 9.93,13.2 9.38,13.24V14H8.55V13.25C6.83,13.13 5.74,12.22 5.74,10.86H7.02C7.08,11.56 7.66,12.04 8.55,12.14V9.48L8.02,9.35C6.67,9.02 5.94,8.23 5.94,7.09C5.94,5.75 6.93,4.85 8.55,4.71V3.9H9.38V4.71C10.94,4.84 11.97,5.77 12,7.06H10.74C10.72,6.42 10.18,5.92 9.38,5.83V8.34L9.94,8.47C11.45,8.82 12.16,9.56 12.16,10.77C12.16,11.03 12.13,11.27 12.07,11.49C13.63,10.05 15.64,9.08 17.87,8.82C17.81,3.94 13.83,0 8.94,0C4,0 0,4 0,8.94C0,13.83 3.94,17.81 8.82,17.87ZM17.78,10.22C13.87,10.79 10.79,13.87 10.22,17.78L17.78,10.22ZM8.55,8.17C7.69,8.01 7.23,7.59 7.23,6.99C7.23,6.35 7.78,5.86 8.55,5.82V8.17ZM9.38,12.15V9.63C10.4,9.83 10.88,10.24 10.88,10.91C10.88,11.65 10.33,12.1 9.38,12.15Z" />
|
||||
|
||||
<path
|
||||
android:fillColor="#1E1E1E"
|
||||
android:pathData="M10.13,19.07C10.13,14.13 14.13,10.13 19.06,10.13C24,10.13 28,14.13 28,19.07C28,24 24,28 19.06,28C14.13,28 10.13,24 10.13,19.07ZM19.53,17.53L20.48,14.82L19.07,14.32L17.78,17.96L15.34,18.57L15.7,20.03L17.18,19.66L16.23,22.36C16.15,22.59 16.18,22.84 16.33,23.04C16.47,23.24 16.69,23.36 16.94,23.36H22.61V21.86H18L18.93,19.22L21.37,18.61L21.01,17.16L19.53,17.53Z" />
|
||||
</vector>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h28v28h-28z" />
|
||||
<path
|
||||
android:pathData="M8.821,17.869C9.035,16.016 9.741,14.312 10.803,12.892C10.407,13.076 9.929,13.195 9.38,13.241V14H8.552V13.247C6.83,13.134 5.744,12.22 5.738,10.863H7.015C7.077,11.556 7.657,12.04 8.552,12.142V9.483L8.015,9.346C6.67,9.017 5.935,8.234 5.935,7.093C5.935,5.749 6.929,4.852 8.552,4.709V3.902H9.38V4.709C10.941,4.84 11.966,5.766 12.003,7.063H10.744C10.719,6.424 10.176,5.922 9.38,5.826V8.342L9.941,8.467C11.454,8.82 12.163,9.561 12.163,10.768C12.163,11.028 12.13,11.27 12.065,11.493C13.628,10.047 15.64,9.079 17.869,8.822C17.808,3.939 13.831,0 8.935,0C4,0 0,4 0,8.935C0,13.831 3.939,17.808 8.821,17.869ZM17.776,10.222C13.873,10.786 10.786,13.873 10.222,17.776L17.776,10.222ZM8.552,8.174C7.688,8.007 7.225,7.589 7.225,6.991C7.225,6.352 7.781,5.862 8.552,5.82V8.174ZM9.38,12.154V9.632C10.404,9.83 10.88,10.236 10.88,10.905C10.88,11.646 10.33,12.1 9.38,12.154Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd" />
|
||||
<path
|
||||
android:pathData="M10.13,19.066C10.13,14.132 14.13,10.131 19.064,10.131C23.998,10.131 27.999,14.132 27.999,19.066C27.999,24 23.998,28.001 19.064,28.001C14.13,28.001 10.13,24 10.13,19.066ZM19.527,17.526L20.48,14.823L19.066,14.323L17.782,17.962L15.337,18.573L15.701,20.028L17.183,19.658L16.23,22.36C16.149,22.589 16.184,22.844 16.325,23.043C16.465,23.241 16.694,23.36 16.937,23.36H22.61V21.86H17.997L18.928,19.222L21.374,18.61L21.01,17.155L19.527,17.526Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd" />
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -2,11 +2,11 @@ package com.tangem.features.managetokens.component
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface ManageTokensComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(val mode: Mode)
|
||||
data class Params(val userWalletId: UserWalletId?)
|
||||
|
||||
enum class Mode { READ_ONLY, MANAGE, }
|
||||
interface Factory : ComponentFactory<Params, ManageTokensComponent>
|
||||
}
|
||||
|
|
@ -22,8 +22,10 @@ dependencies {
|
|||
implementation(projects.core.featuretoggles)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.manageTokens)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.managetokens.component.impl
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -24,6 +25,8 @@ internal class DefaultManageTokensComponent @AssistedInject constructor(
|
|||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
BackHandler(onBack = state.popBack)
|
||||
|
||||
ManageTokensScreen(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@ import androidx.compose.ui.util.fastForEachIndexed
|
|||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.*
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.CurrencyNetworkUM
|
||||
import com.tangem.features.managetokens.entity.ManageTokensTopBarUM
|
||||
import com.tangem.features.managetokens.entity.ManageTokensUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.ui.ManageTokensScreen
|
||||
import kotlinx.collections.immutable.mutate
|
||||
|
|
@ -47,8 +50,10 @@ internal class PreviewManageTokensComponent : ManageTokensComponent {
|
|||
onActiveChange = ::toggleSearchBar,
|
||||
),
|
||||
hasChanges = false,
|
||||
isLoading = false,
|
||||
onSaveClick = {},
|
||||
isInitialBatchLoading = false,
|
||||
isNextBatchLoading = true,
|
||||
loadMore = { false },
|
||||
saveChanges = {},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -58,7 +63,7 @@ internal class PreviewManageTokensComponent : ManageTokensComponent {
|
|||
initItems()
|
||||
} else {
|
||||
items.filter { currency ->
|
||||
currency.model.name.contains(query, ignoreCase = true)
|
||||
currency.name.contains(query, ignoreCase = true)
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
|
|
@ -96,34 +101,28 @@ internal class PreviewManageTokensComponent : ManageTokensComponent {
|
|||
}.toPersistentList()
|
||||
|
||||
private fun getCustomItem(index: Int) = CurrencyItemUM.Custom(
|
||||
id = index.toString(),
|
||||
model = ChainRowUM(
|
||||
name = "Custom token $index",
|
||||
type = "CT$index",
|
||||
icon = CurrencyIconState.CustomTokenIcon(
|
||||
tint = Color.White,
|
||||
background = Color.Black,
|
||||
topBadgeIconResId = R.drawable.img_eth_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = true,
|
||||
),
|
||||
showCustom = true,
|
||||
id = ManagedCryptoCurrency.ID(index.toString()),
|
||||
name = "Custom token $index",
|
||||
symbol = "CT$index",
|
||||
icon = CurrencyIconState.CustomTokenIcon(
|
||||
tint = Color.White,
|
||||
background = Color.Black,
|
||||
topBadgeIconResId = R.drawable.img_eth_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = true,
|
||||
),
|
||||
onRemoveClick = {},
|
||||
)
|
||||
|
||||
private fun getBasicItem(index: Int) = CurrencyItemUM.Basic(
|
||||
id = index.toString(),
|
||||
model = ChainRowUM(
|
||||
name = "Currency $index",
|
||||
type = "C$index",
|
||||
icon = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.img_btc_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
),
|
||||
showCustom = false,
|
||||
id = ManagedCryptoCurrency.ID(index.toString()),
|
||||
name = "Currency $index",
|
||||
symbol = "C$index",
|
||||
icon = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.img_btc_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
),
|
||||
networks = if (index == 2) {
|
||||
CurrencyItemUM.Basic.NetworksUM.Expanded(getCurrencyNetworks(index))
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
package com.tangem.features.managetokens.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal sealed class CurrencyItemUM {
|
||||
|
||||
abstract val id: String
|
||||
abstract val model: ChainRowUM
|
||||
abstract val id: ManagedCryptoCurrency.ID
|
||||
abstract val name: String
|
||||
abstract val symbol: String
|
||||
abstract val icon: CurrencyIconState
|
||||
|
||||
data class Basic(
|
||||
override val id: String,
|
||||
override val model: ChainRowUM,
|
||||
override val id: ManagedCryptoCurrency.ID,
|
||||
override val name: String,
|
||||
override val symbol: String,
|
||||
override val icon: CurrencyIconState,
|
||||
val networks: NetworksUM,
|
||||
val onExpandClick: () -> Unit,
|
||||
) : CurrencyItemUM() {
|
||||
|
|
@ -29,8 +34,10 @@ internal sealed class CurrencyItemUM {
|
|||
}
|
||||
|
||||
data class Custom(
|
||||
override val id: String,
|
||||
override val model: ChainRowUM,
|
||||
override val id: ManagedCryptoCurrency.ID,
|
||||
override val name: String,
|
||||
override val symbol: String,
|
||||
override val icon: CurrencyIconState,
|
||||
val onRemoveClick: () -> Unit,
|
||||
) : CurrencyItemUM()
|
||||
}
|
||||
|
|
@ -8,26 +8,32 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
internal sealed class ManageTokensUM {
|
||||
|
||||
abstract val popBack: () -> Unit
|
||||
abstract val isLoading: Boolean
|
||||
abstract val isInitialBatchLoading: Boolean
|
||||
abstract val isNextBatchLoading: Boolean
|
||||
abstract val items: ImmutableList<CurrencyItemUM>
|
||||
abstract val topBar: ManageTokensTopBarUM
|
||||
abstract val search: SearchBarUM
|
||||
abstract val loadMore: () -> Boolean
|
||||
|
||||
data class ReadContent(
|
||||
override val popBack: () -> Unit,
|
||||
override val isLoading: Boolean,
|
||||
override val isInitialBatchLoading: Boolean,
|
||||
override val isNextBatchLoading: Boolean,
|
||||
override val items: ImmutableList<CurrencyItemUM>,
|
||||
override val topBar: ManageTokensTopBarUM,
|
||||
override val search: SearchBarUM,
|
||||
override val loadMore: () -> Boolean,
|
||||
) : ManageTokensUM()
|
||||
|
||||
data class ManageContent(
|
||||
override val popBack: () -> Unit,
|
||||
override val isLoading: Boolean,
|
||||
override val isInitialBatchLoading: Boolean,
|
||||
override val isNextBatchLoading: Boolean,
|
||||
override val items: ImmutableList<CurrencyItemUM>,
|
||||
override val topBar: ManageTokensTopBarUM,
|
||||
override val search: SearchBarUM,
|
||||
val onSaveClick: () -> Unit,
|
||||
override val loadMore: () -> Boolean,
|
||||
val saveChanges: () -> Unit,
|
||||
val hasChanges: Boolean,
|
||||
) : ManageTokensUM()
|
||||
|
||||
|
|
@ -35,10 +41,23 @@ internal sealed class ManageTokensUM {
|
|||
search: SearchBarUM = this.search,
|
||||
items: ImmutableList<CurrencyItemUM> = this.items,
|
||||
hasChanges: Boolean = this is ManageContent && this.hasChanges,
|
||||
isInitialBatchLoading: Boolean = this.isInitialBatchLoading,
|
||||
isNextBatchLoading: Boolean = this.isNextBatchLoading,
|
||||
): ManageTokensUM {
|
||||
return when (this) {
|
||||
is ManageContent -> copy(search = search, items = items, hasChanges = hasChanges)
|
||||
is ReadContent -> copy(search = search, items = items)
|
||||
is ManageContent -> copy(
|
||||
search = search,
|
||||
items = items,
|
||||
hasChanges = hasChanges,
|
||||
isInitialBatchLoading = isInitialBatchLoading,
|
||||
isNextBatchLoading = isNextBatchLoading,
|
||||
)
|
||||
is ReadContent -> copy(
|
||||
search = search,
|
||||
items = items,
|
||||
isInitialBatchLoading = isInitialBatchLoading,
|
||||
isNextBatchLoading = isNextBatchLoading,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +1,78 @@
|
|||
package com.tangem.features.managetokens.model
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
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.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.*
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.ManageTokensTopBarUM
|
||||
import com.tangem.features.managetokens.entity.ManageTokensUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.utils.list.ChangedCurrencies
|
||||
import com.tangem.features.managetokens.utils.list.ManageTokensListManager
|
||||
import com.tangem.pagination.PaginationStatus
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
internal class ManageTokensModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val manageTokensListManager: ManageTokensListManager,
|
||||
private val messageSender: UiMessageSender,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: ManageTokensComponent.Params = paramsContainer.require()
|
||||
private val changedItemsIds: MutableSet<String> = mutableSetOf()
|
||||
private var items = initItems()
|
||||
|
||||
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(value = getInitialState(mode = params.mode))
|
||||
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(getInitialState(params.userWalletId))
|
||||
|
||||
private fun getInitialState(mode: ManageTokensComponent.Mode): ManageTokensUM {
|
||||
return when (mode) {
|
||||
ManageTokensComponent.Mode.READ_ONLY -> createReadContentModel()
|
||||
ManageTokensComponent.Mode.MANAGE -> createManageContentModel()
|
||||
init {
|
||||
manageTokensListManager.uiItems
|
||||
.onEach { items -> updateItems(items) }
|
||||
.launchIn(modelScope)
|
||||
|
||||
manageTokensListManager.paginationStatus
|
||||
.onEach { status -> updatePaginationStatus(status) }
|
||||
.launchIn(modelScope)
|
||||
|
||||
combine(
|
||||
manageTokensListManager.currenciesToAdd,
|
||||
manageTokensListManager.currenciesToRemove,
|
||||
::updateChangedItems,
|
||||
).launchIn(modelScope)
|
||||
|
||||
modelScope.launch {
|
||||
manageTokensListManager.launchPagination(params.userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialState(userWalletId: UserWalletId?): ManageTokensUM {
|
||||
return if (userWalletId == null) {
|
||||
createReadContentModel()
|
||||
} else {
|
||||
createManageContentModel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createReadContentModel(): ManageTokensUM.ReadContent {
|
||||
return ManageTokensUM.ReadContent(
|
||||
popBack = router::pop,
|
||||
isLoading = false,
|
||||
items = initItems(),
|
||||
isInitialBatchLoading = true,
|
||||
isNextBatchLoading = false,
|
||||
items = getInitialItems(),
|
||||
topBar = ManageTokensTopBarUM.ReadContent(
|
||||
title = resourceReference(R.string.common_search_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
|
|
@ -59,14 +84,16 @@ internal class ManageTokensModel @Inject constructor(
|
|||
isActive = false,
|
||||
onActiveChange = ::toggleSearchBar,
|
||||
),
|
||||
loadMore = ::loadMoreItems,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createManageContentModel(): ManageTokensUM.ManageContent {
|
||||
return ManageTokensUM.ManageContent(
|
||||
popBack = router::pop,
|
||||
isLoading = false,
|
||||
items = initItems(),
|
||||
isInitialBatchLoading = true,
|
||||
isNextBatchLoading = false,
|
||||
items = getInitialItems(),
|
||||
topBar = ManageTokensTopBarUM.ManageContent(
|
||||
title = resourceReference(id = R.string.main_manage_tokens),
|
||||
onBackButtonClick = router::pop,
|
||||
|
|
@ -82,162 +109,123 @@ internal class ManageTokensModel @Inject constructor(
|
|||
isActive = false,
|
||||
onActiveChange = ::toggleSearchBar,
|
||||
),
|
||||
onSaveClick = ::onSaveClick,
|
||||
hasChanges = false,
|
||||
saveChanges = ::saveChanges,
|
||||
loadMore = ::loadMoreItems,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateItems(items: ImmutableList<CurrencyItemUM>) {
|
||||
state.update { state ->
|
||||
state.copySealed(
|
||||
items = items,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePaginationStatus(status: PaginationStatus<*>) {
|
||||
state.update { state ->
|
||||
when (status) {
|
||||
is PaginationStatus.None,
|
||||
is PaginationStatus.InitialLoading,
|
||||
-> {
|
||||
if (state.search.isActive) {
|
||||
state
|
||||
} else {
|
||||
state.copySealed(
|
||||
isInitialBatchLoading = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
is PaginationStatus.NextBatchLoading -> state.copySealed(
|
||||
isNextBatchLoading = true,
|
||||
)
|
||||
is PaginationStatus.InitialLoadingError -> {
|
||||
val message = SnackbarMessage(
|
||||
message = status.throwable.localizedMessage
|
||||
?.let(::stringReference)
|
||||
?: resourceReference(R.string.common_error),
|
||||
)
|
||||
messageSender.send(message)
|
||||
|
||||
state.copySealed(
|
||||
isInitialBatchLoading = false,
|
||||
isNextBatchLoading = false,
|
||||
)
|
||||
}
|
||||
is PaginationStatus.Paginating,
|
||||
is PaginationStatus.EndOfPagination,
|
||||
-> state.copySealed(
|
||||
isInitialBatchLoading = false,
|
||||
isNextBatchLoading = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateChangedItems(currenciesToAdd: ChangedCurrencies, currenciesToRemove: ChangedCurrencies) {
|
||||
state.update { state ->
|
||||
state.copySealed(
|
||||
hasChanges = currenciesToAdd.isNotEmpty() || currenciesToRemove.isNotEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadMoreItems(): Boolean {
|
||||
val state = state.value
|
||||
if (state.isInitialBatchLoading || state.isNextBatchLoading) return false
|
||||
|
||||
modelScope.launch {
|
||||
manageTokensListManager.loadMore(
|
||||
userWalletId = params.userWalletId,
|
||||
query = state.search.query,
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getInitialItems(): ImmutableList<CurrencyItemUM> {
|
||||
return persistentListOf()
|
||||
}
|
||||
|
||||
private fun onAddCustomToken() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
private fun onSaveClick() {
|
||||
private fun saveChanges() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun searchCurrencies(query: String) {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
val newItems = if (query.isBlank()) {
|
||||
initItems()
|
||||
} else {
|
||||
state.value.items.filter { currency ->
|
||||
currency.model.name.contains(query, ignoreCase = true)
|
||||
}.toPersistentList()
|
||||
}
|
||||
state.update { state ->
|
||||
state.copySealed(search = state.search.copy(query = query), items = newItems)
|
||||
state.copySealed(
|
||||
search = state.search.copy(
|
||||
query = query,
|
||||
isActive = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
modelScope.launch {
|
||||
manageTokensListManager.search(params.userWalletId, query)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleSearchBar(isActive: Boolean) {
|
||||
state.update { state ->
|
||||
state.copySealed(
|
||||
search = state.search.copy(isActive = isActive),
|
||||
search = state.search.copy(
|
||||
query = if (isActive) state.search.query else "",
|
||||
isActive = isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initItems() = List(size = 30) { index ->
|
||||
if (index < 2) {
|
||||
getCustomItem(index)
|
||||
} else {
|
||||
getBasicItem(index)
|
||||
}
|
||||
}.toPersistentList()
|
||||
|
||||
private fun getCustomItem(index: Int) = CurrencyItemUM.Custom(
|
||||
id = index.toString(),
|
||||
model = ChainRowUM(
|
||||
name = "Custom token $index",
|
||||
type = "CT$index",
|
||||
icon = CurrencyIconState.CustomTokenIcon(
|
||||
tint = Color.White,
|
||||
background = Color.Black,
|
||||
topBadgeIconResId = R.drawable.img_eth_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = true,
|
||||
),
|
||||
showCustom = true,
|
||||
),
|
||||
onRemoveClick = {},
|
||||
)
|
||||
|
||||
private fun getBasicItem(index: Int) = CurrencyItemUM.Basic(
|
||||
id = index.toString(),
|
||||
model = ChainRowUM(
|
||||
name = "Currency $index",
|
||||
type = "C$index",
|
||||
icon = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.img_btc_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
),
|
||||
showCustom = false,
|
||||
),
|
||||
networks = if (index == 2) {
|
||||
CurrencyItemUM.Basic.NetworksUM.Expanded(getCurrencyNetworks(index))
|
||||
} else {
|
||||
CurrencyItemUM.Basic.NetworksUM.Collapsed
|
||||
},
|
||||
onExpandClick = { toggleCurrency(index) },
|
||||
)
|
||||
|
||||
private fun getCurrencyNetworks(currencyIndex: Int) = List(size = 3) { networkIndex ->
|
||||
CurrencyNetworkUM(
|
||||
id = Network.ID(networkIndex.toString()),
|
||||
name = "NETWORK$networkIndex",
|
||||
type = "N$networkIndex",
|
||||
iconResId = R.drawable.ic_eth_16,
|
||||
isMainNetwork = networkIndex == 0,
|
||||
isSelected = false,
|
||||
onSelectedStateChange = { toggleNetwork(currencyIndex, networkIndex, isSelected = it) },
|
||||
)
|
||||
}.toImmutableList()
|
||||
|
||||
private fun toggleCurrency(index: Int) {
|
||||
val updatedItem = when (val item = items[index]) {
|
||||
is CurrencyItemUM.Basic -> item.copy(
|
||||
networks = if (item.networks is CurrencyItemUM.Basic.NetworksUM.Collapsed) {
|
||||
CurrencyItemUM.Basic.NetworksUM.Expanded(getCurrencyNetworks(index))
|
||||
} else {
|
||||
CurrencyItemUM.Basic.NetworksUM.Collapsed
|
||||
},
|
||||
)
|
||||
is CurrencyItemUM.Custom -> return
|
||||
}
|
||||
|
||||
state.update { state ->
|
||||
items = items.mutate {
|
||||
it[index] = updatedItem
|
||||
modelScope.launch {
|
||||
if (!isActive) {
|
||||
manageTokensListManager.reload(params.userWalletId)
|
||||
}
|
||||
state.copySealed(items = items)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleNetwork(currencyIndex: Int, networkIndex: Int, isSelected: Boolean) {
|
||||
val updatedItem = when (val item = items[currencyIndex]) {
|
||||
is CurrencyItemUM.Basic -> {
|
||||
val updatedNetworks = (item.networks as? CurrencyItemUM.Basic.NetworksUM.Expanded)
|
||||
?.copy(
|
||||
networks = item.networks.networks.toPersistentList().mutate {
|
||||
it.fastForEachIndexed { index, network ->
|
||||
if (index == networkIndex) {
|
||||
it[index] = network.copy(
|
||||
iconResId = if (isSelected) {
|
||||
R.drawable.img_eth_22
|
||||
} else {
|
||||
R.drawable.ic_eth_16
|
||||
},
|
||||
isSelected = isSelected,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
?: return
|
||||
|
||||
item.copy(networks = updatedNetworks)
|
||||
}
|
||||
is CurrencyItemUM.Custom -> return
|
||||
}
|
||||
|
||||
val id = "${currencyIndex}_$networkIndex"
|
||||
if (changedItemsIds.contains(id)) {
|
||||
changedItemsIds.remove(id)
|
||||
} else {
|
||||
changedItemsIds.add(id)
|
||||
}
|
||||
|
||||
state.update { state ->
|
||||
items = items.mutate {
|
||||
it[currencyIndex] = updatedItem
|
||||
}
|
||||
state.copySealed(
|
||||
items = items,
|
||||
hasChanges = changedItemsIds.isNotEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ private fun NetworkItem(model: CurrencyNetworkUM, modifier: Modifier = Modifier)
|
|||
icon = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = model.iconResId,
|
||||
isGrayscale = false,
|
||||
isGrayscale = !model.isSelected,
|
||||
showCustomBadge = false,
|
||||
),
|
||||
showCustom = false,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
package com.tangem.features.managetokens.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -20,6 +19,12 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -33,14 +38,17 @@ import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
|||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.list.InfiniteListHandler
|
||||
import com.tangem.core.ui.components.rows.ArrowRow
|
||||
import com.tangem.core.ui.components.rows.BlockchainRow
|
||||
import com.tangem.core.ui.components.rows.ChainRow
|
||||
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
import com.tangem.features.managetokens.component.preview.PreviewManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM.Basic.NetworksUM
|
||||
|
|
@ -51,18 +59,30 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
|
||||
private const val CHEVRON_ROTATION_EXPANDED = 180f
|
||||
private const val CHEVRON_ROTATION_COLLAPSED = 0f
|
||||
private const val LOAD_ITEMS_BUFFER = 10
|
||||
|
||||
@Composable
|
||||
internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modifier) {
|
||||
BackHandler(onBack = state.popBack)
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val nestedScrollConnection = remember {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
keyboardController?.hide()
|
||||
|
||||
return super.onPreScroll(available, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
modifier = modifier.nestedScroll(nestedScrollConnection),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
contentWindowInsets = WindowInsetsZero,
|
||||
topBar = {
|
||||
ManageTokensTopBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
topBar = state.topBar,
|
||||
search = state.search,
|
||||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
|
|
@ -70,10 +90,7 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
|
|||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
search = state.search,
|
||||
items = state.items,
|
||||
isLoading = state.isLoading,
|
||||
hasChanges = state is ManageTokensUM.ManageContent && state.hasChanges,
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
|
|
@ -81,10 +98,11 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
|
|||
if (state is ManageTokensUM.ManageContent) {
|
||||
SaveChangesButton(
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
isVisible = state.hasChanges,
|
||||
onClick = state.onSaveClick,
|
||||
onClick = state.saveChanges,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -92,16 +110,26 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ManageTokensTopBar(topBar: ManageTokensTopBarUM, modifier: Modifier = Modifier) {
|
||||
TangemTopAppBar(
|
||||
modifier = modifier,
|
||||
title = topBar.title.resolveReference(),
|
||||
startButton = TopAppBarButtonUM.Back(topBar.onBackButtonClick),
|
||||
endButton = when (topBar) {
|
||||
is ManageTokensTopBarUM.ManageContent -> topBar.endButton
|
||||
is ManageTokensTopBarUM.ReadContent -> null
|
||||
},
|
||||
)
|
||||
private fun ManageTokensTopBar(topBar: ManageTokensTopBarUM, search: SearchBarUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.background(TangemTheme.colors.background.primary),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
title = topBar.title.resolveReference(),
|
||||
startButton = TopAppBarButtonUM.Back(topBar.onBackButtonClick),
|
||||
endButton = when (topBar) {
|
||||
is ManageTokensTopBarUM.ManageContent -> topBar.endButton
|
||||
is ManageTokensTopBarUM.ReadContent -> null
|
||||
},
|
||||
)
|
||||
SearchBar(
|
||||
modifier = Modifier
|
||||
.padding(bottom = TangemTheme.dimens.spacing12)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
state = search,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -122,80 +150,58 @@ private fun SaveChangesButton(isVisible: Boolean, onClick: () -> Unit, modifier:
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingContent() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = TangemTheme.colors.background.primary),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(color = TangemTheme.colors.icon.accent)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(
|
||||
search: SearchBarUM,
|
||||
items: ImmutableList<CurrencyItemUM>,
|
||||
isLoading: Boolean,
|
||||
hasChanges: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
private fun Content(state: ManageTokensUM, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
Currencies(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
items = items,
|
||||
search = search,
|
||||
items = state.items,
|
||||
showLoadingItem = state.isNextBatchLoading,
|
||||
onLoadMore = state.loadMore,
|
||||
isEditable = state is ManageTokensUM.ManageContent,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth(),
|
||||
visible = hasChanges,
|
||||
label = "bottom_fade_visibility",
|
||||
) {
|
||||
BottomFade()
|
||||
}
|
||||
BottomFade(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
|
||||
Crossfade(targetState = isLoading, label = "ManageTokensLoadingContent") {
|
||||
if (it) {
|
||||
LoadingContent()
|
||||
Crossfade(targetState = state.isInitialBatchLoading, label = "ManageTokensLoadingContent") { isVisible ->
|
||||
if (isVisible) {
|
||||
ProgressIndicator(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun Currencies(items: ImmutableList<CurrencyItemUM>, search: SearchBarUM, modifier: Modifier = Modifier) {
|
||||
private fun Currencies(
|
||||
items: ImmutableList<CurrencyItemUM>,
|
||||
showLoadingItem: Boolean,
|
||||
isEditable: Boolean,
|
||||
onLoadMore: () -> Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val bottomBarHeight = with(LocalDensity.current) {
|
||||
WindowInsets.systemBars.getBottom(density = this).toDp()
|
||||
}
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(
|
||||
bottom = TangemTheme.dimens.spacing76 + bottomBarHeight,
|
||||
),
|
||||
) {
|
||||
stickyHeader(key = "search") {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing12,
|
||||
)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
SearchBar(state = search)
|
||||
}
|
||||
}
|
||||
|
||||
items(
|
||||
items = items,
|
||||
key = CurrencyItemUM::id,
|
||||
key = { it.id.value },
|
||||
) { item ->
|
||||
when (item) {
|
||||
is CurrencyItemUM.Basic -> {
|
||||
BasicCurrencyItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
item = item,
|
||||
isEditable = isEditable,
|
||||
)
|
||||
}
|
||||
is CurrencyItemUM.Custom -> {
|
||||
|
|
@ -206,6 +212,32 @@ private fun Currencies(items: ImmutableList<CurrencyItemUM>, search: SearchBarUM
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showLoadingItem) {
|
||||
item(key = "loading_item") {
|
||||
ProgressIndicator(
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InfiniteListHandler(
|
||||
listState = listState,
|
||||
buffer = LOAD_ITEMS_BUFFER,
|
||||
onLoadMore = onLoadMore,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProgressIndicator(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.background(color = TangemTheme.colors.background.primary),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(color = TangemTheme.colors.icon.informative)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +245,14 @@ private fun Currencies(items: ImmutableList<CurrencyItemUM>, search: SearchBarUM
|
|||
private fun CustomCurrencyItem(item: CurrencyItemUM.Custom, modifier: Modifier = Modifier) {
|
||||
ChainRow(
|
||||
modifier = modifier,
|
||||
model = item.model,
|
||||
model = with(item) {
|
||||
ChainRowUM(
|
||||
name = name,
|
||||
type = symbol,
|
||||
icon = icon,
|
||||
showCustom = true,
|
||||
)
|
||||
},
|
||||
action = {
|
||||
SecondarySmallButton(
|
||||
config = SmallButtonConfig(
|
||||
|
|
@ -226,13 +265,20 @@ private fun CustomCurrencyItem(item: CurrencyItemUM.Custom, modifier: Modifier =
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun BasicCurrencyItem(item: CurrencyItemUM.Basic, modifier: Modifier = Modifier) {
|
||||
private fun BasicCurrencyItem(item: CurrencyItemUM.Basic, isEditable: Boolean, modifier: Modifier = Modifier) {
|
||||
val isExpanded = item.networks is NetworksUM.Expanded
|
||||
|
||||
Column(modifier = modifier) {
|
||||
ChainRow(
|
||||
modifier = Modifier.clickable(onClick = item.onExpandClick),
|
||||
model = item.model,
|
||||
model = with(item) {
|
||||
ChainRowUM(
|
||||
name = name,
|
||||
type = symbol,
|
||||
icon = icon,
|
||||
showCustom = false,
|
||||
)
|
||||
},
|
||||
action = {
|
||||
val rotation by animateFloatAsState(
|
||||
targetValue = if (isExpanded) {
|
||||
|
|
@ -260,13 +306,19 @@ private fun BasicCurrencyItem(item: CurrencyItemUM.Basic, modifier: Modifier = M
|
|||
end = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
networks = item.networks,
|
||||
currencyId = item.id,
|
||||
currencyId = item.id.value,
|
||||
isEditable = isEditable,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NetworksList(networks: NetworksUM, currencyId: String, modifier: Modifier = Modifier) {
|
||||
private fun NetworksList(
|
||||
networks: NetworksUM,
|
||||
currencyId: String,
|
||||
isEditable: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier,
|
||||
visible = networks is NetworksUM.Expanded,
|
||||
|
|
@ -297,10 +349,12 @@ private fun NetworksList(networks: NetworksUM, currencyId: String, modifier: Mod
|
|||
)
|
||||
},
|
||||
action = {
|
||||
TangemSwitch(
|
||||
checked = network.isSelected,
|
||||
onCheckedChange = network.onSelectedStateChange,
|
||||
)
|
||||
if (isEditable) {
|
||||
TangemSwitch(
|
||||
checked = network.isSelected,
|
||||
onCheckedChange = network.onSelectedStateChange,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.features.managetokens.ui.dialog
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun HasLinkedTokensWarning(currency: ManagedCryptoCurrency, network: Network, onDismiss: () -> Unit) {
|
||||
BasicDialog(
|
||||
title = stringResource(
|
||||
R.string.token_details_unable_hide_alert_title,
|
||||
currency.name,
|
||||
),
|
||||
message = stringResource(
|
||||
R.string.token_details_unable_hide_alert_message,
|
||||
currency.name,
|
||||
currency.symbol,
|
||||
network.name,
|
||||
),
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(R.string.common_ok),
|
||||
onClick = onDismiss,
|
||||
),
|
||||
onDismissDialog = onDismiss,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.features.managetokens.ui.dialog
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun HideTokenWarning(currency: ManagedCryptoCurrency, onConfirm: () -> Unit, onDismiss: () -> Unit) {
|
||||
BasicDialog(
|
||||
title = stringResource(
|
||||
R.string.token_details_hide_alert_title,
|
||||
currency.name,
|
||||
),
|
||||
message = stringResource(R.string.token_details_hide_alert_message),
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(R.string.token_details_hide_alert_hide),
|
||||
warning = true,
|
||||
onClick = onConfirm,
|
||||
),
|
||||
dismissButton = DialogButtonUM(
|
||||
title = stringResource(R.string.common_cancel),
|
||||
onClick = onDismiss,
|
||||
),
|
||||
onDismissDialog = onDismiss,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.features.managetokens.utils.list
|
||||
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
internal typealias ChangedCurrencies = Map<ManagedCryptoCurrency.ID, Set<Network.ID>>
|
||||
|
||||
internal class ChangedCurrenciesManager {
|
||||
|
||||
val currenciesToAdd: MutableStateFlow<ChangedCurrencies> = MutableStateFlow(emptyMap())
|
||||
val currenciesToRemove: MutableStateFlow<ChangedCurrencies> = MutableStateFlow(emptyMap())
|
||||
|
||||
fun addCurrency(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) {
|
||||
updateChangedItems(currencyId, networkId, currenciesToRemove, currenciesToAdd)
|
||||
}
|
||||
|
||||
fun removeCurrency(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) {
|
||||
updateChangedItems(currencyId, networkId, currenciesToAdd, currenciesToRemove)
|
||||
}
|
||||
|
||||
fun containsCurrency(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID): Boolean {
|
||||
return networkId in currenciesToAdd.value[currencyId].orEmpty() ||
|
||||
networkId in currenciesToRemove.value[currencyId].orEmpty()
|
||||
}
|
||||
|
||||
private fun updateChangedItems(
|
||||
currencyId: ManagedCryptoCurrency.ID,
|
||||
networkId: Network.ID,
|
||||
removeFromIfPresent: MutableStateFlow<ChangedCurrencies>,
|
||||
addToIfNotPresent: MutableStateFlow<ChangedCurrencies>,
|
||||
) {
|
||||
val present = removeFromIfPresent.value[currencyId].orEmpty()
|
||||
|
||||
if (networkId in present) {
|
||||
removeFromIfPresent.update { items ->
|
||||
items.toMutableMap().apply {
|
||||
val ids = present - networkId
|
||||
|
||||
if (ids.isEmpty()) {
|
||||
remove(currencyId)
|
||||
} else {
|
||||
set(currencyId, ids)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addToIfNotPresent.update { items ->
|
||||
val alreadyAdded = items[currencyId] ?: emptySet()
|
||||
if (networkId in alreadyAdded) {
|
||||
return@update items
|
||||
}
|
||||
|
||||
items + (currencyId to alreadyAdded + networkId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
package com.tangem.features.managetokens.utils.list
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.managetokens.GetManagedTokensUseCase
|
||||
import com.tangem.domain.managetokens.model.ManageTokensListBatchingContext
|
||||
import com.tangem.domain.managetokens.model.ManageTokensListConfig
|
||||
import com.tangem.domain.managetokens.model.ManageTokensUpdateAction
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.tokens.CheckHasLinkedTokensUseCase
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.pagination.BatchAction
|
||||
import com.tangem.pagination.BatchListState
|
||||
import com.tangem.pagination.PaginationStatus
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
internal class ManageTokensListManager @Inject constructor(
|
||||
private val getManagedTokensUseCase: GetManagedTokensUseCase,
|
||||
private val checkHasLinkedTokensUseCase: CheckHasLinkedTokensUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ManageTokensUiActions {
|
||||
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
private val jobHolder = JobHolder()
|
||||
private val actionsFlow: MutableSharedFlow<ManageTokensBatchAction> = MutableSharedFlow(
|
||||
replay = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
)
|
||||
|
||||
private val state: MutableStateFlow<ManageTokensListState> = MutableStateFlow(ManageTokensListState())
|
||||
|
||||
private val changedCurrenciesManager = ChangedCurrenciesManager()
|
||||
private val uiManager = ManageTokensUiManager(
|
||||
state = state,
|
||||
messageSender = messageSender,
|
||||
dispatchers = dispatchers,
|
||||
actions = this,
|
||||
scopeProvider = Provider { scope },
|
||||
)
|
||||
|
||||
val currenciesToAdd: StateFlow<ChangedCurrencies> = changedCurrenciesManager.currenciesToAdd
|
||||
val currenciesToRemove: StateFlow<ChangedCurrencies> = changedCurrenciesManager.currenciesToRemove
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val paginationStatus: Flow<PaginationStatus<*>> = state
|
||||
.mapLatest { it.status }
|
||||
.distinctUntilChanged()
|
||||
val uiItems: Flow<ImmutableList<CurrencyItemUM>> = uiManager.items
|
||||
|
||||
suspend fun launchPagination(userWalletId: UserWalletId?) = coroutineScope {
|
||||
scope = this
|
||||
|
||||
val batchFlow = getManagedTokensUseCase(
|
||||
context = ManageTokensListBatchingContext(
|
||||
actionsFlow = actionsFlow,
|
||||
coroutineScope = this,
|
||||
),
|
||||
)
|
||||
|
||||
batchFlow.state
|
||||
.onEach { state -> updateState(state, userWalletId) }
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(scope = this)
|
||||
.saveIn(jobHolder)
|
||||
|
||||
// Initial load
|
||||
reload(userWalletId)
|
||||
}
|
||||
|
||||
suspend fun reload(userWalletId: UserWalletId?) {
|
||||
actionsFlow.emit(
|
||||
BatchAction.Reload(
|
||||
requestParams = ManageTokensListConfig(userWalletId, searchText = null),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun loadMore(userWalletId: UserWalletId?, query: String) {
|
||||
actionsFlow.emit(
|
||||
BatchAction.LoadMore(
|
||||
requestParams = ManageTokensListConfig(userWalletId, query),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun search(userWalletId: UserWalletId?, query: String) {
|
||||
state.value = ManageTokensListState()
|
||||
actionsFlow.emit(
|
||||
BatchAction.Reload(
|
||||
requestParams = ManageTokensListConfig(
|
||||
userWalletId = userWalletId,
|
||||
searchText = query,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateState(
|
||||
batchListState: BatchListState<Int, List<ManagedCryptoCurrency>>,
|
||||
userWalletId: UserWalletId?,
|
||||
) {
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
status = batchListState.status,
|
||||
)
|
||||
}
|
||||
|
||||
state.update { state ->
|
||||
val newBatches = batchListState.data
|
||||
val currentBatches = state.currencyBatches
|
||||
|
||||
// Distinct until changed
|
||||
if (newBatches.size == currentBatches.size &&
|
||||
newBatches.map { it.key } == currentBatches.map { it.key } &&
|
||||
newBatches.flatMap { it.data } == currentBatches.flatMap { it.data }
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
val canEditItems = userWalletId != null
|
||||
state.copy(
|
||||
userWalletId = userWalletId,
|
||||
currencyBatches = newBatches,
|
||||
uiBatches = uiManager.createOrUpdateUiBatches(newBatches, canEditItems),
|
||||
canEditItems = canEditItems,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) {
|
||||
changedCurrenciesManager.addCurrency(currencyId, networkId)
|
||||
|
||||
sendSelectCurrencyAction(batchKey, currencyId, networkId, isSelected = true)
|
||||
}
|
||||
|
||||
override fun removeCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID) {
|
||||
changedCurrenciesManager.removeCurrency(currencyId, networkId)
|
||||
|
||||
sendSelectCurrencyAction(batchKey, currencyId, networkId, isSelected = false)
|
||||
}
|
||||
|
||||
override fun checkNeedToShowRemoveNetworkWarning(
|
||||
currencyId: ManagedCryptoCurrency.ID,
|
||||
networkId: Network.ID,
|
||||
): Boolean = !changedCurrenciesManager.containsCurrency(currencyId, networkId)
|
||||
|
||||
private fun sendSelectCurrencyAction(
|
||||
batchKey: Int,
|
||||
currencyId: ManagedCryptoCurrency.ID,
|
||||
networkId: Network.ID,
|
||||
isSelected: Boolean,
|
||||
) {
|
||||
val request = ManageTokensUpdateAction.AddCurrency(
|
||||
currencyId = currencyId,
|
||||
networkId = networkId,
|
||||
isSelected = isSelected,
|
||||
)
|
||||
val action = BatchAction.UpdateBatches(
|
||||
keys = setOf(batchKey),
|
||||
async = true,
|
||||
updateRequest = request,
|
||||
)
|
||||
|
||||
actionsFlow.tryEmit(action)
|
||||
}
|
||||
|
||||
override suspend fun checkHasLinkedTokens(userWalletId: UserWalletId, network: Network): Boolean {
|
||||
return checkHasLinkedTokensUseCase(userWalletId, network).getOrElse {
|
||||
Timber.e(
|
||||
it,
|
||||
"""
|
||||
Failed to check linked tokens
|
||||
|- User wallet ID: $userWalletId
|
||||
|- Network ID: ${network.id}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
val message = SnackbarMessage(
|
||||
message = it.localizedMessage
|
||||
?.let(::stringReference)
|
||||
?: resourceReference(R.string.common_error),
|
||||
)
|
||||
messageSender.send(message)
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.features.managetokens.utils.list
|
||||
|
||||
import com.tangem.domain.managetokens.model.ManageTokensListConfig
|
||||
import com.tangem.domain.managetokens.model.ManageTokensUpdateAction
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.pagination.Batch
|
||||
import com.tangem.pagination.BatchAction
|
||||
import com.tangem.pagination.PaginationStatus
|
||||
|
||||
internal typealias ManageTokensBatchAction = BatchAction<Int, ManageTokensListConfig, ManageTokensUpdateAction>
|
||||
|
||||
internal data class ManageTokensListState(
|
||||
val status: PaginationStatus<*> = PaginationStatus.None,
|
||||
val userWalletId: UserWalletId? = null,
|
||||
val uiBatches: List<Batch<Int, List<CurrencyItemUM>>> = mutableListOf(),
|
||||
val currencyBatches: List<Batch<Int, List<ManagedCryptoCurrency>>> = mutableListOf(),
|
||||
val canEditItems: Boolean = true,
|
||||
) {
|
||||
|
||||
fun batchIndexByCurrencyId(currencyId: ManagedCryptoCurrency.ID): Int {
|
||||
return currencyBatches
|
||||
.indexOfFirst { batch -> batch.data.any { it.id == currencyId } }
|
||||
.takeIf { it != -1 }
|
||||
?: error("Batch with currency '$currencyId' not found")
|
||||
}
|
||||
|
||||
fun updateUiBatchesItem(
|
||||
indexToBatch: Pair<Int, Batch<Int, List<CurrencyItemUM>>>,
|
||||
indexToItem: Pair<Int, CurrencyItemUM>,
|
||||
): ManageTokensListState {
|
||||
val updatedUiBatch = indexToBatch.second.copy(
|
||||
data = indexToBatch.second.data.toMutableList().apply {
|
||||
set(indexToItem.first, indexToItem.second)
|
||||
},
|
||||
)
|
||||
|
||||
return copy(
|
||||
uiBatches = uiBatches.toMutableList().apply {
|
||||
set(indexToBatch.first, updatedUiBatch)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.managetokens.utils.list
|
||||
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
internal interface ManageTokensUiActions {
|
||||
|
||||
fun addCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID)
|
||||
|
||||
fun removeCurrency(batchKey: Int, currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID)
|
||||
|
||||
fun checkNeedToShowRemoveNetworkWarning(currencyId: ManagedCryptoCurrency.ID, networkId: Network.ID): Boolean
|
||||
|
||||
suspend fun checkHasLinkedTokens(userWalletId: UserWalletId, network: Network): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
package com.tangem.features.managetokens.utils.list
|
||||
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.message.ContentMessage
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.ui.dialog.HasLinkedTokensWarning
|
||||
import com.tangem.features.managetokens.ui.dialog.HideTokenWarning
|
||||
import com.tangem.features.managetokens.utils.mapper.toUiModel
|
||||
import com.tangem.features.managetokens.utils.ui.toggleExpanded
|
||||
import com.tangem.features.managetokens.utils.ui.update
|
||||
import com.tangem.pagination.Batch
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class ManageTokensUiManager(
|
||||
private val state: MutableStateFlow<ManageTokensListState>,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val scopeProvider: Provider<CoroutineScope>,
|
||||
private val actions: ManageTokensUiActions,
|
||||
) {
|
||||
|
||||
private val scope: CoroutineScope
|
||||
get() = scopeProvider()
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val items: Flow<ImmutableList<CurrencyItemUM>> = state
|
||||
.mapLatest { state ->
|
||||
state.uiBatches.asSequence()
|
||||
.flatMap { it.data }
|
||||
.toImmutableList()
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
fun createOrUpdateUiBatches(
|
||||
newCurrencyBatches: List<Batch<Int, List<ManagedCryptoCurrency>>>,
|
||||
canEditItems: Boolean,
|
||||
): List<Batch<Int, List<CurrencyItemUM>>> {
|
||||
val currentUiBatches = state.value.uiBatches
|
||||
val batches = currentUiBatches.toMutableList()
|
||||
|
||||
newCurrencyBatches.forEach { (key, data) ->
|
||||
val indexToUpdate = currentUiBatches.indexOfFirst { it.key == key }
|
||||
|
||||
if (indexToUpdate == -1) {
|
||||
val newBatch = Batch(
|
||||
key = key,
|
||||
data = data.map { item ->
|
||||
item.toUiModel(
|
||||
isEditable = canEditItems,
|
||||
onRemoveCustomCurrencyClick = ::removeCustomCurrency,
|
||||
onExpandNetworksClick = ::toggleCurrencyNetworksVisibility,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
batches.add(newBatch)
|
||||
} else {
|
||||
val uiBatchToUpdate = currentUiBatches[indexToUpdate]
|
||||
|
||||
if (uiBatchToUpdate.data == data) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val currentCurrencyBatches = state.value.currencyBatches
|
||||
val currencyBatch = currentCurrencyBatches[indexToUpdate]
|
||||
val updatedBatch = uiBatchToUpdate.copy(
|
||||
data = data.mapIndexed { index, item ->
|
||||
if (item == currencyBatch.data[index]) {
|
||||
return@mapIndexed uiBatchToUpdate.data[index]
|
||||
}
|
||||
|
||||
val previousUiItem = uiBatchToUpdate.data.getOrNull(index)
|
||||
if (previousUiItem == null || previousUiItem.id != item.id) {
|
||||
item.toUiModel(
|
||||
isEditable = canEditItems,
|
||||
onRemoveCustomCurrencyClick = ::removeCustomCurrency,
|
||||
onExpandNetworksClick = ::toggleCurrencyNetworksVisibility,
|
||||
)
|
||||
} else {
|
||||
previousUiItem.update(item)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
batches[indexToUpdate] = updatedBatch
|
||||
}
|
||||
}
|
||||
|
||||
return batches
|
||||
}
|
||||
|
||||
private fun removeCustomCurrency(currency: ManagedCryptoCurrency.Custom) = scope.launch(dispatchers.default) {
|
||||
showRemoveNetworkWarning(
|
||||
currency = currency,
|
||||
network = currency.network,
|
||||
isCoin = currency is ManagedCryptoCurrency.Custom.Coin,
|
||||
onConfirm = {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun toggleCurrencyNetworksVisibility(currency: ManagedCryptoCurrency.Token) = scope.launch(
|
||||
dispatchers.default,
|
||||
) {
|
||||
state.update { batches ->
|
||||
val batchIndex = batches.batchIndexByCurrencyId(currency.id)
|
||||
val currencyBatch = batches.currencyBatches[batchIndex]
|
||||
val currencyIndex = currencyBatch.currencyIndexById(currency.id)
|
||||
|
||||
val uiBatch = batches.uiBatches[batchIndex]
|
||||
val updatedUiItem = uiBatch.data[currencyIndex].toggleExpanded(
|
||||
currency = currencyBatch.data[currencyIndex],
|
||||
isEditable = batches.canEditItems,
|
||||
onSelectCurrencyNetwork = { networkId, isSelected ->
|
||||
selectNetwork(currencyBatch.key, currency, networkId, isSelected)
|
||||
},
|
||||
)
|
||||
|
||||
batches.updateUiBatchesItem(
|
||||
indexToBatch = batchIndex to uiBatch,
|
||||
indexToItem = currencyIndex to updatedUiItem,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectNetwork(
|
||||
batchKey: Int,
|
||||
currency: ManagedCryptoCurrency,
|
||||
source: ManagedCryptoCurrency.SourceNetwork,
|
||||
isSelected: Boolean,
|
||||
) = scope.launch(dispatchers.default) {
|
||||
if (currency !is ManagedCryptoCurrency.Token) return@launch
|
||||
|
||||
if (isSelected) {
|
||||
actions.addCurrency(batchKey, currency.id, source.id)
|
||||
} else {
|
||||
if (actions.checkNeedToShowRemoveNetworkWarning(currency.id, source.id)) {
|
||||
showRemoveNetworkWarning(
|
||||
currency = currency,
|
||||
network = source.network,
|
||||
isCoin = source is ManagedCryptoCurrency.SourceNetwork.Main,
|
||||
onConfirm = {
|
||||
actions.removeCurrency(batchKey, currency.id, source.id)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
actions.removeCurrency(batchKey, currency.id, source.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun showRemoveNetworkWarning(
|
||||
currency: ManagedCryptoCurrency,
|
||||
network: Network,
|
||||
isCoin: Boolean,
|
||||
onConfirm: () -> Unit,
|
||||
) {
|
||||
val userWalletId = state.value.userWalletId
|
||||
val hasLinkedTokens = if (userWalletId == null || !isCoin) {
|
||||
false
|
||||
} else {
|
||||
actions.checkHasLinkedTokens(userWalletId, network)
|
||||
}
|
||||
|
||||
val message = ContentMessage { onDismiss ->
|
||||
if (hasLinkedTokens) {
|
||||
HasLinkedTokensWarning(
|
||||
currency = currency,
|
||||
network = network,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
} else {
|
||||
HideTokenWarning(
|
||||
currency = currency,
|
||||
onConfirm = {
|
||||
onConfirm()
|
||||
onDismiss()
|
||||
},
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
private fun Batch<Int, List<ManagedCryptoCurrency>>.currencyIndexById(id: ManagedCryptoCurrency.ID): Int {
|
||||
return data
|
||||
.indexOfFirst { it.id == id }
|
||||
.takeIf { it != -1 }
|
||||
?: error("Currency with currency '$id' not found in batch #$key")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.tangem.features.managetokens.utils.mapper
|
||||
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.getTintForTokenIcon
|
||||
import com.tangem.core.ui.extensions.tryGetBackgroundForTokenIcon
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM.Basic.NetworksUM
|
||||
import com.tangem.features.managetokens.utils.ui.getIconRes
|
||||
|
||||
internal fun ManagedCryptoCurrency.toUiModel(
|
||||
isEditable: Boolean,
|
||||
onExpandNetworksClick: (ManagedCryptoCurrency.Token) -> Unit,
|
||||
onRemoveCustomCurrencyClick: (ManagedCryptoCurrency.Custom) -> Unit,
|
||||
): CurrencyItemUM = when (this) {
|
||||
is ManagedCryptoCurrency.Custom -> toUiModel(onRemoveCustomCurrencyClick)
|
||||
is ManagedCryptoCurrency.Token -> toUiModel(isEditable, onExpandNetworksClick)
|
||||
}
|
||||
|
||||
private fun ManagedCryptoCurrency.Custom.toUiModel(
|
||||
onRemoveCustomCurrency: (ManagedCryptoCurrency.Custom) -> Unit,
|
||||
): CurrencyItemUM = CurrencyItemUM.Custom(
|
||||
id = id,
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
icon = when (this) {
|
||||
is ManagedCryptoCurrency.Custom.Coin -> {
|
||||
CurrencyIconState.CoinIcon(
|
||||
url = iconUrl,
|
||||
fallbackResId = network.id.getIconRes(isColored = true),
|
||||
isGrayscale = false,
|
||||
showCustomBadge = true,
|
||||
)
|
||||
}
|
||||
is ManagedCryptoCurrency.Custom.Token -> {
|
||||
val background = tryGetBackgroundForTokenIcon(contractAddress)
|
||||
|
||||
CurrencyIconState.TokenIcon(
|
||||
url = iconUrl,
|
||||
fallbackBackground = background,
|
||||
fallbackTint = getTintForTokenIcon(background),
|
||||
topBadgeIconResId = network.id.getIconRes(isColored = true),
|
||||
isGrayscale = false,
|
||||
showCustomBadge = true,
|
||||
)
|
||||
}
|
||||
},
|
||||
onRemoveClick = {
|
||||
onRemoveCustomCurrency(this)
|
||||
},
|
||||
)
|
||||
|
||||
private fun ManagedCryptoCurrency.Token.toUiModel(
|
||||
isEditable: Boolean,
|
||||
onExpandNetworksClick: (ManagedCryptoCurrency.Token) -> Unit,
|
||||
): CurrencyItemUM {
|
||||
val background = TangemColorPalette.Black
|
||||
|
||||
return CurrencyItemUM.Basic(
|
||||
id = id,
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
icon = CurrencyIconState.TokenIcon(
|
||||
url = iconUrl,
|
||||
topBadgeIconResId = null,
|
||||
isGrayscale = if (isEditable) !isAdded else false,
|
||||
showCustomBadge = false,
|
||||
fallbackTint = getTintForTokenIcon(background),
|
||||
fallbackBackground = background,
|
||||
),
|
||||
networks = NetworksUM.Collapsed,
|
||||
onExpandClick = {
|
||||
onExpandNetworksClick(this)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.features.managetokens.utils.mapper
|
||||
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM.Basic.NetworksUM
|
||||
import com.tangem.features.managetokens.entity.CurrencyNetworkUM
|
||||
import com.tangem.features.managetokens.utils.ui.getIconRes
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal fun ManagedCryptoCurrency.Token.toUiNetworksModel(
|
||||
isExpanded: Boolean,
|
||||
isItemsEditable: Boolean,
|
||||
onSelectedStateChange: (SourceNetwork, Boolean) -> Unit,
|
||||
): NetworksUM {
|
||||
return if (isExpanded) {
|
||||
NetworksUM.Expanded(
|
||||
networks = availableNetworks.map {
|
||||
it.toUiModel(
|
||||
isSelected = it.id in addedIn,
|
||||
isEditable = isItemsEditable,
|
||||
onSelectedStateChange = onSelectedStateChange,
|
||||
)
|
||||
}.toImmutableList(),
|
||||
)
|
||||
} else {
|
||||
NetworksUM.Collapsed
|
||||
}
|
||||
}
|
||||
|
||||
private fun SourceNetwork.toUiModel(
|
||||
isSelected: Boolean,
|
||||
isEditable: Boolean,
|
||||
onSelectedStateChange: (SourceNetwork, Boolean) -> Unit,
|
||||
): CurrencyNetworkUM {
|
||||
return CurrencyNetworkUM(
|
||||
id = id,
|
||||
name = network.name.uppercase(),
|
||||
iconResId = id.getIconRes(isColored = isSelected || !isEditable),
|
||||
isSelected = isSelected || !isEditable,
|
||||
type = typeName,
|
||||
isMainNetwork = this is SourceNetwork.Main,
|
||||
onSelectedStateChange = { selected ->
|
||||
onSelectedStateChange(this, selected)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.tangem.features.managetokens.utils.ui
|
||||
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.CurrencyItemUM.Basic.NetworksUM
|
||||
import com.tangem.features.managetokens.utils.mapper.toUiNetworksModel
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal fun CurrencyItemUM.toggleExpanded(
|
||||
currency: ManagedCryptoCurrency,
|
||||
isEditable: Boolean,
|
||||
onSelectCurrencyNetwork: (SourceNetwork, Boolean) -> Unit,
|
||||
): CurrencyItemUM {
|
||||
if (currency !is ManagedCryptoCurrency.Token) return this
|
||||
|
||||
return when (this) {
|
||||
is CurrencyItemUM.Custom -> this
|
||||
is CurrencyItemUM.Basic -> {
|
||||
val isExpanded = networks !is NetworksUM.Expanded
|
||||
|
||||
copy(
|
||||
icon = icon.copySealed(
|
||||
isGrayscale = if (isEditable) !currency.isAdded && !isExpanded else false,
|
||||
),
|
||||
networks = currency.toUiNetworksModel(
|
||||
isExpanded = isExpanded,
|
||||
isItemsEditable = isEditable,
|
||||
onSelectedStateChange = onSelectCurrencyNetwork,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun CurrencyItemUM.update(currency: ManagedCryptoCurrency): CurrencyItemUM {
|
||||
return when (this) {
|
||||
is CurrencyItemUM.Custom -> this
|
||||
is CurrencyItemUM.Basic -> {
|
||||
if (currency !is ManagedCryptoCurrency.Token) {
|
||||
return this
|
||||
}
|
||||
|
||||
copy(
|
||||
icon = icon.copySealed(
|
||||
isGrayscale = networks is NetworksUM.Collapsed && !currency.isAdded,
|
||||
),
|
||||
networks = updateNetworks(currency),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CurrencyItemUM.Basic.updateNetworks(currency: ManagedCryptoCurrency.Token): NetworksUM = when (networks) {
|
||||
is NetworksUM.Collapsed -> networks
|
||||
is NetworksUM.Expanded -> networks.copy(
|
||||
networks = networks.networks.map { network ->
|
||||
val isSelected = network.id in currency.addedIn
|
||||
|
||||
network.copy(
|
||||
iconResId = network.id.getIconRes(isSelected),
|
||||
isSelected = isSelected,
|
||||
)
|
||||
}.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.features.managetokens.utils.ui
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.core.ui.extensions.getGreyedOutIconRes
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.managetokens.entity.CurrencyNetworkUM
|
||||
|
||||
internal fun CurrencyNetworkUM.select(isSelected: Boolean): CurrencyNetworkUM {
|
||||
return copy(
|
||||
iconResId = id.getIconRes(isSelected),
|
||||
isSelected = isSelected,
|
||||
)
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
internal fun Network.ID.getIconRes(isColored: Boolean): Int = if (isColored) {
|
||||
getActiveIconRes(value)
|
||||
} else {
|
||||
getGreyedOutIconRes(value)
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.components.list.InfiniteListHandler
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -196,26 +197,4 @@ private fun VisibleItemsTracker(listState: LazyListState, state: ListUM) {
|
|||
state.visibleIdsChanged(visibleItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InfiniteListHandler(listState: LazyListState, onLoadMore: () -> Boolean, buffer: Int = 2) {
|
||||
val loadMore by remember {
|
||||
derivedStateOf {
|
||||
val layoutInfo = listState.layoutInfo
|
||||
val totalItemsNumber = layoutInfo.totalItemsCount
|
||||
val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1
|
||||
|
||||
lastVisibleItemIndex > totalItemsNumber - buffer
|
||||
}
|
||||
}
|
||||
|
||||
val totalItemsCount by remember { derivedStateOf { listState.layoutInfo.totalItemsCount } }
|
||||
var emitted by remember(totalItemsCount) { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(loadMore) {
|
||||
if (loadMore && !emitted) {
|
||||
emitted = onLoadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -195,7 +195,7 @@ private fun ReferralInfo(
|
|||
}
|
||||
|
||||
is ReferralInfoState.Loading -> {
|
||||
LoadingCondition(iconResId = R.drawable.ic_tether_28)
|
||||
LoadingCondition(iconResId = R.drawable.ic_tether_24)
|
||||
SpacerH32()
|
||||
LoadingCondition(iconResId = R.drawable.ic_discount_28)
|
||||
}
|
||||
|
|
@ -211,7 +211,7 @@ private fun Conditions(state: ReferralInfoContentState) {
|
|||
|
||||
@Composable
|
||||
private fun ConditionForYou(state: ReferralInfoContentState) {
|
||||
Condition(iconResId = R.drawable.ic_tether_28) {
|
||||
Condition(iconResId = R.drawable.ic_tether_24) {
|
||||
when (state) {
|
||||
is ReferralInfoState.ParticipantContent -> InfoForYou(
|
||||
award = state.award,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
|
|
@ -45,24 +45,24 @@ internal fun SendEventEffect(event: StateEvent<SendEvent>, snackbarHostState: Sn
|
|||
|
||||
@Composable
|
||||
internal fun SendAlert(state: SendAlertState, onDismiss: () -> Unit) {
|
||||
val confirmButton: DialogButton
|
||||
val dismissButton: DialogButton?
|
||||
val confirmButton: DialogButtonUM
|
||||
val dismissButton: DialogButtonUM?
|
||||
|
||||
val onActionClick = state.onConfirmClick
|
||||
if (onActionClick != null) {
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
onClick = {
|
||||
onActionClick()
|
||||
onDismiss()
|
||||
},
|
||||
)
|
||||
dismissButton = DialogButton(
|
||||
dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
} else {
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
|
|||
BasicDialog(
|
||||
title = state.alert.title?.resolveReference(),
|
||||
message = message,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = state.alert.onClick,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsDialogConfig
|
||||
|
|
@ -19,7 +19,7 @@ internal fun TokenDetailsDialogs(state: TokenDetailsState) {
|
|||
private fun TokenDetailsDialog(config: TokenDetailsDialogConfig) {
|
||||
BasicDialog(
|
||||
message = config.content.message.resolveReference(),
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = config.content.confirmButtonConfig.text.resolveReference(),
|
||||
warning = config.content.confirmButtonConfig.warning,
|
||||
onClick = config.content.confirmButtonConfig.onClick,
|
||||
|
|
@ -27,7 +27,7 @@ private fun TokenDetailsDialog(config: TokenDetailsDialogConfig) {
|
|||
onDismissDialog = config.onDismissRequest,
|
||||
title = config.content.title?.resolveReference(),
|
||||
dismissButton = config.content.cancelButtonConfig?.let { cancelButtonConfig ->
|
||||
DialogButton(
|
||||
DialogButtonUM(
|
||||
title = cancelButtonConfig.text.resolveReference(),
|
||||
warning = cancelButtonConfig.warning,
|
||||
onClick = cancelButtonConfig.onClick,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dependencies {
|
|||
|
||||
/* Project - API */
|
||||
implementation(projects.features.walletSettings.api)
|
||||
implementation(projects.features.manageTokens.api)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.feature.walletsettings.component.WalletSettingsComponent
|
|||
import com.tangem.feature.walletsettings.entity.WalletSettingsUM
|
||||
import com.tangem.feature.walletsettings.ui.WalletSettingsScreen
|
||||
import com.tangem.feature.walletsettings.utils.ItemsBuilder
|
||||
import com.tangem.features.managetokens.ManageTokensToggles
|
||||
|
||||
internal class PreviewWalletSettingsComponent : WalletSettingsComponent {
|
||||
|
||||
|
|
@ -15,6 +16,9 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent {
|
|||
popBack = {},
|
||||
items = ItemsBuilder(
|
||||
router = DummyRouter(),
|
||||
manageTokensToggles = object : ManageTokensToggles {
|
||||
override val isFeatureEnabled: Boolean = true
|
||||
},
|
||||
).buildItems(
|
||||
userWalletId = UserWalletId("011"),
|
||||
userWalletName = "My Wallet",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ 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.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.ContentMessage
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
|
|
@ -93,7 +93,7 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
BasicDialog(
|
||||
message = stringResource(R.string.user_wallet_list_delete_prompt),
|
||||
onDismissDialog = onDismiss,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(R.string.common_delete),
|
||||
warning = true,
|
||||
onClick = {
|
||||
|
|
@ -101,7 +101,7 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
onDismiss()
|
||||
},
|
||||
),
|
||||
dismissButton = DialogButton(
|
||||
dismissButton = DialogButtonUM(
|
||||
title = stringResource(R.string.common_cancel),
|
||||
onClick = onDismiss,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.AdditionalTextInputDialogParams
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.AdditionalTextInputDialogUM
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.components.TextInputDialog
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.walletsettings.component.preview.PreviewRenameWalletComponent
|
||||
|
|
@ -21,18 +21,18 @@ internal fun RenameWalletDialog(model: RenameWalletUM, onDismiss: () -> Unit) {
|
|||
TextInputDialog(
|
||||
title = stringResource(id = R.string.user_wallet_list_rename_popup_title),
|
||||
fieldValue = value,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
enabled = model.isConfirmEnabled,
|
||||
onClick = model.onConfirm,
|
||||
),
|
||||
dismissButton = DialogButton(
|
||||
dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = onDismiss,
|
||||
),
|
||||
onDismissDialog = onDismiss,
|
||||
onValueChange = model.updateValue,
|
||||
textFieldParams = AdditionalTextInputDialogParams(
|
||||
textFieldParams = AdditionalTextInputDialogUM(
|
||||
label = stringResource(id = R.string.user_wallet_list_rename_popup_placeholder),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.walletsettings.entity.WalletSettingsItemUM
|
||||
import com.tangem.feature.walletsettings.impl.R
|
||||
import com.tangem.features.managetokens.ManageTokensToggles
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -17,6 +18,7 @@ import javax.inject.Inject
|
|||
@ComponentScoped
|
||||
internal class ItemsBuilder @Inject constructor(
|
||||
private val router: Router,
|
||||
private val manageTokensToggles: ManageTokensToggles,
|
||||
) {
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -50,6 +52,14 @@ internal class ItemsBuilder @Inject constructor(
|
|||
id = "card",
|
||||
description = resourceReference(R.string.settings_card_settings_footer),
|
||||
blocks = buildList {
|
||||
if (manageTokensToggles.isFeatureEnabled) {
|
||||
BlockUM(
|
||||
text = resourceReference(R.string.add_tokens_title),
|
||||
iconRes = R.drawable.ic_tether_24,
|
||||
onClick = { router.push(AppRoute.ManageTokens(userWalletId)) },
|
||||
).let(::add)
|
||||
}
|
||||
|
||||
if (isLinkMoreCardsAvailable) {
|
||||
BlockUM(
|
||||
text = resourceReference(R.string.details_row_title_create_backup),
|
||||
|
|
|
|||
|
|
@ -139,8 +139,8 @@ internal class DefaultWalletRouter(
|
|||
return router.stack.lastOrNull() is AppRoute.Wallet
|
||||
}
|
||||
|
||||
override fun openManageTokensScreen() {
|
||||
router.push(AppRoute.ManageTokens(readOnlyContent = false))
|
||||
override fun openManageTokensScreen(userWalletId: UserWalletId) {
|
||||
router.push(AppRoute.ManageTokens(userWalletId = userWalletId))
|
||||
}
|
||||
|
||||
override fun openScanFailedDialog(onTryAgain: () -> Unit) {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ internal interface InnerWalletRouter : WalletRouter {
|
|||
fun isWalletLastScreen(): Boolean
|
||||
|
||||
/** Open manage tokens screen */
|
||||
fun openManageTokensScreen()
|
||||
fun openManageTokensScreen(userWalletId: UserWalletId)
|
||||
|
||||
/** Open scan failed dialog */
|
||||
fun openScanFailedDialog(onTryAgain: () -> Unit)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package com.tangem.feature.wallet.presentation.wallet.ui
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.core.ui.components.AdditionalTextInputDialogParams
|
||||
import com.tangem.core.ui.components.AdditionalTextInputDialogUM
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.components.TextInputDialog
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
|
@ -21,12 +21,12 @@ internal fun WalletAlert(state: WalletAlertState, onDismiss: () -> Unit) {
|
|||
|
||||
@Composable
|
||||
private fun BasicAlert(state: WalletAlertState.Basic, onDismiss: () -> Unit) {
|
||||
val confirmButton: DialogButton
|
||||
val dismissButton: DialogButton?
|
||||
val confirmButton: DialogButtonUM
|
||||
val dismissButton: DialogButtonUM?
|
||||
|
||||
val onActionClick = state.onConfirmClick
|
||||
if (onActionClick != null) {
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
warning = state.isWarningConfirmButton,
|
||||
onClick = {
|
||||
|
|
@ -34,12 +34,12 @@ private fun BasicAlert(state: WalletAlertState.Basic, onDismiss: () -> Unit) {
|
|||
onDismiss()
|
||||
},
|
||||
)
|
||||
dismissButton = DialogButton(
|
||||
dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
} else {
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
warning = state.isWarningConfirmButton,
|
||||
onClick = onDismiss,
|
||||
|
|
@ -62,7 +62,7 @@ private fun TextInputAlert(state: WalletAlertState.TextInput, onDismiss: () -> U
|
|||
|
||||
TextInputDialog(
|
||||
fieldValue = value,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
enabled = value.text.isNotEmpty() &&
|
||||
value.text != state.text &&
|
||||
|
|
@ -75,8 +75,8 @@ private fun TextInputAlert(state: WalletAlertState.TextInput, onDismiss: () -> U
|
|||
onDismissDialog = onDismiss,
|
||||
onValueChange = { value = it },
|
||||
title = state.title.resolveReference(),
|
||||
dismissButton = DialogButton(title = stringResource(id = R.string.common_cancel), onClick = onDismiss),
|
||||
textFieldParams = AdditionalTextInputDialogParams(
|
||||
dismissButton = DialogButtonUM(title = stringResource(id = R.string.common_cancel), onClick = onDismiss),
|
||||
textFieldParams = AdditionalTextInputDialogUM(
|
||||
label = state.label.resolveReference(),
|
||||
isError = state.errorTextProvider(value.text) != null,
|
||||
caption = state.errorTextProvider(value.text)?.resolveReference(),
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
override fun onManageTokensClick() {
|
||||
analyticsEventHandler.send(PortfolioEvent.ButtonManageTokens)
|
||||
reduxStateHolder.dispatch(action = TokensAction.SetArgs.ManageAccess)
|
||||
router.openManageTokensScreen()
|
||||
router.openManageTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
|
||||
}
|
||||
|
||||
override fun onOrganizeTokensClick() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue