#13 and #17 form for creating, editing and deleting tasks

This commit is contained in:
brreynie 2023-05-03 23:18:53 +02:00
parent 6a676c2fad
commit 6765229d37
14 changed files with 249 additions and 30 deletions

View file

@ -44,6 +44,8 @@ import be.ugent.sel.studeez.screens.tasks.SubjectRoute
import be.ugent.sel.studeez.screens.tasks.TaskRoute import be.ugent.sel.studeez.screens.tasks.TaskRoute
import be.ugent.sel.studeez.screens.tasks.forms.SubjectAddRoute import be.ugent.sel.studeez.screens.tasks.forms.SubjectAddRoute
import be.ugent.sel.studeez.screens.tasks.forms.SubjectEditRoute import be.ugent.sel.studeez.screens.tasks.forms.SubjectEditRoute
import be.ugent.sel.studeez.screens.tasks.forms.TaskAddRoute
import be.ugent.sel.studeez.screens.tasks.forms.TaskEditRoute
import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute
import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
@ -161,7 +163,7 @@ fun StudeezNavGraph(
composable(StudeezDestinations.ADD_SUBJECT_FORM) { composable(StudeezDestinations.ADD_SUBJECT_FORM) {
SubjectAddRoute( SubjectAddRoute(
goBack = goBack, goBack = goBack,
open = open, openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(), viewModel = hiltViewModel(),
) )
} }
@ -169,7 +171,23 @@ fun StudeezNavGraph(
composable(StudeezDestinations.EDIT_SUBJECT_FORM) { composable(StudeezDestinations.EDIT_SUBJECT_FORM) {
SubjectEditRoute( SubjectEditRoute(
goBack = goBack, goBack = goBack,
open = open, openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.ADD_TASK_FORM) {
TaskAddRoute(
goBack = goBack,
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.EDIT_TASK_FORM) {
TaskEditRoute(
goBack = goBack,
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(), viewModel = hiltViewModel(),
) )
} }

View file

@ -91,10 +91,11 @@ fun StealthButtonCardPreview() {
@Composable @Composable
fun DeleteButton( fun DeleteButton(
@StringRes text: Int,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
BasicButton( BasicButton(
text = R.string.delete_subject, text = text,
modifier = Modifier.basicButton(), modifier = Modifier.basicButton(),
onClick = onClick, onClick = onClick,
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
@ -107,7 +108,7 @@ fun DeleteButton(
@Preview @Preview
@Composable @Composable
fun DeleteButtonPreview() { fun DeleteButtonPreview() {
DeleteButton {} DeleteButton(text = R.string.delete_subject) {}
} }
@Composable @Composable

View file

@ -5,7 +5,7 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
/** /**
* Used to communicate the selected subject from the subject overview to the task overview of that subject. * Used to communicate the selected subject from the subject overview other screens.
* Because this is a singleton-class the view-models of both screens observe the same data. * Because this is a singleton-class the view-models of both screens observe the same data.
*/ */
@Singleton @Singleton
@ -15,4 +15,6 @@ class SelectedSubject @Inject constructor() {
fun set(subject: Subject) { fun set(subject: Subject) {
this.subject = subject this.subject = subject
} }
fun isSet() = this::subject.isInitialized
} }

View file

@ -0,0 +1,21 @@
package be.ugent.sel.studeez.data
import be.ugent.sel.studeez.data.local.models.task.Task
import javax.inject.Inject
import javax.inject.Singleton
/**
* Used to communicate the selected task from the task overview other screens.
* Because this is a singleton-class the view-models of both screens observe the same data.
*/
@Singleton
class SelectedTask @Inject constructor() {
private lateinit var task: Task
operator fun invoke() = task
fun set(task: Task) {
this.task = task
}
fun isSet() = this::task.isInitialized
}

View file

@ -10,6 +10,8 @@ interface TaskDAO {
fun saveTask(newTask: Task) fun saveTask(newTask: Task)
fun updateTask(newTask: Task)
fun deleteTask(oldTask: Task) fun deleteTask(oldTask: Task)
fun toggleTaskCompleted(task: Task, completed: Boolean) fun toggleTaskCompleted(task: Task, completed: Boolean)

View file

@ -22,7 +22,11 @@ class FireBaseTaskDAO @Inject constructor(
} }
override fun saveTask(newTask: Task) { override fun saveTask(newTask: Task) {
TODO("Not yet implemented") selectedSubjectTasksCollection(newTask.subjectId).add(newTask)
}
override fun updateTask(newTask: Task) {
selectedSubjectTasksCollection(newTask.id).document(newTask.id).set(newTask)
} }
override fun deleteTask(oldTask: Task) { override fun deleteTask(oldTask: Task) {

View file

@ -15,6 +15,8 @@ object StudeezDestinations {
const val ADD_SUBJECT_FORM = "add_subject" const val ADD_SUBJECT_FORM = "add_subject"
const val EDIT_SUBJECT_FORM = "edit_subject" const val EDIT_SUBJECT_FORM = "edit_subject"
const val TASKS_SCREEN = "tasks" const val TASKS_SCREEN = "tasks"
const val ADD_TASK_FORM = "add_task"
const val EDIT_TASK_FORM = "edit_task"
// const val SESSIONS_SCREEN = "sessions" // const val SESSIONS_SCREEN = "sessions"
const val PROFILE_SCREEN = "profile" const val PROFILE_SCREEN = "profile"

View file

@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -12,15 +11,12 @@ import androidx.compose.material.icons.filled.Edit
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
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.NewTaskSubjectButton import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.composable.tasks.TaskEntry import be.ugent.sel.studeez.common.composable.tasks.TaskEntry
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.data.local.models.task.Subject import be.ugent.sel.studeez.data.local.models.task.Subject
import be.ugent.sel.studeez.data.local.models.task.Task import be.ugent.sel.studeez.data.local.models.task.Task
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
@ -38,7 +34,7 @@ data class TaskActions(
fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions { fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions {
return TaskActions( return TaskActions(
addTask = viewModel::addTask, addTask = { viewModel.addTask(open) },
getTasks = viewModel::getTasks, getTasks = viewModel::getTasks,
getSubject = viewModel::getSelectedSubject, getSubject = viewModel::getSelectedSubject,
deleteTask = viewModel::deleteTask, deleteTask = viewModel::deleteTask,

View file

@ -19,8 +19,8 @@ class TaskViewModel @Inject constructor(
private val selectedSubject: SelectedSubject, private val selectedSubject: SelectedSubject,
logService: LogService, logService: LogService,
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
fun addTask() { fun addTask(open: (String) -> Unit) {
open(StudeezDestinations.ADD_TASK_FORM)
} }
fun getTasks(): Flow<List<Task>> { fun getTasks(): Flow<List<Task>> {

View file

@ -21,7 +21,7 @@ import be.ugent.sel.studeez.resources
@Composable @Composable
fun SubjectAddRoute( fun SubjectAddRoute(
goBack: () -> Unit, goBack: () -> Unit,
open: (String) -> Unit, openAndPopUp: (String, String) -> Unit,
viewModel: SubjectFormViewModel, viewModel: SubjectFormViewModel,
) { ) {
val uiState by viewModel.uiState val uiState by viewModel.uiState
@ -29,7 +29,7 @@ fun SubjectAddRoute(
title = R.string.new_subject, title = R.string.new_subject,
goBack = goBack, goBack = goBack,
uiState = uiState, uiState = uiState,
onConfirm = { viewModel.onCreate(open) }, onConfirm = { viewModel.onCreate(openAndPopUp) },
onNameChange = viewModel::onNameChange, onNameChange = viewModel::onNameChange,
onColorChange = {}, onColorChange = {},
) )
@ -38,7 +38,7 @@ fun SubjectAddRoute(
@Composable @Composable
fun SubjectEditRoute( fun SubjectEditRoute(
goBack: () -> Unit, goBack: () -> Unit,
open: (String) -> Unit, openAndPopUp: (String, String) -> Unit,
viewModel: SubjectFormViewModel, viewModel: SubjectFormViewModel,
) { ) {
val uiState by viewModel.uiState val uiState by viewModel.uiState
@ -46,11 +46,13 @@ fun SubjectEditRoute(
title = R.string.edit_subject, title = R.string.edit_subject,
goBack = goBack, goBack = goBack,
uiState = uiState, uiState = uiState,
onConfirm = { viewModel.onEdit(open) }, onConfirm = { viewModel.onEdit(openAndPopUp) },
onNameChange = viewModel::onNameChange, onNameChange = viewModel::onNameChange,
onColorChange = {}, onColorChange = {},
) { ) {
DeleteButton(onClick = { viewModel.onDelete(open) }) DeleteButton(text = R.string.delete_subject) {
viewModel.onDelete(openAndPopUp)
}
} }
} }
@ -112,6 +114,6 @@ fun EditSubjectFormPreview() {
onNameChange = {}, onNameChange = {},
onColorChange = {}, onColorChange = {},
) { ) {
DeleteButton {} DeleteButton(text = R.string.delete_subject) {}
} }
} }

View file

@ -16,7 +16,13 @@ class SubjectFormViewModel @Inject constructor(
private val selectedSubject: SelectedSubject, private val selectedSubject: SelectedSubject,
logService: LogService, logService: LogService,
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
var uiState = mutableStateOf(SubjectFormUiState()) var uiState = mutableStateOf(
if (selectedSubject.isSet()) SubjectFormUiState(
name = selectedSubject().name,
color = selectedSubject().argb_color
)
else SubjectFormUiState()
)
private set private set
private val name: String private val name: String
@ -33,12 +39,12 @@ class SubjectFormViewModel @Inject constructor(
uiState.value = uiState.value.copy(color = newValue) uiState.value = uiState.value.copy(color = newValue)
} }
fun onDelete(open: (String) -> Unit) { fun onDelete(openAndPopUp: (String, String) -> Unit) {
subjectDAO.deleteSubject(selectedSubject()) subjectDAO.deleteSubject(selectedSubject())
open(StudeezDestinations.SUBJECT_SCREEN) openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM)
} }
fun onCreate(open: (String) -> Unit) { fun onCreate(openAndPopUp: (String, String) -> Unit) {
val newSubject = Subject( val newSubject = Subject(
name = name, name = name,
argb_color = color, argb_color = color,
@ -46,20 +52,18 @@ class SubjectFormViewModel @Inject constructor(
subjectDAO.saveSubject( subjectDAO.saveSubject(
newSubject newSubject
) )
selectedSubject.set(newSubject)
// TODO open newly created subject // TODO open newly created subject
// selectedSubject.set(newSubject)
// open(StudeezDestinations.TASKS_SCREEN) // open(StudeezDestinations.TASKS_SCREEN)
open(StudeezDestinations.SUBJECT_SCREEN) openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.ADD_SUBJECT_FORM)
} }
fun onEdit(open: (String) -> Unit) { fun onEdit(openAndPopUp: (String, String) -> Unit) {
val newSubject = selectedSubject().copy( val newSubject = selectedSubject().copy(
name = name, name = name,
argb_color = color, argb_color = color,
) )
subjectDAO.updateSubject( subjectDAO.updateSubject(newSubject)
newSubject openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM)
)
open(StudeezDestinations.TASKS_SCREEN)
} }
} }

View file

@ -0,0 +1,113 @@
package be.ugent.sel.studeez.screens.tasks.forms
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.DeleteButton
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.ext.fieldModifier
import be.ugent.sel.studeez.resources
@Composable
fun TaskAddRoute(
goBack: () -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: TaskFormViewModel,
) {
val uiState by viewModel.uiState
TaskForm(
title = R.string.new_task,
goBack = goBack,
uiState = uiState,
onConfirm = { viewModel.onCreate(openAndPopUp) },
onNameChange = viewModel::onNameChange
)
}
@Composable
fun TaskEditRoute(
goBack: () -> Unit,
openAndPopUp: (String, String) -> Unit,
viewModel: TaskFormViewModel,
) {
val uiState by viewModel.uiState
TaskForm(
title = R.string.edit_task,
goBack = goBack,
uiState = uiState,
onConfirm = { viewModel.onEdit(openAndPopUp) },
onNameChange = viewModel::onNameChange
) {
DeleteButton(text = R.string.delete_task) {
viewModel.onDelete(openAndPopUp)
}
}
}
@Composable
fun TaskForm(
@StringRes title: Int,
goBack: () -> Unit,
uiState: TaskFormUiState,
onConfirm: () -> Unit,
onNameChange: (String) -> Unit,
extraButton: @Composable () -> Unit = {}
) {
SecondaryScreenTemplate(
title = resources().getString(title),
popUp = goBack,
) {
Column {
OutlinedTextField(
singleLine = true,
value = uiState.name,
onValueChange = onNameChange,
placeholder = { Text(stringResource(id = R.string.name)) },
modifier = Modifier.fieldModifier(),
)
BasicButton(
text = R.string.confirm,
modifier = Modifier.basicButton(),
onClick = onConfirm,
)
extraButton()
}
}
}
@Preview
@Composable
fun AddTaskFormPreview() {
TaskForm(
title = R.string.new_task,
goBack = {},
uiState = TaskFormUiState(),
onConfirm = {},
onNameChange = {},
)
}
@Preview
@Composable
fun EditTaskFormPreview() {
TaskForm(
title = R.string.edit_task,
goBack = {},
uiState = TaskFormUiState(
name = "Test Task",
),
onConfirm = {},
onNameChange = {},
) {
DeleteButton(text = R.string.delete_task) {}
}
}

View file

@ -0,0 +1,5 @@
package be.ugent.sel.studeez.screens.tasks.forms
data class TaskFormUiState(
val name: String = "",
)

View file

@ -0,0 +1,49 @@
package be.ugent.sel.studeez.screens.tasks.forms
import androidx.compose.runtime.mutableStateOf
import be.ugent.sel.studeez.data.SelectedSubject
import be.ugent.sel.studeez.data.SelectedTask
import be.ugent.sel.studeez.data.local.models.task.Task
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.TaskDAO
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class TaskFormViewModel @Inject constructor(
private val taskDAO: TaskDAO,
private val selectedSubject: SelectedSubject,
private val selectedTask: SelectedTask,
logService: LogService,
) : StudeezViewModel(logService) {
var uiState = mutableStateOf(
if (selectedTask.isSet()) TaskFormUiState(selectedTask().name) else TaskFormUiState()
)
private set
private val name: String
get() = uiState.value.name
fun onNameChange(newValue: String) {
uiState.value = uiState.value.copy(name = newValue)
}
fun onDelete(openAndPopUp: (String, String) -> Unit) {
taskDAO.deleteTask(selectedTask())
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM)
}
fun onCreate(openAndPopUp: (String, String) -> Unit) {
val newTask = Task(name = name, subjectId = selectedSubject().id)
taskDAO.saveTask(newTask)
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.ADD_TASK_FORM)
}
fun onEdit(openAndPopUp: (String, String) -> Unit) {
val newTask = Task(name = name)
taskDAO.updateTask(newTask)
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM)
}
}