diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index cd54531..53a1745 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -42,6 +42,5 @@
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 704c883..0ad17cb 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/app/build.gradle b/app/build.gradle
index 6489d31..a19cbd7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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
diff --git a/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt b/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt
index c002c61..3c93072 100644
--- a/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt
@@ -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,
)
}
diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt
index ae675e5..73ae1b5 100644
--- a/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt
@@ -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)
}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarComposable.kt
index 56b81eb..c4d6e33 100644
--- a/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarComposable.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarComposable.kt
@@ -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
)
diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarViewModel.kt
index e7678e5..07a5bf9 100644
--- a/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarViewModel.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/navbar/NavigationBarViewModel.kt
@@ -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) {
diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/SubjectEntry.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/SubjectEntry.kt
new file mode 100644
index 0000000..8655ba3
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/SubjectEntry.kt
@@ -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
+ ),
+ ) {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/TaskEntry.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/TaskEntry.kt
new file mode 100644
index 0000000..fefb924
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/TaskEntry.kt
@@ -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,
+ ),
+ {}, {},
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/data/SelectedSubject.kt b/app/src/main/java/be/ugent/sel/studeez/data/SelectedSubject.kt
new file mode 100644
index 0000000..fbc7e48
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/data/SelectedSubject.kt
@@ -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
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/data/SelectedTask.kt b/app/src/main/java/be/ugent/sel/studeez/data/SelectedTask.kt
new file mode 100644
index 0000000..9c3f042
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/data/SelectedTask.kt
@@ -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
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/SessionReport.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/SessionReport.kt
index af6b1c0..20a44a8 100644
--- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/SessionReport.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/SessionReport.kt
@@ -7,4 +7,4 @@ data class SessionReport(
@DocumentId val id: String = "",
val studyTime: Int = 0,
val endTime: Timestamp = Timestamp(0, 0)
- )
\ No newline at end of file
+)
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Subject.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Subject.kt
new file mode 100644
index 0000000..e84c2bb
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Subject.kt
@@ -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,
+)
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Task.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Task.kt
new file mode 100644
index 0000000..f2618db
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Task.kt
@@ -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"
+}
diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/HoursMinutesSeconds.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/HoursMinutesSeconds.kt
index d09d8a7..edccbd0 100644
--- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/HoursMinutesSeconds.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/HoursMinutesSeconds.kt
@@ -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"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/Time.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/Time.kt
index 7260faa..e37b374 100644
--- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/Time.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/Time.kt
@@ -10,4 +10,4 @@ class Time(var time: Int) {
fun getAsHMS(): HoursMinutesSeconds {
return HoursMinutesSeconds(time)
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt b/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt
index 1b696fe..7ee4992 100644
--- a/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt
@@ -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
}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt
new file mode 100644
index 0000000..2749fac
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt
@@ -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>
+
+ fun saveSubject(newSubject: Subject)
+
+ fun deleteSubject(oldSubject: Subject)
+
+ fun updateSubject(newSubject: Subject)
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt
new file mode 100644
index 0000000..0f629ea
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt
@@ -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>
+
+ fun saveTask(newTask: Task)
+
+ fun updateTask(newTask: Task)
+
+ fun deleteTask(oldTask: Task)
+
+ fun toggleTaskCompleted(task: Task, completed: Boolean)
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseCollectionRoutes.kt b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseCollections.kt
similarity index 61%
rename from app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseCollectionRoutes.kt
rename to app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseCollections.kt
index 2471301..78867c9 100644
--- a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseCollectionRoutes.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseCollections.kt
@@ -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"
}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSessionDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSessionDAO.kt
index 07afdda..a818236 100644
--- a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSessionDAO.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSessionDAO.kt
@@ -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)
}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSubjectDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSubjectDAO.kt
new file mode 100644
index 0000000..7d90fbf
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSubjectDAO.kt
@@ -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> {
+ 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)
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseTaskDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseTaskDAO.kt
new file mode 100644
index 0000000..b8855e6
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseTaskDAO.kt
@@ -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> {
+ 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)
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseTimerDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseTimerDAO.kt
index 901f9d6..1f37a18 100644
--- a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseTimerDAO.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseTimerDAO.kt
@@ -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)
}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt
index 999ece1..49856c9 100644
--- a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt
@@ -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"
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt
index 06a80b6..65f5d24 100644
--- a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt
@@ -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
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectScreen.kt
new file mode 100644
index 0000000..15a3925
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectScreen.kt
@@ -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>,
+ 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 = {},
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectViewModel.kt
new file mode 100644
index 0000000..f1d6071
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectViewModel.kt
@@ -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> {
+ return subjectDAO.getSubjects()
+ }
+
+ fun onViewSubject(subject: Subject, open: (String) -> Unit) {
+ selectedSubject.set(subject)
+ open(StudeezDestinations.TASKS_SCREEN)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskScreen.kt
new file mode 100644
index 0000000..67f0e93
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskScreen.kt
@@ -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>,
+ 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 {} },
+ {},
+ )
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskViewModel.kt
new file mode 100644
index 0000000..138d32c
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskViewModel.kt
@@ -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> {
+ 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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormScreen.kt
new file mode 100644
index 0000000..74bc7d2
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormScreen.kt
@@ -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) {}
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormUiState.kt
new file mode 100644
index 0000000..5418b74
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormUiState.kt
@@ -0,0 +1,6 @@
+package be.ugent.sel.studeez.screens.tasks.forms
+
+data class SubjectFormUiState(
+ val name: String = "",
+ val color: Long = 0xFFFFD200,
+)
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormViewModel.kt
new file mode 100644
index 0000000..68ebd3e
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormViewModel.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormScreen.kt
new file mode 100644
index 0000000..62b6c6c
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormScreen.kt
@@ -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) {}
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormUiState.kt
new file mode 100644
index 0000000..d967d59
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormUiState.kt
@@ -0,0 +1,5 @@
+package be.ugent.sel.studeez.screens.tasks.forms
+
+data class TaskFormUiState(
+ val name: String = "",
+)
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormViewModel.kt
new file mode 100644
index 0000000..03ad32b
--- /dev/null
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormViewModel.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt
index 44043ed..5f4a17b 100644
--- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt
+++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt
@@ -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
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 263dc69..d51259c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -32,6 +32,14 @@
Tasks
Task
+ My Subjects
+ New Subject
+ New Task
+ Edit Subject
+ Edit Task
+ Delete Subject
+ Delete Task
+ View
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.