Kotlin how to write two conditionals inside one modifier - kotlin

How to write two conditionals under one Modifier? For example for .background i have one conditional that handles pressed state and other is just for setting background color depending of passed variants.
If code is written with two .background modifier then isPressed part is not working.
Box(
Modifier
.pointerInput(Unit) {
detectTapGestures(
onPress = {
try {
isPressed = true
awaitRelease()
} finally {
isPressed = false
}
},
)
}
.background(if (!isPressed) Red else DarkRed)
.background(if (variant == VariantButtons.Primary) Red else if (variant == VariantButtons.Green) Green else Color.Transparent)
)

If I understand your question correctly you can use only one background modifier and combine statements inside it, just like below:
.background(
if (isPressed) {
DarkRed
} else {
when (variant) {
VariantButtons.Primary -> Red
VariantButtons.Green -> Green
else -> Color.Transparent
}
}
)

Related

Can I change the value of a component from a separate button in Compose Multiplatform?

I am trying to make a desktop application that allows you to search through a number of predefined locations stored in Kotlin classes in a separate directory. To accomplish this, I've used the reflections and compose-jb libraries.
The problem I've run into is that I can't figure out how to update a Column of Boxes (located in another Box component) to change when I press the search button after entering tags that I want to search by.
My code is below (for the Main.kt file) that describes the entire desktop application.
val reflections = Reflections("io.github.mobomega.project.attractions")
var display = mutableSetOf<Attraction>()
fun main() = application {
val stateVertical = rememberScrollState(0)
val stateHorizontal = rememberScrollState(0)
var state = Box(
modifier = Modifier
.fillMaxSize()
.verticalScroll(stateVertical)
.padding(end = 12.dp, bottom = 12.dp)
.horizontalScroll(stateHorizontal)
)
Window(
onCloseRequest = ::exitApplication,
title = "Search",
state = rememberWindowState(width = 2256.dp, height = 1504.dp)
) {
val count = remember { mutableStateOf(1) }
MaterialTheme {
Column {
val text = remember { mutableStateOf("") }
OutlinedTextField(
value = text.value,
singleLine = true,
onValueChange = { text.value = it },
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Row (modifier = Modifier.size(2256.dp, 50.dp), horizontalArrangement = Arrangement.Center) {
Button(modifier = Modifier.align(Alignment.Top),
onClick = {
val tags = text.value.split(", ", ",")
for (tag in tags) {
search(tag.lowercase())
println("$display have tag $tag")
}
// Setting the new value of the Box
state = create(stateVertical, stateHorizontal)
// Creates error:
// "#Composable invocations can only happen from the context of a #Composable function"
}) {
Text("Search")
}
Button (modifier = Modifier.align(Alignment.Top),
onClick = {
display.clear()
}) {
Text("Reset")
}
}
Row (horizontalArrangement = Arrangement.Center) {
Box(
modifier = Modifier.fillMaxSize()
.background(color = Color(red = 0xFF, green = 0xFF, blue = 0xFF))
.padding(10.dp)
) {
state // Creating the state Box component in the Row
VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd)
.fillMaxHeight(),
adapter = rememberScrollbarAdapter(stateVertical)
)
HorizontalScrollbar(
modifier = Modifier.align(Alignment.BottomStart)
.fillMaxWidth()
.padding(end = 12.dp),
adapter = rememberScrollbarAdapter(stateHorizontal)
)
}
}
}
}
}
}
#Composable
fun textBox(text: String = "Item") {
Box(
modifier = Modifier.height(32.dp)
.width(400.dp)
.background(color = Color(200, 0, 0, 20))
.padding(start = 10.dp),
contentAlignment = Alignment.CenterStart
) {
Text(text = text)
}
}
#Composable
fun create(stateVertical: ScrollState, stateHorizontal: ScrollState) = Box(
modifier = Modifier
.fillMaxSize()
.verticalScroll(stateVertical)
.padding(end = 12.dp, bottom = 12.dp)
.horizontalScroll(stateHorizontal)
) {
Column {
var x = 0
for (attr in display) {
x++
textBox(attr.name)
if (x < display.size) {
Spacer(modifier = Modifier.height(5.dp).align(Alignment.CenterHorizontally))
}
}
}
}
fun search(text: String) {
for (attr in reflections.getSubTypesOf(Attraction::class.java)) {
val temp = attr.getConstructor().newInstance()
println("${temp.name} has tags ${temp.tags}")
if (temp.matches(text) && (temp !in display)) {
display += temp
}
}
}
I have tried to update the value of the Box that contains all of the items that match any of the search criteria, but I have run into a number of issues, such as the "onClick" function in which I set the new value of the "state" variable (storing all of the matching items) not being a Composable function, and therefore I can't change the value.
How would I accomplish changing the value of a Component such as a Box from another Component, such as a Button?
In Compose you can't create a view like you're doing with state variable. Result of your call is just Unit, and when you later call it you should see a warning "The expression is unused". The view is added at the tree hierarchy at the moment your variable is created.
To solve your problem you need to declare display as a mutable state - it's a new thing made especially for Compose, which allows triggering recomposition when this state changes:
val display by mutableStateOf<Attraction>(setOf())
And then update like this in your search:
val mutableDisplay = mutableSetOf<Attraction>()
// for
// ...
mutableDisplay += temp
// ...
display = mutableDisplay
Note that you can't use mutable set inside your mutable state, as mutable state won't be able to track changes of this set.
To learn more about state in Compose I suggest you checking this youtube video which explains the basic principles, and Compose mental model for better understanding of how to work with it.

Jetpack Compose Canvas drawPath doesn't work

I have this composable that is supposed to allow drawing with the pointer to a canvas but when I move the pointer nothing happens
#ExperimentalComposeUiApi
#Composable
fun CanvasDraw() {
val path = remember { Path() }
Canvas(modifier = Modifier
.fillMaxSize()
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
path.moveTo(it.x, it.y)
}
MotionEvent.ACTION_MOVE -> {
path.lineTo(it.x, it.y)
}
else -> false
}
true
}) {
drawPath(
path = path,
color = Color.Blue,
style = Stroke(10f)
)
}
}
remember { Path() } is caching lambda content value for next recompositions, but it cannot trigger recomposition when content on this object is changed. To make some state, which will trigger recomposition on changes in Compose, you need to use a mutable state of some kind - it's a new thing made for Compose.
You can find more info about state in Compose in documentation, including this youtube video which explains the basic principles.
Storing points inside Path will not be clean, as it's not a basic type. Instead I'm using a mutable state list of points, like this:
val points = remember { mutableStateListOf<Offset>() }
Canvas(modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectDragGestures { change, _ ->
points.add(change.position)
}
}
) {
drawPath(
path = Path().apply {
points.forEachIndexed { i, point ->
if (i == 0) {
moveTo(point.x, point.y)
} else {
lineTo(point.x, point.y)
}
}
},
color = Color.Blue,
style = Stroke(10f)
)
}

Should I use remember with animateDpAsState?

The Code A is from the official sample code here.
I know that in order to preserve state across recompositions, remember the mutable state using remember.
I think that the code val extraPadding by animateDpAsState(...) should be val extraPadding by remember { animateDpAsState(...) }, is it right?
BTW, val extraPadding by remember { animateDpAsState(...) } will cause the error
Composable calls are not allowed inside the calculation parameter of inline fun remember(calculation: () -> TypeVariable(T)): TypeVariable(T)
Code A
#Composable
private fun Greeting(name: String) {
var expanded by remember { mutableStateOf(false) }
val extraPadding by animateDpAsState( //Should I add remember
if (expanded) 48.dp else 0.dp
)
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello, ")
Text(text = name)
}
OutlinedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
No, you shouldn't. This function is marked with #Composable so it should be used directly in the view builder.
animateDpAsState will calculate its value depending on targetValue on each recomposition.
If you check it source code you'll see, that it uses remember inside, that's why it's marked with #Composable, and that's why you shouldn't bother about remembering some values manually.
For anyone that comes across this in the future, instead of attempting to remember the animateDpAsState it would be better to remember the expansion state. Because in this case we're trying to remember the state in an item of a list we use rememberSaveable:
var expanded by rememberSaveable { mutableStateOf(false) }
A very similar case of storing indexes across list items is shown in the official documentation here

Problem with Kotlin compose for desktop Window

I'm trying to make a chess engine desktop app with an ui component to it.
The game() method that I mention is a simple while loop that allows me to ask for moves in algebraic notation and make the moves if they are valid ones.
I have the following main and my problem is that if I uncomment and run the game() method inside the classe it wont start the App window, and if I instead try to uncomment the same method but from outside the Window it still wont start the desktop App.
On the other hand if i run it has it is, it will start the UI window.
fun main() = application {
resetBoard()
printBoardSmall()
Window(onCloseRequest = ::exitApplication, icon = painterResource("black_knight.png"), title = "Chess") {
ui()
//game()
}
//game()
}
#Composable
fun ui() {
var squarePair = false
Row {
Column {
for (n in 8 downTo 1) {
Row {
Text(
"" + n,
textAlign = TextAlign.Center,
modifier = Modifier.width(SIZE_TILE),
fontSize = FONT_SIZE_BOARD,
fontWeight = FontWeight.Bold
)
squarePair = boardLines(n, squarePair)
}
}
Row {
Text(" ", textAlign = TextAlign.Center, modifier = Modifier.width(SIZE_TILE))
for (n in 0..7) {
Text(
"" + ('A' + n),
textAlign = TextAlign.Center,
modifier = Modifier.width(SIZE_TILE),
fontSize = FONT_SIZE_BOARD,
fontWeight = FontWeight.Bold
)
}
}
}
Column {
Text(" Play", textAlign = TextAlign.Center, fontSize = 30.sp)
var move = ""
//var move by remember { mutableStateOf("") }
TextField(
value = move,
onValueChange = { move = it },
label = { Text("Move") },
maxLines = 1,
textStyle = TextStyle(color = Color.Black, fontWeight = FontWeight.Bold),
modifier = Modifier.padding(20.dp)
)
print(move)
}
}
}
#Composable
fun board(n: Int, i: Int){
var team = ""
if(utils.isWhite(BOARD[n-1][i-1])) team = TEAM[0]
if(utils.isBlack(BOARD[n-1][i-1])) team = TEAM[1]
for(k in LOWER_CASE_LETTERS.indices) {
if (BOARD[n-1][i-1] == LOWER_CASE_LETTERS[k] || BOARD[n-1][i-1] == UPPER_CASE_LETTERS[k]) {
Image(painter = painterResource(team + "_" + PIECES[k] + ".png"), contentDescription = PIECES[k])
}
}
}
Im new to compose and I can't figure out whats the problem, expecially since im trying to run the method outside the Window
I'm no expert, but I'll try my best to help.
It doesn't work, when you call it in the Window, 'cause that's what it uses to init it's layout. So the Window won't show, 'till the game() stops.
I don't know, why it doesn't work, when you put it after the Window. Looking at fun main() = application { ... } I suspect, that initiating the application is similar to initiating the Window, so it also won't start, untill the game() is over.
You should try putting game() in a seperate thread. Something like this should work:
fun() main = application {
resetBoard()
printBoardSmall()
Window( ... ){
ui()
}
Thread {
game()
}.start()
}
P.S. I'm also very new to compose. And kinda new to programming. And really, really sorry for all the gross oversimplifications I made.
P.P.S. You could add a TextField to your app and use Button's onClick parameter to call a move checking function. Wouldn't need to play with Threads and coroutines then. And you could also use Buttons for the board squares, cause you can assign them a background and an image. If you would need help with any of that - HMU.

Jetbrains Compose Desktop Elements Overlap

I am writing a compose desktop app.
I have a main window:
Window(size = IntSize(600, 600)) {
// snip logic
Column(horizontalAlignment = Alignment.CenterHorizontally) {
ChessBoard(board.value, modifier = Modifier.fillMaxWidth(0.8f))
Row(modifier = Modifier
.fillMaxSize(), horizontalArrangement = Arrangement.SpaceEvenly) {
Button(onClick = previousBoard) {
Text("<")
}
Button(onClick = nextBoard) {
Text(">")
}
}
}
}
Where ChessBoardis defined as
#Composable
fun ChessBoard(board: Board, modifier: Modifier = Modifier) {
Canvas(modifier = modifier) {
// snip logic, I checked that it does not influence the result
}
}
The chessboard takes up the correct amount of space, but the buttons overlap and aren't added at the bottom as expected.
I tried tweaking the modifiers on ChessBoard, but that did not change the fact that the buttons are at the top.
On the Row, remove the Modifier.fillMaxSize()
I think what you need is Modifier.fillMaxWidth()
The Row is taking up the entire space of the screen, and the buttons inside the row, by default stay at the top of the row. To check that this is the case, try adding a Modifier.align(CenterVerticlly) to the row, and the buttons will move to the center of the screen, since the row is still occupying the whole screen. Removing the Modifier.fillMaxSize() should do it, but if you still want to keep it for other Composables, use Modifier.align(Alignment) to fix it