Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-06 19:08:34 +03:00
parent 1290fcbc3f
commit fcf3237c59
4 changed files with 326 additions and 0 deletions

View file

@ -0,0 +1,69 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.height
import androidx.compose.material.Button
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
/**
[REDACTED_AUTHOR]
*/
private class Button {}
@Composable
fun Button(
text: String = "",
textId: Int? = null,
modifier: Modifier = Modifier,
leftContent: @Composable RowScope.() -> Unit = {},
rightContent: @Composable RowScope.() -> Unit = {},
onClick: () -> Unit,
) {
Button(
modifier = modifier.height(42.dp),
onClick = onClick,
) {
leftContent()
ButtonText(text = textId?.let { stringResource(id = it) } ?: text)
rightContent()
}
}
@Composable
fun ButtonText(
text: String,
modifier: Modifier = Modifier
) {
Text(
text,
modifier = modifier,
maxLines = 1,
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center,
)
)
}
@Preview
@Composable
fun ButtonTest() {
Scaffold {
Button(
"Some button",
onClick = {}
)
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.tap.common.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import com.tangem.domain.common.ValueDebouncer
/**
[REDACTED_AUTHOR]
* This is an empty compose view. It just remember the ValueDebouncer inside of itself.
*/
@Composable
fun ComposableTextDebouncer(
text: String,
debounce: Long = 400,
onTextChanged: (String) -> Unit
): ValueDebouncer<String> {
val rTextDebounce = remember {
mutableStateOf(ValueDebouncer(text, debounce) { changedValue ->
changedValue?.let { onTextChanged(it) }
})
}
rTextDebounce.value.value = text
return rTextDebounce.value
}

View file

@ -0,0 +1,78 @@
package com.tangem.tap.common.compose
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.extensions.ValueCallback
/**
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun <T> OutlinedSpinner(
title: String,
itemList: List<T>,
selectedItem: T,
onItemSelected: ValueCallback<T>,
modifier: Modifier = Modifier,
itemNameConverter: (T) -> String = { it.toString() },
onClose: VoidCallback = {}
) {
val rSelectedItem = remember { mutableStateOf(selectedItem) }
val rIsExpanded = remember { mutableStateOf(false) }
val onItemSelectedInternal: (T) -> Unit = {
rSelectedItem.value = it
rIsExpanded.value = false
onItemSelected(it)
}
val onDismissRequest = {
rIsExpanded.value = false
onClose()
}
ExposedDropdownMenuBox(
expanded = rIsExpanded.value,
onExpandedChange = { rIsExpanded.value = !rIsExpanded.value },
) {
OutlinedTextField(
modifier = modifier,
readOnly = true,
value = itemNameConverter(rSelectedItem.value),
onValueChange = {},
label = { Text(title) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = rIsExpanded.value) },
)
ExposedDropdownMenu(
expanded = rIsExpanded.value,
onDismissRequest = onDismissRequest,
) {
itemList.forEach { item ->
DropdownMenuItem(
onClick = { onItemSelectedInternal(item) }
) {
Text(itemNameConverter(item))
}
}
}
}
}
@Preview
@Composable
fun TestSpinnerPreview(){
Scaffold() {
OutlinedSpinner(
title = "Blockchain name",
itemList = listOf(Blockchain.values()),
selectedItem = Blockchain.Avalanche,
onItemSelected = {},
)
}
}

View file

@ -0,0 +1,153 @@
package com.tangem.tap.common.compose
import androidx.compose.animation.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.domain.common.DomainError
import com.tangem.domain.common.ErrorConverter
/**
[REDACTED_AUTHOR]
*/
private class OutlinedTextFieldWidget
@Composable
fun OutlinedTextFieldWidget(
text: String,
modifier: Modifier = Modifier,
labelId: Int? = null,
label: String = "",
placeholderId: Int? = null,
placeholder: String = "",
isEnabled: Boolean = true,
error: DomainError? = null,
errorConverter: ErrorConverter<String>? = null,
debounceTextChanges: Long = 400,
onTextChanged: (String) -> Unit,
) {
val placeholder = placeholderId?.let { stringResource(id = it) } ?: placeholder
val label = labelId?.let { stringResource(id = it) } ?: label
val rTextValue = remember { mutableStateOf(text) }
val textDebouncer = ComposableTextDebouncer(text, debounceTextChanges, onTextChanged)
Column(
modifier = modifier.animateContentSize(),
) {
OutlinedTextField(
value = rTextValue.value,
onValueChange = {
rTextValue.value = it
textDebouncer.emmit(it)
},
modifier = Modifier.fillMaxWidth(),
label = { Text(label) },
placeholder = { Text(placeholder) },
singleLine = true,
enabled = isEnabled,
isError = error != null,
)
errorConverter?.let { TextFieldErrorWidget(error, it) }
}
}
@Composable
fun TextFieldErrorWidget(
error: DomainError? = null,
errorConverter: ErrorConverter<String>,
) {
AnimatedVisibility(
visible = error != null,
enter = fadeIn() + slideInVertically(),
exit = slideOutVertically() + fadeOut(),
) {
ErrorView(
errorConverter.convertError(error!!),
style = TextStyle(
fontSize = 14.sp
)
)
}
}
@Composable
fun ErrorView(
text: String,
modifier: Modifier = Modifier,
style: TextStyle = LocalTextStyle.current
) {
Text(
text,
color = MaterialTheme.colors.error,
modifier = modifier,
style = style
)
}
@Preview
@Composable
fun OutlinedTextFieldWithErrorTest() {
val converter = remember {
object : ErrorConverter<String> {
override fun convertError(error: DomainError): String {
return "Hello, i'am the error: ${error::class.java.simpleName}"
}
}
}
class SimpleError(
override val code: Int = 1,
override val message: String = "Error message",
override val data: Any? = null,
) : DomainError
val modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
Scaffold(
) {
Column(
) {
OutlinedTextFieldWidget(
modifier = modifier,
text = "",
label = "First label",
placeholder = "1 placeholder",
error = null,
errorConverter = converter,
onTextChanged = {},
)
OutlinedTextFieldWidget(
modifier = modifier,
text = "First",
label = "First label",
placeholder = "1 placeholder",
error = null,
errorConverter = converter,
onTextChanged = {},
)
OutlinedTextFieldWidget(
modifier = modifier,
text = "First",
label = "First label",
placeholder = "1 placeholder",
error = SimpleError(),
errorConverter = converter,
onTextChanged = {},
)
}
}
}