diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt index aadcee3..47bdec5 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt @@ -3,7 +3,6 @@ package be.ugent.sel.studeez.common.composable import androidx.annotation.StringRes import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.material.icons.Icons @@ -22,7 +21,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import be.ugent.sel.studeez.common.ext.fieldModifier 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.string as AppText @@ -47,7 +45,7 @@ fun LabelledInputField( value: String, onNewValue: (String) -> Unit, @StringRes label: Int, - singleLine: Boolean = false + singleLine: Boolean = true ) { OutlinedTextField( value = value, @@ -119,7 +117,9 @@ fun LabeledErrorTextField( initialValue: String, @StringRes label: Int, singleLine: Boolean = false, - errorText: Int, + isValid: MutableState = remember { mutableStateOf(true) }, + isFirst: MutableState = remember { mutableStateOf(false) }, + @StringRes errorText: Int, keyboardType: KeyboardType, predicate: (String) -> Boolean, onNewCorrectValue: (String) -> Unit @@ -128,31 +128,28 @@ fun LabeledErrorTextField( mutableStateOf(initialValue) } - var isValid by remember { - mutableStateOf(predicate(value)) - } - Column { OutlinedTextField( modifier = modifier.fieldModifier(), value = value, onValueChange = { newText -> + isFirst.value = false value = newText - isValid = predicate(value) - if (isValid) { + isValid.value = predicate(value) + if (isValid.value) { onNewCorrectValue(newText) } }, singleLine = singleLine, label = { Text(text = stringResource(id = label)) }, - isError = !isValid, + isError = !isValid.value && !isFirst.value, keyboardOptions = KeyboardOptions( keyboardType = keyboardType, imeAction = ImeAction.Done ) ) - if (!isValid) { + if (!isValid.value && !isFirst.value) { Text( modifier = Modifier.padding(start = 16.dp), text = stringResource(id = errorText), diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormScreen.kt index 542a7f0..fea01e6 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormScreen.kt @@ -3,6 +3,8 @@ package be.ugent.sel.studeez.screens.timer_form import androidx.annotation.StringRes import androidx.compose.runtime.Composable 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.data.local.models.timer_info.TimerInfo import be.ugent.sel.studeez.R.string as AppText @@ -12,8 +14,16 @@ fun TimerAddRoute( popUp: () -> Unit, 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, 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) } } @@ -31,6 +54,7 @@ fun TimerEditRoute( fun TimerFormScreen( popUp: () -> Unit, getTimerInfo: () -> TimerInfo, + extraButton: @Composable () -> Unit, @StringRes label: Int, onConfirmClick: (TimerInfo) -> Unit ) { @@ -40,6 +64,6 @@ fun TimerFormScreen( title = stringResource(id = label), popUp = popUp ) { - timerFormScreen(onConfirmClick) + timerFormScreen(onConfirmClick, extraButton) } } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormViewModel.kt index 8a0a4d4..c34cd06 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormViewModel.kt @@ -23,6 +23,11 @@ class TimerFormViewModel @Inject constructor( goBack() } + fun deleteTimer(timerInfo: TimerInfo, goBack: () -> Unit) { + timerDAO.deleteTimer(timerInfo) + goBack() + } + fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) { timerDAO.saveTimer(timerInfo) goBack() diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt index 69d02ef..9560dd1 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt @@ -3,49 +3,82 @@ package be.ugent.sel.studeez.screens.timer_form.form_screens import androidx.compose.foundation.layout.Column import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.KeyboardType import be.ugent.sel.studeez.R 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.snackbar.SnackbarManager import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo import be.ugent.sel.studeez.R.string as AppText 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 - operator fun invoke(onSaveClick: (TimerInfo) -> Unit) { - - var name by remember { mutableStateOf(timerInfo.name) } - var description by remember { mutableStateOf(timerInfo.description) } - - // This shall rerun whenever name and description change - timerInfo.name = name - timerInfo.description = description + operator fun invoke( + onSaveClick: (TimerInfo) -> Unit, + extraButton: @Composable () -> Unit = {}, + ) { Column { // Fields that every timer shares (ommited id) - LabelledInputField( - value = name, - onNewValue = { name = it }, - label = R.string.name - ) + LabeledErrorTextField( + initialValue = timerInfo.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( - value = description, - onNewValue = { description = it }, - label = AppText.description, - singleLine = false - ) + LabeledErrorTextField( + initialValue = timerInfo.description, + label = R.string.description, + errorText = AppText.description_error, + isValid = valids.getValue("description"), + isFirst = firsts.getValue("description"), + singleLine = false, + keyboardType = KeyboardType.Text, + predicate = { textPredicate(it) } + ) { correctName -> + timerInfo.description = correctName + } ExtraFields() 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 open fun ExtraFields() { // By default no extra fields, unless overwritten by subclass. diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/BreakTimerFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/BreakTimerFormScreen.kt index 12d07a4..c87bd7b 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/BreakTimerFormScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/BreakTimerFormScreen.kt @@ -15,6 +15,8 @@ class BreakTimerFormScreen( private val breakTimerInfo: PomodoroTimerInfo ): AbstractTimerFormScreen(breakTimerInfo) { + + @Composable override fun ExtraFields() { // 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 } + valids["repeats"] = remember {mutableStateOf(true)} + firsts["repeats"] = remember { mutableStateOf(true) } + LabeledErrorTextField( initialValue = breakTimerInfo.repeats.toString(), label = R.string.repeats, errorText = AppText.repeats_error, + isValid = valids.getValue("repeats"), + isFirst = firsts.getValue("repeats"), keyboardType = KeyboardType.Decimal, - predicate = { it.matches(Regex("[1-9]+\\d*")) } + predicate = { isNumber(it) } ) { correctlyTypedInt -> breakTimerInfo.repeats = correctlyTypedInt.toInt() } @@ -39,6 +46,10 @@ class BreakTimerFormScreen( } } +fun isNumber(text: String): Boolean { + return text.matches(Regex("[1-9]+\\d*")) +} + @Preview @Composable fun BreakEditScreenPreview() { diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/timer_type_select/TimerTypeSelectScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/timer_type_select/TimerTypeSelectScreen.kt index fa8d650..4825c63 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/timer_type_select/TimerTypeSelectScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/timer_type_select/TimerTypeSelectScreen.kt @@ -1,13 +1,13 @@ package be.ugent.sel.studeez.screens.timer_form.timer_type_select -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.* import androidx.compose.material.Button import androidx.compose.material.Text 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.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate import be.ugent.sel.studeez.data.local.models.timer_info.* @@ -37,7 +37,10 @@ fun TimerTypeSelectScreen( ) { TimerType.values().forEach { 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) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0d23458..a5f350e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -70,8 +70,15 @@ Timers + Delete Timer Edit Add timer + + Name should not be blank + Description should not be blank + Fill out all the fields correctly! + + Select time Focus!