From 90b49e37739ff8785732995aece530a3e5be31e1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Jun 2023 13:15:01 +0800 Subject: [PATCH 01/39] Updated on 2026-08-14 --- gradle/dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ab2fb12799..49c4ee774b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -77,9 +77,9 @@ walletConnectWeb3 = "1.8.0" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-240" +tangemBlockchainSdk = "release-app_4.7-241" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-266" +tangemCardSdk = "release-app_4.7-268" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds # endregion Tangem From 74dea8f85ba19b2e2319d78f6df752a4692c340c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Jun 2023 18:28:02 +0400 Subject: [PATCH 02/39] Updated on 2026-08-14 --- .../presentation/viewmodels/AddCustomTokenViewModel.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt index 1d886914c7..5ccb8e66e6 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt @@ -510,7 +510,7 @@ internal class AddCustomTokenViewModel @Inject constructor( val sameAddress = contractAddress == wrappedCurrency.token.contractAddress val sameBlockchain = Blockchain.fromNetworkId(networkSelectorValue.toNetworkId()) == wrappedCurrency.blockchain - val isSameDerivationPath = getDerivationPath()?.rawPath == wrappedCurrency.derivationPath + val isSameDerivationPath = getDerivationPath().isSameDerivationPath(wrappedCurrency.derivationPath) sameId && sameAddress && sameBlockchain && isSameDerivationPath } } @@ -522,10 +522,15 @@ internal class AddCustomTokenViewModel @Inject constructor( .filterIsInstance() .any { val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain - networkSelectorValue == it.blockchain && getDerivationPath()?.rawPath == it.derivationPath + networkSelectorValue == it.blockchain && + getDerivationPath().isSameDerivationPath(it.derivationPath) } } + private fun DerivationPath?.isSameDerivationPath(rawDerivationPath: String?): Boolean { + return this == rawDerivationPath?.let { DerivationPath(it) } + } + private fun handleContractAddressErrorValidation(type: AddCustomTokenError) { when { isNetworkSelected() && type == AddCustomTokenError.InvalidContractAddress -> { From f49f61ace0e9dc331fbb4c9ba694da8070791b51 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Jun 2023 14:13:56 +0300 Subject: [PATCH 03/39] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 1 + app/src/main/res/values-v24/bool.xml | 5 +++++ app/src/main/res/values/bool.xml | 5 +++++ 3 files changed, 11 insertions(+) create mode 100644 app/src/main/res/values-v24/bool.xml create mode 100644 app/src/main/res/values/bool.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a9226c6eff..2bb55e36b4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -37,6 +37,7 @@ android:fullBackupContent="false" android:networkSecurityConfig="@xml/network_security_config" android:hardwareAccelerated="true" + android:largeHeap="@bool/largeHeap" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:allowBackup, android:fullBackupContent"> diff --git a/app/src/main/res/values-v24/bool.xml b/app/src/main/res/values-v24/bool.xml new file mode 100644 index 0000000000..093ecf3bc6 --- /dev/null +++ b/app/src/main/res/values-v24/bool.xml @@ -0,0 +1,5 @@ + + + false + + diff --git a/app/src/main/res/values/bool.xml b/app/src/main/res/values/bool.xml new file mode 100644 index 0000000000..d5677354f0 --- /dev/null +++ b/app/src/main/res/values/bool.xml @@ -0,0 +1,5 @@ + + + true + + From 8cfc4bfe051d6266ebdd2ad6a7460723f74df2b6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 16 Jun 2023 18:55:28 +0300 Subject: [PATCH 04/39] Updated on 2026-08-14 --- .gitignore | 1 + app/src/main/AndroidManifest.xml | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 25c98d5d1f..3e96fb9767 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # Built application files /build +/buildSrc # Local configuration file (sdk path, etc) local.properties diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2bb55e36b4..ef93ea2179 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,17 +27,16 @@ @@ -46,10 +45,11 @@ android:value="true" /> @@ -126,13 +126,17 @@ - + - + Date: Fri, 16 Jun 2023 23:56:26 +0800 Subject: [PATCH 05/39] Updated on 2026-08-14 --- .../tap/features/send/ui/EditTextCustomPaste.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt b/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt index d3b563025d..d93147b24b 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt @@ -5,14 +5,16 @@ import android.content.Context import android.util.AttributeSet import com.google.android.material.textfield.TextInputEditText -class EditTextCustomPaste @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : TextInputEditText(context, attrs, defStyleAttr) { +class EditTextCustomPaste : TextInputEditText { private var onSystemPasteButtonClickListener: (() -> Unit)? = null + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + fun setOnSystemPasteButtonClickListener(callback: () -> Unit) { onSystemPasteButtonClickListener = callback } From 16f8451745083c4aac6a1396619e7fcac36bcf30 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 16 Jun 2023 23:57:48 +0800 Subject: [PATCH 06/39] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 49c4ee774b..4b14f8518a 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -77,7 +77,7 @@ walletConnectWeb3 = "1.8.0" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_4.7-241" +tangemBlockchainSdk = "release-app_4.7-243" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_4.7-268" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds From 00de15f8e0b09a1b65f77b331b4813d90ee521e4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Jun 2023 16:36:56 +0800 Subject: [PATCH 07/39] Updated on 2026-08-14 --- .../shop/data/DefaultShopRepository.kt | 37 ++++++++++ .../tap/features/shop/di/ShopUseCaseModule.kt | 28 ++++++++ ...faultShopifyOrderingAvailabilityUseCase.kt | 15 ++++ .../features/shop/domain/ShopRepository.kt | 12 ++++ .../ShopifyOrderingAvailabilityUseCase.kt | 12 ++++ .../shop/presentation/ShopViewModel.kt | 39 ++++++++++ .../tap/features/shop/redux/ShopAction.kt | 40 ++++++----- .../tap/features/shop/redux/ShopReducer.kt | 71 ++++++------------- .../tap/features/shop/redux/ShopState.kt | 1 + .../tap/features/shop/ui/ShopFragment.kt | 16 ++++- app/src/main/res/layout/fragment_shop.xml | 2 +- .../api/tangemTech/TangemTechApi.kt | 3 + .../api/tangemTech/models/ShopResponse.kt | 14 ++++ core/res/src/main/res/values-ru/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 2 +- 15 files changed, 223 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/features/shop/di/ShopUseCaseModule.kt create mode 100644 app/src/main/java/com/tangem/tap/features/shop/domain/DefaultShopifyOrderingAvailabilityUseCase.kt create mode 100644 app/src/main/java/com/tangem/tap/features/shop/domain/ShopRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/features/shop/domain/ShopifyOrderingAvailabilityUseCase.kt create mode 100644 app/src/main/java/com/tangem/tap/features/shop/presentation/ShopViewModel.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ShopResponse.kt diff --git a/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt b/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt new file mode 100644 index 0000000000..8f61a43c5f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt @@ -0,0 +1,37 @@ +package com.tangem.tap.features.shop.data + +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.ShopResponse +import com.tangem.tap.features.shop.domain.ShopRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import timber.log.Timber + +/** + * Default implementation of shop feature repository + * + * @property tangemTechApi TangemTech API + * @property dispatchers coroutine dispatchers provider + * +[REDACTED_AUTHOR] + */ +internal class DefaultShopRepository( + private val tangemTechApi: TangemTechApi, + private val dispatchers: CoroutineDispatcherProvider, +) : ShopRepository { + + override suspend fun isShopifyOrderingAvailable(): Boolean { + return runCatching(dispatchers.io) { tangemTechApi.getShopInfo(name = SHOPIFY_NAME) } + .fold( + onSuccess = ShopResponse::isOrderingAvailable, + onFailure = { + Timber.e("Server error. isShopifyOrderingAvailable returns default value (false)") + false + }, + ) + } + + private companion object { + const val SHOPIFY_NAME = "shopify" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/di/ShopUseCaseModule.kt b/app/src/main/java/com/tangem/tap/features/shop/di/ShopUseCaseModule.kt new file mode 100644 index 0000000000..2c00aa1181 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/shop/di/ShopUseCaseModule.kt @@ -0,0 +1,28 @@ +package com.tangem.tap.features.shop.di + +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.tap.features.shop.data.DefaultShopRepository +import com.tangem.tap.features.shop.domain.DefaultShopifyOrderingAvailabilityUseCase +import com.tangem.tap.features.shop.domain.ShopifyOrderingAvailabilityUseCase +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped + +@Module +@InstallIn(ViewModelComponent::class) +internal object ShopUseCaseModule { + + @Provides + @ViewModelScoped + fun provideShopifyOrderingAvailabilityUseCase( + tangemTechApi: TangemTechApi, + dispatchers: CoroutineDispatcherProvider, + ): ShopifyOrderingAvailabilityUseCase { + return DefaultShopifyOrderingAvailabilityUseCase( + shopRepository = DefaultShopRepository(tangemTechApi, dispatchers), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/DefaultShopifyOrderingAvailabilityUseCase.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/DefaultShopifyOrderingAvailabilityUseCase.kt new file mode 100644 index 0000000000..41bbe3d449 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/shop/domain/DefaultShopifyOrderingAvailabilityUseCase.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.features.shop.domain + +/** + * Default implementation of use case to define shopify ordering availability + * + * @property shopRepository shop feature repository + * +[REDACTED_AUTHOR] + */ +internal class DefaultShopifyOrderingAvailabilityUseCase( + private val shopRepository: ShopRepository, +) : ShopifyOrderingAvailabilityUseCase { + + override suspend fun invoke() = shopRepository.isShopifyOrderingAvailable() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/ShopRepository.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/ShopRepository.kt new file mode 100644 index 0000000000..c8cd6f3780 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/shop/domain/ShopRepository.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.features.shop.domain + +/** + * Shop feature repository + * +[REDACTED_AUTHOR] + */ +internal interface ShopRepository { + + /** Get shopify ordering availability */ + suspend fun isShopifyOrderingAvailable(): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/ShopifyOrderingAvailabilityUseCase.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/ShopifyOrderingAvailabilityUseCase.kt new file mode 100644 index 0000000000..5f6c351ce2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/shop/domain/ShopifyOrderingAvailabilityUseCase.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.features.shop.domain + +/** + * Use case to define shopify ordering availability + * +[REDACTED_AUTHOR] + */ +internal interface ShopifyOrderingAvailabilityUseCase { + + /** Get availability */ + suspend operator fun invoke(): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/presentation/ShopViewModel.kt b/app/src/main/java/com/tangem/tap/features/shop/presentation/ShopViewModel.kt new file mode 100644 index 0000000000..34966466cc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/shop/presentation/ShopViewModel.kt @@ -0,0 +1,39 @@ +package com.tangem.tap.features.shop.presentation + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.tangem.tap.features.shop.domain.ShopifyOrderingAvailabilityUseCase +import com.tangem.tap.features.shop.redux.ShopAction +import com.tangem.tap.proxy.AppStateHolder +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +/** + * Shop screen view model + * + * @property shopifyOrderingAvailabilityUseCase use case to define shopify ordering availability + * @property dispatchers coroutine dispatchers provider + * @property appStateHolder redux state holder + * +[REDACTED_AUTHOR] + */ +@HiltViewModel +internal class ShopViewModel @Inject constructor( + private val shopifyOrderingAvailabilityUseCase: ShopifyOrderingAvailabilityUseCase, + private val dispatchers: CoroutineDispatcherProvider, + private val appStateHolder: AppStateHolder, +) : ViewModel() { + + /** Check ordering delay block visibility */ + fun checkOrderingDelayBlockVisibility() { + viewModelScope.launch(dispatchers.main) { + val visibility = runCatching(dispatchers.io) { shopifyOrderingAvailabilityUseCase() } + .fold(onSuccess = { !it }, onFailure = { false }) + + appStateHolder.mainStore?.dispatch(action = ShopAction.SetOrderingDelayBlockVisibility(visibility)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt index 219753c469..54c6bcad43 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt @@ -8,38 +8,40 @@ import com.tangem.tap.common.shop.googlepay.GooglePayService import com.tangem.wallet.R import org.rekotlin.Action -sealed class ShopAction : Action { +sealed interface ShopAction : Action { - object LoadProducts : ShopAction() { - data class Success(val products: List) : ShopAction() - object Failure : ShopAction(), NotificationAction { + object LoadProducts : ShopAction { + data class Success(val products: List) : ShopAction + object Failure : ShopAction, NotificationAction { override val messageResource = R.string.common_server_unavailable } } - data class ApplyPromoCode(val promoCode: String) : ShopAction() { - data class Success(val promoCode: String?, val products: List) : ShopAction() - object InvalidPromoCode : ShopAction() + data class ApplyPromoCode(val promoCode: String) : ShopAction { + data class Success(val promoCode: String?, val products: List) : ShopAction + object InvalidPromoCode : ShopAction } - object BuyWithGooglePay : ShopAction() { - object UserCancelled : ShopAction() - data class HandleGooglePayResponse(val resultCode: Int, val data: Intent?) : ShopAction() + object BuyWithGooglePay : ShopAction { + object UserCancelled : ShopAction + data class HandleGooglePayResponse(val resultCode: Int, val data: Intent?) : ShopAction - data class Failure(val exception: Throwable) : ShopAction() - object Success : ShopAction() + data class Failure(val exception: Throwable) : ShopAction + object Success : ShopAction } - object StartWebCheckout : ShopAction() + object StartWebCheckout : ShopAction - data class CheckIfGooglePayAvailable(val googlePayService: GooglePayService) : ShopAction() { - object Success : ShopAction() - object Failure : ShopAction() + data class CheckIfGooglePayAvailable(val googlePayService: GooglePayService) : ShopAction { + object Success : ShopAction + object Failure : ShopAction } - data class SelectProduct(val productType: ProductType) : ShopAction() + data class SelectProduct(val productType: ProductType) : ShopAction - object FinishSuccessfulOrder : ShopAction() + object FinishSuccessfulOrder : ShopAction - object ResetState : ShopAction() + object ResetState : ShopAction + + data class SetOrderingDelayBlockVisibility(val visibility: Boolean) : ShopAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt index 8a97a1a58c..60cb0667ad 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt @@ -6,63 +6,38 @@ object ShopReducer { fun reduce(action: Action, state: ShopState): ShopState = internalReduce(action, state) } -@Suppress("ComplexMethod") private fun internalReduce(action: Action, state: ShopState): ShopState { if (action !is ShopAction) return state return when (action) { - is ShopAction.ApplyPromoCode -> state.copy( - promoCode = action.promoCode, - promoCodeLoading = true, - ) - ShopAction.BuyWithGooglePay -> state - ShopAction.LoadProducts -> state - is ShopAction.LoadProducts.Success -> { - state.copy( - availableProducts = action.products, - ) - } - ShopAction.StartWebCheckout -> state - ShopAction.ApplyPromoCode.InvalidPromoCode -> state.copy( - promoCode = null, - promoCodeLoading = false, - ) + is ShopAction.ApplyPromoCode -> state.copy(promoCode = action.promoCode, promoCodeLoading = true) + is ShopAction.LoadProducts.Success -> state.copy(availableProducts = action.products) + is ShopAction.ApplyPromoCode.InvalidPromoCode -> state.copy(promoCode = null, promoCodeLoading = false) is ShopAction.ApplyPromoCode.Success -> { state.copy( promoCode = action.promoCode, availableProducts = action.products, promoCodeLoading = false, + ) + } + is ShopAction.SelectProduct -> state.copy(selectedProduct = action.productType) - ) - } - is ShopAction.SelectProduct -> { - state.copy( - selectedProduct = action.productType, - ) - } - is ShopAction.CheckIfGooglePayAvailable -> { - state - } - ShopAction.CheckIfGooglePayAvailable.Failure -> { - state.copy(isGooglePayAvailable = false) - } - ShopAction.CheckIfGooglePayAvailable.Success -> { - state.copy(isGooglePayAvailable = false) // TODO: change when we add support for GPay - } - is ShopAction.BuyWithGooglePay.Failure -> { - state - } - is ShopAction.BuyWithGooglePay.HandleGooglePayResponse -> { - state - } - ShopAction.BuyWithGooglePay.Success -> { - state - } - ShopAction.BuyWithGooglePay.UserCancelled -> { - state - } - ShopAction.FinishSuccessfulOrder -> state - ShopAction.ResetState -> ShopState() - ShopAction.LoadProducts.Failure -> state + // TODO: change when we add support for GPay + is ShopAction.CheckIfGooglePayAvailable.Failure -> state.copy(isGooglePayAvailable = false) + is ShopAction.CheckIfGooglePayAvailable.Success -> state.copy(isGooglePayAvailable = false) + + is ShopAction.ResetState -> ShopState() + is ShopAction.SetOrderingDelayBlockVisibility -> state.copy(isOrderingDelayBlockVisible = action.visibility) + is ShopAction.BuyWithGooglePay, + is ShopAction.LoadProducts, + is ShopAction.StartWebCheckout, + is ShopAction.CheckIfGooglePayAvailable, + is ShopAction.BuyWithGooglePay.Failure, + is ShopAction.BuyWithGooglePay.HandleGooglePayResponse, + is ShopAction.BuyWithGooglePay.Success, + is ShopAction.BuyWithGooglePay.UserCancelled, + is ShopAction.FinishSuccessfulOrder, + is ShopAction.LoadProducts.Failure, + -> state } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt index ebbbb7cb79..491e961e58 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt @@ -10,6 +10,7 @@ data class ShopState( val promoCode: String? = null, val promoCodeLoading: Boolean = false, val isGooglePayAvailable: Boolean = false, // TODO: change when we add support for GPay + val isOrderingDelayBlockVisible: Boolean = false, ) : StateType { val total: String? get() = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum?.finalValue diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt index dccbe4d7b7..56ffe54b0f 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt @@ -9,28 +9,35 @@ import android.view.View.OnFocusChangeListener import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.viewModels import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.tap.common.GlobalLayoutStateHandler import com.tangem.tap.common.KeyboardObserver import com.tangem.tap.common.extensions.getQuantityString +import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.shop.data.ProductType import com.tangem.tap.features.BaseStoreFragment +import com.tangem.tap.features.shop.presentation.ShopViewModel import com.tangem.tap.features.shop.redux.ShopAction import com.tangem.tap.features.shop.redux.ShopState import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentShopBinding +import dagger.hilt.android.AndroidEntryPoint import org.rekotlin.StoreSubscriber -class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber { +@AndroidEntryPoint +internal class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber { private val binding: FragmentShopBinding by viewBinding(FragmentShopBinding::bind) private var cardTranslationY = 70f private lateinit var keyboardObserver: KeyboardObserver + private val viewModel by viewModels() + override fun subscribeToStore() { store.subscribe(this) { state -> state.skipRepeats { oldState, newState -> @@ -43,6 +50,8 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + viewModel.checkOrderingDelayBlockVisibility() + activity?.onBackPressedDispatcher?.addCallback( this, object : OnBackPressedCallback(true) { @@ -133,6 +142,7 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< animateProductSelection(state.selectedProduct) handlePriceState(state) handlePromoCodeState(state) + handleOrderingDelayBlock(isVisible = state.isOrderingDelayBlockVisible) handleButtonsState(state) } @@ -173,6 +183,10 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< pbPromoCode.show(state.promoCodeLoading) } + private fun handleOrderingDelayBlock(isVisible: Boolean) { + if (isVisible) binding.tvSoldOutDesc.show() else binding.tvSoldOutDesc.hide() + } + private fun handleButtonsState(state: ShopState) = with(binding) { btnPayGooglePay.root.show(state.isGooglePayAvailable) btnAlternativePayment.show(state.isGooglePayAvailable) diff --git a/app/src/main/res/layout/fragment_shop.xml b/app/src/main/res/layout/fragment_shop.xml index 523b68c535..0a61bf5b4a 100644 --- a/app/src/main/res/layout/fragment_shop.xml +++ b/app/src/main/res/layout/fragment_shop.xml @@ -271,7 +271,7 @@ android:layout_marginTop="14dp" android:background="@drawable/shape_rectangle_rounded_4" android:padding="16dp" - android:text="@string/shop_sold_out_description_prefix" + android:text="@string/shop_sold_out_description" android:textColor="@color/text_tertiary" android:textSize="16sp" /> diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 4125f7ed48..992f6d0eb5 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -51,4 +51,7 @@ interface TangemTechApi { @Header("card_id") cardId: String, @Body startReferralBody: StartReferralBody, ): ReferralResponse + + @GET("shops") + suspend fun getShopInfo(@Query(value = "name") name: String): ShopResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ShopResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ShopResponse.kt new file mode 100644 index 0000000000..6331011641 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ShopResponse.kt @@ -0,0 +1,14 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json + +/** + * Shop response + * + * @property isOrderingAvailable ordering availability + * +[REDACTED_AUTHOR] + */ +data class ShopResponse( + @Json(name = "canOrder") val isOrderingAvailable: Boolean, +) \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 2baace2c4d..e994de868f 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -336,7 +336,7 @@ У меня есть промо-код… Tangem Wallet Другие способы оплаты - Из-за большого количества заказов, которые мы получаем, cроки доставки могут быть увеличены. + Из-за высокого количества заказов, которые мы получаем, доставка может быть задержана на срок до 5 недель в зависимости от вашего местоположения Итого Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ea3c2c3c93..11ef0d6d75 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -332,7 +332,7 @@ I have a promo code… Tangem Wallet Other payment methods - Due to the high volume of orders we are receiving, Shipping and Local Delivery orders may be delayed. + Due to the high volume of orders we are receiving shipping may be delayed up to 5 weeks depending on your location Total Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. Store your crypto assets secure while keeping private keys contained in your card From 0fc9ce834b6724d210f06bff054f268c63771798 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Jun 2023 18:19:24 +0800 Subject: [PATCH 08/39] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 174175e4e6..b0b9550686 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -9,7 +9,7 @@ }, { "name": "NEW_CARD_SCANNING_ENABLED", - "version": "4.7.0" + "version": "4.8.0" }, { "name": "REDESIGNED_WALLET_SCREEN_ENABLED", From 9ad060cc85a636e2e01e4a4ef2afd85164cd2714 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 16 Jun 2023 15:31:21 +0400 Subject: [PATCH 09/39] Updated on 2026-08-14 --- .../com/tangem/tap/common/DialogManager.kt | 11 +++++-- .../data/WalletConnectRepositoryImpl.kt | 27 ++++++++++------ .../domain/WalletConnectInteractor.kt | 32 +++++++++++-------- .../domain/models/WalletConnectError.kt | 2 +- .../walletconnect/WalletConnectMiddleware.kt | 15 +++++---- .../redux/walletconnect/WalletConnectState.kt | 2 +- core/res/src/main/res/values-ru/strings.xml | 1 + .../src/main/res/values-zh-rTW/strings.xml | 1 + core/res/src/main/res/values/strings.xml | 1 + 9 files changed, 59 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index e4520b39d6..2089abefa6 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -100,12 +100,19 @@ class DialogManager : StoreSubscriber { preparedData = state.dialog.data, context = context, ) - is WalletConnectDialog.UnsupportedNetwork -> + is WalletConnectDialog.UnsupportedNetwork -> { + val warning = if (state.dialog.networks.isNullOrEmpty()) { + context.getString(R.string.wallet_connect_scanner_error_unsupported_network) + } else { + context.getString(R.string.wallet_connect_error_unsupported_blockchains) + + state.dialog.networks + } SimpleAlertDialog.create( titleRes = R.string.wallet_connect_title, - messageRes = R.string.wallet_connect_scanner_error_unsupported_network, + message = warning, context = context, ) + } is WalletConnectDialog.SessionProposalDialog -> { SessionProposalDialog.create( sessionProposal = state.dialog.sessionProposal, diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt index 80d7ed0b09..01e3803798 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt @@ -1,15 +1,22 @@ package com.tangem.tap.domain.walletconnect2.data -import android.app.* -import com.tangem.tap.domain.walletconnect2.domain.* +import android.app.Application +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository +import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer import com.tangem.tap.domain.walletconnect2.domain.models.* -import com.walletconnect.android.* -import com.walletconnect.android.relay.* -import com.walletconnect.web3.wallet.client.* -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* -import timber.log.* -import javax.inject.* +import com.walletconnect.android.Core +import com.walletconnect.android.CoreClient +import com.walletconnect.android.relay.ConnectionType +import com.walletconnect.web3.wallet.client.Wallet +import com.walletconnect.web3.wallet.client.Web3Wallet +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject class WalletConnectRepositoryImpl @Inject constructor( private val application: Application, @@ -181,7 +188,7 @@ class WalletConnectRepositoryImpl @Inject constructor( userNamespaces = userNamespaces, ) if (missingNetworks.isNotEmpty()) { - Timber.e("Unsupported blockchains: $missingNetworks") + Timber.e("Not added blockchains: $missingNetworks") scope.launch { _events.emit( WalletConnectEvents.SessionApprovalError( diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 03bd4e548e..21e5a73147 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -1,13 +1,16 @@ package com.tangem.tap.domain.walletconnect2.domain -import com.tangem.tap.common.extensions.* -import com.tangem.tap.domain.walletconnect.* +import com.tangem.tap.common.extensions.filterNotNull +import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper import com.tangem.tap.domain.walletconnect2.domain.models.* -import com.tangem.tap.features.details.ui.walletconnect.* -import com.tangem.utils.coroutines.* -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* -import timber.log.* +import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import timber.log.Timber class WalletConnectInteractor( private val handler: WalletConnectEventsHandler, @@ -47,6 +50,14 @@ class WalletConnectInteractor( when (wcEvent) { is WalletConnectEvents.SessionProposal -> { Timber.d("WC session proposal event received") + val unsupportedNetworks = wcEvent.chainIds + .filter { blockchainHelper.chainIdToNetworkIdOrNull(it) == null } + if (unsupportedNetworks.isNotEmpty()) { + val error = WalletConnectError.ApprovalErrorUnsupportedNetwork(unsupportedNetworks) + handler.onSessionRejected(error) + return@onEach + } + val networksFormatted = wcEvent.chainIds .mapNotNull { blockchainHelper.chainIdToFullNameOrNull(it) } .toString() @@ -57,12 +68,7 @@ class WalletConnectInteractor( is WalletConnectError.ApprovalErrorMissingNetworks -> { val missingNetworks = wcEvent.error.missingChains .map { blockchainHelper.chainIdToNetworkIdOrNull(it) } - val containsUnsupportedNetworks = missingNetworks.any { it == null } - if (containsUnsupportedNetworks) { - WalletConnectError.ApprovalErrorUnsupportedNetwork - } else { - WalletConnectError.ApprovalErrorAddNetwork(missingNetworks.filterNotNull()) - } + WalletConnectError.ApprovalErrorAddNetwork(missingNetworks.filterNotNull()) } else -> wcEvent.error } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt index 1f9b7f6771..35da4b88f8 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.walletconnect2.domain.models sealed class WalletConnectError : Exception() { data class ApprovalErrorMissingNetworks(val missingChains: List) : WalletConnectError() data class ApprovalErrorAddNetwork(val networks: List) : WalletConnectError() - object ApprovalErrorUnsupportedNetwork : WalletConnectError() + data class ApprovalErrorUnsupportedNetwork(val unsupportedNetworks: List) : WalletConnectError() data class ExternalApprovalError(override val message: String?) : WalletConnectError() object WrongUserWallet : WalletConnectError() object UnsupportedMethod : WalletConnectError() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index b83b2ad75a..9632a15368 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -238,7 +238,7 @@ class WalletConnectMiddleware { return } val blockchain = action.blockchain.guard { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork)) + store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork())) return } val walletManager = getWalletManager( @@ -298,9 +298,12 @@ class WalletConnectMiddleware { ), ) } - - WalletConnectError.ApprovalErrorUnsupportedNetwork -> { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork)) + is WalletConnectError.ApprovalErrorUnsupportedNetwork -> { + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.UnsupportedNetwork(action.error.unsupportedNetworks), + ), + ) } is WalletConnectError.ExternalApprovalError -> { store.dispatchOnMain( @@ -327,7 +330,7 @@ class WalletConnectMiddleware { scope.launch { walletConnectInteractor.continueWithRequest(action.sessionRequest) } } is WalletConnectAction.RejectUnsupportedRequest -> { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork)) + store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork())) } } } @@ -337,7 +340,7 @@ class WalletConnectMiddleware { chainId = chainId, peer = session.peerMeta, ).guard { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork)) + store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork())) return } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index 71f4a14ca6..135f597dc1 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -91,7 +91,7 @@ data class WalletForSession( sealed class WalletConnectDialog : StateDialog { data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog() object UnsupportedCard : WalletConnectDialog() - object UnsupportedNetwork : WalletConnectDialog() + data class UnsupportedNetwork(val networks: List? = null) : WalletConnectDialog() data class AddNetwork(val network: String) : WalletConnectDialog() object OpeningSessionRejected : WalletConnectDialog() object SessionTimeout : WalletConnectDialog() diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index e994de868f..4d71fe324b 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -457,6 +457,7 @@ Невозможно отправить транзакцию. Недостаточно средств. Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. + Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. Вставить из буфера обмена diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index 08f4e9e68b..461c90bc77 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -436,6 +436,7 @@ 無法交易,無足夠資金 未能建立 WalletConnect 連接。請稍後再試 無法建立 WalletConnect 連接:超時錯誤。請稍後再試 + 會話請求包含不支持 WalletConnect 連接的區塊鏈。不支持的區塊鏈: 由於技術問題,無法與此 Dapp 建立連接 沒有 %s 網路,請先加入後再試一次 從剪貼板貼上 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 11ef0d6d75..695c74084b 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -449,6 +449,7 @@ Can\'t send transaction. Not enough funds. Failed to establish WalletConnect session. Please, try again later. Failed to establish WalletConnect session: timeout error. Please, try again later. + Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n Connection with this Dapp cannot be established due to its technical implementation. %s network not found. Please, add it first and try again. Paste from clipboard From c4488bee29e16fc29ded975c6a460ea5662e2707 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 16 Jun 2023 15:48:32 +0400 Subject: [PATCH 10/39] Updated on 2026-08-14 --- .../details/redux/walletconnect/WalletConnectMiddleware.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 9632a15368..fd67e805d2 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -66,7 +66,7 @@ class WalletConnectMiddleware { walletConnectManager.restoreSessions(action.scanResponse) } is WalletConnectAction.HandleDeepLink -> { - if (!action.wcUri.isNullOrBlank() && WalletConnectManager.isCorrectWcUri(action.wcUri)) { + if (!action.wcUri.isNullOrBlank()) { store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri)) } } From e5bd65c2852634c44569abd02edd6e70cff82b6f Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 16 Jun 2023 16:29:25 +0400 Subject: [PATCH 11/39] Updated on 2026-08-14 --- .../walletconnect2/domain/WcSessionRequestConverter.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt index bc93e31462..fe67571134 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt @@ -130,7 +130,8 @@ class WcSessionRequestConverter( ): String? { return sessionsRepository.loadSessions(userWalletId) .firstOrNull { it.topic == sessionRequest.topic } - ?.accounts?.firstOrNull { it.chainId == sessionRequest.chainId && it.walletAddress == walletAddress } - ?.derivationPath + ?.accounts?.firstOrNull { + it.chainId == sessionRequest.chainId && it.walletAddress.lowercase() == walletAddress?.lowercase() + }?.derivationPath } } \ No newline at end of file From be017175348e245f5f3b7eec4aeac29a7992bff2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 16 Jun 2023 18:09:08 +0400 Subject: [PATCH 12/39] Updated on 2026-08-14 --- .../tap/domain/walletconnect/WalletConnectSdkHelper.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index e315dace10..dc3169c18c 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -33,7 +33,6 @@ import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder import com.tangem.tap.features.details.redux.walletconnect.* -import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData import com.tangem.tap.store @@ -194,7 +193,12 @@ class WalletConnectSdkHelper { val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message(), cardId = cardId) return when (result) { is CompletionResult.Success -> { - HEX_PREFIX + result.data + HEX_PREFIX + EthereumUtils.prepareTransactionToSend( + signature = result.data.signature, + transactionToSign = dataToSign, + walletPublicKey = data.walletManager.wallet.publicKey, + blockchain = data.walletManager.wallet.blockchain, + ).toHexString() } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.SignError(it)) } From 69f13c06fdd5168b2052d1138a921a52b371e8e1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 16 Jun 2023 18:20:05 +0400 Subject: [PATCH 13/39] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 50182bde55..f9be91dcce 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -83,9 +83,9 @@ class TapWalletManager( } private fun setupWalletConnectV2(userWallet: UserWallet) { - val cardId = if (userWallet.cardsInWallet.size == 1) { + val cardId = if (userWallet.scanResponse.card.backupStatus?.isActive != true) { userWallet.cardId - } else { + } else { // if wallet has backup, any card from wallet can be used to sign null } scope.launch { From 18f18f84cc188890433bbc5242df222f3fd9497e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Jun 2023 09:28:53 +0400 Subject: [PATCH 14/39] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 4b14f8518a..8d89456b8c 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -77,7 +77,7 @@ walletConnectWeb3 = "1.8.0" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_4.7-243" +tangemBlockchainSdk = "release-app_4.7-245" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_4.7-268" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds From dfe2dc0a0e206235f4f458001cb21471a615d27f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Jun 2023 14:01:07 +0400 Subject: [PATCH 15/39] Updated on 2026-08-14 --- .../tap/domain/walletconnect2/domain/WalletConnectInteractor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 21e5a73147..ef202cd085 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -37,11 +37,11 @@ class WalletConnectInteractor( suspend fun startListening(userWalletId: String, cardId: String?) { this.userWalletId = userWalletId this.cardId = cardId + walletConnectRepository.updateSessions() coroutineScope { launch { subscribeToEvents() } launch { subscribeToSessions() } } - walletConnectRepository.updateSessions() } private suspend fun subscribeToEvents() { From c15c647db0a7b0cf7e10278149fa02c3bc74a4d3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Jun 2023 19:31:58 +0800 Subject: [PATCH 16/39] Updated on 2026-08-14 --- .../send/redux/middlewares/SendMiddleware.kt | 39 ++++++++++++------- .../send/redux/states/AddressPayIdState.kt | 16 ++++++++ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index c66271923f..d54adda3d5 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -6,7 +6,7 @@ import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.ton.TonTransactionExtras -import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder +import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder.XrpTransactionExtras import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError @@ -168,9 +168,7 @@ private fun sendTransaction( transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) } transactionExtras.binanceMemo?.memo?.let { txData = txData.copy(extras = BinanceTransactionExtras(it.toString())) } - transactionExtras.xrpDestinationTag?.tag?.let { - txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it)) - } + transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionExtras(it)) } transactionExtras.cosmosMemoState?.memo?.let { txData = txData.copy(extras = CosmosTransactionExtras(it)) } transactionExtras.tonMemoState?.memo?.let { txData = txData.copy(extras = TonTransactionExtras(it)) } @@ -250,17 +248,10 @@ private fun sendTransaction( Analytics.send( Basic.TransactionSent( sentFrom = AnalyticsParam.TxSentFrom.Sell, - memoType = if (txData.extras != null) MemoType.Full else MemoType.Empty, - ), - ) - Analytics.send( - Token.Send.SelectedCurrency( - currency = when (mainCurrencyType) { - MainCurrencyType.FIAT -> CurrencyType.AppCurrency - MainCurrencyType.CRYPTO -> CurrencyType.AppCurrency - }, + memoType = getMemoType(transactionExtras), ), ) + Analytics.sendSelectedCurrencyEvent(mainCurrencyType) dispatch(WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId)) } else { Analytics.send( @@ -270,9 +261,10 @@ private fun sendTransaction( token = amountToSend.currencySymbol, feeType = feeType.convertToAnalyticsFeeType(), ), - memoType = if (txData.extras != null) MemoType.Full else MemoType.Empty, + memoType = getMemoType(transactionExtras), ), ) + Analytics.sendSelectedCurrencyEvent(mainCurrencyType) dispatch(NavigationAction.PopBackTo()) } scope.launch(Dispatchers.IO) { @@ -336,6 +328,25 @@ private fun sendTransaction( } } +private fun getMemoType(transactionExtras: TransactionExtrasState): MemoType { + return when { + transactionExtras.isEmpty() -> MemoType.Empty + transactionExtras.isNull() -> MemoType.Null + else -> MemoType.Full + } +} + +private fun Analytics.sendSelectedCurrencyEvent(mainCurrencyType: MainCurrencyType) { + send( + Token.Send.SelectedCurrency( + currency = when (mainCurrencyType) { + MainCurrencyType.FIAT -> CurrencyType.AppCurrency + MainCurrencyType.CRYPTO -> CurrencyType.Token + }, + ), + ) +} + private fun updateFeedbackManagerInfo( walletManager: WalletManager, amountToSend: Amount, diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt index b1e3261e67..9f0851e06d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt @@ -34,6 +34,21 @@ data class TransactionExtrasState( val cosmosMemoState: CosmosMemoState? = null, ) : IdStateHolder { override val stateId: StateId = StateId.TRANSACTION_EXTRAS + + fun isNull(): Boolean { + return xlmMemo == null && binanceMemo == null && xrpDestinationTag == null && tonMemoState == null && + cosmosMemoState == null + } + + fun isEmpty(): Boolean { + val isXlmEmpty = xlmMemo?.viewFieldValue?.value?.isEmpty() ?: false + val isBinanceEmpty = binanceMemo?.viewFieldValue?.value?.isEmpty() ?: false + val isXrpEmpty = xrpDestinationTag?.viewFieldValue?.value?.isEmpty() ?: false + val isTonEmpty = tonMemoState?.viewFieldValue?.value?.isEmpty() ?: false + val isCosmosEmpty = cosmosMemoState?.viewFieldValue?.value?.isEmpty() ?: false + + return isXlmEmpty || isBinanceEmpty || isXrpEmpty || isTonEmpty || isCosmosEmpty + } } enum class XlmMemoType { @@ -105,6 +120,7 @@ data class TonMemoState( val memo: String? = null, val error: TransactionExtraError? = null, ) + data class CosmosMemoState( val viewFieldValue: InputViewValue = InputViewValue(""), val memo: String? = null, From 7dd02865025fd58c2d3f7d1b0140f9e268a35f02 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Jun 2023 23:32:18 +0800 Subject: [PATCH 17/39] Updated on 2026-08-14 --- .../tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt index f40f0d38dc..273d52d384 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt @@ -82,7 +82,7 @@ internal fun WalletDataModel.getAvailableActions( internal fun WalletDataModel.shouldShowMultipleAddress(): Boolean { val listOfAddresses = walletAddresses?.list.orEmpty() - return listOfAddresses.size > 1 + return listOfAddresses.size > 1 && currency.blockchain != Blockchain.BitcoinCash } internal fun WalletDataModel.assembleWarnings( From ae3d68a8d58752e3e75ecc3a8a81f0990f26cea1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Jun 2023 16:10:02 +0300 Subject: [PATCH 18/39] Updated on 2026-08-14 --- .../wallet/ui/view/TotalBalanceCard.kt | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/TotalBalanceCard.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/TotalBalanceCard.kt index afd1ba8c88..d89d245766 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/TotalBalanceCard.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/TotalBalanceCard.kt @@ -4,14 +4,7 @@ import android.content.Context import android.util.AttributeSet import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.material.Divider import androidx.compose.material.Surface import androidx.compose.material.Text @@ -27,11 +20,7 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.SelectorButton -import com.tangem.core.ui.components.SpacerH12 -import com.tangem.core.ui.components.SpacerH4 -import com.tangem.core.ui.components.SpacerW16 -import com.tangem.core.ui.components.SpacerW4 +import com.tangem.core.ui.components.* import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.domain.model.TotalFiatBalance @@ -42,7 +31,8 @@ import java.math.BigDecimal import java.math.RoundingMode import java.text.DecimalFormat import java.text.NumberFormat -import java.util.* +import java.util.Currency +import java.util.Locale internal class TotalBalanceCard @JvmOverloads constructor( context: Context, @@ -125,7 +115,7 @@ private fun TotalBalanceCardContent(state: TotalBalanceCardState, modifier: Modi -> LoadedAmount( amount = buildAmountString( amount = state.amount, - fiatCurrencySymbol = state.fiatCurrency.symbol, + fiatCurrency = state.fiatCurrency, ), ) } @@ -228,30 +218,45 @@ private fun LoadedAmount(amount: AnnotatedString, modifier: Modifier = Modifier) } @Composable -private fun buildAmountString(amount: BigDecimal?, fiatCurrencySymbol: String): AnnotatedString { +private fun buildAmountString(amount: BigDecimal?, fiatCurrency: FiatCurrency): AnnotatedString { if (amount == null) return AnnotatedString(text = UNKNOWN_AMOUNT_SIGN) - val formatter = NumberFormat.getInstance(Locale.getDefault()) as? DecimalFormat - ?: return AnnotatedString("${amount.toPlainString()} $fiatCurrencySymbol") - val decimalFormat = formatter.apply { - maximumFractionDigits = 2 - minimumFractionDigits = 2 - isGroupingUsed = true - this.roundingMode = RoundingMode.HALF_UP + val locale = Locale.getDefault() + val fractionDigits = 2 + val formatter = NumberFormat.getCurrencyInstance(locale) as? DecimalFormat + ?: return AnnotatedString("${amount.toPlainString()} ${fiatCurrency.symbol}") + + val currencyToShow = "${fiatCurrency.symbol} " + val scaledAmount = Currency.getInstance(fiatCurrency.code)?.let { currency -> + formatter.currency = currency + formatter.maximumFractionDigits = fractionDigits + formatter.minimumFractionDigits = fractionDigits + formatter.isGroupingUsed = true + formatter.roundingMode = RoundingMode.HALF_UP + formatter.format(amount).replace(currency.symbol, currencyToShow) + } ?: formatter.format(amount) + + val integer = scaledAmount.substringBefore(formatter.decimalFormatSymbols.decimalSeparator) + var reminder = scaledAmount.substringAfter(formatter.decimalFormatSymbols.decimalSeparator) + + // if locale formatted currency at the end, remember it and place out of AnnotatedString + val currency = if (reminder.endsWith(currencyToShow)) { + reminder = reminder.dropLast(currencyToShow.length) + currencyToShow + } else { + "" } - val scaledAmount = decimalFormat.format(amount) - val integer = scaledAmount.substringBefore(decimalFormat.decimalFormatSymbols.decimalSeparator) - val reminder = scaledAmount.substringAfter(decimalFormat.decimalFormatSymbols.decimalSeparator) return buildAnnotatedString { append(integer) - append(decimalFormat.decimalFormatSymbols.decimalSeparator) + append(formatter.decimalFormatSymbols.decimalSeparator) append( AnnotatedString( - text = "$reminder $fiatCurrencySymbol", + text = reminder, spanStyle = TangemTheme.typography.h3.toSpanStyle(), ), ) + append(currency) // it is not empty if was placed at the end after locale formatting } } From 99745c2ee4232a84a2279f6e5003dcdd4bff4cb6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Jun 2023 14:25:38 +0300 Subject: [PATCH 19/39] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 4 + core/res/src/main/res/values/strings.xml | 4 + core/ui/src/main/res/drawable/ic_group_24.xml | 9 + core/ui/src/main/res/drawable/ic_sort_24.xml | 9 + .../wallet/presentation/WalletFragment.kt | 13 +- .../presentation/common/WalletPreviewData.kt | 102 +++++--- .../common/component/NetworkGroupItem.kt | 67 +++-- .../common/component/TokenItem.kt | 11 +- .../common/state/NetworkGroupState.kt | 4 +- .../common/state/TokenListState.kt | 16 -- .../organizetokens/OrganizeTokensScreen.kt | 229 +++++++++++++++--- .../OrganizeTokensStateHolder.kt | 49 +++- .../organizetokens/OrganizeTokensViewModel.kt | 26 +- .../router/DefaultWalletRouter.kt | 29 +-- 14 files changed, 442 insertions(+), 130 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_group_24.xml create mode 100644 core/ui/src/main/res/drawable/ic_sort_24.xml delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 2baace2c4d..636d609568 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -46,6 +46,7 @@ Чат Принять Добавить + Применить Внимание Баланс: %s биометрическую аутентификацию @@ -255,6 +256,9 @@ Восстановление кода доступа Идентичные карты Код доступа + Группировка + По балансу + Сортировка токенов Участвовать Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ea3c2c3c93..af04c2fd30 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -44,6 +44,7 @@ Support Accept Add + Apply Attention Balance: %s biometric authentication @@ -253,6 +254,9 @@ Access code restore Identical cards Access code + Group + By balance + Organize tokens Participate Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Error code: %s. Please try again later. diff --git a/core/ui/src/main/res/drawable/ic_group_24.xml b/core/ui/src/main/res/drawable/ic_group_24.xml new file mode 100644 index 0000000000..0f5f5f4487 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_group_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_sort_24.xml b/core/ui/src/main/res/drawable/ic_sort_24.xml new file mode 100644 index 0000000000..538a24d8e0 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_sort_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt index 1bc2cd04fd..d62c79855f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -8,6 +8,8 @@ import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.features.wallet.navigation.WalletRouter @@ -41,8 +43,15 @@ internal class WalletFragment : Fragment() { return ComposeView(inflater.context).apply { setContent { - isTransitionGroup = true - _walletRouter.Initialize() + TangemTheme { + val systemBarsColor = TangemTheme.colors.background.secondary + SystemBarsEffect { + setSystemBarsColor(systemBarsColor) + } + + isTransitionGroup = true + _walletRouter.Initialize() + } } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index 40817e750e..cdd8d70132 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -4,11 +4,13 @@ import com.tangem.core.ui.R import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState -import com.tangem.feature.wallet.presentation.common.state.TokenListState +import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem +import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList import java.util.UUID internal object WalletPreviewData { @@ -107,42 +109,6 @@ internal object WalletPreviewData { val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString()) - val organizeTokensState = OrganizeTokensStateHolder( - tokens = TokenListState.GroupedByNetwork( - groups = persistentListOf(), - ), - header = OrganizeTokensStateHolder.HeaderConfig( - onSortByBalanceClick = {}, - onGroupByNetworkClick = {}, - ), - actions = OrganizeTokensStateHolder.ActionsConfig( - onApplyClick = {}, - onCancelClick = {}, - ), - ) - - private val draggableTokenList = persistentListOf( - tokenItemDragState.copy( - id = "token_1", - name = "Ethereum", - tokenIconResId = R.drawable.img_eth_22, - networkIconResId = null, - fiatAmount = "3 172,14 $", - ), - tokenItemDragState.copy( - id = "token_2", - networkIconResId = R.drawable.img_eth_22, - fiatAmount = "803,65 $", - ), - tokenItemDragState.copy( - id = "token_3", - name = "USDT", - tokenIconResId = R.drawable.img_arbitrum_22, - networkIconResId = R.drawable.img_eth_22, - fiatAmount = "88,01 $", - ), - ) - val networkGroup = NetworkGroupState.Content( id = UUID.randomUUID().toString(), networkName = "Ethereum", @@ -170,8 +136,66 @@ internal object WalletPreviewData { ) val draggableNetworkGroup = NetworkGroupState.Draggable( - id = UUID.randomUUID().toString(), + id = "group_1", networkName = "Ethereum", - tokens = draggableTokenList, + ) + + private const val networksSize = 10 + private const val tokensSize = 3 + val draggableItems = List(networksSize) { it } + .flatMap { index -> + val n = index + 1 + + val group = DraggableItem.GroupHeader( + groupState = NetworkGroupState.Draggable( + id = "group_$n", + networkName = "$n", + ), + ) + + val tokens: MutableList = mutableListOf() + repeat(times = tokensSize) { i -> + val nt = i + 1 + tokens.add( + DraggableItem.Token( + tokenItemState = tokenItemDragState.copy( + id = "${group.id}_token_$nt", + name = "Token $nt", + networkIconResId = R.drawable.img_eth_22.takeIf { i != 0 }, + ), + groupId = group.id, + ), + ) + } + + buildList { + add(group) + addAll(tokens) + } + } + .toPersistentList() + + val draggableTokens = draggableItems + .filterIsInstance() + .toPersistentList() + + val groupedOrganizeTokensState = OrganizeTokensStateHolder( + itemsState = OrganizeTokensListState.GroupedByNetwork( + items = draggableItems, + ), + header = OrganizeTokensStateHolder.HeaderConfig( + onSortByBalanceClick = {}, + onGroupByNetworkClick = {}, + ), + actions = OrganizeTokensStateHolder.ActionsConfig( + onApplyClick = {}, + onCancelClick = {}, + ), + ) + + val organizeTokensState = groupedOrganizeTokensState.copy( + itemsState = OrganizeTokensListState.Ungrouped( + items = draggableTokens, + ), ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt index ee2d924e85..70e1eeb242 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt @@ -19,7 +19,42 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState @Composable -internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Modifier) { +internal fun NetworkGroupItem(state: NetworkGroupState.Content, modifier: Modifier = Modifier) { + InternalNetworkGroupItem( + modifier = modifier, + networkName = state.networkName, + tokens = { + state.tokens.forEach { token -> + key(token.id) { + TokenItem(state = token) + } + } + }, + ) +} + +@Composable +internal fun DraggableNetworkGroupItem(state: NetworkGroupState.Draggable, modifier: Modifier = Modifier) { + InternalNetworkGroupItem( + modifier = modifier, + networkName = state.networkName, + endIcon = { + Icon( + painter = painterResource(id = R.drawable.ic_group_drop_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + }, + ) +} + +@Composable +private fun InternalNetworkGroupItem( + networkName: String, + modifier: Modifier = Modifier, + tokens: @Composable ColumnScope.() -> Unit = {}, + endIcon: @Composable RowScope.() -> Unit = {}, +) { Column(modifier = modifier) { Row( modifier = Modifier @@ -31,34 +66,30 @@ internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Mod horizontalArrangement = Arrangement.SpaceBetween, ) { Text( - text = stringResource(id = R.string.wallet_network_group_title, state.networkName), + text = stringResource(id = R.string.wallet_network_group_title, networkName), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, ) - if (state is NetworkGroupState.Draggable) { - Icon( - painter = painterResource(id = R.drawable.ic_group_drop_24), - tint = TangemTheme.colors.icon.informative, - contentDescription = null, - ) - } - } - Column { - state.tokens.forEach { token -> - key(token.id) { - TokenItem(state = token) - } - } + endIcon() } + tokens() } } // region Preview +@Composable +private fun NetworkGroupItemSample(group: NetworkGroupState) { + when (group) { + is NetworkGroupState.Content -> NetworkGroupItem(group) + is NetworkGroupState.Draggable -> DraggableNetworkGroupItem(group) + } +} + @Preview(showBackground = true, widthDp = 360) @Composable private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) { TangemTheme { - NetworkGroupItem(group) + NetworkGroupItemSample(group) } } @@ -66,7 +97,7 @@ private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider @Composable private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) { TangemTheme(isDark = true) { - NetworkGroupItem(group) + NetworkGroupItemSample(group) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt index 78c5b5f322..b2cb67a6d1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt @@ -9,6 +9,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -18,6 +19,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstrainedLayoutReference import androidx.constraintlayout.compose.ConstraintLayout @@ -34,6 +36,10 @@ import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState private const val DOTS = "•••" +val TOKEN_ITEM_HEIGHT: Dp + @Composable + @ReadOnlyComposable + get() = TangemTheme.dimens.size68 @Composable internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) { @@ -76,7 +82,8 @@ internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modif hasPending = false, options = { ref -> Icon( - modifier = Modifier.constrainAsOptionsItem(scope = this, ref), + modifier = Modifier + .constrainAsOptionsItem(scope = this, ref), painter = painterResource(id = R.drawable.ic_drag_24), tint = TangemTheme.colors.icon.informative, contentDescription = null, @@ -251,7 +258,7 @@ private fun BaseSurface( content: @Composable () -> Unit, ) { Surface( - modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68), + modifier = modifier.defaultMinSize(minHeight = TOKEN_ITEM_HEIGHT), color = TangemTheme.colors.background.primary, onClick = onClick ?: {}, enabled = onClick != null, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt index c34fdd126d..854730e663 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt @@ -7,17 +7,15 @@ import kotlinx.collections.immutable.ImmutableList internal sealed interface NetworkGroupState { val id: String val networkName: String - val tokens: ImmutableList data class Draggable( override val id: String, override val networkName: String, - override val tokens: ImmutableList, ) : NetworkGroupState data class Content( override val id: String, override val networkName: String, - override val tokens: ImmutableList, + val tokens: ImmutableList, ) : NetworkGroupState } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt deleted file mode 100644 index fe3b7fba46..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.feature.wallet.presentation.common.state - -import androidx.compose.runtime.Immutable -import kotlinx.collections.immutable.ImmutableList - -@Immutable -internal sealed interface TokenListState { - - data class GroupedByNetwork( - val groups: ImmutableList, - ) : TokenListState - - data class Ungrouped( - val tokens: ImmutableList, - ) : TokenListState -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt index e626eb0029..cea7c1211a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt @@ -1,88 +1,255 @@ -// TODO: Remove after components implementation -@file:Suppress("UNUSED_PARAMETER") - package com.tangem.feature.wallet.presentation.organizetokens +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.foundation.lazy.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.AppBarDefaults +import androidx.compose.material3.FabPosition +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.BackgroundActionButton +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.common.state.TokenListState +import com.tangem.feature.wallet.presentation.common.component.DraggableNetworkGroupItem +import com.tangem.feature.wallet.presentation.common.component.DraggableTokenItem @Composable internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder) { + val tokensListState = rememberLazyListState() + Scaffold( topBar = { - TopBar(state.header) + TopBar(state.header, tokensListState) }, content = { paddingValues -> TokenList( - state = state.tokens, modifier = Modifier.padding(paddingValues), + listState = tokensListState, + state = state.itemsState, ) }, floatingActionButtonPosition = FabPosition.Center, floatingActionButton = { Actions(state.actions) }, - contentColor = TangemTheme.colors.background.primary, + containerColor = TangemTheme.colors.background.secondary, ) } @Composable -private fun TokenList(state: TokenListState, modifier: Modifier = Modifier) { - when (state) { - is TokenListState.GroupedByNetwork -> { - /* [REDACTED_TODO_COMMENT] */ - } - is TokenListState.Ungrouped -> { - /* [REDACTED_TODO_COMMENT] */ +private fun TokenList(listState: LazyListState, state: OrganizeTokensListState, modifier: Modifier = Modifier) { + Box(modifier = modifier) { + LazyColumn( + modifier = Modifier + .align(Alignment.TopCenter) + .padding(horizontal = TangemTheme.dimens.spacing16), + state = listState, + contentPadding = PaddingValues( + top = TangemTheme.dimens.spacing12, + bottom = TangemTheme.dimens.spacing92, + ), + ) { + itemsIndexed( + items = state.items, + key = { _, item -> item.id }, + ) { index, item -> + DraggableItem( + item = item, + index = index, + lastItemIndex = state.items.lastIndex, + ) + } } + + BottomGradient(modifier = Modifier.align(Alignment.BottomCenter)) } } -@OptIn(ExperimentalMaterial3Api::class) @Composable -private fun TopBar(config: OrganizeTokensStateHolder.HeaderConfig, modifier: Modifier = Modifier) { - TopAppBar( - title = { +private fun DraggableItem(index: Int, item: DraggableItem, lastItemIndex: Int) { + val itemModifier = Modifier + .clipFirstAndLastItems(index, lastItemIndex) + + when (item) { + is DraggableItem.GroupHeader -> DraggableNetworkGroupItem( + modifier = itemModifier, + state = item.groupState, + ) + is DraggableItem.Token -> DraggableTokenItem( + modifier = itemModifier, + state = item.tokenItemState, + ) + } +} + +@Composable +private fun BottomGradient(modifier: Modifier = Modifier) { + Box( + modifier = modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size116) + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color.Transparent, + TangemTheme.colors.background.secondary, + ), + ), + ), + ) +} + +@Composable +private fun TopBar( + config: OrganizeTokensStateHolder.HeaderConfig, + tokensListState: LazyListState, + modifier: Modifier = Modifier, +) { + val isElevationEnabled by remember { + derivedStateOf { + tokensListState.firstVisibleItemScrollOffset > 0 + } + } + val elevation by animateDpAsState( + targetValue = if (isElevationEnabled) AppBarDefaults.TopAppBarElevation else TangemTheme.dimens.elevation0, + label = "top_bar_shadow_elevation", + ) + + Column( + modifier = modifier + .shadow(elevation) + .background(TangemTheme.colors.background.secondary) + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size56), + contentAlignment = Alignment.CenterStart, + ) { Text( - text = "Organize tokens", // TODO: Move to resources + text = stringResource(id = R.string.organize_tokens_title), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.primary1, maxLines = 1, ) - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = TangemTheme.colors.background.secondary, - titleContentColor = TangemTheme.colors.text.primary1, - ), - ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size56), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + BackgroundActionButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.organize_tokens_sort_by_balance), + iconResId = R.drawable.ic_sort_24, + onClick = config.onSortByBalanceClick, + ) + BackgroundActionButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.organize_tokens_group), + iconResId = R.drawable.ic_group_24, + onClick = config.onGroupByNetworkClick, + ) + } + } } @Composable private fun Actions(config: OrganizeTokensStateHolder.ActionsConfig, modifier: Modifier = Modifier) { - // TODO: Implement list apply and cancel actions + Row( + modifier = modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + ) { + SecondaryButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.common_cancel), + onClick = config.onCancelClick, + ) + PrimaryButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.common_apply), + onClick = config.onApplyClick, + ) + } +} + +private fun Modifier.clipFirstAndLastItems(index: Int, lastItemIndex: Int): Modifier = composed { + when (index) { + 0 -> { + this + .clip( + RoundedCornerShape( + topStart = TangemTheme.dimens.radius16, + topEnd = TangemTheme.dimens.radius16, + ), + ) + } + lastItemIndex -> { + this + .clip( + RoundedCornerShape( + bottomStart = TangemTheme.dimens.radius16, + bottomEnd = TangemTheme.dimens.radius16, + ), + ) + } + else -> this + } } // region Preview @Preview(showBackground = true, widthDp = 360) @Composable -private fun OrganizeTokensScreenPreview_Light() { +private fun OrganizeTokensScreenPreview_Light( + @PreviewParameter(OrganizeTokensStateProvider::class) state: OrganizeTokensStateHolder, +) { TangemTheme { - OrganizeTokensScreen(state = WalletPreviewData.organizeTokensState) + OrganizeTokensScreen(state) } } @Preview(showBackground = true, widthDp = 360) @Composable -private fun OrganizeTokensScreenPreview_Dark() { +private fun OrganizeTokensScreenPreview_Dark( + @PreviewParameter(OrganizeTokensStateProvider::class) state: OrganizeTokensStateHolder, +) { TangemTheme(isDark = true) { - OrganizeTokensScreen(state = WalletPreviewData.organizeTokensState) + OrganizeTokensScreen(state) } } + +private class OrganizeTokensStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletPreviewData.organizeTokensState, + WalletPreviewData.groupedOrganizeTokensState, + ), +) // endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt index b9a8e797cb..9e76b66248 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt @@ -1,10 +1,14 @@ package com.tangem.feature.wallet.presentation.organizetokens -import com.tangem.feature.wallet.presentation.common.state.TokenListState +import androidx.compose.runtime.Immutable +import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList internal data class OrganizeTokensStateHolder( - val tokens: TokenListState, val header: HeaderConfig, + val itemsState: OrganizeTokensListState, val actions: ActionsConfig, ) { @@ -17,4 +21,45 @@ internal data class OrganizeTokensStateHolder( val onApplyClick: () -> Unit, val onCancelClick: () -> Unit, ) +} + +@Immutable +internal sealed class OrganizeTokensListState { + abstract val items: ImmutableList + + data class GroupedByNetwork( + override val items: ImmutableList, + ) : OrganizeTokensListState() + + data class Ungrouped( + override val items: ImmutableList, + ) : OrganizeTokensListState() + + @Suppress("UNCHECKED_CAST") + inline fun updateItems(update: (List) -> List): OrganizeTokensListState { + val updatedItems = update(this.items).toPersistentList() + + return when (this) { + is GroupedByNetwork -> this.copy(items = updatedItems) + is Ungrouped -> this.copy(items = updatedItems as ImmutableList) + } + } +} + +@Immutable +internal sealed interface DraggableItem { + val id: String + + data class GroupHeader( + val groupState: NetworkGroupState.Draggable, + ) : DraggableItem { + override val id: String = groupState.id + } + + data class Token( + val tokenItemState: TokenItemState.Draggable, + val groupId: String, + ) : DraggableItem { + override val id: String = tokenItemState.id + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt index 16f726eab1..cbdd25e47e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt @@ -5,11 +5,14 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder.HeaderConfig import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.collections.immutable.toPersistentList import javax.inject.Inject import kotlin.properties.Delegates +// FIXME: Implemented with preview data @HiltViewModel internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() { @@ -18,5 +21,26 @@ internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() { var uiState: OrganizeTokensStateHolder by mutableStateOf(getInitialState()) private set - private fun getInitialState(): OrganizeTokensStateHolder = WalletPreviewData.organizeTokensState + private fun getInitialState(): OrganizeTokensStateHolder = WalletPreviewData.organizeTokensState.copy( + itemsState = OrganizeTokensListState.Ungrouped( + items = WalletPreviewData.draggableTokens, + ), + header = HeaderConfig( + onSortByBalanceClick = { /* no-op */ }, + onGroupByNetworkClick = this::toggleTokensByNetworkGrouping, + ), + ) + + private fun toggleTokensByNetworkGrouping() { + val newListState = when (val itemsState = uiState.itemsState) { + is OrganizeTokensListState.GroupedByNetwork -> OrganizeTokensListState.Ungrouped( + items = itemsState.items.filterIsInstance().toPersistentList(), + ) + is OrganizeTokensListState.Ungrouped -> OrganizeTokensListState.GroupedByNetwork( + items = WalletPreviewData.draggableItems, + ) + } + + uiState = uiState.copy(itemsState = newListState) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index f2768ed116..b06a719e1f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -8,7 +8,6 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.WalletFragment import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel @@ -25,24 +24,22 @@ internal class DefaultWalletRouter : InnerWalletRouter { @Composable override fun Initialize() { - TangemTheme { - NavHost( - navController = rememberNavController().apply { navController = this }, - startDestination = WalletScreens.WALLET.name, - ) { - composable(WalletScreens.WALLET.name) { - val viewModel = hiltViewModel().apply { router = this@DefaultWalletRouter } - WalletScreen(state = viewModel.uiState) - } + NavHost( + navController = rememberNavController().apply { navController = this }, + startDestination = WalletScreens.WALLET.name, + ) { + composable(WalletScreens.WALLET.name) { + val viewModel = hiltViewModel().apply { router = this@DefaultWalletRouter } + WalletScreen(state = viewModel.uiState) + } - composable(WalletScreens.ORGANIZE_TOKENS.name) { - BackHandler(onBack = ::popBackStack) + composable(WalletScreens.ORGANIZE_TOKENS.name) { + BackHandler(onBack = ::popBackStack) - val viewModel: OrganizeTokensViewModel = hiltViewModel() - .apply { router = this@DefaultWalletRouter } + val viewModel: OrganizeTokensViewModel = hiltViewModel() + .apply { router = this@DefaultWalletRouter } - OrganizeTokensScreen(state = viewModel.uiState) - } + OrganizeTokensScreen(state = viewModel.uiState) } } } From 673e812fd1f36662a960a0fc3591c9e0ff7057e7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 20 Jun 2023 22:10:23 +0800 Subject: [PATCH 20/39] Updated on 2026-08-14 --- .../tangem/tap/features/shop/data/DefaultShopRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt b/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt index 8f61a43c5f..74505b69d3 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt @@ -25,8 +25,8 @@ internal class DefaultShopRepository( .fold( onSuccess = ShopResponse::isOrderingAvailable, onFailure = { - Timber.e("Server error. isShopifyOrderingAvailable returns default value (false)") - false + Timber.e("Server error. isShopifyOrderingAvailable returns default value (true)") + true }, ) } From 53100fe46a31f4511b89d21af29391a7d062cd08 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 21 Jun 2023 14:54:43 +0800 Subject: [PATCH 21/39] Updated on 2026-08-14 --- .../presentation/states/TokenItemState.kt | 6 +- .../states/TokensListStateHolder.kt | 2 + .../states/TokensListToolbarState.kt | 9 +- .../com/tangem/core/ui/components/Buttons.kt | 183 +++++------------- .../core/ui/extensions/TextReference.kt | 32 ++- .../tangem/core/ui/extensions/WrappedList.kt | 10 + .../ui/src/main/res/drawable/ic_filter_24.xml | 9 + features/wallet/impl/build.gradle.kts | 2 + .../presentation/common/WalletPreviewData.kt | 120 +++++++----- .../common/component/NetworkGroupItem.kt | 46 ++--- .../common/component/TokenItem.kt | 2 +- .../common/state/NetworkGroupState.kt | 21 -- .../organizetokens/OrganizeTokensScreen.kt | 2 +- .../OrganizeTokensStateHolder.kt | 8 +- .../wallet/state/WalletCardState.kt | 3 + .../wallet/state/WalletContentItemState.kt | 13 ++ .../wallet/state/WalletStateHolder.kt | 22 ++- .../presentation/wallet/ui/WalletScreen.kt | 135 ++++++++++++- .../wallet/ui/components/WalletCard.kt | 10 +- .../wallet/ui/components/WalletCardsList.kt | 78 ++++++++ .../wallet/ui/components/WalletHeader.kt | 123 ------------ .../wallet/ui/components/WalletTopBar.kt | 55 ++++++ .../wallet/viewmodels/WalletViewModel.kt | 4 +- 23 files changed, 495 insertions(+), 400 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/extensions/WrappedList.kt create mode 100644 core/ui/src/main/res/drawable/ic_filter_24.xml delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletContentItemState.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCardsList.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletTopBar.kt diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt index df128ac9b4..790a83fb5f 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt @@ -1,12 +1,16 @@ package com.tangem.tap.features.tokens.impl.presentation.states +import androidx.compose.runtime.Immutable import kotlinx.collections.immutable.ImmutableList /** - * Network item state + * Token item state. + * All subclasses is stable, but @Immutable annotation is required to use this sealed class like as + * field of TokensListStateHolder. * [REDACTED_AUTHOR] */ +@Immutable sealed interface TokenItemState { /** Token id */ diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt index e083f7991b..101cdc8f52 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.tokens.impl.presentation.states +import androidx.compose.runtime.Immutable import androidx.paging.LoadState import androidx.paging.PagingData import kotlinx.coroutines.flow.Flow @@ -9,6 +10,7 @@ import kotlinx.coroutines.flow.Flow * [REDACTED_AUTHOR] */ +@Immutable internal sealed interface TokensListStateHolder { /** Toolbar state */ diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt index b4104f692b..a269659991 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt @@ -1,6 +1,13 @@ package com.tangem.tap.features.tokens.impl.presentation.states -/** Toolbar state */ +import androidx.compose.runtime.Immutable + +/** + * Toolbar state. + * All subclasses is stable, but @Immutable annotation is required to use this sealed class like as + * field of TokensListStateHolder. + */ +@Immutable sealed interface TokensListToolbarState { /** Callback to be invoked when BackButton is being clicked */ diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index 00a6b2ac03..5ca9a16de8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -312,14 +312,8 @@ private fun TangemButton( textStyle: TextStyle = TangemTheme.typography.button, ) { Button( - modifier = modifier - .width(IntrinsicSize.Min) - .heightIn(min = size.toHeightDp()), - onClick = { - if (!showProgress) { - onClick() - } - }, + modifier = modifier.heightIn(min = size.toHeightDp()), + onClick = { if (!showProgress) onClick() }, enabled = enabled, elevation = elevation, shape = size.toShape(), @@ -588,59 +582,44 @@ private open class TangemButtonColors( // region Preview @Composable -private fun PrimaryButtonSample(modifier: Modifier = Modifier) { +private fun PrimaryButtonSample() { Column( - modifier = modifier - .background(TangemTheme.colors.background.primary), + modifier = Modifier.background(TangemTheme.colors.background.primary), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { - PrimaryButton( - modifier = Modifier.fillMaxWidth(), - text = "Manage tokens", - onClick = { /* no-op */ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - PrimaryButton( - modifier = Modifier.fillMaxWidth(), - showProgress = true, - text = "Manage tokens", - onClick = { /* no-op */ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButton(modifier = Modifier.fillMaxWidth(), text = "Manage tokens", onClick = { }) + PrimaryButton(modifier = Modifier.fillMaxWidth(), showProgress = true, text = "Manage tokens", onClick = { }) PrimaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, - onClick = { /* no-op */ }, + onClick = { }, ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) PrimaryButtonIconStart( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, - onClick = { /* no-op */ }, + onClick = { }, ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) PrimaryButton( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", enabled = false, - onClick = { /* no-op */ }, + onClick = { }, ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) PrimaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, enabled = false, - onClick = { /* no-op */ }, + onClick = { }, ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) PrimaryButtonIconStart( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, enabled = false, - onClick = { /* no-op */ }, + onClick = { }, ) } } @@ -662,59 +641,44 @@ private fun PrimaryButtonPreview_Dark() { } @Composable -private fun SecondaryButtonSample(modifier: Modifier = Modifier) { +private fun SecondaryButtonSample() { Column( - modifier = modifier - .background(TangemTheme.colors.background.primary), + modifier = Modifier.background(TangemTheme.colors.background.primary), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { - SecondaryButton( - modifier = Modifier.fillMaxWidth(), - text = "Manage tokens", - onClick = { /* no-op */ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - SecondaryButton( - modifier = Modifier.fillMaxWidth(), - showProgress = true, - text = "Manage tokens", - onClick = { /* no-op */ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButton(modifier = Modifier.fillMaxWidth(), text = "Manage tokens", onClick = { }) + SecondaryButton(modifier = Modifier.fillMaxWidth(), showProgress = true, text = "Manage tokens", onClick = { }) SecondaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, - onClick = { /* no-op */ }, + onClick = { }, ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) SecondaryButtonIconStart( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, - onClick = { /* no-op */ }, + onClick = { }, ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) SecondaryButton( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", enabled = false, - onClick = { /* no-op */ }, + onClick = { }, ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) SecondaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, enabled = false, - onClick = { /* no-op */ }, + onClick = { }, ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) SecondaryButtonIconStart( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", iconResId = R.drawable.ic_tangem_24, enabled = false, - onClick = { /* no-op */ }, + onClick = { }, ) } } @@ -736,50 +700,23 @@ private fun SecondaryButtonPreview_Dark() { } @Composable -private fun TextButtonSample(modifier: Modifier = Modifier) { +private fun TextButtonSample() { Column( - modifier = modifier - .background(TangemTheme.colors.background.primary), + modifier = Modifier.background(TangemTheme.colors.background.primary), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { - TextButton( - text = "Enabled", - onClick = { /* no-op */ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - TextButtonIconStart( - text = "Enabled", - iconResId = R.drawable.ic_plus_24, - onClick = { /* no-op */ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - TextButton( - text = "Enabled", - enabled = false, - onClick = { /* no-op */ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - TextButtonIconStart( - text = "Enabled", - iconResId = R.drawable.ic_plus_24, - enabled = false, - onClick = { /* no-op */ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - WarningTextButton( - text = "Delete", - onClick = { /* no-op */ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - SelectorButton( - text = "USD", - onClick = { /* no-op */ }, - ) + TextButton(text = "Enabled", onClick = { }) + TextButtonIconStart(text = "Enabled", iconResId = R.drawable.ic_plus_24, onClick = { }) + TextButton(text = "Enabled", enabled = false, onClick = { }) + TextButtonIconStart(text = "Enabled", iconResId = R.drawable.ic_plus_24, enabled = false, onClick = { }) + WarningTextButton(text = "Delete", onClick = { }) + SelectorButton(text = "USD", onClick = { }) } } @Preview(showBackground = true, widthDp = 360) @Composable -private fun TextButtonPreview_Light() { +private fun TextButtonPreview_LightTheme() { TangemTheme { TextButtonSample() } @@ -787,62 +724,30 @@ private fun TextButtonPreview_Light() { @Preview(showBackground = true, widthDp = 360) @Composable -private fun TextButtonPreview_Dark() { +private fun TextButtonPreview_DarkTheme() { TangemTheme(isDark = true) { TextButtonSample() } } @Composable -private fun ActionButtonSample(modifier: Modifier = Modifier) { +private fun ActionButtonSample() { Column( - modifier = modifier - .background(TangemTheme.colors.background.primary), + modifier = Modifier.background(TangemTheme.colors.background.primary), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { - RoundedActionButton( - text = "Send", - iconResId = R.drawable.ic_arrow_up_24, - onClick = { /* [REDACTED_TODO_COMMENT]*/ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - ActionButton( - text = "Send", - iconResId = R.drawable.ic_arrow_up_24, - onClick = { /* [REDACTED_TODO_COMMENT]*/ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - BackgroundActionButton( - text = "Send", - iconResId = R.drawable.ic_arrow_up_24, - onClick = { /* [REDACTED_TODO_COMMENT]*/ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - RoundedActionButton( - text = "Send", - iconResId = R.drawable.ic_arrow_up_24, - enabled = false, - onClick = { /* [REDACTED_TODO_COMMENT]*/ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - ActionButton( - text = "Send", - iconResId = R.drawable.ic_arrow_up_24, - enabled = false, - onClick = { /* [REDACTED_TODO_COMMENT]*/ }, - ) - Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) - BackgroundActionButton( - text = "Send", - iconResId = R.drawable.ic_arrow_up_24, - enabled = false, - onClick = { /* [REDACTED_TODO_COMMENT]*/ }, - ) + RoundedActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { }) + ActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { }) + BackgroundActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { }) + RoundedActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { }) + ActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { }) + BackgroundActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { }) } } @Preview(showBackground = true, widthDp = 360) @Composable -private fun ActionButtonPreview_Light() { +private fun ActionButtonPreview_LightTheme() { TangemTheme { ActionButtonSample() } @@ -850,7 +755,7 @@ private fun ActionButtonPreview_Light() { @Preview(showBackground = true, widthDp = 360) @Composable -private fun ActionButtonPreview_Dark() { +private fun ActionButtonPreview_DarkTheme() { TangemTheme(isDark = true) { ActionButtonSample() } diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt index e70b0d7015..a27dcb58c4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt @@ -2,22 +2,42 @@ package com.tangem.core.ui.extensions import androidx.annotation.StringRes import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.res.stringResource +/** + * Utility class for creating text as [String] or [StringRes]. + * It necessary to use [Immutable] annotation because all sealed interface has runtime stability. + * All subclasses are stable. + */ +@Immutable sealed interface TextReference { - class Res(@StringRes val id: Int, val formatArgs: List = emptyList()) : TextReference { - constructor(@StringRes id: Int, vararg formatArgs: Any) : this(id, formatArgs.toList()) - } - class Str(val value: String) : TextReference + /** + * Text resource id + * + * @property id resource id + * @property formatArgs arguments + * + * Impossible to use [kotlinx.collections.immutable.ImmutableList] because [Any] is unstable. + */ + data class Res(@StringRes val id: Int, val formatArgs: WrappedList = WrappedList(emptyList())) : TextReference + + /** + * Text string + * + * @property value value + */ + data class Str(val value: String) : TextReference } +/** Get text */ @Composable @ReadOnlyComposable fun TextReference.resolveReference(): String { return when (this) { - is TextReference.Res -> stringResource(this.id, *this.formatArgs.toTypedArray()) - is TextReference.Str -> this.value + is TextReference.Res -> stringResource(id, *formatArgs.toTypedArray()) + is TextReference.Str -> value } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/WrappedList.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/WrappedList.kt new file mode 100644 index 0000000000..6f529c9574 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/WrappedList.kt @@ -0,0 +1,10 @@ +package com.tangem.core.ui.extensions + +import androidx.compose.runtime.Immutable + +/** +[REDACTED_AUTHOR] + */ +@JvmInline +@Immutable +value class WrappedList(val data: List) : List by data \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_filter_24.xml b/core/ui/src/main/res/drawable/ic_filter_24.xml new file mode 100644 index 0000000000..b88ca7dcef --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_filter_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 183e43c174..c639f23ac8 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -22,6 +22,8 @@ dependencies { implementation(deps.compose.ui.tooling) implementation(deps.compose.shimmer) implementation(deps.compose.accompanist.systemUiController) + + /** Other libraries */ implementation(deps.kotlin.immutable.collections) /** DI */ diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index cdd8d70132..92c8d8f644 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -1,13 +1,13 @@ package com.tangem.feature.wallet.presentation.common import com.tangem.core.ui.R -import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -15,7 +15,9 @@ import java.util.UUID internal object WalletPreviewData { - val walletCardContent = WalletCardState.Content( + val walletTopBarConfig = WalletStateHolder.TopBarConfig(onScanCardClick = {}, onMoreClick = {}) + + val walletCardContentState = WalletCardState.Content( id = UUID.randomUUID().toString(), title = "Wallet 1", balance = "8923,05 $", @@ -24,7 +26,7 @@ internal object WalletPreviewData { onClick = {}, ) - val walletCardLoading = WalletCardState.Loading( + val walletCardLoadingState = WalletCardState.Loading( id = UUID.randomUUID().toString(), title = "Wallet 1", additionalInfo = "3 cards • Seed enabled", @@ -32,7 +34,7 @@ internal object WalletPreviewData { onClick = {}, ) - val walletCardHiddenContent = WalletCardState.HiddenContent( + val walletCardHiddenContentState = WalletCardState.HiddenContent( id = UUID.randomUUID().toString(), title = "Wallet 1", additionalInfo = "3 cards • Seed enabled", @@ -40,7 +42,7 @@ internal object WalletPreviewData { onClick = {}, ) - val walletCardError = WalletCardState.Error( + val walletCardErrorState = WalletCardState.Error( id = UUID.randomUUID().toString(), title = "Wallet 1", additionalInfo = "3 cards • Seed enabled", @@ -48,15 +50,6 @@ internal object WalletPreviewData { onClick = {}, ) - val walletScreenState = WalletStateHolder( - onBackClick = {}, - headerConfig = WalletStateHolder.HeaderConfig( - wallets = persistentListOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError), - onScanCardClick = {}, - onMoreClick = {}, - ), - ) - val tokenItemVisibleState = TokenItemState.Content( id = UUID.randomUUID().toString(), tokenIconUrl = null, @@ -109,37 +102,6 @@ internal object WalletPreviewData { val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString()) - val networkGroup = NetworkGroupState.Content( - id = UUID.randomUUID().toString(), - networkName = "Ethereum", - tokens = persistentListOf( - tokenItemVisibleState.copy( - id = "token_1", - name = "Ethereum", - tokenIconResId = R.drawable.img_eth_22, - networkIconResId = null, - amount = "1,89340821 ETH", - ), - tokenItemVisibleState.copy( - id = "token_2", - networkIconResId = R.drawable.img_eth_22, - amount = "733,71097 MATIC", - ), - tokenItemVisibleState.copy( - id = "token_3", - name = "USDT", - tokenIconResId = R.drawable.img_arbitrum_22, - networkIconResId = R.drawable.img_eth_22, - amount = "0,25404523 ARB", - ), - ), - ) - - val draggableNetworkGroup = NetworkGroupState.Draggable( - id = "group_1", - networkName = "Ethereum", - ) - private const val networksSize = 10 private const val tokensSize = 3 val draggableItems = List(networksSize) { it } @@ -147,10 +109,8 @@ internal object WalletPreviewData { val n = index + 1 val group = DraggableItem.GroupHeader( - groupState = NetworkGroupState.Draggable( - id = "group_$n", - networkName = "$n", - ), + id = "group_$n", + networkName = "$n", ) val tokens: MutableList = mutableListOf() @@ -198,4 +158,66 @@ internal object WalletPreviewData { items = draggableTokens, ), ) + + val groupedWalletScreenState = WalletStateHolder( + onBackClick = {}, + topBarConfig = walletTopBarConfig, + selectedWallet = walletCardContentState, + wallets = persistentListOf( + walletCardContentState, + walletCardLoadingState, + walletCardHiddenContentState, + walletCardErrorState, + ), + contentItems = persistentListOf( + WalletContentItemState.NetworkGroupTitle("Bitcoin"), + WalletContentItemState.Token( + tokenItemVisibleState.copy( + id = "token_1", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + ), + WalletContentItemState.Token( + tokenItemVisibleState.copy( + id = "token_2", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + ), + WalletContentItemState.Token( + tokenItemVisibleState.copy( + id = "token_3", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + ), + WalletContentItemState.Token( + tokenItemVisibleState.copy( + id = "token_4", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + ), + WalletContentItemState.NetworkGroupTitle("Ethereum"), + WalletContentItemState.Token( + tokenItemVisibleState.copy( + id = "token_5", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + ), + ), + onOrganizeTokensClick = {}, + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt index 70e1eeb242..82e40737c5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -15,29 +14,20 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState @Composable -internal fun NetworkGroupItem(state: NetworkGroupState.Content, modifier: Modifier = Modifier) { +internal fun NetworkGroupItem(networkName: String, modifier: Modifier = Modifier) { InternalNetworkGroupItem( modifier = modifier, - networkName = state.networkName, - tokens = { - state.tokens.forEach { token -> - key(token.id) { - TokenItem(state = token) - } - } - }, + networkName = networkName, ) } @Composable -internal fun DraggableNetworkGroupItem(state: NetworkGroupState.Draggable, modifier: Modifier = Modifier) { +internal fun DraggableNetworkGroupItem(networkName: String, modifier: Modifier = Modifier) { InternalNetworkGroupItem( modifier = modifier, - networkName = state.networkName, + networkName = networkName, endIcon = { Icon( painter = painterResource(id = R.drawable.ic_group_drop_24), @@ -52,14 +42,13 @@ internal fun DraggableNetworkGroupItem(state: NetworkGroupState.Draggable, modif private fun InternalNetworkGroupItem( networkName: String, modifier: Modifier = Modifier, - tokens: @Composable ColumnScope.() -> Unit = {}, endIcon: @Composable RowScope.() -> Unit = {}, ) { Column(modifier = modifier) { Row( modifier = Modifier .background(TangemTheme.colors.background.primary) - .padding(horizontal = TangemTheme.dimens.spacing12) + .padding(horizontal = TangemTheme.dimens.spacing14) .fillMaxWidth() .heightIn(min = TangemTheme.dimens.size48), verticalAlignment = Alignment.CenterVertically, @@ -72,39 +61,36 @@ private fun InternalNetworkGroupItem( ) endIcon() } - tokens() } } // region Preview @Composable -private fun NetworkGroupItemSample(group: NetworkGroupState) { - when (group) { - is NetworkGroupState.Content -> NetworkGroupItem(group) - is NetworkGroupState.Draggable -> DraggableNetworkGroupItem(group) +private fun NetworkGroupItemSample(isDraggable: Boolean) { + if (isDraggable) { + DraggableNetworkGroupItem(networkName = "Ethereum") + } else { + NetworkGroupItem(networkName = "Ethereum") } } @Preview(showBackground = true, widthDp = 360) @Composable -private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) { +private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) isDraggable: Boolean) { TangemTheme { - NetworkGroupItemSample(group) + NetworkGroupItemSample(isDraggable) } } @Preview(showBackground = true, widthDp = 360) @Composable -private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) { +private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) isDraggable: Boolean) { TangemTheme(isDark = true) { - NetworkGroupItemSample(group) + NetworkGroupItemSample(isDraggable) } } -private class NetworkGroupProvider : CollectionPreviewParameterProvider( - collection = listOf( - WalletPreviewData.networkGroup, - WalletPreviewData.draggableNetworkGroup, - ), +private class NetworkGroupProvider : CollectionPreviewParameterProvider( + collection = listOf(true, false), ) // endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt index b2cb67a6d1..de8daa9cb8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt @@ -206,7 +206,7 @@ private fun InternalTokenItem( modifier = Modifier .fillMaxWidth() .padding( - horizontal = TangemTheme.dimens.spacing12, + horizontal = TangemTheme.dimens.spacing14, vertical = TangemTheme.dimens.spacing4, ), ) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt deleted file mode 100644 index 854730e663..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.feature.wallet.presentation.common.state - -import androidx.compose.runtime.Immutable -import kotlinx.collections.immutable.ImmutableList - -@Immutable -internal sealed interface NetworkGroupState { - val id: String - val networkName: String - - data class Draggable( - override val id: String, - override val networkName: String, - ) : NetworkGroupState - - data class Content( - override val id: String, - override val networkName: String, - val tokens: ImmutableList, - ) : NetworkGroupState -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt index cea7c1211a..ac4d146213 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt @@ -93,7 +93,7 @@ private fun DraggableItem(index: Int, item: DraggableItem, lastItemIndex: Int) { when (item) { is DraggableItem.GroupHeader -> DraggableNetworkGroupItem( modifier = itemModifier, - state = item.groupState, + networkName = item.networkName, ) is DraggableItem.Token -> DraggableTokenItem( modifier = itemModifier, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt index 9e76b66248..0e0a07d2e6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt @@ -1,7 +1,6 @@ package com.tangem.feature.wallet.presentation.organizetokens import androidx.compose.runtime.Immutable -import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState import com.tangem.feature.wallet.presentation.common.state.TokenItemState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList @@ -51,10 +50,9 @@ internal sealed interface DraggableItem { val id: String data class GroupHeader( - val groupState: NetworkGroupState.Draggable, - ) : DraggableItem { - override val id: String = groupState.id - } + override val id: String, + val networkName: String, + ) : DraggableItem data class Token( val tokenItemState: TokenItemState.Draggable, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt index 7685a267da..2f000581e2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt @@ -1,9 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.state import androidx.annotation.DrawableRes +import androidx.compose.runtime.Immutable /** Wallet card state */ +@Immutable internal sealed interface WalletCardState { + /** Id */ val id: String diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletContentItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletContentItemState.kt new file mode 100644 index 0000000000..66a426f04a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletContentItemState.kt @@ -0,0 +1,13 @@ +package com.tangem.feature.wallet.presentation.wallet.state + +import com.tangem.feature.wallet.presentation.common.state.TokenItemState + +/** +[REDACTED_AUTHOR] + */ +internal sealed interface WalletContentItemState { + + data class NetworkGroupTitle(val networkName: String) : WalletContentItemState + + data class Token(val tokenItemState: TokenItemState) : WalletContentItemState +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt index 8142667ad3..061647e21b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt @@ -5,26 +5,28 @@ import kotlinx.collections.immutable.ImmutableList /** * Wallet state holder * - * @property onBackClick lambda be invoked when back button is clicked - * @property headerConfig header config + * @property onBackClick lambda be invoked when back button is clicked + * @property topBarConfig top bar config + * @property selectedWallet selected wallet + * @property wallets list of wallets states + * @property contentItems content items * [REDACTED_AUTHOR] */ internal data class WalletStateHolder( val onBackClick: () -> Unit, - val headerConfig: HeaderConfig, + val topBarConfig: TopBarConfig, + val selectedWallet: WalletCardState, + val wallets: ImmutableList, + val contentItems: ImmutableList, + val onOrganizeTokensClick: () -> Unit, ) { /** - * Header config + * Top bar config * - * @property wallets list of wallets states * @property onScanCardClick lambda be invoked when scan card button is clicked * @property onMoreClick lambda be invoked when more button is clicked */ - data class HeaderConfig( - val wallets: ImmutableList, - val onScanCardClick: () -> Unit, - val onMoreClick: () -> Unit, - ) + data class TopBarConfig(val onScanCardClick: () -> Unit, val onMoreClick: () -> Unit) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index ba17e6e692..701f730a14 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -1,10 +1,32 @@ package com.tangem.feature.wallet.presentation.wallet.ui import androidx.activity.compose.BackHandler -import androidx.compose.material.Scaffold +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.clip +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.RoundedActionButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.common.component.NetworkGroupItem +import com.tangem.feature.wallet.presentation.common.component.TokenItem +import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder -import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletHeader +import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletCardsList +import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletTopBar /** * Wallet screen @@ -18,8 +40,109 @@ internal fun WalletScreen(state: WalletStateHolder) { BackHandler(onBack = state.onBackClick) Scaffold( - topBar = { WalletHeader(config = state.headerConfig) }, - ) { - // TODO: [REDACTED_TASK_KEY] Design a body with tokens and transactions + topBar = { WalletTopBar(config = state.topBarConfig) }, + containerColor = TangemTheme.colors.background.secondary, + ) { scaffoldPaddings -> + val lastContentItemIndex = remember(state.contentItems) { + state.contentItems.lastIndex + } + + LazyColumn( + modifier = Modifier + .padding(scaffoldPaddings) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + item { WalletCardsList(wallets = state.wallets) } + + itemsIndexed( + items = state.contentItems, + key = { _, item -> + when (item) { + is WalletContentItemState.NetworkGroupTitle -> item.networkName + is WalletContentItemState.Token -> item.tokenItemState.id + } + }, + ) { index, item -> + val itemModifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .clipFirstAndLastItems(index, lastContentItemIndex) + + when (item) { + is WalletContentItemState.NetworkGroupTitle -> { + NetworkGroupItem( + networkName = item.networkName, + modifier = itemModifier, + ) + } + is WalletContentItemState.Token -> { + TokenItem( + state = item.tokenItemState, + modifier = itemModifier, + ) + } + } + } + + item { + RoundedActionButton( + text = stringResource(id = R.string.organize_tokens_title), + iconResId = R.drawable.ic_filter_24, + onClick = state.onOrganizeTokensClick, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing14), + ) + } + } } -} \ No newline at end of file +} + +private fun Modifier.clipFirstAndLastItems(index: Int, lastItemIndex: Int): Modifier = composed { + when (index) { + 0 -> { + this + .padding(top = TangemTheme.dimens.spacing14) + .clip( + RoundedCornerShape( + topStart = TangemTheme.dimens.radius16, + topEnd = TangemTheme.dimens.radius16, + ), + ) + } + lastItemIndex -> { + this + .clip( + RoundedCornerShape( + bottomStart = TangemTheme.dimens.radius16, + bottomEnd = TangemTheme.dimens.radius16, + ), + ) + } + else -> this + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun WalletScreenPreview_Light( + @PreviewParameter(WalletScreenParameterProvider::class) state: WalletStateHolder, +) { + TangemTheme { + WalletScreen(state) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun WalletScreenPreview_Dark( + @PreviewParameter(WalletScreenParameterProvider::class) state: WalletStateHolder, +) { + TangemTheme(isDark = true) { + WalletScreen(state) + } +} + +private class WalletScreenParameterProvider : CollectionPreviewParameterProvider( + collection = listOf(WalletPreviewData.groupedWalletScreenState), +) +// endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt index 562c954023..d92651ac6f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt @@ -44,7 +44,7 @@ internal fun WalletCard(state: WalletCardState, modifier: Modifier = Modifier) { ConstraintLayout( modifier = Modifier .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.spacing12), + .padding(horizontal = TangemTheme.dimens.spacing14), ) { val (balanceBlock, imageItem) = createRefs() Column( @@ -182,10 +182,10 @@ private fun Preview_WalletCard_DarkTheme(@PreviewParameter(WalletCardStateProvid private class WalletCardStateProvider : CollectionPreviewParameterProvider( collection = listOf( - WalletPreviewData.walletCardContent, - WalletPreviewData.walletCardLoading, - WalletPreviewData.walletCardHiddenContent, - WalletPreviewData.walletCardError, + WalletPreviewData.walletCardContentState, + WalletPreviewData.walletCardLoadingState, + WalletPreviewData.walletCardHiddenContentState, + WalletPreviewData.walletCardErrorState, ), ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCardsList.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCardsList.kt new file mode 100644 index 0000000000..d37d359252 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCardsList.kt @@ -0,0 +1,78 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components + +import androidx.compose.animation.core.* +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +/** + * Wallets list + * + * @param wallets list of wallet state + * +[REDACTED_AUTHOR] + */ +@OptIn(ExperimentalFoundationApi::class) +@Composable +internal fun WalletCardsList(wallets: ImmutableList) { + val horizontalCardPadding = TangemTheme.dimens.spacing16 + val itemWidth = LocalConfiguration.current.screenWidthDp.dp - horizontalCardPadding * 2 + + val lazyListState = rememberLazyListState() + LazyRow( + modifier = Modifier.background(color = TangemTheme.colors.background.secondary), + state = lazyListState, + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState), + ) { + items(items = wallets, key = WalletCardState::id) { state -> + WalletCard(state = state, modifier = Modifier.width(itemWidth)) + } + } +} + +@Preview +@Composable +private fun Preview_WalletHeader_LightTheme() { + TangemTheme(isDark = false) { + WalletCardsList( + wallets = persistentListOf( + WalletPreviewData.walletCardContentState, + WalletPreviewData.walletCardLoadingState, + WalletPreviewData.walletCardHiddenContentState, + WalletPreviewData.walletCardErrorState, + ), + ) + } +} + +@Preview +@Composable +private fun Preview_WalletHeader_DarkTheme() { + TangemTheme(isDark = true) { + WalletCardsList( + wallets = persistentListOf( + WalletPreviewData.walletCardContentState, + WalletPreviewData.walletCardLoadingState, + WalletPreviewData.walletCardHiddenContentState, + WalletPreviewData.walletCardErrorState, + ), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt deleted file mode 100644 index cb01b48377..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt +++ /dev/null @@ -1,123 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.ui.components - -import androidx.compose.animation.core.* -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.LocalOverscrollConfiguration -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import androidx.compose.ui.unit.dp -import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState -import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder -import kotlinx.collections.immutable.persistentListOf - -/** - * Wallet screen header - * - * @param config config - * -[REDACTED_AUTHOR] - */ -@OptIn(ExperimentalFoundationApi::class) -@Composable -internal fun WalletHeader(config: WalletStateHolder.HeaderConfig) { - Column( - modifier = Modifier - .background(color = TangemTheme.colors.background.secondary) - .padding(bottom = TangemTheme.dimens.spacing14), - ) { - TopBar(onScanCardClick = config.onScanCardClick, onMoreClick = config.onMoreClick) - - CompositionLocalProvider(LocalOverscrollConfiguration provides null) { - val lazyListState = rememberLazyListState() - LazyRow( - state = lazyListState, - contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), - flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState), - ) { - items(items = config.wallets, key = WalletCardState::id) { state -> - WalletCard( - state = state, - modifier = Modifier.width( - LocalConfiguration.current.screenWidthDp.dp - TangemTheme.dimens.size32, - ), - ) - } - } - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun TopBar(onScanCardClick: () -> Unit, onMoreClick: () -> Unit) { - TopAppBar( - title = { - Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null) - }, - actions = { - IconButton(onClick = onScanCardClick) { - Icon(painter = painterResource(id = R.drawable.ic_tap_card_24), contentDescription = "Scan card") - } - IconButton(onClick = onMoreClick) { - Icon(painter = painterResource(id = R.drawable.ic_more_vertical_24), contentDescription = "More") - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = TangemTheme.colors.background.secondary, - titleContentColor = TangemTheme.colors.icon.primary1, - actionIconContentColor = TangemTheme.colors.icon.primary1, - ), - ) -} - -@Preview -@Composable -private fun Preview_WalletHeader_LightTheme( - @PreviewParameter(WalletHeaderProvider::class) state: WalletStateHolder.HeaderConfig, -) { - TangemTheme(isDark = false) { - WalletHeader(state) - } -} - -@Preview -@Composable -private fun Preview_WalletHeader_DarkTheme( - @PreviewParameter(WalletHeaderProvider::class) state: WalletStateHolder.HeaderConfig, -) { - TangemTheme(isDark = true) { - WalletHeader(state) - } -} - -private class WalletHeaderProvider : CollectionPreviewParameterProvider( - collection = listOf( - WalletStateHolder.HeaderConfig( - wallets = persistentListOf( - WalletPreviewData.walletCardContent, - WalletPreviewData.walletCardLoading, - WalletPreviewData.walletCardHiddenContent, - WalletPreviewData.walletCardError, - ), - onScanCardClick = {}, - onMoreClick = {}, - ), - ), -) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletTopBar.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletTopBar.kt new file mode 100644 index 0000000000..29b91f97bd --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletTopBar.kt @@ -0,0 +1,55 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components + +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder + +/** + * Wallet screen top bar + * + * @param config top bar config + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun WalletTopBar(config: WalletStateHolder.TopBarConfig) { + TopAppBar( + title = { + Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null) + }, + actions = { + IconButton(onClick = config.onScanCardClick) { + Icon(painter = painterResource(id = R.drawable.ic_tap_card_24), contentDescription = "Scan card") + } + IconButton(onClick = config.onMoreClick) { + Icon(painter = painterResource(id = R.drawable.ic_more_vertical_24), contentDescription = "More") + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = TangemTheme.colors.background.secondary, + titleContentColor = TangemTheme.colors.icon.primary1, + actionIconContentColor = TangemTheme.colors.icon.primary1, + ), + scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), + ) +} + +@Preview +@Composable +private fun Preview_WalletTopBar_LightTheme() { + TangemTheme(isDark = false) { + WalletTopBar(config = WalletPreviewData.walletTopBarConfig) + } +} + +@Preview +@Composable +private fun Preview_WalletTopBar_DarkTheme() { + TangemTheme(isDark = true) { + WalletTopBar(config = WalletPreviewData.walletTopBarConfig) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index a565bb8c91..9cb70bd530 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -27,9 +27,9 @@ internal class WalletViewModel @Inject constructor() : ViewModel() { private set // TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData - private fun getInitialState(): WalletStateHolder = WalletPreviewData.walletScreenState.copy( + private fun getInitialState(): WalletStateHolder = WalletPreviewData.groupedWalletScreenState.copy( onBackClick = { router.popBackStack() }, - headerConfig = WalletPreviewData.walletScreenState.headerConfig.copy( + topBarConfig = WalletPreviewData.groupedWalletScreenState.topBarConfig.copy( onScanCardClick = { router.openOrganizeTokensScreen() }, ), ) From 150436c3ff4626be827cf6568498526f9d1c5b97 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 21 Jun 2023 17:48:18 +0800 Subject: [PATCH 22/39] Updated on 2026-08-14 --- .../impl/presentation/viewmodels/AddCustomTokenViewModel.kt | 3 ++- .../java/com/tangem/domain/common/extensions/Blockchain.kt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt index 5ccb8e66e6..47245502cf 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt @@ -207,7 +207,8 @@ internal class AddCustomTokenViewModel @Inject constructor( val defaultNetwork = createNetworkSelectorItem(blockchain = Blockchain.Unknown) return listOf(defaultNetwork) + Blockchain.values() .filter { blockchain -> - reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true + reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true && + blockchain != Blockchain.Cardano } .sortedBy(Blockchain::fullName) .map(::createNetworkSelectorItem) diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 151aecafc4..c891e19e44 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -173,5 +173,6 @@ fun Blockchain.isSupportedInApp(): Boolean { } private val excludedBlockchains = listOf( + Blockchain.Ducatus, Blockchain.Unknown, ) \ No newline at end of file From e644fbf181a76c8cbca67992199e3a2264c66b58 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 21 Jun 2023 13:58:02 +0300 Subject: [PATCH 23/39] Updated on 2026-08-14 --- .../implementation/DefaultWalletCurrenciesManager.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index f344f46d4a..0bbd76b7bc 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -1,13 +1,10 @@ package com.tangem.tap.domain.walletCurrencies.implementation import com.tangem.blockchain.common.DerivationStyle -import com.tangem.common.CompletionResult -import com.tangem.common.flatMap -import com.tangem.common.fold -import com.tangem.common.map -import com.tangem.domain.models.scan.CardDTO +import com.tangem.common.* import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.util.UserWalletId +import com.tangem.domain.models.scan.CardDTO import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.builders.WalletStoreBuilder @@ -103,7 +100,7 @@ internal class DefaultWalletCurrenciesManager( .flatMap { updateWalletStores(userWallet, remainingCurrencies.toBlockchainNetworks()) } - .map { + .doOnResult { saveUserCurrencies(card, remainingCurrencies) } } From 6310754739454d8ba032683063c6d0befef84be7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 21 Jun 2023 16:14:45 +0300 Subject: [PATCH 24/39] Updated on 2026-08-14 --- .../walletconnect/WalletConnectMiddleware.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index fd67e805d2..c42aa9f320 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -306,13 +306,16 @@ class WalletConnectMiddleware { ) } is WalletConnectError.ExternalApprovalError -> { - store.dispatchOnMain( - GlobalAction.ShowDialog( - AppDialog.SimpleOkWarningDialog( - message = action.error.message ?: "", + val message = action.error.message + if (!message.isNullOrEmpty()) { + store.dispatchOnMain( + GlobalAction.ShowDialog( + AppDialog.SimpleOkWarningDialog( + message = message, + ), ), - ), - ) + ) + } } else -> Unit } From e22137c544e31fd8d67eef91093b7890351e137f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 Jun 2023 16:01:49 +0800 Subject: [PATCH 25/39] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 4 +- .../src/main/res/values-zh-rTW/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 4 +- core/ui/build.gradle.kts | 2 + .../ui/components/transactions/Transaction.kt | 342 ++++++++++++++++++ .../transactions/TransactionState.kt | 155 ++++++++ .../com/tangem/core/ui/res/TangemDimens.kt | 2 + core/ui/src/main/res/drawable/ic_doc_24.xml | 9 + .../feature/swap/ui/SwapScreenContent.kt | 6 +- .../feature/swap/ui/SwapSuccessScreen.kt | 2 +- 10 files changed, 521 insertions(+), 7 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt create mode 100644 core/ui/src/main/res/drawable/ic_doc_24.xml diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 636d609568..8730764f58 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -47,6 +47,7 @@ Принять Добавить Применить + Одобрение Внимание Баланс: %s биометрическую аутентификацию @@ -74,7 +75,9 @@ Начать Отправить Успешно + Обмен условия участия + Перевод Я понял Да Адрес контракта скопирован! @@ -386,7 +389,6 @@ Безлимитно Открыть в обозревателе В процессе - Обмен Обменять Обмен %s на Котировки включают дополнительную комиссию Tangem в размере %s. Это помогает нам предоставлять первоклассный продукт. diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index 08f4e9e68b..f9ce322037 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -69,6 +69,7 @@ 開始 提交 成功 + 交換 條款和條件 我了解 @@ -367,7 +368,6 @@ 要繼續,您需要允許 1inch 智能合約使用您的 %s 在瀏覽器中查看 進行中 - 交換 交易 交易 %s 至 此外,報價包括%s的 Tangem 費用。這有助於我們提供一流的產品 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index af04c2fd30..0c933df75f 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -45,6 +45,7 @@ Accept Add Apply + Approval Attention Balance: %s biometric authentication @@ -72,7 +73,9 @@ Start Submit Success + Swap terms and conditions + Transfer I understand Yes Contract address copied! @@ -382,7 +385,6 @@ Unlimited View in Explorer In progress - Swap Swap Swap of %s to Quotes include an additional Tangem commission of %s. This helps us deliver a top-of-the-line product. diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 1dc31c8335..94bb016bdc 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -9,8 +9,10 @@ dependencies { implementation(deps.androidx.fragment.ktx) /** Compose */ + implementation(deps.compose.constraintLayout) implementation(deps.compose.foundation) implementation(deps.compose.material) + implementation(deps.compose.material3) implementation(deps.compose.ui.tooling) /** Other libraries */ diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt new file mode 100644 index 0000000000..0f265ecce9 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt @@ -0,0 +1,342 @@ +package com.tangem.core.ui.components.transactions + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension +import com.tangem.core.ui.R +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.res.TangemTheme + +/** + * Transaction component + * + * @param state state + * + * @see Figma Component + * +[REDACTED_AUTHOR] + */ +@Composable +fun Transaction(state: TransactionState) { + Surface( + modifier = Modifier + .background(TangemTheme.colors.background.primary) + .defaultMinSize(minHeight = TangemTheme.dimens.size56) + .padding(horizontal = TangemTheme.dimens.spacing12, vertical = TangemTheme.dimens.spacing10), + color = TangemTheme.colors.background.primary, + ) { + @Suppress("DestructuringDeclarationWithTooManyEntries") + ConstraintLayout(modifier = Modifier.fillMaxWidth()) { + val (iconItem, titleItem, subtitleItem, amountItem, timestampItem) = createRefs() + + Icon( + state = state, + modifier = Modifier + .size(TangemTheme.dimens.size40) + .constrainAs(iconItem) { + start.linkTo(parent.start) + centerVerticallyTo(parent) + }, + ) + + Title( + state = state, + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing12) + .constrainAs(titleItem) { + start.linkTo(iconItem.end) + top.linkTo(iconItem.top) + }, + ) + + Subtitle( + state = state, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing6) + .padding(horizontal = TangemTheme.dimens.spacing12) + .constrainAs(subtitleItem) { + start.linkTo(iconItem.end) + top.linkTo(amountItem.bottom) + end.linkTo(timestampItem.start) + width = Dimension.fillToConstraints + }, + ) + + Amount( + state = state, + modifier = Modifier.constrainAs(amountItem) { + start.linkTo(titleItem.end) + top.linkTo(titleItem.top) + end.linkTo(parent.end) + width = Dimension.fillToConstraints + }, + ) + + Timestamp( + state = state, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing6) + .constrainAs(timestampItem) { + top.linkTo(amountItem.bottom) + end.linkTo(parent.end) + }, + ) + } + } +} + +@Composable +private fun Icon(state: TransactionState, modifier: Modifier = Modifier) { + when (state) { + is TransactionState.Content -> { + Box( + modifier = modifier + .size(TangemTheme.dimens.size40) + .background( + color = when (state) { + is TransactionState.ProcessedTransactionContent -> { + TangemTheme.colors.icon.attention.copy(alpha = 0.1f) + } + is TransactionState.CompletedTransactionContent -> { + TangemTheme.colors.background.secondary + } + }, + shape = CircleShape, + ), + ) { + Icon( + painter = painterResource( + id = when (state) { + is TransactionState.Sending, + is TransactionState.Send, + -> R.drawable.ic_arrow_up_24 + + is TransactionState.Receiving, + is TransactionState.Receive, + -> R.drawable.ic_arrow_down_24 + + is TransactionState.Approving, + is TransactionState.Approved, + -> R.drawable.ic_doc_24 + + is TransactionState.Swapping, + is TransactionState.Swapped, + -> R.drawable.ic_exchange_vertical_24 + }, + ), + contentDescription = null, + modifier = Modifier + .size(TangemTheme.dimens.size20) + .align(Alignment.Center), + tint = when (state) { + is TransactionState.ProcessedTransactionContent -> TangemTheme.colors.icon.attention + is TransactionState.CompletedTransactionContent -> TangemTheme.colors.icon.informative + }, + ) + } + } + is TransactionState.Loading -> { + CircleShimmer(modifier = modifier.size(TangemTheme.dimens.size40)) + } + } +} + +@Composable +private fun Title(state: TransactionState, modifier: Modifier = Modifier) { + when (state) { + is TransactionState.ProcessedTransactionContent -> { + Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) { + Text( + text = stringResource( + id = when (state) { + is TransactionState.Sending -> R.string.common_transfer + is TransactionState.Receiving -> R.string.common_transfer + is TransactionState.Approving -> R.string.common_approval + is TransactionState.Swapping -> R.string.common_swap + }, + ), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle2, + ) + + Image( + modifier = Modifier.align(Alignment.CenterVertically), + painter = painterResource(id = R.drawable.img_loader_15), + contentDescription = null, + ) + } + } + is TransactionState.CompletedTransactionContent -> { + Text( + text = stringResource( + id = when (state) { + is TransactionState.Send -> R.string.common_transfer + is TransactionState.Receive -> R.string.common_transfer + is TransactionState.Approved -> R.string.common_approval + is TransactionState.Swapped -> R.string.common_swap + }, + ), + modifier = modifier, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle2, + ) + } + is TransactionState.Loading -> { + RectangleShimmer( + modifier = modifier.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12), + ) + } + } +} + +@Composable +private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) { + when (state) { + is TransactionState.Content -> { + Text( + text = when (state) { + is TransactionState.Sending -> "to: ${state.address}" + is TransactionState.Send -> "to: ${state.address}" + is TransactionState.Receiving -> "from: ${state.address}" + is TransactionState.Receive -> "from: ${state.address}" + is TransactionState.Approving -> "from: ${state.address}" + is TransactionState.Approved -> "from: ${state.address}" + is TransactionState.Swapping -> "contract: ${state.address}" + is TransactionState.Swapped -> "contract ${state.address}" + }, + modifier = modifier, + textAlign = TextAlign.Start, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption, + ) + } + is TransactionState.Loading -> { + RectangleShimmer( + modifier = modifier.size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12), + ) + } + } +} + +@Composable +private fun Amount(state: TransactionState, modifier: Modifier = Modifier) { + when (state) { + is TransactionState.Content -> { + Text( + text = state.amount, + modifier = modifier, + textAlign = TextAlign.End, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.body2, + ) + } + is TransactionState.Loading -> { + RectangleShimmer( + modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12), + ) + } + } +} + +@Composable +private fun Timestamp(state: TransactionState, modifier: Modifier = Modifier) { + when (state) { + is TransactionState.Content -> { + Text( + text = state.timestamp, + modifier = modifier, + textAlign = TextAlign.End, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption, + ) + } + is TransactionState.Loading -> { + RectangleShimmer( + modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12), + ) + } + } +} + +@Preview +@Composable +private fun Preview_TransactionItem_LightTheme( + @PreviewParameter(TransactionItemStateProvider::class) state: TransactionState, +) { + TangemTheme(isDark = false) { + Transaction(state) + } +} + +@Preview +@Composable +private fun Preview_TransactionItem_DarkTheme( + @PreviewParameter(TransactionItemStateProvider::class) state: TransactionState, +) { + TangemTheme(isDark = true) { + Transaction(state) + } +} + +private class TransactionItemStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + TransactionState.Sending( + address = "33BddS...ga2B", + amount = "-0.500913 BTC", + timestamp = "8:41", + ), + TransactionState.Receiving( + address = "33BddS...ga2B", + amount = "+0.500913 BTC", + timestamp = "8:41", + ), + TransactionState.Approving( + address = "33BddS...ga2B", + amount = "+0.500913 BTC", + timestamp = "8:41", + ), + TransactionState.Swapping( + address = "33BddS...ga2B", + amount = "+0.500913 BTC", + timestamp = "8:41", + ), + TransactionState.Send( + address = "33BddS...ga2B", + amount = "-0.500913 BTC", + timestamp = "8:41", + ), + TransactionState.Receive( + address = "33BddS...ga2B", + amount = "+0.500913 BTC", + timestamp = "8:41", + ), + TransactionState.Approved( + address = "33BddS...ga2B", + amount = "+0.500913 BTC", + timestamp = "8:41", + ), + TransactionState.Swapped( + address = "33BddS...ga2B", + amount = "+0.500913 BTC", + timestamp = "8:41", + ), + TransactionState.Loading, + ), +) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt new file mode 100644 index 0000000000..f0502a4a21 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt @@ -0,0 +1,155 @@ +package com.tangem.core.ui.components.transactions + +/** + * Transaction component state + * +[REDACTED_AUTHOR] + */ +sealed interface TransactionState { + + /** + * Content state + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + sealed class Content( + open val address: String, + open val amount: String, + open val timestamp: String, + ) : TransactionState + + /** + * Content state for processed transaction + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + sealed class ProcessedTransactionContent( + override val address: String, + override val amount: String, + override val timestamp: String, + ) : Content(address, amount, timestamp) + + /** + * Content state for completed transaction + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + sealed class CompletedTransactionContent( + override val address: String, + override val amount: String, + override val timestamp: String, + ) : Content(address, amount, timestamp) + + /** + * Processed sending transaction state + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + data class Sending( + override val address: String, + override val amount: String, + override val timestamp: String, + ) : ProcessedTransactionContent(address, amount, timestamp) + + /** + * Processed receiving transaction state + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + data class Receiving( + override val address: String, + override val amount: String, + override val timestamp: String, + ) : ProcessedTransactionContent(address, amount, timestamp) + + /** + * Processed approving transaction state + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + data class Approving( + override val address: String, + override val amount: String, + override val timestamp: String, + ) : ProcessedTransactionContent(address, amount, timestamp) + + /** + * Processed swapping transaction state + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + data class Swapping( + override val address: String, + override val amount: String, + override val timestamp: String, + ) : ProcessedTransactionContent(address, amount, timestamp) + + /** + * Completed sending transaction state + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + data class Send( + override val address: String, + override val amount: String, + override val timestamp: String, + ) : CompletedTransactionContent(address, amount, timestamp) + + /** + * Completed receiving transaction state + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + data class Receive( + override val address: String, + override val amount: String, + override val timestamp: String, + ) : CompletedTransactionContent(address, amount, timestamp) + + /** + * Completed approving transaction state + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + data class Approved( + override val address: String, + override val amount: String, + override val timestamp: String, + ) : CompletedTransactionContent(address, amount, timestamp) + + /** + * Completed swapping transaction state + * + * @property address address + * @property amount amount + * @property timestamp timestamp + */ + data class Swapped( + override val address: String, + override val amount: String, + override val timestamp: String, + ) : CompletedTransactionContent(address, amount, timestamp) + + /** Loading state */ + object Loading : TransactionState +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index c2fbac9891..421c82c9e1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -56,9 +56,11 @@ data class TangemDimens internal constructor( val size46: Dp = 46.dp, val size48: Dp = 48.dp, val size50: Dp = 50.dp, + val size52: Dp = 52.dp, val size56: Dp = 56.dp, val size62: Dp = 62.dp, val size68: Dp = 68.dp, + val size70: Dp = 70.dp, val size72: Dp = 72.dp, val size80: Dp = 80.dp, val size84: Dp = 84.dp, diff --git a/core/ui/src/main/res/drawable/ic_doc_24.xml b/core/ui/src/main/res/drawable/ic_doc_24.xml new file mode 100644 index 0000000000..8855153e59 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_doc_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index f5a39df29e..acad315b28 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -54,7 +54,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, onPermissionWarningClick: Column { AppBarWithBackButton( - text = stringResource(R.string.swapping_swap), + text = stringResource(R.string.common_swap), onBackClick = state.onBackClicked, iconRes = R.drawable.ic_close_24, ) @@ -348,7 +348,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U else -> { PrimaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), - text = stringResource(id = R.string.swapping_swap), + text = stringResource(id = R.string.common_swap), iconResId = R.drawable.ic_tangem_24, enabled = state.swapButton.enabled, showProgress = state.swapButton.loading, @@ -384,7 +384,7 @@ private val receiveCard = SwapCardData( coinId = "", ) -val stateSelectable = SelectableItemsState( +val stateSelectable = SelectableItemsState( selectedItem = Item( 0, TextReference.Str("Balance"), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt index d8ad3e646a..2d6f3e52b4 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt @@ -39,7 +39,7 @@ fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) { }, topBar = { AppBarWithBackButton( - text = stringResource(R.string.swapping_swap), + text = stringResource(R.string.common_swap), onBackClick = onBack, iconRes = R.drawable.ic_close_24, ) From d97265ca03ef7f405deef066eac06c2262e3c76d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 22 Jun 2023 13:19:45 +0300 Subject: [PATCH 26/39] Updated on 2026-08-14 --- .../wallet/ui/utils/WalletDataOperations.kt | 18 +++++++++++++++++- gradle/dependencies.toml | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt index f40f0d38dc..3f72a34acb 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.extensions.toFormattedCurrencyString import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.features.wallet.models.PendingTransactionType import com.tangem.tap.features.wallet.models.WalletWarning import com.tangem.tap.features.wallet.redux.WalletMainButton import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN @@ -19,9 +20,24 @@ import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import java.math.BigDecimal internal fun WalletDataModel.mainButton(blockchainAmount: BigDecimal): WalletMainButton = WalletMainButton.SendButton( - enabled = !isEmptyAmount && status.pendingTransactions.isEmpty() && !blockchainAmount.isZero(), + enabled = !isEmptyAmount && + hasPendingTransactions() && + !blockchainAmount.isZero(), ) +internal fun WalletDataModel.hasPendingTransactions(): Boolean { + // for now check pending ongoing only just for BTC, later test and add other utxo networks + val isBitcoinBlockchain = + currency.blockchain == Blockchain.Bitcoin || currency.blockchain == Blockchain.BitcoinTestnet + if (currency.isBlockchain() && isBitcoinBlockchain) { + val outgoingTransactions = status.pendingTransactions.filter { + it.type == PendingTransactionType.Outgoing + } + return outgoingTransactions.isEmpty() + } + return status.pendingTransactions.isEmpty() +} + internal fun WalletDataModel.getFormattedAmount(): String { return status.amount.toFormattedCurrencyString( decimals = currency.decimals, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ab2fb12799..8cfc0a564c 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -77,7 +77,7 @@ walletConnectWeb3 = "1.8.0" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-240" +tangemBlockchainSdk = "develop-246" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-266" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds From e1ff73091211f803877442ebb4a2b38e8a8e1946 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 22 Jun 2023 12:21:57 +0300 Subject: [PATCH 27/39] Updated on 2026-08-14 --- .../presentation/AddCustomTokenFragment.kt | 10 +- .../presentation/ui/AddCustomTokenContent.kt | 9 +- .../presentation/ui/AddCustomTokenScreen.kt | 7 +- .../ui/AddCustomTokenTestContent.kt | 29 +--- .../ui/walletconnect/QrScanFragment.kt | 15 +- .../impl/presentation/TokensListFragment.kt | 10 +- .../impl/presentation/ui/TokensListScreen.kt | 3 +- .../feature/referral/ReferralFragment.kt | 12 +- .../feature/referral/ui/ReferralScreen.kt | 133 +++++++----------- .../wallet/presentation/WalletFragment.kt | 3 - .../organizetokens/OrganizeTokensScreen.kt | 3 +- .../router/DefaultWalletRouter.kt | 12 +- .../presentation/wallet/ui/WalletScreen.kt | 4 +- 13 files changed, 113 insertions(+), 137 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt index f4478fc4fc..a203e348dd 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt @@ -4,9 +4,10 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.hilt.navigation.compose.hiltViewModel import androidx.transition.TransitionInflater @@ -25,8 +26,6 @@ import dagger.hilt.android.AndroidEntryPoint internal class AddCustomTokenFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } - with(TransitionInflater.from(requireContext())) { enterTransition = inflateTransition(R.transition.fade) exitTransition = inflateTransition(R.transition.fade) @@ -41,7 +40,10 @@ internal class AddCustomTokenFragment : Fragment() { } TangemTheme { - AddCustomTokenScreen(stateHolder = viewModel.uiState) + AddCustomTokenScreen( + modifier = Modifier.systemBarsPadding(), + stateHolder = viewModel.uiState, + ) } } } diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt index e57d82cc15..55afd696af 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt @@ -8,11 +8,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.FabPosition import androidx.compose.material.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity @@ -33,11 +29,12 @@ import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCu [REDACTED_AUTHOR] */ @Composable -internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content) { +internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content, modifier: Modifier = Modifier) { BackHandler(onBack = state.onBackButtonClick) var floatingButtonHeight by remember { mutableStateOf(0.dp) } Scaffold( + modifier = modifier, topBar = { AddCustomTokenToolbar( title = state.toolbar.title, diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt index 71d079e899..eb099e7ee8 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.customtoken.impl.presentation.ui import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider @@ -15,10 +16,10 @@ import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTok [REDACTED_AUTHOR] */ @Composable -internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder) { +internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder, modifier: Modifier = Modifier) { when (stateHolder) { - is AddCustomTokenStateHolder.Content -> AddCustomTokenContent(state = stateHolder) - is AddCustomTokenStateHolder.TestContent -> AddCustomTokenTestContent(state = stateHolder) + is AddCustomTokenStateHolder.Content -> AddCustomTokenContent(stateHolder, modifier) + is AddCustomTokenStateHolder.TestContent -> AddCustomTokenTestContent(stateHolder, modifier) } } diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt index c23fa86835..9304675ad7 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt @@ -1,31 +1,11 @@ package com.tangem.tap.features.customtoken.impl.presentation.ui import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.BottomSheetScaffold -import androidx.compose.material.BottomSheetScaffoldState -import androidx.compose.material.BottomSheetState -import androidx.compose.material.BottomSheetValue -import androidx.compose.material.Divider -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FabPosition -import androidx.compose.material.Text -import androidx.compose.material.rememberBottomSheetScaffoldState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue +import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -59,7 +39,7 @@ import kotlinx.coroutines.launch */ @OptIn(ExperimentalMaterialApi::class) @Composable -internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestContent) { +internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestContent, modifier: Modifier = Modifier) { val coroutineScope = rememberCoroutineScope() val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( bottomSheetState = BottomSheetState(initialValue = BottomSheetValue.Collapsed), @@ -77,6 +57,7 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont var floatingButtonHeight by remember { mutableStateOf(0.dp) } BottomSheetScaffold( + modifier = modifier, sheetContent = { SheetContent( coroutineScope = coroutineScope, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt index a08b9af8b2..91a8e803b3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt @@ -24,7 +24,7 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } + setFitSystemWindows(fit = true) activity?.onBackPressedDispatcher?.addCallback( this, object : OnBackPressedCallback(true) { @@ -47,6 +47,11 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler { scannerView?.stopCamera() } + override fun onDestroy() { + super.onDestroy() + setFitSystemWindows(fit = false) + } + override fun onResume() { super.onResume() scannerView?.setResultHandler(this) @@ -55,7 +60,7 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler { override fun handleResult(result: Result) { store.dispatch(NavigationAction.PopBackTo()) - activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, false) } + setFitSystemWindows(fit = false) if (!result.text.isNullOrBlank()) { store.dispatch(WalletConnectAction.OpenSession(result.text)) } @@ -83,4 +88,10 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler { private fun requestPermission() { requestPermissions(arrayOf(Manifest.permission.CAMERA), CameraView.PERMISSION_REQUEST_CODE) } + + private fun setFitSystemWindows(fit: Boolean) { + activity?.window?.let { + WindowCompat.setDecorFitsSystemWindows(it, fit) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt index e5ac57cfc8..8af509f84f 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt @@ -4,9 +4,10 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.hilt.navigation.compose.hiltViewModel import androidx.transition.TransitionInflater @@ -25,8 +26,6 @@ import dagger.hilt.android.AndroidEntryPoint internal class TokensListFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } - with(TransitionInflater.from(requireContext())) { enterTransition = inflateTransition(R.transition.fade) exitTransition = inflateTransition(R.transition.fade) @@ -41,7 +40,10 @@ internal class TokensListFragment : Fragment() { } TangemTheme { - TokensListScreen(stateHolder = viewModel.uiState) + TokensListScreen( + modifier = Modifier.systemBarsPadding(), + stateHolder = viewModel.uiState, + ) } } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt index d378d6ba75..44e620b8ac 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt @@ -56,12 +56,13 @@ import kotlinx.coroutines.flow.flowOf [REDACTED_AUTHOR] */ @Composable -internal fun TokensListScreen(stateHolder: TokensListStateHolder) { +internal fun TokensListScreen(stateHolder: TokensListStateHolder, modifier: Modifier = Modifier) { BackHandler(onBack = stateHolder.toolbarState.onBackButtonClick) var floatingButtonHeight by remember { mutableStateOf(value = 0.dp) } Scaffold( + modifier = modifier, topBar = { TokensListToolbar(state = stateHolder.toolbarState) }, floatingActionButton = { if (stateHolder is TokensListStateHolder.ManageContent) { diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt index 938566bd50..b04b901b66 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt @@ -4,11 +4,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.transition.TransitionInflater +import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.presentation.R import com.tangem.feature.referral.router.ReferralRouter import com.tangem.feature.referral.ui.ReferralScreen @@ -28,13 +30,17 @@ class ReferralFragment : Fragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } viewModel.setRouter(ReferralRouter(fragmentManager = WeakReference(parentFragmentManager))) viewModel.onScreenOpened() return ComposeView(inflater.context).apply { isTransitionGroup = true setContent { - ReferralScreen(stateHolder = viewModel.uiState) + TangemTheme { + ReferralScreen( + modifier = Modifier.systemBarsPadding(), + stateHolder = viewModel.uiState, + ) + } } } } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 7f1ff6b6a8..7c3e8b2ddf 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -5,41 +5,11 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.BottomSheetScaffold -import androidx.compose.material.BottomSheetState -import androidx.compose.material.BottomSheetValue -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.Snackbar -import androidx.compose.material.SnackbarDuration -import androidx.compose.material.SnackbarHost -import androidx.compose.material.SnackbarHostState -import androidx.compose.material.SnackbarResult -import androidx.compose.material.Text -import androidx.compose.material.rememberBottomSheetScaffoldState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue +import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -63,9 +33,7 @@ import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.models.DemoModeException import com.tangem.feature.referral.models.ReferralStateHolder -import com.tangem.feature.referral.models.ReferralStateHolder.ErrorSnackbar -import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoContentState -import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState +import com.tangem.feature.referral.models.ReferralStateHolder.* import com.tangem.feature.referral.presentation.R import com.valentinilk.shimmer.shimmer import kotlinx.coroutines.launch @@ -77,47 +45,46 @@ import kotlinx.coroutines.launch */ @OptIn(ExperimentalMaterialApi::class) @Composable -internal fun ReferralScreen(stateHolder: ReferralStateHolder) { +internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier = Modifier) { val coroutineScope = rememberCoroutineScope() val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed), ) - TangemTheme { - BottomSheetScaffold( - sheetContent = { - AgreementBottomSheetContent( - url = when (val state = stateHolder.referralInfoState) { - is ReferralInfoState.NonParticipantContent -> state.url - is ReferralInfoState.ParticipantContent -> state.url - is ReferralInfoState.Loading -> "" - }, - ) - }, - scaffoldState = bottomSheetScaffoldState, - sheetShape = RoundedCornerShape( - topStart = TangemTheme.dimens.radius16, - topEnd = TangemTheme.dimens.radius16, - ), - sheetElevation = TangemTheme.dimens.elevation24, - sheetPeekHeight = TangemTheme.dimens.size0, - content = { - ReferralContent( - stateHolder = stateHolder, - onAgreementClick = { - stateHolder.analytics.onAgreementClicked.invoke() - coroutineScope.launch { - if (bottomSheetScaffoldState.bottomSheetState.isCollapsed) { - bottomSheetScaffoldState.bottomSheetState.expand() - } else { - bottomSheetScaffoldState.bottomSheetState.collapse() - } + BottomSheetScaffold( + modifier = modifier, + sheetContent = { + AgreementBottomSheetContent( + url = when (val state = stateHolder.referralInfoState) { + is ReferralInfoState.NonParticipantContent -> state.url + is ReferralInfoState.ParticipantContent -> state.url + is ReferralInfoState.Loading -> "" + }, + ) + }, + scaffoldState = bottomSheetScaffoldState, + sheetShape = RoundedCornerShape( + topStart = TangemTheme.dimens.radius16, + topEnd = TangemTheme.dimens.radius16, + ), + sheetElevation = TangemTheme.dimens.elevation24, + sheetPeekHeight = TangemTheme.dimens.size0, + content = { + ReferralContent( + stateHolder = stateHolder, + onAgreementClick = { + stateHolder.analytics.onAgreementClicked.invoke() + coroutineScope.launch { + if (bottomSheetScaffoldState.bottomSheetState.isCollapsed) { + bottomSheetScaffoldState.bottomSheetState.expand() + } else { + bottomSheetScaffoldState.bottomSheetState.collapse() } - }, - ) - }, - ) - } + } + }, + ) + }, + ) } @OptIn(ExperimentalFoundationApi::class) @@ -487,7 +454,7 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() { TangemTheme(isDark = false) { ReferralScreen( stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + headerState = HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.ParticipantContent( award = "10 USDT", networkName = "Tron", @@ -499,7 +466,7 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() { url = "", ), errorSnackbar = null, - analytics = ReferralStateHolder.Analytics( + analytics = Analytics( onAgreementClicked = {}, onCopyClicked = {}, onShareClicked = {}, @@ -515,7 +482,7 @@ private fun Preview_ReferralScreen_Participant_InDarkTheme() { TangemTheme(isDark = true) { ReferralScreen( stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + headerState = HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.ParticipantContent( award = "10 USDT", networkName = "Tron", @@ -527,7 +494,7 @@ private fun Preview_ReferralScreen_Participant_InDarkTheme() { url = "", ), errorSnackbar = null, - analytics = ReferralStateHolder.Analytics( + analytics = Analytics( onAgreementClicked = {}, onCopyClicked = {}, onShareClicked = {}, @@ -543,7 +510,7 @@ private fun Preview_ReferralScreen_NonParticipant_InLightTheme() { TangemTheme(isDark = false) { ReferralScreen( stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + headerState = HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.NonParticipantContent( award = "10 USDT", networkName = "Tron", @@ -552,7 +519,7 @@ private fun Preview_ReferralScreen_NonParticipant_InLightTheme() { onParticipateClicked = {}, ), errorSnackbar = null, - analytics = ReferralStateHolder.Analytics( + analytics = Analytics( onAgreementClicked = {}, onCopyClicked = {}, onShareClicked = {}, @@ -568,7 +535,7 @@ private fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { TangemTheme(isDark = true) { ReferralScreen( stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + headerState = HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.NonParticipantContent( award = "10 USDT", networkName = "Tron", @@ -577,7 +544,7 @@ private fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { onParticipateClicked = {}, ), errorSnackbar = null, - analytics = ReferralStateHolder.Analytics( + analytics = Analytics( onAgreementClicked = {}, onCopyClicked = {}, onShareClicked = {}, @@ -593,10 +560,10 @@ private fun Preview_ReferralScreen_Loading_InLightTheme() { TangemTheme(isDark = false) { ReferralScreen( stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + headerState = HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.Loading, errorSnackbar = null, - analytics = ReferralStateHolder.Analytics( + analytics = Analytics( onAgreementClicked = {}, onCopyClicked = {}, onShareClicked = {}, @@ -612,10 +579,10 @@ private fun Preview_ReferralScreen_Loading_InDarkTheme() { TangemTheme(isDark = true) { ReferralScreen( stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + headerState = HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.Loading, errorSnackbar = null, - analytics = ReferralStateHolder.Analytics( + analytics = Analytics( onAgreementClicked = {}, onCopyClicked = {}, onShareClicked = {}, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt index 1bc2cd04fd..04e93bf88f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -6,7 +6,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView -import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.router.InnerWalletRouter @@ -32,8 +31,6 @@ internal class WalletFragment : Fragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } - with(TransitionInflater.from(requireContext())) { enterTransition = inflateTransition(R.transition.slide_right) exitTransition = inflateTransition(R.transition.fade) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt index e626eb0029..f4b5bb65d0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt @@ -13,8 +13,9 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.common.state.TokenListState @Composable -internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder) { +internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder, modifier: Modifier = Modifier) { Scaffold( + modifier = modifier, topBar = { TopBar(state.header) }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index f2768ed116..b963f8f7b7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -1,7 +1,9 @@ package com.tangem.feature.wallet.presentation.router import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.fragment.app.Fragment import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController @@ -32,7 +34,10 @@ internal class DefaultWalletRouter : InnerWalletRouter { ) { composable(WalletScreens.WALLET.name) { val viewModel = hiltViewModel().apply { router = this@DefaultWalletRouter } - WalletScreen(state = viewModel.uiState) + WalletScreen( + modifier = Modifier.systemBarsPadding(), + state = viewModel.uiState, + ) } composable(WalletScreens.ORGANIZE_TOKENS.name) { @@ -41,7 +46,10 @@ internal class DefaultWalletRouter : InnerWalletRouter { val viewModel: OrganizeTokensViewModel = hiltViewModel() .apply { router = this@DefaultWalletRouter } - OrganizeTokensScreen(state = viewModel.uiState) + OrganizeTokensScreen( + modifier = Modifier.systemBarsPadding(), + state = viewModel.uiState, + ) } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index ba17e6e692..098d1e1e45 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.ui import androidx.activity.compose.BackHandler import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletHeader @@ -14,10 +15,11 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletHeader [REDACTED_AUTHOR] */ @Composable -internal fun WalletScreen(state: WalletStateHolder) { +internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifier) { BackHandler(onBack = state.onBackClick) Scaffold( + modifier = modifier, topBar = { WalletHeader(config = state.headerConfig) }, ) { // TODO: [REDACTED_TASK_KEY] Design a body with tokens and transactions From 23dc56c8b287acd5b0116c5238e0a1f70119a8de Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 22 Jun 2023 21:08:10 +0800 Subject: [PATCH 28/39] Updated on 2026-08-14 --- .../impl/presentation/viewmodels/AddCustomTokenViewModel.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt index 47245502cf..6429affe5e 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt @@ -271,7 +271,9 @@ internal class AddCustomTokenViewModel @Inject constructor( type = DerivationPathSelectorType.CUSTOM, ), ) + Blockchain.values() - .filter { blockchain -> blockchain.isSupportedInApp() && !blockchain.isTestnet() } + .filter { blockchain -> + blockchain.isSupportedInApp() && !blockchain.isTestnet() && blockchain != Blockchain.Cardano + } .sortedBy(Blockchain::fullName) .map(::createDerivationPathSelectorAdditionalItem) } From b9e3753ae8c8e1d28f292e3db52baca55635929f Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 23 Jun 2023 16:32:13 +0300 Subject: [PATCH 29/39] Updated on 2026-08-14 --- .../impl/presentation/ui/components/AddCustomTokenForm.kt | 2 -- .../presentation/viewmodels/AddCustomTokenViewModel.kt | 7 ++++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt index 0051e2e6c4..0090ef8223 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt @@ -8,7 +8,6 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -161,7 +160,6 @@ private fun SelectorField(model: AddCustomTokenSelectorField) { expanded = isExpanded && isEnabled, onDismissRequest = { isExpanded = false }, ) { - FocusRequester model.items.forEachIndexed { index, item -> DropdownMenuItem( onClick = { diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt index 6429affe5e..297bf37f54 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt @@ -41,6 +41,7 @@ import com.tangem.wallet.BuildConfig import com.tangem.wallet.R import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -613,7 +614,11 @@ internal class AddCustomTokenViewModel @Inject constructor( private inner class ActionsHandler(private val featureRouter: CustomTokenRouter) { fun onBackButtonClick() { - featureRouter.popBackStack() + viewModelScope.launch(dispatchers.main) { + // need delay before close, cause crashed in compose PopUpMenu as + delay(timeMillis = 100) + featureRouter.popBackStack() + } } fun onContactAddressValueChange(enteredValue: String) { From 92f2ba5f0173314bd657b36108a92c67b8947e4f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 22 Jun 2023 14:14:22 +0300 Subject: [PATCH 30/39] Updated on 2026-08-14 --- .../features/wallet/ui/MultipleAddressUiHelper.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt index 67e10ca9a6..e82c2f9547 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/MultipleAddressUiHelper.kt @@ -22,14 +22,22 @@ object MultipleAddressUiHelper { return when (id) { R.id.chip_default -> { when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Segwit + Blockchain.Bitcoin, + Blockchain.BitcoinTestnet, + Blockchain.Litecoin, + Blockchain.BitcoinCash, + -> BitcoinAddressType.Segwit Blockchain.CardanoShelley -> CardanoAddressType.Shelley else -> null } } R.id.chip_legacy -> { when (blockchain) { - Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.Litecoin -> BitcoinAddressType.Legacy + Blockchain.Bitcoin, + Blockchain.BitcoinTestnet, + Blockchain.Litecoin, + Blockchain.BitcoinCash, + -> BitcoinAddressType.Legacy Blockchain.CardanoShelley -> CardanoAddressType.Byron else -> null } From 847a430533aab17b69fc2cbc82f153a2bb76513a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Jun 2023 09:30:18 +0300 Subject: [PATCH 31/39] Updated on 2026-08-14 --- .../tap/common/redux/global/GlobalState.kt | 1 + .../BiometricUserWalletsListManager.kt | 4 +- .../DefaultWalletAmountsRepository.kt | 71 ++++++++----------- .../middlewares/MultiWalletMiddleware.kt | 5 +- .../redux/middlewares/WalletMiddleware.kt | 11 +-- .../com/tangem/tap/proxy/AppStateHolder.kt | 1 + 6 files changed, 42 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index c9f2314efc..c4becfb45b 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -15,6 +15,7 @@ import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import org.rekotlin.StateType data class GlobalState( + @Deprecated("Use scan response from selected user wallet") val scanResponse: ScanResponse? = null, val onboardingState: OnboardingState = OnboardingState(), val cardVerifiedOnline: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 78f74e0c3d..4ea180e270 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -118,7 +118,9 @@ internal class BiometricUserWalletsListManager( } .flatMap { updatedUserWallet -> saveInternal(updatedUserWallet, changeSelectedUserWallet = false) - .map { updatedUserWallet } + } + .flatMap { + get(userWalletId) } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index e276bd51db..22dd64bff1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -7,16 +7,11 @@ import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.Result.Failure import com.tangem.blockchain.extensions.Result.Success -import com.tangem.common.CompletionResult -import com.tangem.common.catching +import com.tangem.common.* import com.tangem.common.core.TangemError -import com.tangem.common.flatMap -import com.tangem.common.flatMapOnFailure -import com.tangem.common.fold -import com.tangem.common.map import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.domain.common.util.hasDerivation import com.tangem.domain.common.util.UserWalletId +import com.tangem.domain.common.util.hasDerivation import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.TestActions import com.tangem.tap.common.entities.FiatCurrency @@ -25,15 +20,7 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.walletStores.WalletStoresError import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository -import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStore -import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStores -import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithAmounts -import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithDemoAmounts -import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithError -import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithFiatRates -import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithMissedDerivation -import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithRent -import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithUnreachable +import com.tangem.tap.domain.walletStores.repository.implementation.utils.* import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage import com.tangem.tap.features.demo.DemoHelper @@ -45,13 +32,8 @@ import com.tangem.tap.features.wallet.models.getPendingTransactions import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay +import kotlinx.coroutines.* import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal import kotlin.time.Duration @@ -200,37 +182,40 @@ internal class DefaultWalletAmountsRepository( walletStore: WalletStoreModel, walletManager: WalletManager?, ): CompletionResult { - val hasMissedDerivations = with(walletStore) { + val isDerivationMissed = with(walletStore) { derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath.rawPath) } return when { - hasMissedDerivations -> { + isDerivationMissed -> { updateWalletStoreWithMissedDerivation(walletStore) } walletManager == null -> { updateWalletStoreWithUnreachable(walletStore) } else -> { - updateWalletManager(scanResponse, walletManager).map { - updateWalletManagerInStorage( - userWalletId, - walletManager, - ) - }.flatMap { - updateWalletStoreWithAmounts( - walletStore = walletStore, - updatedWallet = walletManager.wallet, - // FIXME: move DemoHelper to Demo core module maybe - isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId), - ) - }.flatMap { fetchWalletStoreRentIfNeeded(walletStore, walletManager) }.flatMapOnFailure { error -> - updateWalletStoreWithError( - walletStore = walletStore, - wallet = walletManager.wallet, - error = error, - ) - } + updateWalletManager(scanResponse, walletManager) + .map { + updateWalletManagerInStorage(userWalletId, walletManager) + } + .flatMap { + updateWalletStoreWithAmounts( + walletStore = walletStore, + updatedWallet = walletManager.wallet, + // FIXME: move DemoHelper to Demo core module maybe + isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId), + ) + } + .flatMap { + fetchWalletStoreRentIfNeeded(walletStore, walletManager) + } + .flatMapOnFailure { error -> + updateWalletStoreWithError( + walletStore = walletStore, + wallet = walletManager.wallet, + error = error, + ) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 607d8c63c4..d86a41c389 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -9,7 +9,7 @@ import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken import com.tangem.tap.common.extensions.addContext import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification -import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -114,7 +114,8 @@ class MultiWalletMiddleware { ) } .doOnSuccess { updatedUserWallet -> - store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) + store.dispatchWithMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) + store.dispatchWithMain(GlobalAction.SaveScanResponse(updatedUserWallet.scanResponse)) store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index c7bde94141..d7a0da7703 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -17,6 +17,7 @@ import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError @@ -97,12 +98,12 @@ class WalletMiddleware { Timber.e("Unable to create wallet, no user wallet selected") return@launch } + val updatedScanResponse = selectedUserWallet.scanResponse.copy( + card = result.data, + ) + store.dispatchWithMain(GlobalAction.SaveScanResponse(updatedScanResponse)) userWalletsListManager.update(selectedUserWallet.walletId) { userWallet -> - userWallet.copy( - scanResponse = userWallet.scanResponse.copy( - card = result.data, - ), - ) + userWallet.copy(scanResponse = updatedScanResponse) } } is CompletionResult.Failure -> Unit diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index aab3cfd808..35c2d83718 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -19,6 +19,7 @@ import javax.inject.Inject */ class AppStateHolder @Inject constructor() { + @Deprecated("Use scan response from selected user wallet") var scanResponse: ScanResponse? = null var walletState: WalletState? = null var userTokensRepository: UserTokensRepository? = null From 8bc6c921f3dbf6e951a3896bddf5633426cbfd35 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Jun 2023 12:21:44 +0300 Subject: [PATCH 32/39] Updated on 2026-08-14 --- .idea/codeStyles/Project.xml | 5 +---- .../wallet/presentation/router/DefaultWalletRouter.kt | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 7fc608245b..0db8c6d7c9 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -13,9 +13,6 @@ -