Normal text with clickable text (not hyper link) - android

I feel that there is a better way to do this thing, I know that this code works, but I just want to know if there is another (best) way to do this.
Row() {
Text(text = "By signing up, you agree with the ")
Text(
text = "Terms of Service",
modifier = Modifier.clickable { },
color = Color.Blue
)
}
Row(
modifier = Modifier.offset(y = (-20).dp)
){
Text(text = "and ")
Text(
text = "Privacy Policy",
modifier = Modifier
.clickable { },
color = Color.Blue
)
}

You can use an AnnotatedString and the UriHandler to open the url.
Something like:
val annotatedString = buildAnnotatedString {
append("By signing up, you agree with the ")
pushStringAnnotation(tag = "terms", annotation = "https://....")
withStyle(style = SpanStyle(color = Blue)) {
append("Terms of Service")
}
pop()
append(" and ")
pushStringAnnotation(tag = "policy", annotation = "https://....")
withStyle(style = SpanStyle(color = Blue)) {
append("Privacy Policy")
}
pop()
}
val uriHandler = LocalUriHandler.current
ClickableText(
text = annotatedString,
onClick = { offset ->
annotatedString.getStringAnnotations(tag = "terms", start = offset, end = offset).firstOrNull()?.let {
uriHandler.openUri(it.item)
}
annotatedString.getStringAnnotations(tag = "policy", start = offset, end = offset).firstOrNull()?.let {
uriHandler.openUri(it.item)
}
})

Related

How to Updating jetpack compose with MutableLiveData.observeAsState() more often

fun Conversation(mainViewModel: MainViewModel) {
var stat = mainViewModel.getTweet().observeAsState()
val listState = rememberScrollState()
stat?.let {
if( stat.value == null){
Log.d("DAD","DASSA")
// null 값이면 로딩 에니메이션 뜨게 만들면 좋을듯>????
}
else{
Log.d("DAD","됐다")
var author = it.value!!.first
var messages = it.value!!.second
LazyColumn(modifier = Modifier.verticalScroll(listState).height(100.dp)) {
items(messages) { message ->
MessageCard(author, message)
}
}
}
}
}
In this composable, it seems like the composable "DID NOT" update value of mutableLivedata( mainViewModel.getTweet()). but I confuse about below code
fun InfoCard(mainViewModel: MainViewModel){
val Name = mainViewModel.getData().observeAsState()
Name.value?.let { it ->
Row(modifier = Modifier
.fillMaxWidth()
.background(shape = RoundedCornerShape(6.dp), color = Color.LightGray)
.padding(30.dp)
.height(40.dp),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.height(15.dp))
Text(it.get(0), modifier = Modifier.width(200.dp), textAlign = TextAlign.Center)
Spacer(modifier = Modifier.width(30.dp))
Text(text = it.get(1))
}
}
}
it also composable and use value of mutableLivedata( mainViewModel.getData())
But IT UPDATE VERY WELL..
what is a difference and How can I update first composable..
Self solution for someone like me.
Change
val Name = mainViewModel.getData().observeAsState()
to
val Name by mainViewModel.getData().observeAsState()

Jetpack Compose Text(color = Color.***) Change color

I have 2 Radio Buttons to change the text color of a text (red text, hardcoded).
But i cant get the Text(color = Color.colorsTextRadio) to work.
I know it says it is a string but who do i get the Red or Green to convert to color.
If i have done something that could be better in thecode please tell because I'm a beginner.
#Composable
fun MainScreen() {
/**
* Text
*/
var text by remember {
mutableStateOf("test")
}
// Event handler
val onTextChange = { value: String ->
text = value
}
/**
* Colors
*/
val colors = listOf("Red", "Green")
var colorsTextRadio by remember {
mutableStateOf(colors[0])
}
// Event Handler
val onTextColorChange = { value: String ->
colorsTextRadio = value
}
Log.d("TAG", "MainScreen: colorsTextRadio $colorsTextRadio")
Column(modifier = Modifier.padding(6.dp)) {
TextField(value = text, onValueChange = onTextChange)
Text(text = text.replace("\n", " "), maxLines = 1, color = Color.Red)
RadioButtonGroup(colors = colors, colorsTextRadio = colorsTextRadio, onClick = onTextColorChange)
}
}
#Composable
fun RadioButtonGroup(
colors: List<String>,
colorsTextRadio: String,
onClick: (String) -> Unit
) {
Column(modifier = Modifier.selectableGroup()) {
colors.forEach { label ->
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.selectable(
selected = (colorsTextRadio == label),
onClick = { onClick.invoke(label) },
role = Role.RadioButton
)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
modifier = Modifier.padding(end = 16.dp),
selected = (colorsTextRadio == label),
onClick = null // null recommended for accessibility with screen readers
)
Text(text = label)
}
}
}
}
You can define a data class:
data class ColorLabel(
val label: String,
val color: Color
)
val colors = listOf(
ColorLabel("Red",Red),
ColorLabel("Green", Green))
var colorsTextRadio by remember { mutableStateOf(colors[0].label) }
var colorsText by remember { mutableStateOf(colors[0].color) }
Then apply them to your Composables:
Text(text = text.replace("\n", " "),
maxLines = 1,
color = colorsText)
Column(modifier = Modifier.selectableGroup()) {
colors.forEach { colorlabel->
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.selectable(
selected = (colorsTextRadio == colorlabel.label),
onClick = {
colorsTextRadio = colorlabel.label
colorsText = colorlabel.color
},
role = Role.RadioButton
)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
modifier = Modifier.padding(end = 16.dp),
selected = (colorsTextRadio == colorlabel.label),
onClick = null // null recommended for accessibility with screen readers
)
Text(text = colorlabel.label)
}
}
}

Compose TextField with blinking cursor and without the system's keyboard

Is it possible to use the BasicTextField without the system's keyboard showing when it is on focus (I have my own custom keyboard onscreen)?
I tried setting the readOnly = true. The system's keyboard is not shown, but the blinking cursor isn't either.
The key to achieving this is to use LocalSoftwareKeyboardController and FocusRequester in combination.
First, focus on the TextField using FocusRequester and then hide the soft keyboard.
val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember {
FocusRequester()
}
LaunchedEffect(
key1 = Unit,
) {
focusRequester.requestFocus()
keyboardController?.hide()
}
Sample code
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun FocusedTextFieldWithoutKeyboard() {
val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember {
FocusRequester()
}
LaunchedEffect(
key1 = Unit,
) {
focusRequester.requestFocus()
keyboardController?.hide()
}
val initialText = "Sample Text"
var text by remember {
mutableStateOf(
value = TextFieldValue(
text = "Sample Text",
selection = TextRange(
start = initialText.length,
end = initialText.length,
),
),
)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box {
BasicTextField(
value = text,
onValueChange = { value: TextFieldValue ->
text = value
},
modifier = Modifier
.focusRequester(focusRequester),
singleLine = true,
)
Box(
modifier = Modifier
.matchParentSize()
.alpha(0f)
.clickable(
onClick = {},
),
)
}
Row {
Button(
onClick = {
text = text.copy(
text = "${text.text}1",
selection = TextRange(
start = text.text.length + 1,
end = text.text.length + 1,
),
)
},
modifier = Modifier.padding(horizontal = 8.dp),
) {
Text(text = "1")
}
Button(
onClick = {
text = text.copy(
text = "${text.text}2",
selection = TextRange(
start = text.text.length + 1,
end = text.text.length + 1,
),
)
},
modifier = Modifier.padding(horizontal = 8.dp),
) {
Text(text = "2")
}
Button(
onClick = {
text = text.copy(
text = "${text.text}3",
selection = TextRange(
start = text.text.length + 1,
end = text.text.length + 1,
),
)
},
modifier = Modifier.padding(horizontal = 8.dp),
) {
Text(text = "3")
}
}
}
}

Jetpack compose single number text field

I am trying to create a phone verification screen where the user must enter 5 numbers each in their own text field like below.
I have two questions:
Is there a way to limit a TextField to 1 character. I can set single line and max lines, but don't see a way to limit to character length like 'Ms' from the view system. I can easily limit the character length in code by ignoring characters after the first one, but this still lets the user 'scroll' to the left and right even though there is only 1 character.
Is there a way to wrap the width to the 1 character? Currently the only way I have found to limit the width is to set it specifically, but then if the system text size is changed it could break.
Here is some code incase it helps, this is some very jumbled together solution so apologies if something is incorrect:
#Composable
fun CodeTextFields(
modifier: Modifier = Modifier,
length: Int = 5,
onFilled: (code: String) -> Unit
) {
var code: List<Char> by remember {
mutableStateOf(listOf())
}
val focusRequesters: List<FocusRequester> = remember {
val temp = mutableListOf<FocusRequester>()
repeat(length) {
temp.add(FocusRequester())
}
temp
}
Row(modifier = modifier) {
(0 until length).forEach { index ->
OutlinedTextField(
modifier = Modifier
.weight(1f)
.padding(vertical = 2.dp)
.focusRequester(focusRequesters[index]),
textStyle = MaterialTheme.typography.h4.copy(textAlign = TextAlign.Center),
singleLine = true,
value = code.getOrNull(index)?.takeIf { it.isDigit() }?.toString() ?: "",
onValueChange = { value: String ->
if (focusRequesters[index].freeFocus()) { //For some reason this fixes the issue of focusrequestor causing on value changed to call twice
val temp = code.toMutableList()
if (value == "") {
if (temp.size > index) {
temp.removeAt(index)
code = temp
focusRequesters.getOrNull(index - 1)?.requestFocus()
}
} else {
if (code.size > index) {
temp[index] = value.getOrNull(0) ?: ' '
} else if (value.getOrNull(0)?.isDigit() == true) {
temp.add(value.getOrNull(0) ?: ' ')
code = temp
focusRequesters.getOrNull(index + 1)?.requestFocus() ?: onFilled(
code.joinToString(separator = "")
)
}
}
}
},
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
)
Spacer(modifier = Modifier.width(16.dp))
}
}
}
To solve this usecase you can use decoration box in BasicTextfield.
#Composable
fun InputField(
modifier: Modifier = Modifier,
text: String = "",
length: Int = 5,
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
onFocusChanged: () -> Unit = {},
onTextChange: (String) -> Unit
) {
val BoxHeight = 40.dp
val BoxWidth = 40.dp
var textValue by remember { mutableStateOf(text) }
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
var isFocused by remember { mutableStateOf(false) }
if (text.length == length) {
textValue = text
}
BasicTextField(
modifier = modifier
.focusRequester(focusRequester)
.onFocusChanged {
isFocused = it.isFocused
onFocusChanged(it)
},
value = textValue,
singleLine = true,
onValueChange = {
textValue = it
if (it.length <= length) {
onTextChange.invoke(it)
}
},
enabled = enabled,
keyboardOptions = keyboardOptions,
decorationBox = {
Row(Modifier.fillMaxWidth()) {
repeat(length) { index ->
Text(
text = textValue,
modifier = Modifier
.size(
width =
BoxWidth,
height = BoxHeight
)
.clip(RoundedCornerShape(4.dp))
,
)
Spacer(modifier = Modifier.width(8.dp))
}
}
}
)
if (acquireFocus && textValue.length != length) {
focusRequester.requestFocus()
}
}
This will create a boxes as shown below length number of times(5 here). This is just a simple TextField with decoration as multiple boxes. You can use this text in viewModel.
TextField Examples: https://developer.android.com/jetpack/compose/text
To limit to 1 number you can use something like:
#Composable
fun Field (modifier: Modifier = Modifier,
onValueChange: (String, String) -> String = { _, new -> new }){
val state = rememberSaveable { mutableStateOf("") }
OutlinedTextField(
modifier = modifier.requiredWidth(75.dp),
singleLine = true,
value = state.value,
onValueChange = {
val value = onValueChange(state.value, it)
state.value = value
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next),
)
}
and then use:
Field(onValueChange = { old, new ->
if (new.length > 1 || new.any { !it.isDigit() }) old else new
})
set your keyboard option number password type see the below code
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
below the keyboard output
It might be late but hopefully, this will help someone.
I came here to find a solution but found #Nikhil code which gave me an idea of how to do it (But failed to do it). So based on his answer I have improved the composable and fixed the issues. Have not heavily tested it yet but it acts the same as you want:
#Composable
fun DecoratedTextField(
value: String,
length: Int,
modifier: Modifier = Modifier,
boxWidth: Dp = 38.dp,
boxHeight: Dp = 38.dp,
enabled: Boolean = true,
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
keyboardActions: KeyboardActions = KeyboardActions(),
onValueChange: (String) -> Unit,
) {
val spaceBetweenBoxes = 8.dp
BasicTextField(modifier = modifier,
value = value,
singleLine = true,
onValueChange = {
if (it.length <= length) {
onValueChange(it)
}
},
enabled = enabled,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
decorationBox = {
Row(
Modifier.size(width = (boxWidth + spaceBetweenBoxes) * length, height = boxHeight),
horizontalArrangement = Arrangement.spacedBy(spaceBetweenBoxes),
) {
repeat(length) { index ->
Box(
modifier = Modifier
.size(boxWidth, boxHeight)
.border(
1.dp,
color = MaterialTheme.colors.primary,
shape = RoundedCornerShape(4.dp)
),
contentAlignment = Alignment.Center
) {
Text(
text = value.getOrNull(index)?.toString() ?: "",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h6
)
}
}
}
})
}
Here is what it looks like:
You can customize the size of the font + the border color to your liking.
The only drawback is that you don't have a cursor and cannot edit each box separately. Will try to fix these issues and if I do, I will update my answer

How to manage Focus state in Jetpack's Compose

I have a custom composable View (Surface + Text essentially) and I want to change the color of the surface depending on the focus state. The FocusManager#FocusNode is marked internal and I am unaware of any way to achieve this. Is this simply just not available yet? Any one else have to tackle this?
With 1.0.x you can use the Modifier.onFocusChanged to observe focus state events.
Something like:
var color by remember { mutableStateOf(Black) }
val focusRequester = FocusRequester()
Text(
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged { color = if (it.isFocused) Green else Black }
.focusModifier()
.pointerInput(Unit) { detectTapGestures { focusRequester.requestFocus() } },
text = "Text",
color = color
)
As of dev11, FocusManagerAmbient has been deprecated in favor of FocusModifier. For more examples check out the KeyInputDemo, ComposeInputFieldFocusTransition or FocusableDemo.
#Composable
private fun FocusableText(text: MutableState<String>) {
val focusModifier = FocusModifier()
Text(
modifier = focusModifier
.tapGestureFilter { focusModifier.requestFocus() }
.keyInputFilter { it.value?.let { text.value += it; true } ?: false },
text = text.value,
color = when (focusModifier.focusState) {
Focused -> Color.Green
NotFocused -> Color.Black
NotFocusable -> Color.Gray
}
)
}
Compose has since updated and made the FocusManager members public; although, I'm not exactly sure how final the api is as of dev10. But as of now you can create a FocusNode and listen for changes using FocusManager.registerObserver.
val focusNode = remember {
FocusNode().apply {
focusManager.registerObserver(node = this) { fromNode, toNode ->
if (toNode == this) {
// has focus
} else {
// lost focus
}
}
}
}
If you'd like to gain focus, you can now call FocusManager.requestFocus:
val focusManager = FocusManagerAmbient.current
focusManager.requestFocus(focusNode)
You can also set a focusIdentifier on your FocusNode:
val focusNode = remember {
FocusNode().apply {
...
focusManager.registerFocusNode("your-focus-identifier", node = this)
}
}
To gain focus for a specific identifier, you just call FocusManager.requestFocusById
Using that you can easily create a Composable that can provide and request focus for you, for instance:
#Composable
fun useFocus(focusIdentifier: String? = null): Pair<Boolean, () -> Unit> {
val focusManager = FocusManagerAmbient.current
val (hasFocus, setHasFocus) = state { false }
val focusNode = remember {
FocusNode().apply {
focusManager.registerObserver(node = this) { fromNode, toNode ->
setHasFocus(toNode == this)
}
focusIdentifier?.let { identifier ->
focusManager.registerFocusNode(identifier, node = this)
}
}
}
onDispose {
focusIdentifier?.let { identifier ->
focusManager.unregisterFocusNode(identifier)
}
}
return hasFocus to {
focusManager.requestFocus(focusNode)
}
}
val (hasFocus, requestFocus) = useFocus("your-focus-identifier")
You could also compose children along with it:
#Composable
fun FocusableTextButton(
text: String,
focusedColor: Color = Color.Unset,
unFocusedColor: Color = Color.Unset,
textColor: Color = Color.White,
focusIdentifier: String? = null
) {
val (hasFocus, requestFocus) = useFocus(focusIdentifier)
Surface(color = if (hasFocus) focusedColor else unFocusedColor) {
TextButton(onClick = requestFocus) {
Text(text = text, color = textColor)
}
}
}
Alternatively, there's also FocusModifier, which as of now is:
/**
* A [Modifier.Element] that wraps makes the modifiers on the right into a Focusable. Use a
* different instance of [FocusModifier] for each focusable component.
*
* TODO(b/152528891): Write tests for [FocusModifier] after we finalize on the api (API
* review tracked by b/152529882).
*/
But I don't think you can apply an identifier with it right now.
val focusModifier = FocusModifier()
val hasFocus = focusModifier.focusDetailedState == FocusDetailedState.Active
Surface(
modifier = focusModifier,
color = if (hasFocus) focusedColor else unFocusedColor
) {
TextButton(onClick = { focusModifier.requestFocus() }) {
Text(text = text, color = textColor)
}
}
All that being said, I'm not 100% sure this is the intended way to handle focus right now. I referenced CoreTextField a lot to see how it was being handled there.
Example:
#Composable
fun FocusTest() {
val focusManager = FocusManagerAmbient.current
val selectRandomIdentifier: () -> Unit = {
focusManager.requestFocusById(arrayOf("red,", "blue", "green", "yellow").random())
}
Column(verticalArrangement = Arrangement.SpaceBetween) {
FocusableTextButton(
text = "When I gain focus, I turn red",
focusedColor = Color.Red,
focusIdentifier = "red"
)
FocusableTextButton(
text = "When I gain focus, I turn blue",
focusedColor = Color.Blue,
focusIdentifier = "blue"
)
FocusableTextButton(
text = "When I gain focus, I turn green",
focusedColor = Color.Green,
focusIdentifier = "green"
)
FocusableTextButton(
text = "When I gain focus, I turn yellow",
focusedColor = Color.Yellow,
focusIdentifier = "yellow"
)
Button(onClick = selectRandomIdentifier) {
Text(text = "Click me to randomly select a node to focus")
}
}
}
this code works for me
var haveFocus by remember { mutableStateOf(false) }
Surface(
modifier = Modifier.onFocusEvent {
haveFocus = it.isFocused
},
shape = RectangleShape
){...}
This answer is base on Gabriele Mariotti answer but I have change
Use remember { FocusRequester() } to prevent crash FocusRequester is not initialized after do 2nd click on Text
Use focusTarget instead of focusModifier because deprecated
.
var color by remember { mutableStateOf(Color.Black) }
val focusRequester = remember { FocusRequester() }
Text(
text = "Hello",
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged { color = if (it.isFocused) Color.Green else Color.Black }
.focusTarget()
.pointerInput(Unit) { detectTapGestures { focusRequester.requestFocus() } }
)

Categories

Resources