resolve conflicts and merge
This commit is contained in:
commit
13b9c0591f
38 changed files with 1291 additions and 64 deletions
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
|
@ -42,6 +42,5 @@
|
|||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TestFunctionName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
|
|
@ -124,6 +124,8 @@ dependencies {
|
|||
implementation 'com.google.firebase:firebase-perf-ktx'
|
||||
implementation 'com.google.firebase:firebase-config-ktx'
|
||||
|
||||
// Colorpicker
|
||||
implementation 'com.github.skydoves:colorpicker-compose:1.0.2'
|
||||
}
|
||||
|
||||
// Allow references to generate code
|
||||
|
|
|
@ -32,6 +32,12 @@ import be.ugent.sel.studeez.screens.sessions.SessionsRoute
|
|||
import be.ugent.sel.studeez.screens.settings.SettingsRoute
|
||||
import be.ugent.sel.studeez.screens.sign_up.SignUpRoute
|
||||
import be.ugent.sel.studeez.screens.splash.SplashRoute
|
||||
import be.ugent.sel.studeez.screens.tasks.SubjectRoute
|
||||
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.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_form.timer_type_select.TimerTypeSelectScreen
|
||||
import be.ugent.sel.studeez.screens.timer_form.TimerAddRoute
|
||||
import be.ugent.sel.studeez.screens.timer_form.TimerEditRoute
|
||||
|
@ -118,10 +124,56 @@ fun StudeezNavGraph(
|
|||
)
|
||||
}
|
||||
|
||||
composable(StudeezDestinations.TASKS_SCREEN) {
|
||||
// TODO
|
||||
composable(StudeezDestinations.SUBJECT_SCREEN) {
|
||||
SubjectRoute(
|
||||
open = open,
|
||||
viewModel = hiltViewModel(),
|
||||
drawerActions = drawerActions,
|
||||
navigationBarActions = navigationBarActions,
|
||||
)
|
||||
}
|
||||
|
||||
composable(StudeezDestinations.ADD_SUBJECT_FORM) {
|
||||
SubjectAddRoute(
|
||||
goBack = goBack,
|
||||
openAndPopUp = openAndPopUp,
|
||||
viewModel = hiltViewModel(),
|
||||
)
|
||||
}
|
||||
|
||||
composable(StudeezDestinations.EDIT_SUBJECT_FORM) {
|
||||
SubjectEditRoute(
|
||||
goBack = goBack,
|
||||
openAndPopUp = openAndPopUp,
|
||||
viewModel = hiltViewModel(),
|
||||
)
|
||||
}
|
||||
|
||||
composable(StudeezDestinations.TASKS_SCREEN) {
|
||||
TaskRoute(
|
||||
goBack = goBack,
|
||||
open = open,
|
||||
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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
composable(StudeezDestinations.SESSIONS_SCREEN) {
|
||||
SessionsRoute(
|
||||
drawerActions = drawerActions,
|
||||
|
@ -134,7 +186,7 @@ fun StudeezNavGraph(
|
|||
open,
|
||||
viewModel = hiltViewModel(),
|
||||
drawerActions = drawerActions,
|
||||
navigationBarActions = navigationBarActions
|
||||
navigationBarActions = navigationBarActions,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,17 +2,33 @@ package be.ugent.sel.studeez.common.composable
|
|||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import be.ugent.sel.studeez.R
|
||||
import be.ugent.sel.studeez.common.ext.basicButton
|
||||
import be.ugent.sel.studeez.common.ext.card
|
||||
import be.ugent.sel.studeez.common.ext.defaultButtonShape
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) {
|
||||
|
@ -29,7 +45,7 @@ fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit
|
|||
@Composable
|
||||
fun BasicButton(
|
||||
@StringRes text: Int,
|
||||
modifier: Modifier,
|
||||
modifier: Modifier = Modifier,
|
||||
colors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||
border: BorderStroke? = null,
|
||||
onClick: () -> Unit,
|
||||
|
@ -51,53 +67,58 @@ fun BasicButton(
|
|||
@Preview
|
||||
@Composable
|
||||
fun BasicButtonPreview() {
|
||||
BasicButton(text = R.string.add_timer, modifier = Modifier.basicButton()) {}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NotInternationalisedButton(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
colors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||
border: BorderStroke? = null,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
shape = defaultButtonShape(),
|
||||
colors = colors,
|
||||
border = border
|
||||
) {
|
||||
Text(text = text)
|
||||
}
|
||||
BasicButton(text = AppText.add_timer, modifier = Modifier.basicButton()) {}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StealthButton(
|
||||
@StringRes text: Int,
|
||||
modifier: Modifier = Modifier.card(),
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
BasicButton(
|
||||
text = text,
|
||||
onClick = onClick,
|
||||
modifier = Modifier.card(),
|
||||
modifier = modifier,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.surface,
|
||||
contentColor = MaterialTheme.colors.onSurface
|
||||
contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.4f)
|
||||
),
|
||||
border = BorderStroke(1.dp, MaterialTheme.colors.onSurface)
|
||||
border = BorderStroke(3.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.4f))
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun StealthButtonCardPreview() {
|
||||
StealthButton(text = R.string.edit) {
|
||||
StealthButton(text = AppText.edit) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun DeleteButton(
|
||||
@StringRes text: Int,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
BasicButton(
|
||||
text = text,
|
||||
modifier = Modifier.basicButton(),
|
||||
onClick = onClick,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.error,
|
||||
contentColor = MaterialTheme.colors.onSurface,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun DeleteButtonPreview() {
|
||||
DeleteButton(text = AppText.delete_subject) {}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DialogConfirmButton(@StringRes text: Int, action: () -> Unit) {
|
||||
Button(
|
||||
|
@ -119,4 +140,40 @@ fun DialogCancelButton(@StringRes text: Int, action: () -> Unit) {
|
|||
) {
|
||||
Text(text = stringResource(text))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NewTaskSubjectButton(
|
||||
onClick: () -> Unit,
|
||||
@StringRes text: Int,
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(60.dp)
|
||||
.padding(10.dp, 5.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.4f),
|
||||
),
|
||||
shape = RoundedCornerShape(2.dp),
|
||||
border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.4f)),
|
||||
elevation = null,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(text = stringResource(id = text))
|
||||
Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(id = text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun NewTaskButtonPreview() {
|
||||
NewTaskSubjectButton(onClick = {}, text = AppText.new_task)
|
||||
}
|
|
@ -15,7 +15,7 @@ import androidx.compose.ui.unit.dp
|
|||
import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.TASKS_SCREEN
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN
|
||||
import be.ugent.sel.studeez.resources
|
||||
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
@ -43,7 +43,6 @@ fun getNavigationBarActions(
|
|||
isSelectedTab = { screen ->
|
||||
screen == getCurrentScreen()
|
||||
},
|
||||
|
||||
onHomeClick = {
|
||||
navigationBarViewModel.onHomeClick(open)
|
||||
},
|
||||
|
@ -90,7 +89,7 @@ fun NavigationBar(
|
|||
)
|
||||
},
|
||||
label = { Text(text = resources().getString(AppText.tasks)) },
|
||||
selected = navigationBarActions.isSelectedTab(TASKS_SCREEN),
|
||||
selected = navigationBarActions.isSelectedTab(SUBJECT_SCREEN),
|
||||
onClick = navigationBarActions.onTasksClick
|
||||
)
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package be.ugent.sel.studeez.common.composable.navbar
|
||||
|
||||
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
||||
import be.ugent.sel.studeez.domain.AccountDAO
|
||||
import be.ugent.sel.studeez.domain.LogService
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.TASKS_SCREEN
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN
|
||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
@ -14,7 +13,6 @@ import be.ugent.sel.studeez.R.string as AppText
|
|||
|
||||
@HiltViewModel
|
||||
class NavigationBarViewModel @Inject constructor(
|
||||
private val accountDAO: AccountDAO,
|
||||
logService: LogService
|
||||
) : StudeezViewModel(logService) {
|
||||
|
||||
|
@ -23,7 +21,7 @@ class NavigationBarViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun onTasksClick(open: (String) -> Unit) {
|
||||
open(TASKS_SCREEN)
|
||||
open(SUBJECT_SCREEN)
|
||||
}
|
||||
|
||||
fun onSessionsClick(open: (String) -> Unit) {
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
package be.ugent.sel.studeez.common.composable.tasks
|
||||
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.List
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
import be.ugent.sel.studeez.common.composable.StealthButton
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||
|
||||
@Composable
|
||||
fun SubjectEntry(
|
||||
subject: Subject,
|
||||
onViewSubject: () -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 10.dp, vertical = 5.dp),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp)
|
||||
.weight(3f)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(subject.argb_color)),
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(0.dp)
|
||||
) {
|
||||
Text(
|
||||
text = subject.name,
|
||||
fontWeight = FontWeight.Bold,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = HoursMinutesSeconds(subject.time).toString(),
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(3.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.List,
|
||||
contentDescription = stringResource(id = AppText.tasks)
|
||||
)
|
||||
Text(text = "0/0") // TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StealthButton(
|
||||
text = AppText.view_tasks,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp, end = 5.dp)
|
||||
.weight(1f)
|
||||
) {
|
||||
onViewSubject()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SubjectEntryPreview() {
|
||||
SubjectEntry(
|
||||
subject = Subject(
|
||||
name = "Test Subject",
|
||||
argb_color = 0xFFFFD200,
|
||||
time = 60
|
||||
),
|
||||
) {}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun OverflowSubjectEntryPreview() {
|
||||
SubjectEntry(
|
||||
subject = Subject(
|
||||
name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt",
|
||||
argb_color = 0xFFFFD200,
|
||||
time = 60
|
||||
),
|
||||
) {}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package be.ugent.sel.studeez.common.composable.tasks
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.CheckboxDefaults
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import be.ugent.sel.studeez.R
|
||||
import be.ugent.sel.studeez.common.composable.StealthButton
|
||||
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||
import be.ugent.sel.studeez.resources
|
||||
|
||||
@Composable
|
||||
fun TaskEntry(
|
||||
task: Task,
|
||||
onCheckTask: (Boolean) -> Unit,
|
||||
onDeleteTask: () -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 10.dp, vertical = 5.dp),
|
||||
) {
|
||||
val color = if (task.completed) Color.Gray else MaterialTheme.colors.onSurface
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp)
|
||||
.weight(22f),
|
||||
) {
|
||||
Checkbox(
|
||||
checked = task.completed,
|
||||
onCheckedChange = onCheckTask,
|
||||
colors = CheckboxDefaults.colors(
|
||||
checkedColor = Color.Gray,
|
||||
uncheckedColor = MaterialTheme.colors.onSurface,
|
||||
)
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = task.name,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
color = color,
|
||||
modifier = Modifier.weight(13f),
|
||||
)
|
||||
Text(
|
||||
text = "${HoursMinutesSeconds(task.time)}",
|
||||
color = color,
|
||||
modifier = Modifier.weight(7f)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
Box(modifier = Modifier.weight(7f)) {
|
||||
if (task.completed) {
|
||||
IconButton(
|
||||
onClick = onDeleteTask,
|
||||
modifier = Modifier
|
||||
.padding(start = 20.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = resources().getString(R.string.delete_task),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
StealthButton(
|
||||
text = R.string.start,
|
||||
modifier = Modifier
|
||||
.padding(end = 5.dp),
|
||||
) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun TaskEntryPreview() {
|
||||
TaskEntry(
|
||||
task = Task(
|
||||
name = "Test Task",
|
||||
completed = false,
|
||||
),
|
||||
{}, {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun CompletedTaskEntryPreview() {
|
||||
TaskEntry(
|
||||
task = Task(
|
||||
name = "Test Task",
|
||||
completed = true,
|
||||
),
|
||||
{}, {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun OverflowTaskEntryPreview() {
|
||||
TaskEntry(
|
||||
task = Task(
|
||||
name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk",
|
||||
completed = false,
|
||||
),
|
||||
{}, {},
|
||||
)
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package be.ugent.sel.studeez.data
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Singleton
|
||||
class SelectedSubject @Inject constructor() {
|
||||
private lateinit var subject: Subject
|
||||
operator fun invoke() = subject
|
||||
fun set(subject: Subject) {
|
||||
this.subject = subject
|
||||
}
|
||||
|
||||
fun isSet() = this::subject.isInitialized
|
||||
}
|
21
app/src/main/java/be/ugent/sel/studeez/data/SelectedTask.kt
Normal file
21
app/src/main/java/be/ugent/sel/studeez/data/SelectedTask.kt
Normal 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
|
||||
}
|
|
@ -7,4 +7,4 @@ data class SessionReport(
|
|||
@DocumentId val id: String = "",
|
||||
val studyTime: Int = 0,
|
||||
val endTime: Timestamp = Timestamp(0, 0)
|
||||
)
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package be.ugent.sel.studeez.data.local.models.task
|
||||
|
||||
import com.google.firebase.firestore.DocumentId
|
||||
|
||||
data class Subject(
|
||||
@DocumentId val id: String = "",
|
||||
val name: String = "",
|
||||
val time: Int = 0,
|
||||
val argb_color: Long = 0,
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
package be.ugent.sel.studeez.data.local.models.task
|
||||
|
||||
import com.google.firebase.firestore.DocumentId
|
||||
|
||||
data class Task(
|
||||
@DocumentId val id: String = "",
|
||||
val name: String = "",
|
||||
val completed: Boolean = false,
|
||||
val time: Int = 0,
|
||||
val subjectId: String = "",
|
||||
)
|
||||
|
||||
object TaskDocument {
|
||||
const val id = "id"
|
||||
const val name = "name"
|
||||
const val completed = "completed"
|
||||
const val time = "time"
|
||||
const val subjectId = "subjectId"
|
||||
}
|
|
@ -3,19 +3,19 @@ package be.ugent.sel.studeez.data.local.models.timer_functional
|
|||
data class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) {
|
||||
|
||||
constructor(sec: Int): this(
|
||||
sec / (60 * 60),
|
||||
(sec / (60)) % 60,
|
||||
sec % 60
|
||||
hours = sec / (60 * 60),
|
||||
minutes = (sec / (60)) % 60,
|
||||
seconds = sec % 60,
|
||||
)
|
||||
|
||||
fun getTotalSeconds(): Int {
|
||||
return hours * 60 * 60 + minutes * 60 + seconds
|
||||
return (hours * 60 * 60) + (minutes * 60) + seconds
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val hoursString = hours.toString().padStart(2, '0')
|
||||
val minutesString = minutes.toString().padStart(2, '0')
|
||||
val secondsString = seconds.toString().padStart(2, '0')
|
||||
return "$hoursString : $minutesString : $secondsString"
|
||||
return "$hoursString:$minutesString:$secondsString"
|
||||
}
|
||||
}
|
|
@ -10,4 +10,4 @@ class Time(var time: Int) {
|
|||
fun getAsHMS(): HoursMinutesSeconds {
|
||||
return HoursMinutesSeconds(time)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,15 +10,27 @@ import dagger.hilt.components.SingletonComponent
|
|||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class DatabaseModule {
|
||||
@Binds abstract fun provideAccountDAO(impl: FirebaseAccountDAO): AccountDAO
|
||||
@Binds
|
||||
abstract fun provideAccountDAO(impl: FirebaseAccountDAO): AccountDAO
|
||||
|
||||
@Binds abstract fun provideUserDAO(impl: FirebaseUserDAO): UserDAO
|
||||
@Binds
|
||||
abstract fun provideUserDAO(impl: FirebaseUserDAO): UserDAO
|
||||
|
||||
@Binds abstract fun provideTimerDAO(impl: FirebaseTimerDAO): TimerDAO
|
||||
@Binds
|
||||
abstract fun provideTimerDAO(impl: FirebaseTimerDAO): TimerDAO
|
||||
|
||||
@Binds abstract fun provideLogService(impl: LogServiceImpl): LogService
|
||||
@Binds
|
||||
abstract fun provideLogService(impl: LogServiceImpl): LogService
|
||||
|
||||
@Binds abstract fun provideConfigurationService(impl: FirebaseConfigurationService): ConfigurationService
|
||||
@Binds
|
||||
abstract fun provideConfigurationService(impl: FirebaseConfigurationService): ConfigurationService
|
||||
|
||||
@Binds abstract fun provideSessionDAO(impl: FireBaseSessionDAO): SessionDAO
|
||||
@Binds
|
||||
abstract fun provideSessionDAO(impl: FireBaseSessionDAO): SessionDAO
|
||||
|
||||
@Binds
|
||||
abstract fun provideSubjectDAO(impl: FireBaseSubjectDAO): SubjectDAO
|
||||
|
||||
@Binds
|
||||
abstract fun provideTaskDAO(impl: FireBaseTaskDAO): TaskDAO
|
||||
}
|
15
app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt
Normal file
15
app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt
Normal file
|
@ -0,0 +1,15 @@
|
|||
package be.ugent.sel.studeez.domain
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SubjectDAO {
|
||||
|
||||
fun getSubjects(): Flow<List<Subject>>
|
||||
|
||||
fun saveSubject(newSubject: Subject)
|
||||
|
||||
fun deleteSubject(oldSubject: Subject)
|
||||
|
||||
fun updateSubject(newSubject: Subject)
|
||||
}
|
18
app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt
Normal file
18
app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt
Normal file
|
@ -0,0 +1,18 @@
|
|||
package be.ugent.sel.studeez.domain
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TaskDAO {
|
||||
|
||||
fun getTasks(subject: Subject): Flow<List<Task>>
|
||||
|
||||
fun saveTask(newTask: Task)
|
||||
|
||||
fun updateTask(newTask: Task)
|
||||
|
||||
fun deleteTask(oldTask: Task)
|
||||
|
||||
fun toggleTaskCompleted(task: Task, completed: Boolean)
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
package be.ugent.sel.studeez.domain.implementation
|
||||
|
||||
object FirebaseCollectionRoutes {
|
||||
object FireBaseCollections {
|
||||
const val SESSION_COLLECTION = "sessions"
|
||||
const val USER_COLLECTION = "users"
|
||||
const val TIMER_COLLECTION = "timers"
|
||||
|
||||
const val SUBJECT_COLLECTION = "subjects"
|
||||
const val TASK_COLLECTION = "tasks"
|
||||
}
|
|
@ -31,8 +31,7 @@ class FireBaseSessionDAO @Inject constructor(
|
|||
}
|
||||
|
||||
private fun currentUserSessionsCollection(): CollectionReference =
|
||||
firestore.collection(FirebaseCollectionRoutes.USER_COLLECTION)
|
||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
||||
.document(auth.currentUserId)
|
||||
.collection(FirebaseCollectionRoutes.SESSION_COLLECTION)
|
||||
|
||||
.collection(FireBaseCollections.SESSION_COLLECTION)
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package be.ugent.sel.studeez.domain.implementation
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import be.ugent.sel.studeez.domain.AccountDAO
|
||||
import be.ugent.sel.studeez.domain.SubjectDAO
|
||||
import com.google.firebase.firestore.CollectionReference
|
||||
import com.google.firebase.firestore.FirebaseFirestore
|
||||
import com.google.firebase.firestore.ktx.snapshots
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
class FireBaseSubjectDAO @Inject constructor(
|
||||
private val firestore: FirebaseFirestore,
|
||||
private val auth: AccountDAO,
|
||||
) : SubjectDAO {
|
||||
override fun getSubjects(): Flow<List<Subject>> {
|
||||
return currentUserSubjectsCollection()
|
||||
.snapshots()
|
||||
.map { it.toObjects(Subject::class.java) }
|
||||
}
|
||||
|
||||
override fun saveSubject(newSubject: Subject) {
|
||||
currentUserSubjectsCollection().add(newSubject)
|
||||
}
|
||||
|
||||
override fun deleteSubject(oldSubject: Subject) {
|
||||
currentUserSubjectsCollection().document(oldSubject.id).delete()
|
||||
}
|
||||
|
||||
override fun updateSubject(newSubject: Subject) {
|
||||
currentUserSubjectsCollection().document(newSubject.id).set(newSubject)
|
||||
}
|
||||
|
||||
private fun currentUserSubjectsCollection(): CollectionReference =
|
||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
||||
.document(auth.currentUserId)
|
||||
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package be.ugent.sel.studeez.domain.implementation
|
||||
|
||||
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.TaskDocument
|
||||
import be.ugent.sel.studeez.domain.AccountDAO
|
||||
import be.ugent.sel.studeez.domain.TaskDAO
|
||||
import com.google.firebase.firestore.CollectionReference
|
||||
import com.google.firebase.firestore.FirebaseFirestore
|
||||
import com.google.firebase.firestore.ktx.snapshots
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
class FireBaseTaskDAO @Inject constructor(
|
||||
private val firestore: FirebaseFirestore,
|
||||
private val auth: AccountDAO,
|
||||
) : TaskDAO {
|
||||
override fun getTasks(subject: Subject): Flow<List<Task>> {
|
||||
return selectedSubjectTasksCollection(subject.id)
|
||||
.snapshots()
|
||||
.map { it.toObjects(Task::class.java) }
|
||||
}
|
||||
|
||||
override fun saveTask(newTask: Task) {
|
||||
selectedSubjectTasksCollection(newTask.subjectId).add(newTask)
|
||||
}
|
||||
|
||||
override fun updateTask(newTask: Task) {
|
||||
selectedSubjectTasksCollection(newTask.id).document(newTask.id).set(newTask)
|
||||
}
|
||||
|
||||
override fun deleteTask(oldTask: Task) {
|
||||
selectedSubjectTasksCollection(oldTask.subjectId).document(oldTask.id).delete()
|
||||
}
|
||||
|
||||
override fun toggleTaskCompleted(task: Task, completed: Boolean) {
|
||||
selectedSubjectTasksCollection(task.subjectId)
|
||||
.document(task.id)
|
||||
.update(TaskDocument.completed, completed)
|
||||
}
|
||||
|
||||
private fun selectedSubjectTasksCollection(subjectId: String): CollectionReference =
|
||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
||||
.document(auth.currentUserId)
|
||||
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
||||
.document(subjectId)
|
||||
.collection(FireBaseCollections.TASK_COLLECTION)
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
package be.ugent.sel.studeez.domain.implementation
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.timer_info.*
|
||||
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.*
|
||||
import be.ugent.sel.studeez.domain.AccountDAO
|
||||
import be.ugent.sel.studeez.domain.TimerDAO
|
||||
import com.google.firebase.firestore.CollectionReference
|
||||
import com.google.firebase.firestore.DocumentSnapshot
|
||||
import com.google.firebase.firestore.FirebaseFirestore
|
||||
import com.google.firebase.firestore.ktx.snapshots
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -50,8 +48,8 @@ class FirebaseTimerDAO @Inject constructor(
|
|||
}
|
||||
|
||||
private fun currentUserTimersCollection(): CollectionReference =
|
||||
firestore.collection(FirebaseCollectionRoutes.USER_COLLECTION)
|
||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
||||
.document(auth.currentUserId)
|
||||
.collection(FirebaseCollectionRoutes.TIMER_COLLECTION)
|
||||
.collection(FireBaseCollections.TIMER_COLLECTION)
|
||||
|
||||
}
|
|
@ -3,7 +3,7 @@ package be.ugent.sel.studeez.navigation
|
|||
object StudeezDestinations {
|
||||
// NavBar
|
||||
const val HOME_SCREEN = "home"
|
||||
const val TASKS_SCREEN = "tasks"
|
||||
const val SUBJECT_SCREEN = "subjects"
|
||||
const val SESSIONS_SCREEN = "sessions"
|
||||
const val PROFILE_SCREEN = "profile"
|
||||
|
||||
|
@ -23,6 +23,12 @@ object StudeezDestinations {
|
|||
const val SESSION_SCREEN = "session"
|
||||
const val SESSION_RECAP = "session_recap"
|
||||
|
||||
const val ADD_SUBJECT_FORM = "add_subject"
|
||||
const val EDIT_SUBJECT_FORM = "edit_subject"
|
||||
const val TASKS_SCREEN = "tasks"
|
||||
const val ADD_TASK_FORM = "add_task"
|
||||
const val EDIT_TASK_FORM = "edit_task"
|
||||
|
||||
// Friends flow
|
||||
const val SEARCH_FRIENDS_SCREEN = "search_friends"
|
||||
|
||||
|
|
|
@ -9,7 +9,12 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.graphics.Color
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package be.ugent.sel.studeez.screens.tasks
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton
|
||||
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
|
||||
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
|
||||
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
||||
import be.ugent.sel.studeez.common.composable.tasks.SubjectEntry
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun SubjectRoute(
|
||||
open: (String) -> Unit,
|
||||
viewModel: SubjectViewModel,
|
||||
drawerActions: DrawerActions,
|
||||
navigationBarActions: NavigationBarActions,
|
||||
) {
|
||||
SubjectScreen(
|
||||
drawerActions = drawerActions,
|
||||
navigationBarActions = navigationBarActions,
|
||||
addSubject = { viewModel.addSubject(open) },
|
||||
getSubjects = viewModel::getSubjects,
|
||||
onViewSubject = { viewModel.onViewSubject(it, open) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SubjectScreen(
|
||||
drawerActions: DrawerActions,
|
||||
navigationBarActions: NavigationBarActions,
|
||||
addSubject: () -> Unit,
|
||||
getSubjects: () -> Flow<List<Subject>>,
|
||||
onViewSubject: (Subject) -> Unit,
|
||||
) {
|
||||
PrimaryScreenTemplate(
|
||||
title = stringResource(AppText.my_subjects),
|
||||
drawerActions = drawerActions,
|
||||
navigationBarActions = navigationBarActions,
|
||||
barAction = {},
|
||||
) {
|
||||
val subjects = getSubjects().collectAsState(initial = emptyList())
|
||||
Column(
|
||||
modifier = Modifier.padding(top = 5.dp)
|
||||
) {
|
||||
LazyColumn {
|
||||
items(subjects.value) {
|
||||
SubjectEntry(
|
||||
subject = it,
|
||||
onViewSubject = { onViewSubject(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
NewTaskSubjectButton(onClick = addSubject, AppText.new_subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SubjectScreenPreview() {
|
||||
SubjectScreen(
|
||||
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
|
||||
addSubject = {},
|
||||
getSubjects = { flowOf() },
|
||||
onViewSubject = {},
|
||||
)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package be.ugent.sel.studeez.screens.tasks
|
||||
|
||||
import be.ugent.sel.studeez.data.SelectedSubject
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import be.ugent.sel.studeez.domain.LogService
|
||||
import be.ugent.sel.studeez.domain.SubjectDAO
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SubjectViewModel @Inject constructor(
|
||||
private val subjectDAO: SubjectDAO,
|
||||
private val selectedSubject: SelectedSubject,
|
||||
logService: LogService,
|
||||
) : StudeezViewModel(logService) {
|
||||
fun addSubject(open: (String) -> Unit) {
|
||||
open(StudeezDestinations.ADD_SUBJECT_FORM)
|
||||
}
|
||||
|
||||
fun getSubjects(): Flow<List<Subject>> {
|
||||
return subjectDAO.getSubjects()
|
||||
}
|
||||
|
||||
fun onViewSubject(subject: Subject, open: (String) -> Unit) {
|
||||
selectedSubject.set(subject)
|
||||
open(StudeezDestinations.TASKS_SCREEN)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package be.ugent.sel.studeez.screens.tasks
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton
|
||||
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
|
||||
import be.ugent.sel.studeez.common.composable.tasks.TaskEntry
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
data class TaskActions(
|
||||
val addTask: () -> Unit,
|
||||
val getSubject: () -> Subject,
|
||||
val getTasks: () -> Flow<List<Task>>,
|
||||
val deleteTask: (Task) -> Unit,
|
||||
val onCheckTask: (Task, Boolean) -> Unit,
|
||||
val editSubject: () -> Unit,
|
||||
)
|
||||
|
||||
fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions {
|
||||
return TaskActions(
|
||||
addTask = { viewModel.addTask(open) },
|
||||
getTasks = viewModel::getTasks,
|
||||
getSubject = viewModel::getSelectedSubject,
|
||||
deleteTask = viewModel::deleteTask,
|
||||
onCheckTask = { task, isChecked -> viewModel.toggleTaskCompleted(task, isChecked) },
|
||||
editSubject = { viewModel.editSubject(open) }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TaskRoute(
|
||||
goBack: () -> Unit,
|
||||
open: (String) -> Unit,
|
||||
viewModel: TaskViewModel,
|
||||
) {
|
||||
TaskScreen(
|
||||
goBack = goBack,
|
||||
taskActions = getTaskActions(viewModel = viewModel, open = open),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TaskScreen(
|
||||
goBack: () -> Unit,
|
||||
taskActions: TaskActions,
|
||||
) {
|
||||
SecondaryScreenTemplate(
|
||||
title = taskActions.getSubject().name,
|
||||
popUp = goBack,
|
||||
barAction = { EditAction(onClick = taskActions.editSubject) }
|
||||
) {
|
||||
val tasks = taskActions.getTasks().collectAsState(initial = emptyList())
|
||||
Column(
|
||||
modifier = Modifier.padding(top = 5.dp)
|
||||
) {
|
||||
LazyColumn {
|
||||
items(tasks.value) {
|
||||
TaskEntry(
|
||||
task = it,
|
||||
onCheckTask = { isChecked -> taskActions.onCheckTask(it, isChecked) },
|
||||
onDeleteTask = { taskActions.deleteTask(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
NewTaskSubjectButton(onClick = taskActions.addTask, AppText.new_task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditAction(
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Edit,
|
||||
contentDescription = stringResource(AppText.edit_task)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun TaskScreenPreview() {
|
||||
TaskScreen(
|
||||
goBack = {},
|
||||
taskActions = TaskActions(
|
||||
{},
|
||||
{ Subject(name = "Test Subject") },
|
||||
{ flowOf() },
|
||||
{},
|
||||
{ _, _ -> run {} },
|
||||
{},
|
||||
)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package be.ugent.sel.studeez.screens.tasks
|
||||
|
||||
import be.ugent.sel.studeez.data.SelectedSubject
|
||||
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.domain.LogService
|
||||
import be.ugent.sel.studeez.domain.SubjectDAO
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class TaskViewModel @Inject constructor(
|
||||
private val taskDAO: TaskDAO,
|
||||
private val subjectDAO: SubjectDAO,
|
||||
private val selectedSubject: SelectedSubject,
|
||||
logService: LogService,
|
||||
) : StudeezViewModel(logService) {
|
||||
fun addTask(open: (String) -> Unit) {
|
||||
open(StudeezDestinations.ADD_TASK_FORM)
|
||||
}
|
||||
|
||||
fun getTasks(): Flow<List<Task>> {
|
||||
return taskDAO.getTasks(selectedSubject())
|
||||
}
|
||||
|
||||
fun deleteSubject(open: (String) -> Unit) {
|
||||
subjectDAO.deleteSubject(selectedSubject())
|
||||
open(StudeezDestinations.SUBJECT_SCREEN)
|
||||
}
|
||||
|
||||
fun getSelectedSubject(): Subject {
|
||||
return selectedSubject()
|
||||
}
|
||||
|
||||
fun deleteTask(task: Task) {
|
||||
taskDAO.deleteTask(task)
|
||||
}
|
||||
|
||||
fun toggleTaskCompleted(task: Task, completed: Boolean) {
|
||||
taskDAO.toggleTaskCompleted(task, completed)
|
||||
}
|
||||
|
||||
fun editSubject(open: (String) -> Unit) {
|
||||
open(StudeezDestinations.EDIT_SUBJECT_FORM)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
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.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
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
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun SubjectAddRoute(
|
||||
goBack: () -> Unit,
|
||||
openAndPopUp: (String, String) -> Unit,
|
||||
viewModel: SubjectFormViewModel,
|
||||
) {
|
||||
val uiState by viewModel.uiState
|
||||
SubjectForm(
|
||||
title = AppText.new_subject,
|
||||
goBack = goBack,
|
||||
uiState = uiState,
|
||||
onConfirm = { viewModel.onCreate(openAndPopUp) },
|
||||
onNameChange = viewModel::onNameChange,
|
||||
onColorChange = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SubjectEditRoute(
|
||||
goBack: () -> Unit,
|
||||
openAndPopUp: (String, String) -> Unit,
|
||||
viewModel: SubjectFormViewModel,
|
||||
) {
|
||||
val uiState by viewModel.uiState
|
||||
SubjectForm(
|
||||
title = AppText.edit_subject,
|
||||
goBack = goBack,
|
||||
uiState = uiState,
|
||||
onConfirm = { viewModel.onEdit(openAndPopUp) },
|
||||
onNameChange = viewModel::onNameChange,
|
||||
onColorChange = {},
|
||||
) {
|
||||
DeleteButton(text = AppText.delete_subject) {
|
||||
viewModel.onDelete(openAndPopUp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SubjectForm(
|
||||
@StringRes title: Int,
|
||||
goBack: () -> Unit,
|
||||
uiState: SubjectFormUiState,
|
||||
onConfirm: () -> Unit,
|
||||
onNameChange: (String) -> Unit,
|
||||
onColorChange: (Color) -> Unit,
|
||||
extraButton: @Composable () -> Unit = {},
|
||||
) {
|
||||
SecondaryScreenTemplate(
|
||||
title = resources().getString(title),
|
||||
popUp = goBack,
|
||||
) {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
singleLine = true,
|
||||
value = uiState.name,
|
||||
onValueChange = onNameChange,
|
||||
placeholder = { Text(stringResource(id = AppText.name)) },
|
||||
modifier = Modifier.fieldModifier(),
|
||||
)
|
||||
BasicButton(
|
||||
text = AppText.confirm,
|
||||
modifier = Modifier.basicButton(),
|
||||
onClick = onConfirm,
|
||||
)
|
||||
extraButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AddSubjectFormPreview() {
|
||||
SubjectForm(
|
||||
title = AppText.new_subject,
|
||||
goBack = {},
|
||||
uiState = SubjectFormUiState(),
|
||||
onConfirm = {},
|
||||
onNameChange = {},
|
||||
onColorChange = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun EditSubjectFormPreview() {
|
||||
SubjectForm(
|
||||
title = AppText.edit_subject,
|
||||
goBack = {},
|
||||
uiState = SubjectFormUiState(
|
||||
name = "Test Subject",
|
||||
),
|
||||
onConfirm = {},
|
||||
onNameChange = {},
|
||||
onColorChange = {},
|
||||
) {
|
||||
DeleteButton(text = AppText.delete_subject) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package be.ugent.sel.studeez.screens.tasks.forms
|
||||
|
||||
data class SubjectFormUiState(
|
||||
val name: String = "",
|
||||
val color: Long = 0xFFFFD200,
|
||||
)
|
|
@ -0,0 +1,69 @@
|
|||
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.local.models.task.Subject
|
||||
import be.ugent.sel.studeez.domain.LogService
|
||||
import be.ugent.sel.studeez.domain.SubjectDAO
|
||||
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 SubjectFormViewModel @Inject constructor(
|
||||
private val subjectDAO: SubjectDAO,
|
||||
private val selectedSubject: SelectedSubject,
|
||||
logService: LogService,
|
||||
) : StudeezViewModel(logService) {
|
||||
var uiState = mutableStateOf(
|
||||
if (selectedSubject.isSet()) SubjectFormUiState(
|
||||
name = selectedSubject().name,
|
||||
color = selectedSubject().argb_color
|
||||
)
|
||||
else SubjectFormUiState()
|
||||
)
|
||||
private set
|
||||
|
||||
private val name: String
|
||||
get() = uiState.value.name
|
||||
|
||||
private val color: Long
|
||||
get() = uiState.value.color
|
||||
|
||||
fun onNameChange(newValue: String) {
|
||||
uiState.value = uiState.value.copy(name = newValue)
|
||||
}
|
||||
|
||||
fun onColorChange(newValue: Long) {
|
||||
uiState.value = uiState.value.copy(color = newValue)
|
||||
}
|
||||
|
||||
fun onDelete(openAndPopUp: (String, String) -> Unit) {
|
||||
subjectDAO.deleteSubject(selectedSubject())
|
||||
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM)
|
||||
}
|
||||
|
||||
fun onCreate(openAndPopUp: (String, String) -> Unit) {
|
||||
val newSubject = Subject(
|
||||
name = name,
|
||||
argb_color = color,
|
||||
)
|
||||
subjectDAO.saveSubject(
|
||||
newSubject
|
||||
)
|
||||
// TODO open newly created subject
|
||||
// selectedSubject.set(newSubject)
|
||||
// open(StudeezDestinations.TASKS_SCREEN)
|
||||
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.ADD_SUBJECT_FORM)
|
||||
}
|
||||
|
||||
fun onEdit(openAndPopUp: (String, String) -> Unit) {
|
||||
val newSubject = selectedSubject().copy(
|
||||
name = name,
|
||||
argb_color = color,
|
||||
)
|
||||
subjectDAO.updateSubject(newSubject)
|
||||
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM)
|
||||
}
|
||||
}
|
|
@ -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.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
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun TaskAddRoute(
|
||||
goBack: () -> Unit,
|
||||
openAndPopUp: (String, String) -> Unit,
|
||||
viewModel: TaskFormViewModel,
|
||||
) {
|
||||
val uiState by viewModel.uiState
|
||||
TaskForm(
|
||||
title = AppText.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 = AppText.edit_task,
|
||||
goBack = goBack,
|
||||
uiState = uiState,
|
||||
onConfirm = { viewModel.onEdit(openAndPopUp) },
|
||||
onNameChange = viewModel::onNameChange
|
||||
) {
|
||||
DeleteButton(text = AppText.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 = AppText.name)) },
|
||||
modifier = Modifier.fieldModifier(),
|
||||
)
|
||||
BasicButton(
|
||||
text = AppText.confirm,
|
||||
modifier = Modifier.basicButton(),
|
||||
onClick = onConfirm,
|
||||
)
|
||||
extraButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AddTaskFormPreview() {
|
||||
TaskForm(
|
||||
title = AppText.new_task,
|
||||
goBack = {},
|
||||
uiState = TaskFormUiState(),
|
||||
onConfirm = {},
|
||||
onNameChange = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun EditTaskFormPreview() {
|
||||
TaskForm(
|
||||
title = AppText.edit_task,
|
||||
goBack = {},
|
||||
uiState = TaskFormUiState(
|
||||
name = "Test Task",
|
||||
),
|
||||
onConfirm = {},
|
||||
onNameChange = {},
|
||||
) {
|
||||
DeleteButton(text = AppText.delete_task) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package be.ugent.sel.studeez.screens.tasks.forms
|
||||
|
||||
data class TaskFormUiState(
|
||||
val name: String = "",
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -6,7 +6,11 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.*
|
||||
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 be.ugent.sel.studeez.R
|
||||
|
|
|
@ -32,6 +32,14 @@
|
|||
<!-- Tasks -->
|
||||
<string name="tasks">Tasks</string>
|
||||
<string name="task">Task</string>
|
||||
<string name="my_subjects">My Subjects</string>
|
||||
<string name="new_subject">New Subject</string>
|
||||
<string name="new_task">New Task</string>
|
||||
<string name="edit_subject">Edit Subject</string>
|
||||
<string name="edit_task">Edit Task</string>
|
||||
<string name="delete_subject">Delete Subject</string>
|
||||
<string name="delete_task">Delete Task</string>
|
||||
<string name="view_tasks">View</string>
|
||||
|
||||
<!-- Sessions -->
|
||||
<string name="sessions_temp_description">Looks like you found the sessions screen! In here, your upcoming studying sessions with friends will be listed. You can accept invites or edit your own.</string> <!-- TODO Remove this description line once implemented. -->
|
||||
|
|
Reference in a new issue