Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-21 15:05:17 +04:00
parent 9a8ff1830d
commit a0798cfef2
252 changed files with 247 additions and 5857 deletions

View file

@ -1,67 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
@Suppress("MagicNumber")
@Composable
fun TextAutoSize(
text: String,
fontSizeRange: FontSizeRange,
modifier: Modifier = Modifier,
textStyle: TextStyle = LocalTextStyle.current,
) {
val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) }
val readyToDraw = remember { mutableStateOf(false) }
val textState = remember { mutableStateOf(text) }
if (textState.value != text) {
readyToDraw.value = false
fontSizeValue.value = fontSizeRange.max.value
textState.value = text
}
Text(
modifier = modifier.drawWithContent { if (readyToDraw.value) drawContent() },
text = text,
softWrap = false,
style = textStyle,
fontSize = fontSizeValue.value.sp,
onTextLayout = {
if (it.hasVisualOverflow) {
val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value
if (nextFontSizeValue <= fontSizeRange.min.value) {
fontSizeValue.value = fontSizeRange.min.value
readyToDraw.value = true
} else {
fontSizeValue.value = nextFontSizeValue * 0.8f
}
} else {
readyToDraw.value = true
}
},
)
}
data class FontSizeRange(
val min: TextUnit,
val max: TextUnit,
val step: TextUnit = DEFAULT_TEXT_STEP,
) {
init {
require(min < max) { "min should be less than max, $this" }
require(step.value > 0) { "step should be greater than 0, $this" }
}
companion object {
private val DEFAULT_TEXT_STEP = 1.sp
}
}

View file

@ -1,14 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
/**
* Used for disable ripple if button is enable = false
*/
@Composable
fun ToggledRippleTheme(isEnabled: Boolean, content: @Composable () -> Unit) {
val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme()
CompositionLocalProvider(theme) { content() }
}

View file

@ -1,47 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.tangem.domain.common.util.ValueDebouncer
/**
[REDACTED_AUTHOR]
* This is an empty compose view. It just remember the ValueDebouncer inside of itself.
*/
@Composable
fun <T> valueDebouncerAsState(
initialValue: T,
onValueChange: (T) -> Unit,
debounce: Long = 600,
onEmitValueReceive: (T) -> Unit = {},
): ValueDebouncer<T> {
return remember {
ValueDebouncer(
initialValue = initialValue,
debounceDuration = debounce,
onEmitValueReceived = { emitValue ->
emitValue?.let { onEmitValueReceive(it) }
},
onValueChanged = { changedValue ->
changedValue?.let { onValueChange(it) }
},
)
}
}
@Composable
fun <T> valueDebouncerNullableAsState(
initialValue: T?,
onValueChange: (T?) -> Unit,
debounce: Long = 400,
onEmitValueReceive: (T?) -> Unit = {},
): ValueDebouncer<T?> {
return remember {
ValueDebouncer(
initialValue = initialValue,
debounceDuration = debounce,
onEmitValueReceived = onEmitValueReceive,
onValueChanged = onValueChange,
)
}
}

View file

@ -1,33 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
/**
[REDACTED_AUTHOR]
*/
@Composable
fun ErrorView(text: String, modifier: Modifier = Modifier, style: TextStyle = LocalTextStyle.current) {
Text(
text,
color = MaterialTheme.colors.error,
modifier = modifier,
style = style,
)
}
@Preview
@Composable
private fun ErrorViewTest() {
Box(Modifier.padding(16.dp)) {
ErrorView(text = "Some error description")
}
}

View file

@ -1,17 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.RippleTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
/**
[REDACTED_AUTHOR]
*/
class NoRippleTheme : RippleTheme {
@Composable
override fun defaultColor() = Color.Unspecified
@Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f, 0.0f, 0.0f, 0.0f)
}

View file

@ -1,165 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
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.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.text.isDigitsOnly
/**
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun PinCodeWidget(
config: PinViewConfig = tangemPinConfig,
onPinChange: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> },
) {
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
val rTextFieldValue = remember { mutableStateOf(TextFieldValue("")) }
val indexedSymbols: List<String?> = createPinSymbolsList(config.pinsCount, rTextFieldValue.value.text)
fun isLastSymbolEntered(): Boolean = rTextFieldValue.value.text.length == config.pinsCount
fun handleOnTextFieldValueChanged(value: TextFieldValue) {
if (!value.text.isDigitsOnly()) return
if (value.text.length <= config.pinsCount) {
rTextFieldValue.value = value
onPinChange(value.text, isLastSymbolEntered())
}
}
Box(
modifier = config.modifier
.pointerInput(Unit) {
detectTapGestures {
focusRequester.requestFocus()
keyboardController?.show()
}
},
) {
Row {
for (index in 0 until config.pinsCount) {
PinElement(
config = config,
pinSymbol = indexedSymbols[index] ?: "",
)
}
}
TextField(
modifier = Modifier
.alpha(0f)
.size(1.dp)
.align(Alignment.Center)
.focusRequester(focusRequester),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Decimal,
imeAction = if (isLastSymbolEntered()) ImeAction.Done else ImeAction.Next,
),
keyboardActions = KeyboardActions(
onDone = { keyboardController?.hide() },
),
value = rTextFieldValue.value,
onValueChange = ::handleOnTextFieldValueChanged,
)
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
@Composable
private fun PinElement(config: PinViewConfig, pinSymbol: String) {
Box(Modifier.padding(config.pinBoxPadding)) {
Box(config.pinBoxModifier) {
Text(
text = pinSymbol,
modifier = config.pinTextModifier.align(Alignment.Center),
style = config.pinsTextStyle ?: LocalTextStyle.current,
)
}
}
}
private fun createPinSymbolsList(size: Int, text: String): List<String?> = List(size) {
try {
text[it].toString()
} catch (ex: IndexOutOfBoundsException) {
null
}
}
data class PinViewConfig(
val modifier: Modifier = Modifier,
val pinBoxModifier: Modifier = Modifier,
val pinBoxPadding: Dp = 0.dp,
val pinTextModifier: Modifier = Modifier,
val pinsCount: Int = 4,
val pinsTextStyle: TextStyle? = null,
)
private val tangemPinConfig = PinViewConfig(
modifier = Modifier
.wrapContentSize(),
pinBoxModifier = Modifier
.width(42.dp)
.height(56.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color(0xFFF0F0F0)),
pinBoxPadding = 6.dp,
pinTextModifier = Modifier,
pinsCount = 4,
pinsTextStyle = TextStyle(
fontWeight = FontWeight(500),
fontSize = 24.sp,
),
)
@Preview
@Composable
private fun PinCodeWidgetPreview() {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
PinCodeWidget(tangemPinConfig)
}
}

View file

@ -1,49 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.common.module.ModuleMessage
import com.tangem.core.ui.components.SpacerH8
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
fun AddCustomTokenWarning(warning: ModuleMessage, converter: ModuleMessageConverter, modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
shape = MaterialTheme.shapes.small,
color = colorResource(id = R.color.warning_warning),
elevation = 4.dp,
) {
Column(
modifier = Modifier.padding(16.dp),
) {
Text(
text = stringResource(id = R.string.common_warning),
color = colorResource(id = R.color.white),
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
)
SpacerH8()
Text(
text = converter.convert(warning).message,
color = colorResource(id = R.color.white),
fontSize = 13.sp,
lineHeight = 18.sp,
)
}
}
}

View file

@ -1,21 +0,0 @@
package com.tangem.tap.common.compose.extensions
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.getFromClipboard
/**
[REDACTED_AUTHOR]
*/
@Suppress("ComposableFunctionName")
@Composable
fun copyToClipboard(value: Any, label: String = "") {
LocalContext.current.copyToClipboard(value, label)
}
@Suppress("ComposableFunctionName")
@Composable
fun getFromClipboard(default: CharSequence? = null): CharSequence? {
return LocalContext.current.getFromClipboard(default)
}

View file

@ -14,6 +14,4 @@ fun Dp.toPx(): Float {
return with(LocalDensity.current) { currentDp.toPx() }
}
fun DpSize.halfWidth(): Dp = this.width / 2
fun DpSize.halfHeight(): Dp = this.height / 2

View file

@ -1,33 +0,0 @@
package com.tangem.tap.common.compose.extensions
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalView
import com.tangem.tap.common.extensions.hideKeyboard
@Composable
fun LazyListState.OnBottomReached(loadMoreThreshold: Int, onLoadMore: () -> Unit) {
require(loadMoreThreshold >= 0)
val shouldLoadMore by remember {
derivedStateOf {
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
?: return@derivedStateOf false
lastVisibleItem.index >= layoutInfo.totalItemsCount - 1 - loadMoreThreshold
}
}
LaunchedEffect(shouldLoadMore) {
if (shouldLoadMore) onLoadMore()
}
}
@Composable
fun LazyListState.HideKeyboardOnScroll() {
if (isScrollInProgress) {
LocalView.current.hideKeyboard()
}
}

View file

@ -1,14 +0,0 @@
package com.tangem.tap.common.compose.extensions
import androidx.compose.runtime.MutableState
/**
[REDACTED_AUTHOR]
*/
fun <T> MutableState<List<T>>.addAndNotify(value: T) {
this.value = this.value.toMutableList().apply { add(value) }
}
fun <T> MutableState<List<T>>.removeAndNotify(value: T) {
this.value = this.value.toMutableList().apply { remove(value) }
}

View file

@ -5,7 +5,6 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.tangem.sdk.extensions.dpToPx
import com.tangem.sdk.extensions.pxToDp
/**
@ -17,8 +16,5 @@ fun Painter.dpSize(): DpSize = DpSize(
intrinsicSize.height.pxToDp().dp,
)
@Composable
private fun Float.dpToPx(): Float = LocalContext.current.dpToPx(this)
@Composable
private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)