Updated on 2026-08-14
This commit is contained in:
parent
75e2b0b554
commit
0b7c992ec8
15 changed files with 133 additions and 8 deletions
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -7,5 +7,7 @@ interface HapticManager {
|
|||
|
||||
fun vibrateShort()
|
||||
|
||||
fun vibrateMeduim()
|
||||
|
||||
fun vibrateLong()
|
||||
}
|
||||
|
|
@ -6,6 +6,10 @@ object MockHapticManager : HapticManager {
|
|||
/** Intentionnaly do nothing */
|
||||
}
|
||||
|
||||
override fun vibrateMeduim() {
|
||||
/** Intentionnaly do nothing */
|
||||
}
|
||||
|
||||
override fun vibrateLong() {
|
||||
/** Intentionnaly do nothing */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -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 = {}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue