resolve conflicts and merge
This commit is contained in:
		
						commit
						13b9c0591f
					
				
					 38 changed files with 1291 additions and 64 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Reference in a new issue