Skip to content

Commit

Permalink
feat: add support for intermediates/variables (closes #60)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bnyro committed Nov 20, 2024
1 parent 264158b commit c2242b4
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class CompiledExpression internal constructor(
fun execute(): Double? =
Evaluator.execute(expression = this)

fun execute(constant: Pair<String, Double>): Double? =
Evaluator.execute(this, constant)
fun execute(constants: List<Pair<String, Double>>): Double? =
Evaluator.execute(this, constants)

fun setConstant(name: String, value: Double) {
configuration.setConstant(name = name, value = value)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.youapps.calcyou.data.graphing

data class Constant(
val identifier: Char,
val value: Double
)
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ object Defaults {
val defaultVarNameChars: Set<Char> =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$".toCharArray().toSet()
val defaultFuncNameChars: List<Char> = "fghijklmnopqrstuvw".toList()
val defaultConstantNameChars: List<Char> = "abcdeyz".toList()

fun getDefaultGenericFunctions(): Map<String, EvalFunctionBlock> {
return mapOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,11 @@ object Evaluator {
fun execute(expression: CompiledExpression): Double? =
evaluateToken(token = expression.root, configuration = expression.configuration)

fun execute(expression: CompiledExpression, constant: Pair<String, Double>): Double? {
configuration.setConstant(constant.first, constant.second)
fun execute(expression: CompiledExpression, constants: List<Pair<String, Double>>): Double? {
configuration.clearConstants()
for (constant in constants) {
configuration.setConstant(constant.first, constant.second)
}
return evaluateToken(token = expression.root, configuration = configuration)
}

Expand Down
15 changes: 10 additions & 5 deletions app/src/main/java/net/youapps/calcyou/data/graphing/Function.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@ data class Function(
val expression: String,
val color: Color,
val name: String,
val function: (Float) -> Float?
val compiled: CompiledExpression
) {
fun execute(variableX: Float, constants: List<Constant>): Float? {
val variables = listOf(
"x" to variableX.toDouble(),
*constants.map { it.identifier.toString() to it.value }.toTypedArray()
)
return compiled.execute(variables)?.toFloat()
}

companion object {
fun create(expression: String, color: Color, functionName: String): Function {
val compiled: CompiledExpression = Evaluator.compile(expression)
return Function(
expression, color, functionName
) { value ->
compiled.execute("x" to value.toDouble())?.toFloat()
}
expression, color, functionName, compiled
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package net.youapps.calcyou.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import net.youapps.calcyou.R
import net.youapps.calcyou.data.graphing.Constant

@Composable
fun AddNewConstantDialog(
modifier: Modifier = Modifier,
onConfirm: (constant: Constant) -> Unit,
onCancel: () -> Unit,
initialConstant: Constant,
) {
Dialog(onCancel) {
Box(
modifier = modifier
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp))
) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
Text(
text = stringResource(R.string.add_constant),
style = MaterialTheme.typography.titleLarge
)
var text by remember { mutableStateOf(initialConstant.value.toString()) }
var isError by remember { mutableStateOf(false) }
OutlinedTextField(
value = text,
onValueChange = {
text = it
isError = strToDouble(it) == null
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
prefix = {
Text(
text = "${initialConstant.identifier} = ", style = TextStyle(
fontFamily = FontFamily.Serif,
fontWeight = FontWeight.Medium,
fontSize = MaterialTheme.typography.bodyLarge.fontSize,
fontStyle = FontStyle.Italic
)
)
},
textStyle = TextStyle(
fontFamily = FontFamily.Serif,
fontWeight = FontWeight.Medium,
fontSize = MaterialTheme.typography.bodyLarge.fontSize,
fontStyle = FontStyle.Italic
)
)
Row(Modifier.align(Alignment.End)) {
TextButton(onClick = onCancel) {
Text(text = stringResource(id = android.R.string.cancel))
}
TextButton(onClick = {
val value = strToDouble(text) ?: return@TextButton
onConfirm(initialConstant.copy(value = value))
}, enabled = !isError) {
Text(text = stringResource(id = android.R.string.ok))
}
}
}
}
}
}

private fun strToDouble(string: String): Double? {
return string.replace(",", ".").toDoubleOrNull()
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package net.youapps.calcyou.ui.screens.graphing

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
Expand All @@ -19,6 +22,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.material3.rememberStandardBottomSheetState
Expand All @@ -39,15 +43,18 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import net.youapps.calcyou.R
import net.youapps.calcyou.data.graphing.Constant
import net.youapps.calcyou.data.graphing.Defaults
import net.youapps.calcyou.data.graphing.Function
import net.youapps.calcyou.ui.components.AddNewConstantDialog
import net.youapps.calcyou.ui.components.AddNewFunctionDialog
import net.youapps.calcyou.viewmodels.GraphViewModel

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GraphingScreen(graphViewModel: GraphViewModel = viewModel()) {
var showAddFunctionDialog by remember { mutableStateOf(false) }
var showAddConstantDialog by remember { mutableStateOf(false) }
val bottomSheetState = rememberStandardBottomSheetState(
skipHiddenState = true
)
Expand All @@ -63,23 +70,41 @@ fun GraphingScreen(graphViewModel: GraphViewModel = viewModel()) {
.fillMaxWidth()
.padding(8.dp)
) {
FunctionList(
FunctionConstantsList(
modifier = Modifier.padding(top = 16.dp, start = 8.dp, end = 8.dp),
functions = graphViewModel.functions,
constants = graphViewModel.constants,
onClickFunction = {
graphViewModel.updateSelectedFunction(it)
showAddFunctionDialog = true
},
onClickRemove = {
onClickRemoveFunction = {
graphViewModel.removeFunction(it)
})
Button(
modifier = Modifier.align(Alignment.CenterHorizontally),
onClick = {
graphViewModel.updateSelectedFunction(-1)
showAddFunctionDialog = true
}) {
Text(text = stringResource(R.string.add_new_function))
},
onClickConstant = {
graphViewModel.selectedConstantIndex = it
showAddConstantDialog = true
},
onClickRemoveConstant = {
graphViewModel.constants.removeAt(it)
}
)
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
AnimatedVisibility(graphViewModel.constants.size < Defaults.defaultConstantNameChars.size) {
OutlinedButton(onClick = {
showAddConstantDialog = true
}) {
Text(text = stringResource(R.string.add_constant))
}
}
Spacer(modifier = Modifier.width(10.dp))
Button(
onClick = {
graphViewModel.updateSelectedFunction(-1)
showAddFunctionDialog = true
}) {
Text(text = stringResource(R.string.add_new_function))
}
}
}
}) {
Expand All @@ -99,38 +124,76 @@ fun GraphingScreen(graphViewModel: GraphViewModel = viewModel()) {
remember(graphViewModel.selectedFunctionIndex) { graphViewModel.expression },
)
}
if (showAddConstantDialog) {
AddNewConstantDialog(
onConfirm = { constant ->
if (graphViewModel.selectedConstantIndex != -1) {
graphViewModel.constants[graphViewModel.selectedConstantIndex] = constant
} else {
graphViewModel.constants.add(constant)
}

graphViewModel.selectedConstantIndex = -1
showAddConstantDialog = false
},
onCancel = {
graphViewModel.selectedConstantIndex = -1
showAddConstantDialog = false
},
initialConstant = graphViewModel.constants.getOrNull(graphViewModel.selectedConstantIndex)
?: Constant(
Defaults.defaultConstantNameChars.first { identifier ->
graphViewModel.constants.none { it.identifier == identifier }
},
0.0
)
)
}
}

@Composable
fun FunctionList(
fun FunctionConstantsList(
modifier: Modifier = Modifier,
functions: List<Function>,
constants: List<Constant>,
onClickFunction: (Int) -> Unit,
onClickRemove: (Int) -> Unit
onClickRemoveFunction: (Int) -> Unit,
onClickConstant: (Int) -> Unit,
onClickRemoveConstant: (Int) -> Unit
) {
LazyColumn(modifier = modifier) {
itemsIndexed(
functions,
key = { index, function -> "$index-${function.hashCode()}" }) { index, function ->
FunctionRow(
functionName = function.name,
lhsName = "${function.name}(x)",
text = function.expression,
color = function.color,
onClick = { onClickFunction(index) },
onClickRemove = {
onClickRemove(index)
onClickRemoveFunction(index)
}
)
Divider(Modifier.fillMaxWidth())
}

itemsIndexed(constants, key = { _, constant -> constant.hashCode() }) { index, constant ->
FunctionRow(
lhsName = constant.identifier.toString(),
text = constant.value.toString(),
color = null,
onClick = { onClickConstant(index) },
onClickRemove = { onClickRemoveConstant(index) }
)
}
}
}

@Composable
fun FunctionRow(
functionName: String,
lhsName: String,
text: String,
color: Color,
color: Color?,
onClick: () -> Unit,
onClickRemove: () -> Unit
) {
Expand All @@ -144,12 +207,12 @@ fun FunctionRow(
) {

Text(
text = "${functionName}(x) = ", style = TextStyle(
text = "$lhsName = ", style = TextStyle(
fontFamily = FontFamily.Serif,
fontWeight = FontWeight.Bold,
fontSize = MaterialTheme.typography.titleMedium.fontSize,
fontStyle = FontStyle.Italic
), color = color
), color = color ?: MaterialTheme.colorScheme.primary
)
Text(
text = text, style = TextStyle(
Expand Down
Loading

0 comments on commit c2242b4

Please sign in to comment.