Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-03 13:42:07 +03:00
commit d994b7840a
150 changed files with 971 additions and 4621 deletions

View file

@ -31,18 +31,10 @@
"name": "SWAP_REDESIGN_ENABLED",
"version": "undefined"
},
{
"name": "HOT_WALLET_ENABLED",
"version": "5.32.0"
},
{
"name": "HOT_WALLET_CREATION_RESTRICTION_ENABLED",
"version": "5.32.0"
},
{
"name": "HOT_WALLET_VISIBLE",
"version": "5.32.1"
},
{
"name": "TANGEM_PAY_ENABLED",
"version": "5.31.0"

View file

@ -0,0 +1,10 @@
package com.tangem.core.ui.components.buttons.actions
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
val IsDimmedKey = SemanticsPropertyKey<Boolean>("IsDimmed")
val HasBadgeKey = SemanticsPropertyKey<Boolean>("HasBadge")
var SemanticsPropertyReceiver.isDimmed by IsDimmedKey
var SemanticsPropertyReceiver.hasBadge by HasBadgeKey

View file

@ -22,7 +22,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -136,11 +135,8 @@ fun ActionBaseButton(
}
.clip(shape)
.semantics {
contentDescription = if (config.shouldDimContent) {
"Action button is dimmed"
} else {
"Action button is not dimmed"
}
isDimmed = config.shouldDimContent
hasBadge = config.shouldShowBadge
}
.combinedClickable(
enabled = config.isEnabled,

View file

@ -74,13 +74,14 @@ private const val DEFAULT_SCALE = 1f
private const val SHAKE_AMPLITUDE_DP = 5f
private const val SHAKE_DURATION_MS = 120
private const val FADE_DURATION_MS = 150
private const val FADE_DURATION_MS = 100
private const val BOUNCE_VELOCITY_MULTIPLIER = 1.25f
private const val HAPTIC_INITIAL_INTERVAL_MS = 200L
private const val HAPTIC_MIN_INTERVAL_MS = 30L
private const val HAPTIC_INITIAL_INTERVAL_MS = 50L
private const val HAPTIC_MIN_INTERVAL_MS = 15L
private const val HAPTIC_HEARTBEAT_INTERVAL_MS = 100L
private const val HAPTIC_SUCCESS_DELAY_MS = 300L
// Easing: smooth acceleration, gradually picking up speed
private val AccelerateEasing = CubicBezierEasing(
@ -288,6 +289,7 @@ private fun Modifier.holdToConfirmGestures(
state.isProgressVisible = true
// Soft heartbeat on success
delay(HAPTIC_SUCCESS_DELAY_MS)
performSuccessHapticFeedback(config.hapticManager)
state.scaleProgress.animateTo(
@ -324,7 +326,7 @@ private suspend fun performAcceleratingHapticFeedback(hapticManager: HapticManag
// Exponential decrease: interval decreases faster as progress increases
val interval = (maxInterval * (1f - progress * progress) + minInterval).toLong()
hapticManager.perform(TangemHapticEffect.View.SegmentTick)
hapticManager.perform(TangemHapticEffect.View.ClockTick)
delay(interval)
}
}
@ -333,18 +335,18 @@ private suspend fun performAcceleratingHapticFeedback(hapticManager: HapticManag
* Performs strong heartbeat haptic feedback on release (two heavy clicks).
*/
private suspend fun performReleaseHapticFeedback(hapticManager: HapticManager) {
hapticManager.perform(TangemHapticEffect.OneTime.HeavyClick)
hapticManager.perform(TangemHapticEffect.View.Reject)
delay(HAPTIC_HEARTBEAT_INTERVAL_MS)
hapticManager.perform(TangemHapticEffect.OneTime.HeavyClick)
hapticManager.perform(TangemHapticEffect.View.Reject)
}
/**
* Performs soft heartbeat haptic feedback on success (two light clicks).
*/
private suspend fun performSuccessHapticFeedback(hapticManager: HapticManager) {
hapticManager.perform(TangemHapticEffect.OneTime.Click)
hapticManager.perform(TangemHapticEffect.View.Confirm)
delay(HAPTIC_HEARTBEAT_INTERVAL_MS)
hapticManager.perform(TangemHapticEffect.OneTime.Click)
hapticManager.perform(TangemHapticEffect.View.Confirm)
}
private suspend fun CoroutineScope.handleReleaseAnimation(

View file

@ -38,6 +38,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.BaseSearchBarTestTags
import com.tangem.core.ui.test.SearchBarTestTags
@Composable
fun SearchBar(
@ -126,7 +127,9 @@ private fun DecorationBox(
contentPadding = PaddingValues(all = TangemTheme.dimens.spacing12),
leadingIcon = {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size20),
modifier = Modifier
.size(TangemTheme.dimens.size20)
.testTag(SearchBarTestTags.ICON),
painter = painterResource(id = R.drawable.ic_search_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
@ -137,6 +140,7 @@ private fun DecorationBox(
state = state,
focusManager = focusManager,
keyboardController = keyboardController,
modifier = Modifier.testTag(SearchBarTestTags.CLEAR_BUTTON),
)
},
placeholder = {
@ -146,6 +150,7 @@ private fun DecorationBox(
style = TangemTheme.typography.body2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.testTag(SearchBarTestTags.PLACEHOLDER_TEXT),
)
},
)

View file

@ -0,0 +1,8 @@
package com.tangem.core.ui.test
object MarketTokenDetailsBottomSheetTestTags {
const val PORTFOLIO_QUICK_ACTION_BUTTON = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_QUICK_ACTION_BUTTON"
const val PORTFOLIO_QUICK_ACTION_BUTTON_TITLE = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_QUICK_ACTION_BUTTON_TITLE"
const val PORTFOLIO_QUICK_ACTION_BUTTON_ICON = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_QUICK_ACTION_BUTTON_ICON"
const val PORTFOLIO_TOKEN_ITEM = "MARKET_TOKEN_DETAILS_BOTTOM_SHEET_PORTFOLIO_TOKEN_ITEM"
}

View file

@ -0,0 +1,7 @@
package com.tangem.core.ui.test
object SearchBarTestTags {
const val ICON = "SEARCH_BAR_ICON"
const val CLEAR_BUTTON = "SEARCH_BAR_CLEAR_BUTTON"
const val PLACEHOLDER_TEXT = "SEARCH_BAR_PLACEHOLDER_TEXT"
}

View file

@ -0,0 +1,6 @@
package com.tangem.core.ui.test
object SwapSelectTokenScreenTestTags {
const val YOU_SWAP_BLOCK = "SWAP_SELECT_TOKEN_SCREEN_YOU_SWAP_BLOCK"
const val CHOOSE_TOKEN_TEXT = "SWAP_SELECT_TOKEN_SCREEN_CHOOSE_TOKEN_TEXT"
}

View file

@ -1,6 +1,7 @@
package com.tangem.core.ui.test
object SwapTokenScreenTestTags {
const val CONTAINER = "SWAP_TOKEN_SCREEN_CONTAINER"
const val SWAP_BLOCK_HEADER = "SWAP_TOKEN_SCREEN_SWAP_BLOCK"
const val BALANCE = "SWAP_TOKEN_SCREEN_BALANCE"
const val SWAP_TEXT_FIELD = "SWAP_TOKEN_SCREEN_SWAP_TEXT_FIELD"