commit
d22d40439d
24 changed files with 380 additions and 205 deletions
|
@ -2,14 +2,16 @@ package be.ugent.sel.studeez.common.composable.feed
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import be.ugent.sel.studeez.data.SelectedTask
|
import be.ugent.sel.studeez.data.SelectedTask
|
||||||
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
|
||||||
import be.ugent.sel.studeez.domain.FeedDAO
|
import be.ugent.sel.studeez.domain.FeedDAO
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
import be.ugent.sel.studeez.domain.TaskDAO
|
import be.ugent.sel.studeez.domain.TaskDAO
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
package be.ugent.sel.studeez.common.composable.tasks
|
package be.ugent.sel.studeez.common.composable.tasks
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.*
|
||||||
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.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.Card
|
import androidx.compose.material.Card
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
|
@ -15,6 +9,8 @@ import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.List
|
import androidx.compose.material.icons.filled.List
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
@ -24,16 +20,20 @@ import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
|
||||||
import be.ugent.sel.studeez.common.composable.StealthButton
|
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.task.Subject
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SubjectEntry(
|
fun SubjectEntry(
|
||||||
subject: Subject,
|
subject: Subject,
|
||||||
onViewSubject: () -> Unit,
|
onViewSubject: () -> Unit,
|
||||||
|
getStudyTime: () -> Flow<Int>,
|
||||||
) {
|
) {
|
||||||
|
val studytime by getStudyTime().collectAsState(initial = 0)
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
@ -70,7 +70,7 @@ fun SubjectEntry(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = HoursMinutesSeconds(subject.time).toString(),
|
text = HoursMinutesSeconds(studytime).toString(),
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
@ -80,7 +80,7 @@ fun SubjectEntry(
|
||||||
imageVector = Icons.Default.List,
|
imageVector = Icons.Default.List,
|
||||||
contentDescription = stringResource(id = AppText.tasks)
|
contentDescription = stringResource(id = AppText.tasks)
|
||||||
)
|
)
|
||||||
Text(text = "0/0") // TODO
|
Text(text = "${subject.taskCompletedCount}/${subject.taskCount}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,9 +104,12 @@ fun SubjectEntryPreview() {
|
||||||
subject = Subject(
|
subject = Subject(
|
||||||
name = "Test Subject",
|
name = "Test Subject",
|
||||||
argb_color = 0xFFFFD200,
|
argb_color = 0xFFFFD200,
|
||||||
time = 60
|
taskCount = 5,
|
||||||
|
taskCompletedCount = 2,
|
||||||
),
|
),
|
||||||
) {}
|
onViewSubject = {},
|
||||||
|
getStudyTime = { flowOf() }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
|
@ -116,7 +119,8 @@ fun OverflowSubjectEntryPreview() {
|
||||||
subject = Subject(
|
subject = Subject(
|
||||||
name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt",
|
name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt",
|
||||||
argb_color = 0xFFFFD200,
|
argb_color = 0xFFFFD200,
|
||||||
time = 60
|
|
||||||
),
|
),
|
||||||
) {}
|
onViewSubject = {},
|
||||||
|
getStudyTime = { flowOf() }
|
||||||
|
)
|
||||||
}
|
}
|
|
@ -1,17 +1,7 @@
|
||||||
package be.ugent.sel.studeez.common.composable.tasks
|
package be.ugent.sel.studeez.common.composable.tasks
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.material.*
|
||||||
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.Icons
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -31,7 +21,7 @@ import be.ugent.sel.studeez.resources
|
||||||
fun TaskEntry(
|
fun TaskEntry(
|
||||||
task: Task,
|
task: Task,
|
||||||
onCheckTask: (Boolean) -> Unit,
|
onCheckTask: (Boolean) -> Unit,
|
||||||
onDeleteTask: () -> Unit,
|
onArchiveTask: () -> Unit,
|
||||||
onStartTask: () -> Unit
|
onStartTask: () -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
|
@ -81,7 +71,7 @@ fun TaskEntry(
|
||||||
Box(modifier = Modifier.weight(7f)) {
|
Box(modifier = Modifier.weight(7f)) {
|
||||||
if (task.completed) {
|
if (task.completed) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onDeleteTask,
|
onClick = onArchiveTask,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 20.dp)
|
.padding(start = 20.dp)
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,10 +1,22 @@
|
||||||
package be.ugent.sel.studeez.data.local.models.task
|
package be.ugent.sel.studeez.data.local.models.task
|
||||||
|
|
||||||
import com.google.firebase.firestore.DocumentId
|
import com.google.firebase.firestore.DocumentId
|
||||||
|
import com.google.firebase.firestore.Exclude
|
||||||
|
|
||||||
data class Subject(
|
data class Subject(
|
||||||
@DocumentId val id: String = "",
|
@DocumentId val id: String = "",
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
val time: Int = 0,
|
|
||||||
val argb_color: Long = 0,
|
val argb_color: Long = 0,
|
||||||
|
var archived: Boolean = false,
|
||||||
|
@get:Exclude @set:Exclude
|
||||||
|
var taskCount: Int = 0,
|
||||||
|
@get:Exclude @set:Exclude
|
||||||
|
var taskCompletedCount: Int = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
object SubjectDocument {
|
||||||
|
const val id = "id"
|
||||||
|
const val name = "name"
|
||||||
|
const val archived = "archived"
|
||||||
|
const val argb_color = "argb_color"
|
||||||
|
}
|
|
@ -5,10 +5,15 @@ import com.google.firebase.firestore.DocumentId
|
||||||
data class Task(
|
data class Task(
|
||||||
@DocumentId val id: String = "",
|
@DocumentId val id: String = "",
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
val completed: Boolean = false,
|
var completed: Boolean = false,
|
||||||
val time: Int = 0,
|
val time: Int = 0,
|
||||||
val subjectId: String = "",
|
val subjectId: String = "",
|
||||||
)
|
var archived: Boolean = false,
|
||||||
|
) {
|
||||||
|
fun archive() {
|
||||||
|
this.archived = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object TaskDocument {
|
object TaskDocument {
|
||||||
const val id = "id"
|
const val id = "id"
|
||||||
|
@ -16,4 +21,5 @@ object TaskDocument {
|
||||||
const val completed = "completed"
|
const val completed = "completed"
|
||||||
const val time = "time"
|
const val time = "time"
|
||||||
const val subjectId = "subjectId"
|
const val subjectId = "subjectId"
|
||||||
|
const val archived = "archived"
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,5 +12,10 @@ interface SubjectDAO {
|
||||||
fun deleteSubject(oldSubject: Subject)
|
fun deleteSubject(oldSubject: Subject)
|
||||||
|
|
||||||
fun updateSubject(newSubject: Subject)
|
fun updateSubject(newSubject: Subject)
|
||||||
|
|
||||||
|
suspend fun getTaskCount(subject: Subject): Int
|
||||||
|
suspend fun getCompletedTaskCount(subject: Subject): Int
|
||||||
|
fun getStudyTime(subject: Subject): Flow<Int>
|
||||||
|
|
||||||
suspend fun getSubject(subjectId: String): Subject?
|
suspend fun getSubject(subjectId: String): Subject?
|
||||||
}
|
}
|
|
@ -14,7 +14,5 @@ interface TaskDAO {
|
||||||
|
|
||||||
fun deleteTask(oldTask: Task)
|
fun deleteTask(oldTask: Task)
|
||||||
|
|
||||||
fun toggleTaskCompleted(task: Task, completed: Boolean)
|
|
||||||
|
|
||||||
suspend fun getTask(subjectId: String, taskId: String): Task
|
suspend fun getTask(subjectId: String, taskId: String): Task
|
||||||
}
|
}
|
|
@ -1,11 +1,14 @@
|
||||||
package be.ugent.sel.studeez.domain.implementation
|
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.Subject
|
||||||
import be.ugent.sel.studeez.data.local.models.task.Task
|
import be.ugent.sel.studeez.data.local.models.task.SubjectDocument
|
||||||
import be.ugent.sel.studeez.domain.AccountDAO
|
import be.ugent.sel.studeez.domain.AccountDAO
|
||||||
import be.ugent.sel.studeez.domain.SubjectDAO
|
import be.ugent.sel.studeez.domain.SubjectDAO
|
||||||
|
import be.ugent.sel.studeez.domain.TaskDAO
|
||||||
|
import com.google.firebase.firestore.AggregateSource
|
||||||
import com.google.firebase.firestore.CollectionReference
|
import com.google.firebase.firestore.CollectionReference
|
||||||
import com.google.firebase.firestore.FirebaseFirestore
|
import com.google.firebase.firestore.FirebaseFirestore
|
||||||
|
import com.google.firebase.firestore.Query
|
||||||
import com.google.firebase.firestore.ktx.snapshots
|
import com.google.firebase.firestore.ktx.snapshots
|
||||||
import com.google.firebase.firestore.ktx.toObject
|
import com.google.firebase.firestore.ktx.toObject
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -16,11 +19,20 @@ import javax.inject.Inject
|
||||||
class FireBaseSubjectDAO @Inject constructor(
|
class FireBaseSubjectDAO @Inject constructor(
|
||||||
private val firestore: FirebaseFirestore,
|
private val firestore: FirebaseFirestore,
|
||||||
private val auth: AccountDAO,
|
private val auth: AccountDAO,
|
||||||
|
private val taskDAO: TaskDAO,
|
||||||
) : SubjectDAO {
|
) : SubjectDAO {
|
||||||
override fun getSubjects(): Flow<List<Subject>> {
|
override fun getSubjects(): Flow<List<Subject>> {
|
||||||
return currentUserSubjectsCollection()
|
return currentUserSubjectsCollection()
|
||||||
|
.subjectNotArchived()
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.map { it.toObjects(Subject::class.java) }
|
.map { it.toObjects(Subject::class.java) }
|
||||||
|
.map { subjects ->
|
||||||
|
subjects.map { subject ->
|
||||||
|
subject.taskCount = getTaskCount(subject)
|
||||||
|
subject.taskCompletedCount = getCompletedTaskCount(subject)
|
||||||
|
subject
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getSubject(subjectId: String): Subject? {
|
override suspend fun getSubject(subjectId: String): Subject? {
|
||||||
|
@ -39,8 +51,45 @@ class FireBaseSubjectDAO @Inject constructor(
|
||||||
currentUserSubjectsCollection().document(newSubject.id).set(newSubject)
|
currentUserSubjectsCollection().document(newSubject.id).set(newSubject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getTaskCount(subject: Subject): Int {
|
||||||
|
return subjectTasksCollection(subject)
|
||||||
|
.taskNotArchived()
|
||||||
|
.count()
|
||||||
|
.get(AggregateSource.SERVER)
|
||||||
|
.await()
|
||||||
|
.count.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCompletedTaskCount(subject: Subject): Int {
|
||||||
|
return subjectTasksCollection(subject)
|
||||||
|
.taskNotArchived()
|
||||||
|
.taskNotCompleted()
|
||||||
|
.count()
|
||||||
|
.get(AggregateSource.SERVER)
|
||||||
|
.await()
|
||||||
|
.count.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStudyTime(subject: Subject): Flow<Int> {
|
||||||
|
return taskDAO.getTasks(subject)
|
||||||
|
.map { tasks -> tasks.sumOf { it.time } }
|
||||||
|
}
|
||||||
|
|
||||||
private fun currentUserSubjectsCollection(): CollectionReference =
|
private fun currentUserSubjectsCollection(): CollectionReference =
|
||||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
||||||
.document(auth.currentUserId)
|
.document(auth.currentUserId)
|
||||||
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
||||||
|
|
||||||
|
private fun subjectTasksCollection(subject: Subject): CollectionReference =
|
||||||
|
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
||||||
|
.document(auth.currentUserId)
|
||||||
|
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
||||||
|
.document(subject.id)
|
||||||
|
.collection(FireBaseCollections.TASK_COLLECTION)
|
||||||
|
|
||||||
|
fun CollectionReference.subjectNotArchived(): Query =
|
||||||
|
this.whereEqualTo(SubjectDocument.archived, false)
|
||||||
|
|
||||||
|
fun Query.subjectNotArchived(): Query =
|
||||||
|
this.whereEqualTo(SubjectDocument.archived, false)
|
||||||
}
|
}
|
|
@ -7,10 +7,10 @@ import be.ugent.sel.studeez.domain.AccountDAO
|
||||||
import be.ugent.sel.studeez.domain.TaskDAO
|
import be.ugent.sel.studeez.domain.TaskDAO
|
||||||
import com.google.firebase.firestore.CollectionReference
|
import com.google.firebase.firestore.CollectionReference
|
||||||
import com.google.firebase.firestore.FirebaseFirestore
|
import com.google.firebase.firestore.FirebaseFirestore
|
||||||
|
import com.google.firebase.firestore.Query
|
||||||
import com.google.firebase.firestore.ktx.snapshots
|
import com.google.firebase.firestore.ktx.snapshots
|
||||||
import com.google.firebase.firestore.ktx.toObject
|
import com.google.firebase.firestore.ktx.toObject
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.tasks.await
|
import kotlinx.coroutines.tasks.await
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -21,6 +21,7 @@ class FireBaseTaskDAO @Inject constructor(
|
||||||
) : TaskDAO {
|
) : TaskDAO {
|
||||||
override fun getTasks(subject: Subject): Flow<List<Task>> {
|
override fun getTasks(subject: Subject): Flow<List<Task>> {
|
||||||
return selectedSubjectTasksCollection(subject.id)
|
return selectedSubjectTasksCollection(subject.id)
|
||||||
|
.taskNotArchived()
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.map { it.toObjects(Task::class.java) }
|
.map { it.toObjects(Task::class.java) }
|
||||||
}
|
}
|
||||||
|
@ -34,23 +35,34 @@ class FireBaseTaskDAO @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateTask(newTask: Task) {
|
override fun updateTask(newTask: Task) {
|
||||||
selectedSubjectTasksCollection(newTask.id).document(newTask.id).set(newTask)
|
selectedSubjectTasksCollection(newTask.subjectId)
|
||||||
|
.document(newTask.id)
|
||||||
|
.set(newTask)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteTask(oldTask: Task) {
|
override fun deleteTask(oldTask: Task) {
|
||||||
selectedSubjectTasksCollection(oldTask.subjectId).document(oldTask.id).delete()
|
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 =
|
private fun selectedSubjectTasksCollection(subjectId: String): CollectionReference =
|
||||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
||||||
.document(auth.currentUserId)
|
.document(auth.currentUserId)
|
||||||
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
||||||
.document(subjectId)
|
.document(subjectId)
|
||||||
.collection(FireBaseCollections.TASK_COLLECTION)
|
.collection(FireBaseCollections.TASK_COLLECTION)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extend CollectionReference and Query with some filters
|
||||||
|
|
||||||
|
fun CollectionReference.taskNotArchived(): Query =
|
||||||
|
this.whereEqualTo(TaskDocument.archived, false)
|
||||||
|
|
||||||
|
fun Query.taskNotArchived(): Query =
|
||||||
|
this.whereEqualTo(TaskDocument.archived, false)
|
||||||
|
|
||||||
|
fun CollectionReference.taskNotCompleted(): Query =
|
||||||
|
this.whereEqualTo(TaskDocument.completed, true)
|
||||||
|
|
||||||
|
fun Query.taskNotCompleted(): Query =
|
||||||
|
this.whereEqualTo(TaskDocument.completed, true)
|
||||||
|
|
|
@ -7,30 +7,32 @@ import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||||
import be.ugent.sel.studeez.data.local.models.task.Task
|
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||||
import be.ugent.sel.studeez.domain.FeedDAO
|
import be.ugent.sel.studeez.domain.FeedDAO
|
||||||
import be.ugent.sel.studeez.domain.SessionDAO
|
import be.ugent.sel.studeez.domain.SessionDAO
|
||||||
|
import be.ugent.sel.studeez.domain.SubjectDAO
|
||||||
import be.ugent.sel.studeez.domain.TaskDAO
|
import be.ugent.sel.studeez.domain.TaskDAO
|
||||||
import com.google.firebase.Timestamp
|
import com.google.firebase.Timestamp
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class FirebaseFeedDAO @Inject constructor(
|
class FirebaseFeedDAO @Inject constructor(
|
||||||
private val sessionDAO: SessionDAO,
|
private val sessionDAO: SessionDAO,
|
||||||
private val taskDAO: TaskDAO,
|
private val taskDAO: TaskDAO,
|
||||||
private val subjectDAO: FireBaseSubjectDAO
|
private val subjectDAO: SubjectDAO
|
||||||
) : FeedDAO {
|
) : FeedDAO {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a map as with key the day and value a list of feedentries for that day.
|
* Return a map as with key the day and value a list of feedentries for that day.
|
||||||
*/
|
*/
|
||||||
override fun getFeedEntries(): Flow<Map<String, List<FeedEntry>>> {
|
override fun getFeedEntries(): Flow<Map<String, List<FeedEntry>>> {
|
||||||
return sessionDAO.getSessions().map {sessionReports ->
|
return sessionDAO.getSessions().map { sessionReports ->
|
||||||
sessionReports
|
sessionReports
|
||||||
.map { sessionReport -> sessionToFeedEntry(sessionReport) }
|
.map { sessionReport -> sessionToFeedEntry(sessionReport) }
|
||||||
.sortedByDescending { it.endTime }
|
.sortedByDescending { it.endTime }
|
||||||
.groupBy { getFormattedTime(it) }
|
.groupBy { getFormattedTime(it) }
|
||||||
.mapValues { (_, entries) ->
|
.mapValues { (_, entries) ->
|
||||||
entries
|
entries
|
||||||
.groupBy { it.taskId }
|
.groupBy { it.taskId }
|
||||||
.map { fuseFeedEntries(it.component2()) }
|
.map { fuseFeedEntries(it.component2()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,12 +24,12 @@ import be.ugent.sel.studeez.screens.sessions.SessionsRoute
|
||||||
import be.ugent.sel.studeez.screens.settings.SettingsRoute
|
import be.ugent.sel.studeez.screens.settings.SettingsRoute
|
||||||
import be.ugent.sel.studeez.screens.sign_up.SignUpRoute
|
import be.ugent.sel.studeez.screens.sign_up.SignUpRoute
|
||||||
import be.ugent.sel.studeez.screens.splash.SplashRoute
|
import be.ugent.sel.studeez.screens.splash.SplashRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.SubjectRoute
|
import be.ugent.sel.studeez.screens.subjects.SubjectRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.TaskRoute
|
import be.ugent.sel.studeez.screens.tasks.TaskRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.SubjectAddRoute
|
import be.ugent.sel.studeez.screens.subjects.form.SubjectCreateRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.SubjectEditRoute
|
import be.ugent.sel.studeez.screens.subjects.form.SubjectEditRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.TaskAddRoute
|
import be.ugent.sel.studeez.screens.tasks.form.TaskCreateRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.TaskEditRoute
|
import be.ugent.sel.studeez.screens.tasks.form.TaskEditRoute
|
||||||
import be.ugent.sel.studeez.screens.timer_form.TimerAddRoute
|
import be.ugent.sel.studeez.screens.timer_form.TimerAddRoute
|
||||||
import be.ugent.sel.studeez.screens.timer_form.TimerEditRoute
|
import be.ugent.sel.studeez.screens.timer_form.TimerEditRoute
|
||||||
import be.ugent.sel.studeez.screens.timer_form.timer_type_select.TimerTypeSelectScreen
|
import be.ugent.sel.studeez.screens.timer_form.timer_type_select.TimerTypeSelectScreen
|
||||||
|
@ -81,7 +81,7 @@ fun StudeezNavGraph(
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(StudeezDestinations.ADD_SUBJECT_FORM) {
|
composable(StudeezDestinations.ADD_SUBJECT_FORM) {
|
||||||
SubjectAddRoute(
|
SubjectCreateRoute(
|
||||||
goBack = goBack,
|
goBack = goBack,
|
||||||
openAndPopUp = openAndPopUp,
|
openAndPopUp = openAndPopUp,
|
||||||
viewModel = hiltViewModel(),
|
viewModel = hiltViewModel(),
|
||||||
|
@ -105,7 +105,7 @@ fun StudeezNavGraph(
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(StudeezDestinations.ADD_TASK_FORM) {
|
composable(StudeezDestinations.ADD_TASK_FORM) {
|
||||||
TaskAddRoute(
|
TaskCreateRoute(
|
||||||
goBack = goBack,
|
goBack = goBack,
|
||||||
openAndPopUp = openAndPopUp,
|
openAndPopUp = openAndPopUp,
|
||||||
viewModel = hiltViewModel(),
|
viewModel = hiltViewModel(),
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package be.ugent.sel.studeez.screens.session_recap
|
package be.ugent.sel.studeez.screens.session_recap
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.SelectedTask
|
||||||
import be.ugent.sel.studeez.data.SessionReportState
|
import be.ugent.sel.studeez.data.SessionReportState
|
||||||
import be.ugent.sel.studeez.data.local.models.SessionReport
|
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
import be.ugent.sel.studeez.domain.SessionDAO
|
import be.ugent.sel.studeez.domain.SessionDAO
|
||||||
|
import be.ugent.sel.studeez.domain.TaskDAO
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
@ -13,6 +15,8 @@ import javax.inject.Inject
|
||||||
class SessionRecapViewModel @Inject constructor(
|
class SessionRecapViewModel @Inject constructor(
|
||||||
sessionReportState: SessionReportState,
|
sessionReportState: SessionReportState,
|
||||||
private val sessionDAO: SessionDAO,
|
private val sessionDAO: SessionDAO,
|
||||||
|
private val taskDAO: TaskDAO,
|
||||||
|
private val selectedTask: SelectedTask,
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
|
@ -24,6 +28,8 @@ class SessionRecapViewModel @Inject constructor(
|
||||||
|
|
||||||
fun saveSession(open: (String, String) -> Unit) {
|
fun saveSession(open: (String, String) -> Unit) {
|
||||||
sessionDAO.saveSession(getSessionReport())
|
sessionDAO.saveSession(getSessionReport())
|
||||||
|
val newTask = selectedTask().copy(time = selectedTask().time + report.studyTime)
|
||||||
|
taskDAO.updateTask(newTask)
|
||||||
open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP)
|
open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
package be.ugent.sel.studeez.screens.subjects
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
SubjectScreen(
|
||||||
|
drawerActions = drawerActions,
|
||||||
|
navigationBarActions = navigationBarActions,
|
||||||
|
onAddSubject = { viewModel.onAddSubject(open) },
|
||||||
|
onViewSubject = { viewModel.onViewSubject(it, open) },
|
||||||
|
getStudyTime = viewModel::getStudyTime,
|
||||||
|
uiState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SubjectScreen(
|
||||||
|
drawerActions: DrawerActions,
|
||||||
|
navigationBarActions: NavigationBarActions,
|
||||||
|
onAddSubject: () -> Unit,
|
||||||
|
onViewSubject: (Subject) -> Unit,
|
||||||
|
getStudyTime: (Subject) -> Flow<Int>,
|
||||||
|
uiState: SubjectUiState,
|
||||||
|
) {
|
||||||
|
PrimaryScreenTemplate(
|
||||||
|
title = stringResource(AppText.my_subjects),
|
||||||
|
drawerActions = drawerActions,
|
||||||
|
navigationBarActions = navigationBarActions,
|
||||||
|
barAction = {},
|
||||||
|
) {
|
||||||
|
when (uiState) {
|
||||||
|
SubjectUiState.Loading -> Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight(),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(color = MaterialTheme.colors.onBackground)
|
||||||
|
}
|
||||||
|
is SubjectUiState.Succes -> {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(top = 5.dp)
|
||||||
|
) {
|
||||||
|
NewTaskSubjectButton(onClick = onAddSubject, AppText.new_subject)
|
||||||
|
LazyColumn {
|
||||||
|
items(uiState.subjects) {
|
||||||
|
SubjectEntry(
|
||||||
|
subject = it,
|
||||||
|
onViewSubject = { onViewSubject(it) },
|
||||||
|
getStudyTime = { getStudyTime(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SubjectScreenPreview() {
|
||||||
|
SubjectScreen(
|
||||||
|
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||||
|
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
|
||||||
|
onAddSubject = {},
|
||||||
|
onViewSubject = {},
|
||||||
|
getStudyTime = { flowOf() },
|
||||||
|
uiState = SubjectUiState.Succes(
|
||||||
|
listOf(
|
||||||
|
Subject(
|
||||||
|
name = "Test Subject",
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
taskCount = 5, taskCompletedCount = 2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SubjectScreenLoadingPreview() {
|
||||||
|
SubjectScreen(
|
||||||
|
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||||
|
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
|
||||||
|
onAddSubject = {},
|
||||||
|
onViewSubject = {},
|
||||||
|
getStudyTime = { flowOf() },
|
||||||
|
uiState = SubjectUiState.Loading
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package be.ugent.sel.studeez.screens.subjects
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||||
|
|
||||||
|
sealed interface SubjectUiState {
|
||||||
|
object Loading : SubjectUiState
|
||||||
|
data class Succes(val subjects: List<Subject>) : SubjectUiState
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package be.ugent.sel.studeez.screens.tasks
|
package be.ugent.sel.studeez.screens.subjects
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import be.ugent.sel.studeez.data.SelectedSubject
|
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.Subject
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
|
@ -7,7 +8,7 @@ import be.ugent.sel.studeez.domain.SubjectDAO
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
|
@ -16,12 +17,21 @@ class SubjectViewModel @Inject constructor(
|
||||||
private val selectedSubject: SelectedSubject,
|
private val selectedSubject: SelectedSubject,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
fun addSubject(open: (String) -> Unit) {
|
|
||||||
|
val uiState: StateFlow<SubjectUiState> = subjectDAO.getSubjects()
|
||||||
|
.map { SubjectUiState.Succes(it) }
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
initialValue = SubjectUiState.Loading,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun onAddSubject(open: (String) -> Unit) {
|
||||||
open(StudeezDestinations.ADD_SUBJECT_FORM)
|
open(StudeezDestinations.ADD_SUBJECT_FORM)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSubjects(): Flow<List<Subject>> {
|
fun getStudyTime(subject: Subject): Flow<Int> {
|
||||||
return subjectDAO.getSubjects()
|
return subjectDAO.getStudyTime(subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewSubject(subject: Subject, open: (String) -> Unit) {
|
fun onViewSubject(subject: Subject, open: (String) -> Unit) {
|
|
@ -1,4 +1,4 @@
|
||||||
package be.ugent.sel.studeez.screens.tasks.forms
|
package be.ugent.sel.studeez.screens.subjects.form
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
@ -19,10 +19,10 @@ import be.ugent.sel.studeez.resources
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SubjectAddRoute(
|
fun SubjectCreateRoute(
|
||||||
goBack: () -> Unit,
|
goBack: () -> Unit,
|
||||||
openAndPopUp: (String, String) -> Unit,
|
openAndPopUp: (String, String) -> Unit,
|
||||||
viewModel: SubjectFormViewModel,
|
viewModel: SubjectCreateFormViewModel,
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState
|
val uiState by viewModel.uiState
|
||||||
SubjectForm(
|
SubjectForm(
|
||||||
|
@ -39,7 +39,7 @@ fun SubjectAddRoute(
|
||||||
fun SubjectEditRoute(
|
fun SubjectEditRoute(
|
||||||
goBack: () -> Unit,
|
goBack: () -> Unit,
|
||||||
openAndPopUp: (String, String) -> Unit,
|
openAndPopUp: (String, String) -> Unit,
|
||||||
viewModel: SubjectFormViewModel,
|
viewModel: SubjectEditFormViewModel,
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState
|
val uiState by viewModel.uiState
|
||||||
SubjectForm(
|
SubjectForm(
|
|
@ -1,4 +1,4 @@
|
||||||
package be.ugent.sel.studeez.screens.tasks.forms
|
package be.ugent.sel.studeez.screens.subjects.form
|
||||||
|
|
||||||
data class SubjectFormUiState(
|
data class SubjectFormUiState(
|
||||||
val name: String = "",
|
val name: String = "",
|
|
@ -1,5 +1,6 @@
|
||||||
package be.ugent.sel.studeez.screens.tasks.forms
|
package be.ugent.sel.studeez.screens.subjects.form
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import be.ugent.sel.studeez.data.SelectedSubject
|
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.Subject
|
||||||
|
@ -10,25 +11,17 @@ import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
abstract class SubjectFormViewModel(
|
||||||
class SubjectFormViewModel @Inject constructor(
|
protected val subjectDAO: SubjectDAO,
|
||||||
private val subjectDAO: SubjectDAO,
|
protected val selectedSubject: SelectedSubject,
|
||||||
private val selectedSubject: SelectedSubject,
|
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
var uiState = mutableStateOf(
|
abstract val uiState: MutableState<SubjectFormUiState>
|
||||||
if (selectedSubject.isSet()) SubjectFormUiState(
|
|
||||||
name = selectedSubject().name,
|
|
||||||
color = selectedSubject().argb_color
|
|
||||||
)
|
|
||||||
else SubjectFormUiState()
|
|
||||||
)
|
|
||||||
private set
|
|
||||||
|
|
||||||
private val name: String
|
protected val name: String
|
||||||
get() = uiState.value.name
|
get() = uiState.value.name
|
||||||
|
|
||||||
private val color: Long
|
protected val color: Long
|
||||||
get() = uiState.value.color
|
get() = uiState.value.color
|
||||||
|
|
||||||
fun onNameChange(newValue: String) {
|
fun onNameChange(newValue: String) {
|
||||||
|
@ -38,11 +31,15 @@ class SubjectFormViewModel @Inject constructor(
|
||||||
fun onColorChange(newValue: Long) {
|
fun onColorChange(newValue: Long) {
|
||||||
uiState.value = uiState.value.copy(color = newValue)
|
uiState.value = uiState.value.copy(color = newValue)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onDelete(openAndPopUp: (String, String) -> Unit) {
|
@HiltViewModel
|
||||||
subjectDAO.deleteSubject(selectedSubject())
|
class SubjectCreateFormViewModel @Inject constructor(
|
||||||
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM)
|
subjectDAO: SubjectDAO,
|
||||||
}
|
selectedSubject: SelectedSubject,
|
||||||
|
logService: LogService,
|
||||||
|
) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) {
|
||||||
|
override val uiState = mutableStateOf(SubjectFormUiState())
|
||||||
|
|
||||||
fun onCreate(openAndPopUp: (String, String) -> Unit) {
|
fun onCreate(openAndPopUp: (String, String) -> Unit) {
|
||||||
val newSubject = Subject(
|
val newSubject = Subject(
|
||||||
|
@ -57,6 +54,25 @@ class SubjectFormViewModel @Inject constructor(
|
||||||
// open(StudeezDestinations.TASKS_SCREEN)
|
// open(StudeezDestinations.TASKS_SCREEN)
|
||||||
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.ADD_SUBJECT_FORM)
|
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.ADD_SUBJECT_FORM)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class SubjectEditFormViewModel @Inject constructor(
|
||||||
|
subjectDAO: SubjectDAO,
|
||||||
|
selectedSubject: SelectedSubject,
|
||||||
|
logService: LogService,
|
||||||
|
) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) {
|
||||||
|
override val uiState = mutableStateOf(
|
||||||
|
SubjectFormUiState(
|
||||||
|
name = selectedSubject().name,
|
||||||
|
color = selectedSubject().argb_color
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun onDelete(openAndPopUp: (String, String) -> Unit) {
|
||||||
|
subjectDAO.updateSubject(selectedSubject().copy(archived = true))
|
||||||
|
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM)
|
||||||
|
}
|
||||||
|
|
||||||
fun onEdit(openAndPopUp: (String, String) -> Unit) {
|
fun onEdit(openAndPopUp: (String, String) -> Unit) {
|
||||||
val newSubject = selectedSubject().copy(
|
val newSubject = selectedSubject().copy(
|
|
@ -1,80 +0,0 @@
|
||||||
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 = {},
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -27,10 +27,10 @@ data class TaskActions(
|
||||||
val addTask: () -> Unit,
|
val addTask: () -> Unit,
|
||||||
val getSubject: () -> Subject,
|
val getSubject: () -> Subject,
|
||||||
val getTasks: () -> Flow<List<Task>>,
|
val getTasks: () -> Flow<List<Task>>,
|
||||||
val deleteTask: (Task) -> Unit,
|
|
||||||
val onCheckTask: (Task, Boolean) -> Unit,
|
val onCheckTask: (Task, Boolean) -> Unit,
|
||||||
val editSubject: () -> Unit,
|
val editSubject: () -> Unit,
|
||||||
val startTask: (Task) -> Unit
|
val startTask: (Task) -> Unit,
|
||||||
|
val archiveTask: (Task) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions {
|
fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions {
|
||||||
|
@ -38,10 +38,10 @@ fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskAction
|
||||||
addTask = { viewModel.addTask(open) },
|
addTask = { viewModel.addTask(open) },
|
||||||
getTasks = viewModel::getTasks,
|
getTasks = viewModel::getTasks,
|
||||||
getSubject = viewModel::getSelectedSubject,
|
getSubject = viewModel::getSelectedSubject,
|
||||||
deleteTask = viewModel::deleteTask,
|
|
||||||
onCheckTask = { task, isChecked -> viewModel.toggleTaskCompleted(task, isChecked) },
|
onCheckTask = { task, isChecked -> viewModel.toggleTaskCompleted(task, isChecked) },
|
||||||
editSubject = { viewModel.editSubject(open) },
|
editSubject = { viewModel.editSubject(open) },
|
||||||
startTask = { task -> viewModel.startTask(task, open) }
|
startTask = { task -> viewModel.startTask(task, open) },
|
||||||
|
archiveTask = viewModel::archiveTask
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ fun TaskScreen(
|
||||||
TaskEntry(
|
TaskEntry(
|
||||||
task = it,
|
task = it,
|
||||||
onCheckTask = { isChecked -> taskActions.onCheckTask(it, isChecked) },
|
onCheckTask = { isChecked -> taskActions.onCheckTask(it, isChecked) },
|
||||||
onDeleteTask = { taskActions.deleteTask(it) },
|
onArchiveTask = { taskActions.archiveTask(it) },
|
||||||
onStartTask = { taskActions.startTask(it) }
|
onStartTask = { taskActions.startTask(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -108,10 +108,10 @@ fun TaskScreenPreview() {
|
||||||
{},
|
{},
|
||||||
{ Subject(name = "Test Subject") },
|
{ Subject(name = "Test Subject") },
|
||||||
{ flowOf() },
|
{ flowOf() },
|
||||||
{},
|
|
||||||
{ _, _ -> run {} },
|
{ _, _ -> run {} },
|
||||||
{},
|
{},
|
||||||
{}
|
{},
|
||||||
|
{},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -5,7 +5,6 @@ import be.ugent.sel.studeez.data.SelectedTask
|
||||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||||
import be.ugent.sel.studeez.data.local.models.task.Task
|
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
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.domain.TaskDAO
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
|
@ -16,7 +15,6 @@ import javax.inject.Inject
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class TaskViewModel @Inject constructor(
|
class TaskViewModel @Inject constructor(
|
||||||
private val taskDAO: TaskDAO,
|
private val taskDAO: TaskDAO,
|
||||||
private val subjectDAO: SubjectDAO,
|
|
||||||
private val selectedSubject: SelectedSubject,
|
private val selectedSubject: SelectedSubject,
|
||||||
private val selectedTask: SelectedTask,
|
private val selectedTask: SelectedTask,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
|
@ -29,11 +27,6 @@ class TaskViewModel @Inject constructor(
|
||||||
return taskDAO.getTasks(selectedSubject())
|
return taskDAO.getTasks(selectedSubject())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteSubject(open: (String) -> Unit) {
|
|
||||||
subjectDAO.deleteSubject(selectedSubject())
|
|
||||||
open(StudeezDestinations.SUBJECT_SCREEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSelectedSubject(): Subject {
|
fun getSelectedSubject(): Subject {
|
||||||
return selectedSubject()
|
return selectedSubject()
|
||||||
}
|
}
|
||||||
|
@ -42,8 +35,12 @@ class TaskViewModel @Inject constructor(
|
||||||
taskDAO.deleteTask(task)
|
taskDAO.deleteTask(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun archiveTask(task: Task) {
|
||||||
|
taskDAO.updateTask(task.copy(archived = true))
|
||||||
|
}
|
||||||
|
|
||||||
fun toggleTaskCompleted(task: Task, completed: Boolean) {
|
fun toggleTaskCompleted(task: Task, completed: Boolean) {
|
||||||
taskDAO.toggleTaskCompleted(task, completed)
|
taskDAO.updateTask(task.copy(completed = completed))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun editSubject(open: (String) -> Unit) {
|
fun editSubject(open: (String) -> Unit) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package be.ugent.sel.studeez.screens.tasks.forms
|
package be.ugent.sel.studeez.screens.tasks.form
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
@ -18,10 +18,10 @@ import be.ugent.sel.studeez.resources
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TaskAddRoute(
|
fun TaskCreateRoute(
|
||||||
goBack: () -> Unit,
|
goBack: () -> Unit,
|
||||||
openAndPopUp: (String, String) -> Unit,
|
openAndPopUp: (String, String) -> Unit,
|
||||||
viewModel: TaskFormViewModel,
|
viewModel: TaskCreateFormViewModel,
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState
|
val uiState by viewModel.uiState
|
||||||
TaskForm(
|
TaskForm(
|
||||||
|
@ -37,7 +37,7 @@ fun TaskAddRoute(
|
||||||
fun TaskEditRoute(
|
fun TaskEditRoute(
|
||||||
goBack: () -> Unit,
|
goBack: () -> Unit,
|
||||||
openAndPopUp: (String, String) -> Unit,
|
openAndPopUp: (String, String) -> Unit,
|
||||||
viewModel: TaskFormViewModel,
|
viewModel: TaskEditFormViewModel,
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState
|
val uiState by viewModel.uiState
|
||||||
TaskForm(
|
TaskForm(
|
|
@ -1,4 +1,4 @@
|
||||||
package be.ugent.sel.studeez.screens.tasks.forms
|
package be.ugent.sel.studeez.screens.tasks.form
|
||||||
|
|
||||||
data class TaskFormUiState(
|
data class TaskFormUiState(
|
||||||
val name: String = "",
|
val name: String = "",
|
|
@ -1,5 +1,6 @@
|
||||||
package be.ugent.sel.studeez.screens.tasks.forms
|
package be.ugent.sel.studeez.screens.tasks.form
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import be.ugent.sel.studeez.data.SelectedSubject
|
import be.ugent.sel.studeez.data.SelectedSubject
|
||||||
import be.ugent.sel.studeez.data.SelectedTask
|
import be.ugent.sel.studeez.data.SelectedTask
|
||||||
|
@ -11,38 +12,54 @@ import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
abstract class TaskFormViewModel(
|
||||||
class TaskFormViewModel @Inject constructor(
|
protected val taskDAO: TaskDAO,
|
||||||
private val taskDAO: TaskDAO,
|
protected val selectedSubject: SelectedSubject,
|
||||||
private val selectedSubject: SelectedSubject,
|
protected val selectedTask: SelectedTask,
|
||||||
private val selectedTask: SelectedTask,
|
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
var uiState = mutableStateOf(
|
abstract val uiState: MutableState<TaskFormUiState>
|
||||||
if (selectedTask.isSet()) TaskFormUiState(selectedTask().name) else TaskFormUiState()
|
|
||||||
)
|
|
||||||
private set
|
|
||||||
|
|
||||||
private val name: String
|
protected val name: String
|
||||||
get() = uiState.value.name
|
get() = uiState.value.name
|
||||||
|
|
||||||
fun onNameChange(newValue: String) {
|
fun onNameChange(newValue: String) {
|
||||||
uiState.value = uiState.value.copy(name = newValue)
|
uiState.value = uiState.value.copy(name = newValue)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onDelete(openAndPopUp: (String, String) -> Unit) {
|
@HiltViewModel
|
||||||
taskDAO.deleteTask(selectedTask())
|
class TaskCreateFormViewModel @Inject constructor(
|
||||||
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM)
|
taskDAO: TaskDAO,
|
||||||
}
|
selectedSubject: SelectedSubject,
|
||||||
|
selectedTask: SelectedTask,
|
||||||
|
logService: LogService,
|
||||||
|
) : TaskFormViewModel(taskDAO, selectedSubject, selectedTask, logService) {
|
||||||
|
override val uiState = mutableStateOf(TaskFormUiState())
|
||||||
|
|
||||||
fun onCreate(openAndPopUp: (String, String) -> Unit) {
|
fun onCreate(openAndPopUp: (String, String) -> Unit) {
|
||||||
val newTask = Task(name = name, subjectId = selectedSubject().id)
|
val newTask = Task(name = name, subjectId = selectedSubject().id)
|
||||||
taskDAO.saveTask(newTask)
|
taskDAO.saveTask(newTask)
|
||||||
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.ADD_TASK_FORM)
|
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.ADD_TASK_FORM)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class TaskEditFormViewModel @Inject constructor(
|
||||||
|
taskDAO: TaskDAO,
|
||||||
|
selectedSubject: SelectedSubject,
|
||||||
|
selectedTask: SelectedTask,
|
||||||
|
logService: LogService,
|
||||||
|
) : TaskFormViewModel(taskDAO, selectedSubject, selectedTask, logService) {
|
||||||
|
override val uiState = mutableStateOf(TaskFormUiState())
|
||||||
|
|
||||||
|
fun onDelete(openAndPopUp: (String, String) -> Unit) {
|
||||||
|
taskDAO.deleteTask(selectedTask())
|
||||||
|
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM)
|
||||||
|
}
|
||||||
|
|
||||||
fun onEdit(openAndPopUp: (String, String) -> Unit) {
|
fun onEdit(openAndPopUp: (String, String) -> Unit) {
|
||||||
val newTask = Task(name = name)
|
val newTask = selectedTask().copy(name = name)
|
||||||
taskDAO.updateTask(newTask)
|
taskDAO.updateTask(newTask)
|
||||||
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM)
|
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM)
|
||||||
}
|
}
|
Reference in a new issue