Merge pull request #111 from SELab1/delete_timer

Delete timer
This commit is contained in:
lbarraga 2023-05-15 21:43:23 +02:00 committed by GitHub Enterprise
commit 3386d45a04
7 changed files with 121 additions and 41 deletions

View file

@ -3,7 +3,6 @@ package be.ugent.sel.studeez.common.composable
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -22,7 +21,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.common.ext.fieldModifier import be.ugent.sel.studeez.common.ext.fieldModifier
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
import kotlin.math.sin
import be.ugent.sel.studeez.R.drawable as AppIcon import be.ugent.sel.studeez.R.drawable as AppIcon
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
@ -47,7 +45,7 @@ fun LabelledInputField(
value: String, value: String,
onNewValue: (String) -> Unit, onNewValue: (String) -> Unit,
@StringRes label: Int, @StringRes label: Int,
singleLine: Boolean = false singleLine: Boolean = true
) { ) {
OutlinedTextField( OutlinedTextField(
value = value, value = value,
@ -119,7 +117,9 @@ fun LabeledErrorTextField(
initialValue: String, initialValue: String,
@StringRes label: Int, @StringRes label: Int,
singleLine: Boolean = false, singleLine: Boolean = false,
errorText: Int, isValid: MutableState<Boolean> = remember { mutableStateOf(true) },
isFirst: MutableState<Boolean> = remember { mutableStateOf(false) },
@StringRes errorText: Int,
keyboardType: KeyboardType, keyboardType: KeyboardType,
predicate: (String) -> Boolean, predicate: (String) -> Boolean,
onNewCorrectValue: (String) -> Unit onNewCorrectValue: (String) -> Unit
@ -128,31 +128,28 @@ fun LabeledErrorTextField(
mutableStateOf(initialValue) mutableStateOf(initialValue)
} }
var isValid by remember {
mutableStateOf(predicate(value))
}
Column { Column {
OutlinedTextField( OutlinedTextField(
modifier = modifier.fieldModifier(), modifier = modifier.fieldModifier(),
value = value, value = value,
onValueChange = { newText -> onValueChange = { newText ->
isFirst.value = false
value = newText value = newText
isValid = predicate(value) isValid.value = predicate(value)
if (isValid) { if (isValid.value) {
onNewCorrectValue(newText) onNewCorrectValue(newText)
} }
}, },
singleLine = singleLine, singleLine = singleLine,
label = { Text(text = stringResource(id = label)) }, label = { Text(text = stringResource(id = label)) },
isError = !isValid, isError = !isValid.value && !isFirst.value,
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = keyboardType, keyboardType = keyboardType,
imeAction = ImeAction.Done imeAction = ImeAction.Done
) )
) )
if (!isValid) { if (!isValid.value && !isFirst.value) {
Text( Text(
modifier = Modifier.padding(start = 16.dp), modifier = Modifier.padding(start = 16.dp),
text = stringResource(id = errorText), text = stringResource(id = errorText),

View file

@ -3,6 +3,8 @@ package be.ugent.sel.studeez.screens.timer_form
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import be.ugent.sel.studeez.common.composable.DeleteButton
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.composable.FormComposable import be.ugent.sel.studeez.common.composable.FormComposable
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
@ -12,8 +14,16 @@ fun TimerAddRoute(
popUp: () -> Unit, popUp: () -> Unit,
viewModel: TimerFormViewModel viewModel: TimerFormViewModel
) { ) {
TimerFormScreen(popUp = popUp, getTimerInfo = viewModel::getTimerInfo, AppText.add_timer) {
viewModel.saveTimer(it, goBack = popUp)
TimerFormScreen(
popUp = popUp,
getTimerInfo = viewModel::getTimerInfo,
extraButton= { },
AppText.add_timer
) {
viewModel.saveTimer(it, goBack = {popUp(); popUp()})
} }
} }
@ -22,7 +32,20 @@ fun TimerEditRoute(
popUp: () -> Unit, popUp: () -> Unit,
viewModel: TimerFormViewModel viewModel: TimerFormViewModel
) { ) {
TimerFormScreen(popUp = popUp, getTimerInfo = viewModel::getTimerInfo, AppText.edit_timer) {
@Composable
fun deleteButton() {
DeleteButton(text = AppText.delete_timer) {
viewModel.deleteTimer(viewModel.getTimerInfo(), popUp)
}
}
TimerFormScreen(
popUp = popUp,
getTimerInfo = viewModel::getTimerInfo,
extraButton= { deleteButton() },
AppText.edit_timer
) {
viewModel.editTimer(it, goBack = popUp) viewModel.editTimer(it, goBack = popUp)
} }
} }
@ -31,6 +54,7 @@ fun TimerEditRoute(
fun TimerFormScreen( fun TimerFormScreen(
popUp: () -> Unit, popUp: () -> Unit,
getTimerInfo: () -> TimerInfo, getTimerInfo: () -> TimerInfo,
extraButton: @Composable () -> Unit,
@StringRes label: Int, @StringRes label: Int,
onConfirmClick: (TimerInfo) -> Unit onConfirmClick: (TimerInfo) -> Unit
) { ) {
@ -40,6 +64,6 @@ fun TimerFormScreen(
title = stringResource(id = label), title = stringResource(id = label),
popUp = popUp popUp = popUp
) { ) {
timerFormScreen(onConfirmClick) timerFormScreen(onConfirmClick, extraButton)
} }
} }

View file

@ -23,6 +23,11 @@ class TimerFormViewModel @Inject constructor(
goBack() goBack()
} }
fun deleteTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
timerDAO.deleteTimer(timerInfo)
goBack()
}
fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) { fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
timerDAO.saveTimer(timerInfo) timerDAO.saveTimer(timerInfo)
goBack() goBack()

View file

@ -3,49 +3,82 @@ package be.ugent.sel.studeez.screens.timer_form.form_screens
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import be.ugent.sel.studeez.R import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicButton import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.LabelledInputField import be.ugent.sel.studeez.common.composable.LabeledErrorTextField
import be.ugent.sel.studeez.common.ext.basicButton import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) { abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) {
protected val valids = mutableMapOf(
"name" to mutableStateOf(textPredicate(timerInfo.name)),
"description" to mutableStateOf(textPredicate(timerInfo.description))
)
protected val firsts = mutableMapOf(
"name" to mutableStateOf(true),
"description" to mutableStateOf(true)
)
@Composable @Composable
operator fun invoke(onSaveClick: (TimerInfo) -> Unit) { operator fun invoke(
onSaveClick: (TimerInfo) -> Unit,
var name by remember { mutableStateOf(timerInfo.name) } extraButton: @Composable () -> Unit = {},
var description by remember { mutableStateOf(timerInfo.description) } ) {
// This shall rerun whenever name and description change
timerInfo.name = name
timerInfo.description = description
Column { Column {
// Fields that every timer shares (ommited id) // Fields that every timer shares (ommited id)
LabelledInputField( LabeledErrorTextField(
value = name, initialValue = timerInfo.name,
onNewValue = { name = it }, label = R.string.name,
label = R.string.name errorText = AppText.name_error,
) isValid = valids.getValue("name"),
isFirst = firsts.getValue("name"),
keyboardType = KeyboardType.Text,
predicate = { it.isNotBlank() }
) { correctName ->
timerInfo.name = correctName
}
LabelledInputField( LabeledErrorTextField(
value = description, initialValue = timerInfo.description,
onNewValue = { description = it }, label = R.string.description,
label = AppText.description, errorText = AppText.description_error,
singleLine = false isValid = valids.getValue("description"),
) isFirst = firsts.getValue("description"),
singleLine = false,
keyboardType = KeyboardType.Text,
predicate = { textPredicate(it) }
) { correctName ->
timerInfo.description = correctName
}
ExtraFields() ExtraFields()
BasicButton(R.string.save, Modifier.basicButton()) { BasicButton(R.string.save, Modifier.basicButton()) {
onSaveClick(timerInfo) if (valids.all { it.component2().value }) { // All fields are valid
onSaveClick(timerInfo)
} else {
firsts.map {
it.component2().value = false
} // dont mask error because its not been filled out yet
SnackbarManager.showMessage(AppText.fill_out_error)
}
} }
extraButton()
} }
} }
private fun textPredicate(text: String): Boolean {
return text.isNotBlank()
}
@Composable @Composable
open fun ExtraFields() { open fun ExtraFields() {
// By default no extra fields, unless overwritten by subclass. // By default no extra fields, unless overwritten by subclass.

View file

@ -15,6 +15,8 @@ class BreakTimerFormScreen(
private val breakTimerInfo: PomodoroTimerInfo private val breakTimerInfo: PomodoroTimerInfo
): AbstractTimerFormScreen(breakTimerInfo) { ): AbstractTimerFormScreen(breakTimerInfo) {
@Composable @Composable
override fun ExtraFields() { override fun ExtraFields() {
// If the user presses the OK button on the timepicker, the time in the button should change // If the user presses the OK button on the timepicker, the time in the button should change
@ -26,12 +28,17 @@ class BreakTimerFormScreen(
breakTimerInfo.breakTime = newTime breakTimerInfo.breakTime = newTime
} }
valids["repeats"] = remember {mutableStateOf(true)}
firsts["repeats"] = remember { mutableStateOf(true) }
LabeledErrorTextField( LabeledErrorTextField(
initialValue = breakTimerInfo.repeats.toString(), initialValue = breakTimerInfo.repeats.toString(),
label = R.string.repeats, label = R.string.repeats,
errorText = AppText.repeats_error, errorText = AppText.repeats_error,
isValid = valids.getValue("repeats"),
isFirst = firsts.getValue("repeats"),
keyboardType = KeyboardType.Decimal, keyboardType = KeyboardType.Decimal,
predicate = { it.matches(Regex("[1-9]+\\d*")) } predicate = { isNumber(it) }
) { correctlyTypedInt -> ) { correctlyTypedInt ->
breakTimerInfo.repeats = correctlyTypedInt.toInt() breakTimerInfo.repeats = correctlyTypedInt.toInt()
} }
@ -39,6 +46,10 @@ class BreakTimerFormScreen(
} }
} }
fun isNumber(text: String): Boolean {
return text.matches(Regex("[1-9]+\\d*"))
}
@Preview @Preview
@Composable @Composable
fun BreakEditScreenPreview() { fun BreakEditScreenPreview() {

View file

@ -1,13 +1,13 @@
package be.ugent.sel.studeez.screens.timer_form.timer_type_select package be.ugent.sel.studeez.screens.timer_form.timer_type_select
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.data.local.models.timer_info.* import be.ugent.sel.studeez.data.local.models.timer_info.*
@ -37,7 +37,10 @@ fun TimerTypeSelectScreen(
) { ) {
TimerType.values().forEach { timerType -> TimerType.values().forEach { timerType ->
val default: TimerInfo = defaultTimerInfo.getValue(timerType) val default: TimerInfo = defaultTimerInfo.getValue(timerType)
Button(onClick = { viewModel.onTimerTypeChosen(default, open) }) { Button(
onClick = { viewModel.onTimerTypeChosen(default, open) },
modifier = Modifier.fillMaxWidth().padding(5.dp)
) {
Text(text = timerType.name) Text(text = timerType.name)
} }
} }

View file

@ -70,8 +70,15 @@
<!-- Timers --> <!-- Timers -->
<string name="timers">Timers</string> <string name="timers">Timers</string>
<string name="delete_timer">Delete Timer</string>
<string name="edit">Edit</string> <string name="edit">Edit</string>
<string name="add_timer">Add timer</string> <string name="add_timer">Add timer</string>
<string name="name_error">Name should not be blank</string>
<string name="description_error">Description should not be blank</string>
<string name="fill_out_error">Fill out all the fields correctly!</string>
<string name="pick_time">Select time</string> <string name="pick_time">Select time</string>
<string name="state_focus">Focus!</string> <string name="state_focus">Focus!</string>
<plurals name="state_focus_remaining"> <plurals name="state_focus_remaining">