From 0b7c992ec882d249c24f5273fe637e92d41fa2fe Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 May 2024 13:05:17 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../clipboard/DefaultClipboardManager.kt | 12 ++++++++ .../common/clipboard/MockClipboardManager.kt | 11 +++++++ .../tap/common/haptic/DefaultHapticManager.kt | 5 ++++ .../tangem/tap/di/ClipboardManagerModule.kt | 30 +++++++++++++++++++ .../core/ui/clipboard/ClipboardManager.kt | 14 +++++++++ .../buttons/actions/ActionButtonConfig.kt | 2 ++ .../ui/components/buttons/actions/Actions.kt | 21 +++++++++++-- .../tangem/core/ui/haptic/HapticManager.kt | 2 ++ .../core/ui/haptic/MockHapticManager.kt | 4 +++ .../tokendetails/TokenDetailsPreviewData.kt | 2 +- .../components/TokenDetailsActionButton.kt | 10 ++++++- .../TokenDetailsActionButtonsConverter.kt | 1 + .../TokenDetailsSkeletonStateConverter.kt | 2 +- .../viewmodels/TokenDetailsClickIntents.kt | 5 +++- .../viewmodels/TokenDetailsViewModel.kt | 20 ++++++++++++- 15 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt create mode 100644 app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt create mode 100644 app/src/main/java/com/tangem/tap/di/ClipboardManagerModule.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt diff --git a/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt b/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt new file mode 100644 index 0000000000..8b7ee07933 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/clipboard/DefaultClipboardManager.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.common.clipboard + +import android.content.ClipData +import com.tangem.core.ui.clipboard.ClipboardManager +import android.content.ClipboardManager as AndroidClipboardManager + +internal class DefaultClipboardManager(private val clipboardManager: AndroidClipboardManager) : ClipboardManager { + + override fun setText(label: String, text: String) { + clipboardManager.setPrimaryClip(ClipData.newPlainText(label, text)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt b/app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt new file mode 100644 index 0000000000..bdab75088f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/clipboard/MockClipboardManager.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.common.clipboard + +import com.tangem.core.ui.clipboard.ClipboardManager +import timber.log.Timber + +internal object MockClipboardManager : ClipboardManager { + override fun setText(label: String, text: String) { + /** Intentionnaly do nothing */ + Timber.w("Clipboard Manager not available") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt b/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt index 8afeba9a48..688dea7f67 100644 --- a/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt +++ b/app/src/main/java/com/tangem/tap/common/haptic/DefaultHapticManager.kt @@ -11,6 +11,10 @@ class DefaultHapticManager(private val vibrator: Vibrator) : HapticManager { vibrate(VIBRATION_SHORT_DURATION) } + override fun vibrateMeduim() { + vibrate(VIBRATION_MEDIUM_LOW_DURATION) + } + override fun vibrateLong() { vibrate(VIBRATION_MEDIUM_DURATION) } @@ -25,6 +29,7 @@ class DefaultHapticManager(private val vibrator: Vibrator) : HapticManager { companion object { const val VIBRATION_SHORT_DURATION = 50L + const val VIBRATION_MEDIUM_LOW_DURATION = 75L const val VIBRATION_MEDIUM_DURATION = 100L const val VIBRATION_LONG_DURATION = 200L } diff --git a/app/src/main/java/com/tangem/tap/di/ClipboardManagerModule.kt b/app/src/main/java/com/tangem/tap/di/ClipboardManagerModule.kt new file mode 100644 index 0000000000..052f1c4541 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/ClipboardManagerModule.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.di + +import android.content.Context +import com.tangem.core.ui.clipboard.ClipboardManager +import com.tangem.tap.common.clipboard.MockClipboardManager +import com.tangem.tap.common.clipboard.DefaultClipboardManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton +import android.content.ClipboardManager as AndroidClipboardManager + +@Module +@InstallIn(SingletonComponent::class) +internal class ClipboardManagerModule { + + @Provides + @Singleton + fun provideClipboardManager(@ApplicationContext context: Context): ClipboardManager { + val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as? AndroidClipboardManager + + return if (clipboardManager != null) { + DefaultClipboardManager(clipboardManager = clipboardManager) + } else { + MockClipboardManager + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt b/core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt new file mode 100644 index 0000000000..2e6354a2e2 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/clipboard/ClipboardManager.kt @@ -0,0 +1,14 @@ +package com.tangem.core.ui.clipboard + +import androidx.compose.runtime.Stable + +/** + * Ready to use clipboard manager. + * Does not require context to work + */ +@Stable +interface ClipboardManager { + + /** Copies to clipboard [text] with optional [label] */ + fun setText(label: String = "", text: String) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt index 6b9347623a..c93d92643e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt @@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.TextReference * @property text text * @property iconResId icon resource id * @property onClick lambda be invoked when action component is clicked + * @property onLongClick lambda be invoked when action component is long clicked * @property enabled enabled * @property dimContent determines whether the button content will be dimmed. This property will be ignored if [enabled] * is `false`. @@ -19,6 +20,7 @@ data class ActionButtonConfig( val text: TextReference, @DrawableRes val iconResId: Int, val onClick: () -> Unit, + val onLongClick: (() -> TextReference?)? = null, val enabled: Boolean = true, val dimContent: Boolean = false, ) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index 6c7e926dae..60bc6e7e12 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -1,9 +1,11 @@ package com.tangem.core.ui.components.buttons.actions import android.content.res.Configuration +import android.widget.Toast import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon @@ -14,6 +16,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -74,6 +77,7 @@ fun ActionButton( ) } +@OptIn(ExperimentalFoundationApi::class) @Composable private fun Button( config: ActionButtonConfig, @@ -85,13 +89,24 @@ private fun Button( targetValue = if (config.enabled) color else TangemTheme.colors.button.disabled, label = "Update background color", ) - + val context = LocalContext.current Row( modifier = modifier .heightIn(min = TangemTheme.dimens.size36) .clip(shape) .background(color = backgroundColor) - .clickable(enabled = config.enabled, onClick = config.onClick) + .combinedClickable( + enabled = config.enabled, + onClick = config.onClick, + onLongClick = { + val toastReference = config.onLongClick?.invoke() + toastReference?.let { + Toast + .makeText(context, toastReference.resolveReference(context.resources), Toast.LENGTH_SHORT) + .show() + } + }, + ) .padding(start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing24) .padding(vertical = TangemTheme.dimens.spacing8), horizontalArrangement = Arrangement.Center, diff --git a/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt b/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt index c4fa2906f4..b93c08bd26 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/haptic/HapticManager.kt @@ -7,5 +7,7 @@ interface HapticManager { fun vibrateShort() + fun vibrateMeduim() + fun vibrateLong() } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt b/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt index 475402f21e..876ee7a05d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/haptic/MockHapticManager.kt @@ -6,6 +6,10 @@ object MockHapticManager : HapticManager { /** Intentionnaly do nothing */ } + override fun vibrateMeduim() { + /** Intentionnaly do nothing */ + } + override fun vibrateLong() { /** Intentionnaly do nothing */ } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 9e5149bf68..b16fe4ac2c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -89,7 +89,7 @@ internal object TokenDetailsPreviewData { private val actionButtons = persistentListOf( TokenDetailsActionButton.Buy(dimContent = false, onClick = {}), TokenDetailsActionButton.Send(dimContent = false, onClick = {}), - TokenDetailsActionButton.Receive(onClick = {}), + TokenDetailsActionButton.Receive(onClick = {}, onLongClick = null), TokenDetailsActionButton.Swap(dimContent = false, onClick = {}), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt index 85a9c70cf8..c5b6078527 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt @@ -11,6 +11,9 @@ internal sealed class TokenDetailsActionButton(val config: ActionButtonConfig) { /** Lambda be invoked when manage button is clicked */ abstract val onClick: () -> Unit + /** Lambda be invoked when manage button is long clicked */ + open val onLongClick: (() -> TextReference?)? = null + /** * Buy * @@ -44,12 +47,17 @@ internal sealed class TokenDetailsActionButton(val config: ActionButtonConfig) { /** * Receive * @property onClick lambda be invoked when Receive button is clicked + * @property onLongClick lambda be invoked when Receive button is long clicked */ - data class Receive(override val onClick: () -> Unit) : TokenDetailsActionButton( + data class Receive( + override val onClick: () -> Unit, + override val onLongClick: (() -> TextReference?)?, + ) : TokenDetailsActionButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_receive), iconResId = R.drawable.ic_arrow_down_24, onClick = onClick, + onLongClick = onLongClick, enabled = true, ), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt index 24f7f0c2a0..dbf8a99f3c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsActionButtonsConverter.kt @@ -35,6 +35,7 @@ internal class TokenDetailsActionButtonsConverter( is TokenActionsState.ActionState.Receive -> { TokenDetailsActionButton.Receive( onClick = { clickIntents.onReceiveClick(action.unavailabilityReason) }, + onLongClick = clickIntents::onCopyAddress, ) } is TokenActionsState.ActionState.Sell -> { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt index 575565370e..073b8e88ba 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt @@ -90,7 +90,7 @@ internal class TokenDetailsSkeletonStateConverter( return persistentListOf( TokenDetailsActionButton.Buy(dimContent = false, onClick = {}), TokenDetailsActionButton.Send(dimContent = false, onClick = {}), - TokenDetailsActionButton.Receive(onClick = {}), + TokenDetailsActionButton.Receive(onClick = {}, onLongClick = null), TokenDetailsActionButton.Sell(dimContent = false, onClick = {}), TokenDetailsActionButton.Swap(dimContent = false, onClick = {}), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt index 8b290061ed..54c81902ca 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt @@ -1,8 +1,9 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel -import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason +import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason @Suppress("TooManyFunctions") interface TokenDetailsClickIntents { @@ -50,4 +51,6 @@ interface TokenDetailsClickIntents { fun onSwapPromoClick() fun onGenerateExtendedKey() + + fun onCopyAddress(): TextReference? } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 26d69cf20d..c95d1c7ab7 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -10,9 +10,13 @@ import com.tangem.blockchain.common.address.AddressType import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.global.BuyCurrencyDeepLink +import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel +import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels import com.tangem.core.ui.components.transactions.state.TxHistoryState +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.haptic.HapticManager import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -25,10 +29,10 @@ import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo -import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkAddress +import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.models.analytics.TokenExchangeAnalyticsEvent import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent @@ -59,6 +63,7 @@ import com.tangem.utils.Provider import com.tangem.utils.coroutines.* import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.* @@ -96,6 +101,8 @@ internal class TokenDetailsViewModel @Inject constructor( private val isDemoCardUseCase: IsDemoCardUseCase, private val reduxStateHolder: ReduxStateHolder, private val analyticsEventsHandler: AnalyticsEventHandler, + private val hapticManager: HapticManager, + private val clipboardManager: ClipboardManager, featureToggles: TokenDetailsFeatureToggles, deepLinksRegistry: DeepLinksRegistry, savedStateHandle: SavedStateHandle, @@ -695,6 +702,17 @@ internal class TokenDetailsViewModel @Inject constructor( onSwapClick(ScenarioUnavailabilityReason.None) } + override fun onCopyAddress(): TextReference? { + val networkAddress = cryptoCurrencyStatus?.value?.networkAddress ?: return null + val addresses = networkAddress.availableAddresses.mapToAddressModels(cryptoCurrency).toImmutableList() + val defaultAddress = addresses.firstOrNull()?.value ?: return null + + hapticManager.vibrateMeduim() + clipboardManager.setText(text = defaultAddress) + analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(cryptoCurrency.symbol)) + return resourceReference(R.string.wallet_notification_address_copied) + } + private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean { if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false