Updated on 2026-08-14

This commit is contained in:
Tangem 2023-01-19 20:27:59 +08:00
parent 6d69a5706a
commit 9c1931f7d7
81 changed files with 686 additions and 981 deletions

View file

@ -14,10 +14,10 @@ import androidx.compose.ui.unit.sp
@Suppress("MagicNumber")
@Composable
fun TextAutoSize(
modifier: Modifier = Modifier,
text: String,
textStyle: TextStyle = LocalTextStyle.current,
fontSizeRange: FontSizeRange,
modifier: Modifier = Modifier,
textStyle: TextStyle = LocalTextStyle.current,
) {
val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) }
val readyToDraw = remember { mutableStateOf(false) }

View file

@ -17,7 +17,7 @@ fun BlockchainSpinner(
textFieldConverter: (Blockchain) -> String,
dropdownItemView: @Composable ((Blockchain) -> Unit)? = null,
closePopupTrigger: ClosePopupTrigger = ClosePopupTrigger(),
onItemSelected: (Blockchain) -> Unit,
onItemSelect: (Blockchain) -> Unit,
) {
OutlinedSpinner(
modifier = Modifier.fillMaxWidth(),
@ -27,7 +27,7 @@ fun BlockchainSpinner(
textFieldConverter = textFieldConverter,
dropdownItemView = dropdownItemView,
isEnabled = isEnabled,
onItemSelected = onItemSelected,
onItemSelected = onItemSelect,
closePopupTrigger = closePopupTrigger,
)
}

View file

@ -1,137 +1,8 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.components.SpacerH8
import com.tangem.tap.common.compose.extensions.stringResourceDefault
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
fun RectangleButton(
modifier: Modifier = Modifier,
text: String = "",
textId: Int? = null,
isEnabled: Boolean = true,
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
leadingView: @Composable RowScope.() -> Unit = {},
middleView: @Composable RowScope.() -> Unit = { TextInButton(text = text, textId = textId) },
trailingView: @Composable RowScope.() -> Unit = {},
onClick: () -> Unit,
) {
Button(
modifier = modifier,
contentPadding = contentPadding,
enabled = isEnabled,
onClick = onClick,
) {
leadingView()
middleView()
trailingView()
}
}
@Composable
private fun TextInButton(
modifier: Modifier = Modifier,
text: String = "",
textId: Int? = null,
) {
Text(
modifier = modifier,
text = stringResourceDefault(textId, text),
maxLines = 1,
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontWeight = FontWeight.Medium,
)
)
}
@Composable
fun PasteButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
dpSize: DpSize = DpSize(40.dp, 40.dp),
onClick: () -> Unit,
tint: Color? = null,
content: @Composable (() -> Unit)? = null
) {
IconButton(
modifier = modifier.size(dpSize),
enabled = enabled,
onClick = onClick,
) {
when (content) {
null -> {
val tintColor = tint
?: colorResource(id = if (enabled) R.color.button_positive else R.color.button_positive_disabled)
Icon(
painterResource(id = R.drawable.ic_paste),
contentDescription = "Paste",
tint = tintColor,
)
}
else -> content()
}
}
}
@Composable
fun ClearButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
dpSize: DpSize = DpSize(40.dp, 40.dp),
onClick: () -> Unit,
tint: Color? = null,
content: @Composable (() -> Unit)? = null
) {
IconButton(
modifier = modifier.size(dpSize),
enabled = enabled,
onClick = onClick,
) {
when (content) {
null -> {
val tintColor = tint
?: colorResource(id = if (enabled) R.color.button_positive else R.color.button_positive_disabled)
Icon(
painterResource(id = R.drawable.ic_clear),
contentDescription = "Clear",
tint = tintColor,
)
}
else -> content()
}
}
}
/**
* Used for disable ripple if button is enable = false
@ -143,34 +14,4 @@ fun ToggledRippleTheme(
) {
val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme()
CompositionLocalProvider(theme) { content() }
}
@Preview
@Composable
fun ButtonTest() {
Scaffold {
Column(modifier = Modifier.padding(16.dp)) {
PreviewItem("Button") {
RectangleButton(text = "Some button") {}
}
PreviewItem("PasteButton") {
PasteButton(onClick = {})
}
}
}
}
@Composable
fun PreviewItem(
name: String,
content: @Composable RowScope.() -> Unit,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
modifier = Modifier.weight(1f),
text = name,
)
content()
}
SpacerH8()
}

View file

@ -11,19 +11,19 @@ import com.tangem.domain.common.util.ValueDebouncer
@Composable
fun <T> valueDebouncerAsState(
initialValue: T,
onValueChange: (T) -> Unit,
debounce: Long = 600,
onEmitValueReceived: (T) -> Unit = {},
onValueChanged: (T) -> Unit,
onEmitValueReceive: (T) -> Unit = {},
): ValueDebouncer<T> {
return remember {
ValueDebouncer<T>(
ValueDebouncer(
initialValue = initialValue,
debounceDuration = debounce,
onEmitValueReceived = { emitValue ->
emitValue?.let { onEmitValueReceived(it) }
emitValue?.let { onEmitValueReceive(it) }
},
onValueChanged = { changedValue ->
changedValue?.let { onValueChanged(it) }
changedValue?.let { onValueChange(it) }
},
)
}
@ -32,16 +32,16 @@ fun <T> valueDebouncerAsState(
@Composable
fun <T> valueDebouncerNullableAsState(
initialValue: T?,
onValueChange: (T?) -> Unit,
debounce: Long = 400,
onEmitValueReceived: (T?) -> Unit = {},
onValueChanged: (T?) -> Unit,
onEmitValueReceive: (T?) -> Unit = {},
): ValueDebouncer<T?> {
return remember {
ValueDebouncer(
initialValue = initialValue,
debounceDuration = debounce,
onEmitValueReceived = onEmitValueReceived,
onValueChanged = onValueChanged,
onEmitValueReceived = onEmitValueReceive,
onValueChanged = onValueChange,
)
}
}

View file

@ -4,7 +4,6 @@ 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.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -19,23 +18,20 @@ import androidx.compose.ui.unit.dp
fun ErrorView(
text: String,
modifier: Modifier = Modifier,
style: TextStyle = LocalTextStyle.current
style: TextStyle = LocalTextStyle.current,
) {
Text(
text,
color = MaterialTheme.colors.error,
modifier = modifier,
style = style
style = style,
)
}
@Preview
@Composable
fun ErrorViewTest() {
Scaffold(
) {
Box(Modifier.padding(16.dp)) {
ErrorView(text = "Some error description")
}
private fun ErrorViewTest() {
Box(Modifier.padding(16.dp)) {
ErrorView(text = "Some error description")
}
}

View file

@ -21,11 +21,11 @@ import com.tangem.tap.common.extensions.ValueCallback
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun <T> OutlinedSpinner(
modifier: Modifier = Modifier,
label: String,
itemList: List<T>,
selectedItem: Field.Data<T>,
onItemSelected: ValueCallback<T>,
modifier: Modifier = Modifier,
textFieldConverter: (T) -> String = { it.toString() },
dropdownItemView: @Composable ((T) -> Unit)? = null,
isEnabled: Boolean = true,
@ -96,13 +96,11 @@ class ClosePopupTrigger {
@Preview
@Composable
fun TestSpinnerPreview() {
Scaffold {
OutlinedSpinner(
label = "Blockchain name",
itemList = listOf(Blockchain.values()),
selectedItem = Field.Data(Blockchain.Avalanche, false),
onItemSelected = {},
)
}
private fun TestSpinnerPreview() {
OutlinedSpinner(
label = "Blockchain name",
itemList = listOf(Blockchain.values()),
selectedItem = Field.Data(Blockchain.Avalanche, false),
onItemSelected = {},
)
}

View file

@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TextFieldColors
import androidx.compose.runtime.Composable
@ -41,7 +40,6 @@ import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
*/
@Composable
fun OutlinedTextFieldWidget(
modifier: Modifier = Modifier,
fieldData: Field.Data<String>,
labelId: Int? = null,
label: String = "",
@ -56,15 +54,12 @@ fun OutlinedTextFieldWidget(
debounceTextChanges: Long = 400,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
onTextChanged: (String) -> Unit,
onTextChange: (String) -> Unit,
) {
if (!isVisible) return
Column(
modifier = modifier.animateContentSize(),
) {
Column(modifier = Modifier.animateContentSize()) {
OutlinedProgressTextField(
modifier = modifier,
fieldData = fieldData,
label = stringResourceDefault(labelId, label),
placeholder = stringResourceDefault(placeholderId, placeholder),
@ -75,16 +70,15 @@ fun OutlinedTextFieldWidget(
debounce = debounceTextChanges,
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
onTextChanged = onTextChanged,
onTextChange = onTextChange,
)
errorConverter?.let { AnimatedErrorView(error, it) }
errorConverter?.let { AnimatedErrorView(errorConverter = it, error = error) }
}
}
@Suppress("LongMethod", "NestedBlockDepth", "MagicNumber", "MaxLineLength")
@Composable
private fun OutlinedProgressTextField(
modifier: Modifier = Modifier,
fieldData: Field.Data<String>,
label: String = "",
placeholder: String = "",
@ -97,7 +91,7 @@ private fun OutlinedProgressTextField(
colors: TextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
trailingIcon: @Composable (() -> Unit)? = null,
onTextChanged: (String) -> Unit,
onTextChange: (String) -> Unit,
) {
val logger = remember {
CompositionLogger(label, "OutlinedProgressTextField", listOf("Символ токена"))
@ -108,14 +102,14 @@ private fun OutlinedProgressTextField(
val textDebouncer = valueDebouncerAsState(
initialValue = fieldData.value,
debounce = debounce,
onEmitValueReceived = {
onEmitValueReceive = {
logger.log("DEBOUNCER: onEmitValueReceived: [$it]")
logger.log("DEBOUNCER: start RECOMPOSE by new value for textValueState.value = [$it]")
textValueState.value = it
},
onValueChanged = {
onValueChange = {
logger.log("DEBOUNCER: onValueChanged: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> dispatch.toStore([$it])")
onTextChanged(it)
onTextChange(it)
},
)
@ -194,7 +188,7 @@ private fun OutlinedProgressTextField(
interactionSource = interactionSource,
)
AnimatedVisibility(
modifier = modifier
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.padding(start = 6.dp, top = 0.dp, end = 6.dp, bottom = 6.dp),
@ -209,8 +203,8 @@ private fun OutlinedProgressTextField(
@Composable
private fun AnimatedErrorView(
error: ModuleError? = null,
errorConverter: ModuleMessageConverter,
error: ModuleError? = null,
) {
AnimatedVisibility(
visible = error != null,
@ -228,7 +222,7 @@ private fun AnimatedErrorView(
@Preview
@Composable
fun OutlinedTextFieldWithErrorTest() {
private fun OutlinedTextFieldWithErrorTest() {
val context = LocalContext.current
val converter = remember { ModuleMessageConverter(context) }
@ -241,41 +235,35 @@ fun OutlinedTextFieldWithErrorTest() {
val modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
Scaffold {
Column {
OutlinedTextFieldWidget(
modifier = modifier,
fieldData = Field.Data("", false),
label = "First label",
placeholder = "1 placeholder",
error = null,
errorConverter = converter,
) {}
OutlinedTextFieldWidget(
modifier = modifier,
fieldData = Field.Data("First", false),
label = "First label",
placeholder = "1 placeholder",
error = null,
errorConverter = converter,
) {}
OutlinedTextFieldWidget(
modifier = modifier,
fieldData = Field.Data("First", false),
label = "First label",
placeholder = "1 placeholder",
isLoading = true,
error = null,
errorConverter = converter,
) {}
OutlinedTextFieldWidget(
modifier = modifier,
fieldData = Field.Data("First", false),
label = "First label",
placeholder = "1 placeholder",
error = SimpleError(),
errorConverter = converter,
) {}
}
Column {
OutlinedTextFieldWidget(
fieldData = Field.Data("", false),
label = "First label",
placeholder = "1 placeholder",
error = null,
errorConverter = converter,
) {}
OutlinedTextFieldWidget(
fieldData = Field.Data("First", false),
label = "First label",
placeholder = "1 placeholder",
error = null,
errorConverter = converter,
) {}
OutlinedTextFieldWidget(
fieldData = Field.Data("First", false),
label = "First label",
placeholder = "1 placeholder",
isLoading = true,
error = null,
errorConverter = converter,
) {}
OutlinedTextFieldWidget(
fieldData = Field.Data("First", false),
label = "First label",
placeholder = "1 placeholder",
error = SimpleError(),
errorConverter = converter,
) {}
}
}

View file

@ -50,7 +50,7 @@ import androidx.core.text.isDigitsOnly
@Composable
fun PinCodeWidget(
config: PinViewConfig = tangemPinConfig,
onPinChanged: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> },
onPinChange: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> },
) {
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
@ -65,7 +65,7 @@ fun PinCodeWidget(
if (value.text.length <= config.pinsCount) {
rTextFieldValue.value = value
onPinChanged(value.text, isLastSymbolEntered())
onPinChange(value.text, isLastSymbolEntered())
}
}
@ -161,7 +161,7 @@ private val tangemPinConfig = PinViewConfig(
@Preview
@Composable
fun PinCodeWidgetPreview() {
private fun PinCodeWidgetPreview() {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
PinCodeWidget(tangemPinConfig)
}

View file

@ -22,9 +22,9 @@ import com.tangem.wallet.R
*/
@Composable
fun AddCustomTokenWarning(
modifier: Modifier = Modifier,
warning: ModuleMessage,
converter: ModuleMessageConverter
converter: ModuleMessageConverter,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier,

View file

@ -8,11 +8,13 @@ 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

@ -1,12 +1,16 @@
package com.tangem.tap.common.compose.extensions
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.*
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, loadMore: () -> Unit) {
fun LazyListState.OnBottomReached(loadMoreThreshold: Int, onLoadMore: () -> Unit) {
require(loadMoreThreshold >= 0)
val shouldLoadMore by remember {
derivedStateOf {
@ -17,7 +21,7 @@ fun LazyListState.OnBottomReached(loadMoreThreshold: Int, loadMore: () -> Unit)
}
LaunchedEffect(shouldLoadMore) {
if (shouldLoadMore) loadMore()
if (shouldLoadMore) onLoadMore()
}
}