Updated on 2026-08-14
This commit is contained in:
parent
4fc058cd2d
commit
a73558501c
16 changed files with 849 additions and 96 deletions
|
|
@ -1,71 +1,115 @@
|
|||
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.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.tap.common.compose.extensions.stringResourceDefault
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
private class Button {}
|
||||
|
||||
@Composable
|
||||
fun Button(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String = "",
|
||||
textId: Int? = null,
|
||||
isEnabled: Boolean = true,
|
||||
modifier: Modifier = Modifier,
|
||||
leftContent: @Composable RowScope.() -> Unit = {},
|
||||
rightContent: @Composable RowScope.() -> Unit = {},
|
||||
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.height(42.dp),
|
||||
modifier = modifier,
|
||||
contentPadding = contentPadding,
|
||||
enabled = isEnabled,
|
||||
onClick = onClick,
|
||||
) {
|
||||
leftContent()
|
||||
ButtonText(text = textId?.let { stringResource(id = it) } ?: text)
|
||||
rightContent()
|
||||
leadingView()
|
||||
middleView()
|
||||
trailingView()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ButtonText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier
|
||||
private fun TextInButton(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String = "",
|
||||
textId: Int? = null,
|
||||
) {
|
||||
Text(
|
||||
text,
|
||||
modifier = modifier,
|
||||
text = stringResourceDefault(textId, text),
|
||||
maxLines = 1,
|
||||
style = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 20.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PasteButton(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
dpSize: DpSize = DpSize(40.dp, 40.dp),
|
||||
onClick: () -> Unit,
|
||||
content: @Composable (() -> Unit)? = null
|
||||
) {
|
||||
IconButton(
|
||||
modifier = modifier.size(dpSize),
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
) {
|
||||
when (content) {
|
||||
null -> {
|
||||
val icon = if (enabled) R.drawable.ic_paste else R.drawable.ic_paste_disabled
|
||||
Icon(painterResource(id = icon), contentDescription = "Paste")
|
||||
}
|
||||
else -> content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ButtonTest() {
|
||||
Scaffold {
|
||||
Button(
|
||||
"Some button",
|
||||
onClick = {}
|
||||
)
|
||||
Scaffold(
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
PreviewItem("Button") {
|
||||
Button(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()
|
||||
}
|
||||
|
|
@ -1,26 +1,24 @@
|
|||
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
|
||||
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 ComposableTextDebouncer(
|
||||
text: String,
|
||||
fun <T> valueDebouncerAsState(
|
||||
debounce: Long = 400,
|
||||
onTextChanged: (String) -> Unit
|
||||
): ValueDebouncer<String> {
|
||||
val rTextDebounce = remember {
|
||||
mutableStateOf(ValueDebouncer(text, debounce) { changedValue ->
|
||||
changedValue?.let { onTextChanged(it) }
|
||||
})
|
||||
onValueChanged: (T) -> Unit
|
||||
): ValueDebouncer<T> {
|
||||
return remember {
|
||||
ValueDebouncer(
|
||||
debounce = debounce,
|
||||
onValueChanged = { changedValue ->
|
||||
changedValue?.let { onValueChanged(it) }
|
||||
},
|
||||
)
|
||||
}
|
||||
rTextDebounce.value.value = text
|
||||
|
||||
return rTextDebounce.value
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.tangem.domain.DomainDialog
|
||||
import com.tangem.domain.DomainStateDialog
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.domain.redux.global.DomainGlobalAction
|
||||
import com.tangem.domain.redux.global.DomainGlobalState
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
@Composable
|
||||
fun ComposeDialogManager() {
|
||||
val dialogSate = remember { mutableStateOf<DomainStateDialog?>(null) }
|
||||
val subscriber = remember {
|
||||
object : StoreSubscriber<DomainGlobalState> {
|
||||
override fun newState(state: DomainGlobalState) {
|
||||
dialogSate.value = state.dialog
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShowTheDialog(dialogSate)
|
||||
|
||||
LaunchedEffect(key1 = Unit, block = {
|
||||
domainStore.subscribe(subscriber) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.globalState == newState.globalState
|
||||
}.select { it.globalState }
|
||||
}
|
||||
})
|
||||
DisposableEffect(key1 = Unit, effect = {
|
||||
onDispose { domainStore.unsubscribe(subscriber) }
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShowTheDialog(dialogState: MutableState<DomainStateDialog?>) {
|
||||
if (dialogState.value == null) return
|
||||
|
||||
val onDismissRequest = { domainStore.dispatch(DomainGlobalAction.ShowDialog(null)) }
|
||||
|
||||
when (val dialog = dialogState.value) {
|
||||
is DomainDialog.SelectTokenDialog -> {
|
||||
SimpleDialog(
|
||||
title = "Select a token",
|
||||
items = dialog.items,
|
||||
itemNameConverter = dialog.itemNameConverter,
|
||||
onSelect = dialog.onSelect,
|
||||
onDismissRequest = onDismissRequest
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog with single item selection
|
||||
*/
|
||||
@Composable
|
||||
fun <T> SimpleDialog(
|
||||
title: String,
|
||||
items: List<T>,
|
||||
itemNameConverter: (T) -> String,
|
||||
onSelect: (T) -> Unit,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
Dialog(
|
||||
properties = DialogProperties(false, false),
|
||||
onDismissRequest = { }
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(22.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = LocalTextStyle.provides(
|
||||
TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
).value
|
||||
)
|
||||
|
||||
SpacerH16()
|
||||
LazyColumn() {
|
||||
items(items) { item ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp)
|
||||
.clickable {
|
||||
onSelect(item)
|
||||
onDismissRequest()
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = itemNameConverter(item),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun keyboardObserverAsState(): State<Keyboard> {
|
||||
val keyboardState: MutableState<Keyboard> = remember { mutableStateOf(Keyboard.Closed) }
|
||||
val view = LocalView.current
|
||||
DisposableEffect(view) {
|
||||
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
|
||||
val rect = Rect()
|
||||
view.getWindowVisibleDisplayFrame(rect)
|
||||
val screenHeight = view.rootView.height
|
||||
val keypadHeight = screenHeight - rect.bottom
|
||||
keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
|
||||
Keyboard.Opened(keypadHeight)
|
||||
} else {
|
||||
Keyboard.Closed
|
||||
}
|
||||
}
|
||||
view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)
|
||||
|
||||
onDispose {
|
||||
view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
|
||||
}
|
||||
}
|
||||
|
||||
return keyboardState
|
||||
}
|
||||
|
||||
sealed class Keyboard {
|
||||
data class Opened(val height: Int) : Keyboard()
|
||||
object Closed : Keyboard()
|
||||
}
|
||||
|
|
@ -1,11 +1,16 @@
|
|||
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.Scaffold
|
||||
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]
|
||||
|
|
@ -22,4 +27,15 @@ fun ErrorView(
|
|||
modifier = modifier,
|
||||
style = style
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ErrorViewTest() {
|
||||
Scaffold(
|
||||
) {
|
||||
Box(Modifier.padding(16.dp)) {
|
||||
ErrorView(text = "Some error description")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Simple logger for all recompositions
|
||||
*/
|
||||
@Composable
|
||||
fun LogSideEffect(message: String) {
|
||||
Timber.d("SideEffect: $message")
|
||||
}
|
||||
|
|
@ -8,24 +8,31 @@ 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.domain.common.form.Field
|
||||
import com.tangem.tap.common.extensions.ValueCallback
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
private class OutlinedSpinner
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun <T> OutlinedSpinner(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
itemList: List<T>,
|
||||
selectedItem: T,
|
||||
selectedItem: Field.Data<T>,
|
||||
onItemSelected: ValueCallback<T>,
|
||||
modifier: Modifier = Modifier,
|
||||
itemNameConverter: (T) -> String = { it.toString() },
|
||||
isEnabled: Boolean = true,
|
||||
onClose: VoidCallback = {}
|
||||
) {
|
||||
val rSelectedItem = remember { mutableStateOf(selectedItem) }
|
||||
val rIsExpanded = remember { mutableStateOf(false) }
|
||||
val rSelectedItem = remember { mutableStateOf(selectedItem.value) }
|
||||
if (!selectedItem.isUserInput) {
|
||||
rSelectedItem.value = selectedItem.value
|
||||
}
|
||||
|
||||
val onItemSelectedInternal: (T) -> Unit = {
|
||||
rSelectedItem.value = it
|
||||
|
|
@ -44,6 +51,7 @@ fun <T> OutlinedSpinner(
|
|||
OutlinedTextField(
|
||||
modifier = modifier,
|
||||
readOnly = true,
|
||||
enabled = isEnabled,
|
||||
value = itemNameConverter(rSelectedItem.value),
|
||||
onValueChange = {},
|
||||
label = { Text(title) },
|
||||
|
|
@ -66,12 +74,12 @@ fun <T> OutlinedSpinner(
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
fun TestSpinnerPreview(){
|
||||
fun TestSpinnerPreview() {
|
||||
Scaffold() {
|
||||
OutlinedSpinner(
|
||||
title = "Blockchain name",
|
||||
itemList = listOf(Blockchain.values()),
|
||||
selectedItem = Blockchain.Avalanche,
|
||||
selectedItem = Field.Data(Blockchain.Avalanche),
|
||||
onItemSelected = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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
|
||||
|
|
@ -14,14 +15,15 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
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
|
||||
import com.tangem.domain.DomainError
|
||||
import com.tangem.domain.ErrorConverter
|
||||
import com.tangem.domain.common.form.Field
|
||||
import com.tangem.tap.common.compose.extensions.stringResourceDefault
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -30,8 +32,8 @@ private class OutlinedTextFieldWidget
|
|||
|
||||
@Composable
|
||||
fun OutlinedTextFieldWidget(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
textFieldData: Field.Data<String>,
|
||||
labelId: Int? = null,
|
||||
label: String = "",
|
||||
placeholderId: Int? = null,
|
||||
|
|
@ -44,62 +46,66 @@ fun OutlinedTextFieldWidget(
|
|||
errorConverter: ErrorConverter<String>? = null,
|
||||
debounceTextChanges: Long = 400,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
onTextChanged: (String) -> Unit,
|
||||
) {
|
||||
if (!isVisible) return
|
||||
|
||||
val placeholder = placeholderId?.let { stringResource(id = it) } ?: placeholder
|
||||
val label = labelId?.let { stringResource(id = it) } ?: label
|
||||
|
||||
Column(
|
||||
modifier = modifier.animateContentSize(),
|
||||
) {
|
||||
OutlinedProgressTextField(
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
label = label,
|
||||
placeholder = placeholder,
|
||||
textFieldData = textFieldData,
|
||||
label = stringResourceDefault(labelId, label),
|
||||
placeholder = stringResourceDefault(placeholderId, placeholder),
|
||||
trailingIcon = trailingIcon,
|
||||
isEnabled = isEnabled,
|
||||
isLoading = isLoading,
|
||||
error = error,
|
||||
debounceTextChanges = debounceTextChanges,
|
||||
debounce = debounceTextChanges,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
onTextChanged = onTextChanged
|
||||
)
|
||||
errorConverter?.let { TextFieldErrorWidget(error, it) }
|
||||
errorConverter?.let { AnimatedErrorView(error, it) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OutlinedProgressTextField(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
textFieldData: Field.Data<String>,
|
||||
label: String = "",
|
||||
placeholder: String = "",
|
||||
isEnabled: Boolean = true,
|
||||
isLoading: Boolean = false,
|
||||
error: DomainError? = null,
|
||||
debounceTextChanges: Long = 400,
|
||||
debounce: Long = 400,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
onTextChanged: (String) -> Unit,
|
||||
) {
|
||||
val rTextValue = remember { mutableStateOf(text) }
|
||||
val textDebouncer = ComposableTextDebouncer(text, debounceTextChanges, onTextChanged)
|
||||
val rTextDebouncer = valueDebouncerAsState(debounce, onTextChanged)
|
||||
val rText = remember { mutableStateOf(textFieldData.value) }
|
||||
|
||||
// add ability to paste text from state
|
||||
if (rTextValue.value != text) rTextValue.value = text
|
||||
fun updateFieldValueAndEmmit(value: String){
|
||||
rText.value = value
|
||||
rTextDebouncer.emmit(value)
|
||||
}
|
||||
// This action came from redux. Update the field value and send a new event as if from the user
|
||||
if (!textFieldData.isUserInput) {
|
||||
updateFieldValueAndEmmit(textFieldData.value)
|
||||
}
|
||||
|
||||
Box {
|
||||
OutlinedTextField(
|
||||
value = rTextValue.value,
|
||||
onValueChange = {
|
||||
// immediately change text for the OutlinedTextField
|
||||
rTextValue.value = it
|
||||
textDebouncer.emmit(it)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
value = rText.value,
|
||||
onValueChange = ::updateFieldValueAndEmmit,
|
||||
keyboardOptions = keyboardOptions,
|
||||
label = { Text(label) },
|
||||
placeholder = { Text(placeholder) },
|
||||
trailingIcon = trailingIcon,
|
||||
|
|
@ -120,7 +126,7 @@ private fun OutlinedProgressTextField(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun TextFieldErrorWidget(
|
||||
private fun AnimatedErrorView(
|
||||
error: DomainError? = null,
|
||||
errorConverter: ErrorConverter<String>,
|
||||
) {
|
||||
|
|
@ -162,41 +168,37 @@ fun OutlinedTextFieldWithErrorTest() {
|
|||
) {
|
||||
OutlinedTextFieldWidget(
|
||||
modifier = modifier,
|
||||
text = "",
|
||||
textFieldData = Field.Data(""),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
onTextChanged = {},
|
||||
)
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
modifier = modifier,
|
||||
text = "First",
|
||||
textFieldData = Field.Data("First"),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
onTextChanged = {},
|
||||
)
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
modifier = modifier,
|
||||
text = "First",
|
||||
textFieldData = Field.Data("First"),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
isLoading = true,
|
||||
error = null,
|
||||
errorConverter = converter,
|
||||
onTextChanged = {},
|
||||
)
|
||||
) {}
|
||||
OutlinedTextFieldWidget(
|
||||
modifier = modifier,
|
||||
text = "First",
|
||||
textFieldData = Field.Data("First"),
|
||||
label = "First label",
|
||||
placeholder = "1 placeholder",
|
||||
error = SimpleError(),
|
||||
errorConverter = converter,
|
||||
onTextChanged = {},
|
||||
)
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.common.extensions.compose
|
||||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun stringResourceDefault(@StringRes id: Int?, default: String = ""): String {
|
||||
val resources = LocalContext.current.resources
|
||||
return try {
|
||||
resources.getString(requireNotNull(id))
|
||||
} catch (ex: Resources.NotFoundException) {
|
||||
default
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
default
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue