Updated on 2026-08-14
This commit is contained in:
parent
1290fcbc3f
commit
fcf3237c59
4 changed files with 326 additions and 0 deletions
69
app/src/main/java/com/tangem/tap/common/compose/Button.kt
Normal file
69
app/src/main/java/com/tangem/tap/common/compose/Button.kt
Normal 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 = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue