diff --git a/.idea/misc.xml b/.idea/misc.xml index 0ad17cb..8978d23 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/build.gradle b/app/build.gradle index a19cbd7..68d4e47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -123,9 +123,6 @@ dependencies { implementation 'com.google.firebase:firebase-firestore-ktx' 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/common/composable/FloatingActionButtonComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/FloatingActionButtonComposable.kt index bc40ead..ea2b52d 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/FloatingActionButtonComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/FloatingActionButtonComposable.kt @@ -2,7 +2,6 @@ package be.ugent.sel.studeez.common.composable import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.updateTransition -import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.material.FloatingActionButton import androidx.compose.material.Icon diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/FormComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/FormComposable.kt new file mode 100644 index 0000000..1fbcfb2 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/FormComposable.kt @@ -0,0 +1,22 @@ +package be.ugent.sel.studeez.common.composable + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun FormComposable( + title: String, + popUp: () -> Unit, + content: @Composable () -> Unit, +) { + SecondaryScreenTemplate(title = title, popUp = popUp) { + Box( + modifier = Modifier.verticalScroll(rememberScrollState()), + ) { + content() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/ImageComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/ImageComposable.kt new file mode 100644 index 0000000..39e7272 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/ImageComposable.kt @@ -0,0 +1,39 @@ +package be.ugent.sel.studeez.common.composable + +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.dp + +@Composable +fun ImageBackgroundButton( + paint: Painter, + str: String, + background2: Color, + setBackground1: (Color) -> Unit, + setBackground2: (Color) -> Unit +) { + Image( + painter = paint, + str, + modifier = Modifier + .clickable { + if (background2 == Color.Transparent) { + setBackground1(Color.LightGray) + setBackground2(Color.Transparent) + } else { + setBackground2(Color.Transparent) + } + } + .border( + width = 2.dp, + color = background2, + shape = RoundedCornerShape(16.dp) + ) + ) +} \ No newline at end of file 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 index 5db2af3..c6631ce 100644 --- 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 @@ -31,9 +31,13 @@ import be.ugent.sel.studeez.R.string as AppText fun SubjectEntry( subject: Subject, onViewSubject: () -> Unit, + getTaskCount: () -> Flow, + getCompletedTaskCount: () -> Flow, getStudyTime: () -> Flow, ) { val studytime by getStudyTime().collectAsState(initial = 0) + val taskCount by getTaskCount().collectAsState(initial = 0) + val completedTaskCount by getCompletedTaskCount().collectAsState(initial = 0) Card( modifier = Modifier .fillMaxWidth() @@ -80,7 +84,7 @@ fun SubjectEntry( imageVector = Icons.Default.List, contentDescription = stringResource(id = AppText.tasks) ) - Text(text = "${subject.taskCompletedCount}/${subject.taskCount}") + Text(text = "${completedTaskCount}/${taskCount}") } } } @@ -104,11 +108,11 @@ fun SubjectEntryPreview() { subject = Subject( name = "Test Subject", argb_color = 0xFFFFD200, - taskCount = 5, - taskCompletedCount = 2, ), onViewSubject = {}, - getStudyTime = { flowOf() } + getTaskCount = { flowOf() }, + getCompletedTaskCount = { flowOf() }, + getStudyTime = { flowOf() }, ) } @@ -121,6 +125,8 @@ fun OverflowSubjectEntryPreview() { argb_color = 0xFFFFD200, ), onViewSubject = {}, - getStudyTime = { flowOf() } + getTaskCount = { flowOf() }, + getCompletedTaskCount = { flowOf() }, + getStudyTime = { flowOf() }, ) } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/ext/ColorExt.kt b/app/src/main/java/be/ugent/sel/studeez/common/ext/ColorExt.kt new file mode 100644 index 0000000..87ce226 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/ext/ColorExt.kt @@ -0,0 +1,10 @@ +package be.ugent.sel.studeez.common.ext + +import androidx.compose.ui.graphics.Color +import kotlin.random.Random + +fun Color.Companion.generateRandomArgb(): Long { + val random = Random + val mask: Long = (0x000000FFL shl random.nextInt(0, 3)).inv() + return random.nextLong(0xFF000000L, 0xFFFFFFFFL) and mask +} \ 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 index 74ebe9f..261f3e0 100644 --- 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 @@ -8,10 +8,6 @@ data class Subject( val name: String = "", 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 { diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt index 765fbcd..f5237d6 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt @@ -6,14 +6,13 @@ class FunctionalPomodoroTimer( val repeats: Int ) : FunctionalTimer(studyTime) { - var breaksRemaining = repeats + var breaksRemaining = repeats - 1 var isInBreak = false override fun tick() { if (hasEnded()) { return } - if (hasCurrentCountdownEnded()) { if (isInBreak) { breaksRemaining-- 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 index bad8106..d887ef5 100644 --- a/app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt +++ b/app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt @@ -13,8 +13,10 @@ interface SubjectDAO { fun updateSubject(newSubject: Subject) - suspend fun getTaskCount(subject: Subject): Int - suspend fun getCompletedTaskCount(subject: Subject): Int + suspend fun archiveSubject(subject: Subject) + + fun getTaskCount(subject: Subject): Flow + fun getCompletedTaskCount(subject: Subject): Flow fun getStudyTime(subject: Subject): Flow suspend fun getSubject(subjectId: String): Subject? 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 index b023986..66815dc 100644 --- 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 @@ -1,11 +1,13 @@ package be.ugent.sel.studeez.domain.implementation +import android.util.Log import be.ugent.sel.studeez.data.local.models.task.Subject import be.ugent.sel.studeez.data.local.models.task.SubjectDocument +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.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.FirebaseFirestore import com.google.firebase.firestore.Query @@ -15,6 +17,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.tasks.await import javax.inject.Inject +import kotlin.collections.count class FireBaseSubjectDAO @Inject constructor( private val firestore: FirebaseFirestore, @@ -26,13 +29,6 @@ class FireBaseSubjectDAO @Inject constructor( .subjectNotArchived() .snapshots() .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? { @@ -51,23 +47,26 @@ class FireBaseSubjectDAO @Inject constructor( currentUserSubjectsCollection().document(newSubject.id).set(newSubject) } - override suspend fun getTaskCount(subject: Subject): Int { - return subjectTasksCollection(subject) + override suspend fun archiveSubject(subject: Subject) { + currentUserSubjectsCollection().document(subject.id).update(SubjectDocument.archived, true) + currentUserSubjectsCollection().document(subject.id) + .collection(FireBaseCollections.TASK_COLLECTION) .taskNotArchived() - .count() - .get(AggregateSource.SERVER) - .await() - .count.toInt() + .get().await() + .documents + .forEach { + it.reference.update(TaskDocument.archived, true) + } } - override suspend fun getCompletedTaskCount(subject: Subject): Int { - return subjectTasksCollection(subject) - .taskNotArchived() - .taskNotCompleted() - .count() - .get(AggregateSource.SERVER) - .await() - .count.toInt() + override fun getTaskCount(subject: Subject): Flow { + return taskDAO.getTasks(subject) + .map(List::count) + } + + override fun getCompletedTaskCount(subject: Subject): Flow { + return taskDAO.getTasks(subject) + .map { tasks -> tasks.count { it.completed && !it.archived } } } override fun getStudyTime(subject: Subject): Flow { diff --git a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezNavGraph.kt b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezNavGraph.kt index 37085f1..0a0e8b4 100644 --- a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezNavGraph.kt +++ b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezNavGraph.kt @@ -25,9 +25,9 @@ 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.subjects.SubjectRoute -import be.ugent.sel.studeez.screens.tasks.TaskRoute import be.ugent.sel.studeez.screens.subjects.form.SubjectCreateRoute import be.ugent.sel.studeez.screens.subjects.form.SubjectEditRoute +import be.ugent.sel.studeez.screens.tasks.TaskRoute import be.ugent.sel.studeez.screens.tasks.form.TaskCreateRoute import be.ugent.sel.studeez.screens.tasks.form.TaskEditRoute import be.ugent.sel.studeez.screens.timer_form.TimerAddRoute @@ -51,6 +51,7 @@ fun StudeezNavGraph( val open: (String) -> Unit = { appState.navigate(it) } val openAndPopUp: (String, String) -> Unit = { route, popUp -> appState.navigateAndPopUp(route, popUp) } + val clearAndNavigate: (route: String) -> Unit = { route -> appState.clearAndNavigate(route) } val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp) val navigationBarActions: NavigationBarActions = @@ -200,7 +201,7 @@ fun StudeezNavGraph( composable(StudeezDestinations.SESSION_RECAP) { SessionRecapRoute( - openAndPopUp = openAndPopUp, + clearAndNavigate = clearAndNavigate, viewModel = hiltViewModel() ) } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt index 9c59b46..f328c5f 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt @@ -4,18 +4,13 @@ import android.media.MediaPlayer import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.Text 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.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign 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.data.local.models.timer_functional.FunctionalPomodoroTimer import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.R.string as AppText @@ -23,7 +18,7 @@ import be.ugent.sel.studeez.R.string as AppText class BreakSessionScreen( private val funPomoDoroTimer: FunctionalPomodoroTimer, private var mediaplayer: MediaPlayer? -): AbstractSessionScreen() { +) : AbstractSessionScreen() { @Composable override fun MidSection() { @@ -37,23 +32,31 @@ class BreakSessionScreen( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, ) { - repeat(funPomoDoroTimer.repeats - funPomoDoroTimer.breaksRemaining) { - Dot(color = Color.DarkGray) - } - if (!funPomoDoroTimer.isInBreak) Dot(Color.Green) else Dot(Color.DarkGray) - repeat(funPomoDoroTimer.breaksRemaining - 1) { - Dot(color = Color.Gray) + if (funPomoDoroTimer.hasEnded()) { + repeat(funPomoDoroTimer.repeats) { + Dot(Color.Green) + } + } else { + repeat(funPomoDoroTimer.repeats - funPomoDoroTimer.breaksRemaining - 1) { + Dot(color = Color.DarkGray) + } + if (!funPomoDoroTimer.isInBreak) Dot(Color.Green) else Dot(Color.DarkGray) + repeat(funPomoDoroTimer.breaksRemaining) { + Dot(color = Color.Gray) + } } } } @Composable private fun Dot(color: Color) { - Box(modifier = Modifier - .padding(5.dp) - .size(10.dp) - .clip(CircleShape) - .background(color)) + Box( + modifier = Modifier + .padding(5.dp) + .size(10.dp) + .clip(CircleShape) + .background(color) + ) } @Composable diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt index 2d06e0b..3a1e85f 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapScreen.kt @@ -1,13 +1,24 @@ package be.ugent.sel.studeez.screens.session_recap -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.* import androidx.compose.material.ButtonDefaults import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +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.composable.BasicButton +import be.ugent.sel.studeez.common.composable.ImageBackgroundButton import be.ugent.sel.studeez.common.ext.basicButton import be.ugent.sel.studeez.data.local.models.SessionReport import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds @@ -21,24 +32,24 @@ data class SessionRecapActions( fun getSessionRecapActions( viewModel: SessionRecapViewModel, - openAndPopUp: (String, String) -> Unit, + clearAndNavigate: (String) -> Unit, ): SessionRecapActions { return SessionRecapActions( viewModel::getSessionReport, - {viewModel.saveSession(openAndPopUp)}, - {viewModel.discardSession(openAndPopUp)} + { viewModel.saveSession(clearAndNavigate) }, + { viewModel.discardSession(clearAndNavigate) } ) } @Composable fun SessionRecapRoute( - openAndPopUp: (String, String) -> Unit, + clearAndNavigate: (String) -> Unit, modifier: Modifier = Modifier, viewModel: SessionRecapViewModel, ) { SessionRecapScreen( modifier = modifier, - getSessionRecapActions(viewModel, openAndPopUp) + getSessionRecapActions(viewModel, clearAndNavigate) ) } @@ -47,21 +58,88 @@ fun SessionRecapScreen(modifier: Modifier, sessionRecapActions: SessionRecapActi val sessionReport: SessionReport = sessionRecapActions.getSessionReport() val studyTime: Int = sessionReport.studyTime val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS() + val (background1, setBackground1) = remember { mutableStateOf(Color.Transparent) } + val (background2, setBackground2) = remember { mutableStateOf(Color.Transparent) } Column( modifier = modifier + .fillMaxWidth() + .fillMaxHeight() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween ) { - Text(text = "You studied: $hms") + Text( + text = stringResource(R.string.congrats, hms), + modifier = Modifier + .fillMaxWidth(), + textAlign = TextAlign.Center, + fontWeight = FontWeight.Light, + fontSize = 30.sp, - BasicButton( - R.string.save, Modifier.basicButton() + ) + + Column( + modifier = Modifier.fillMaxWidth() ) { - sessionRecapActions.saveSession() + Text( + text = stringResource(R.string.how_did_it_go), + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + fontWeight = FontWeight.Light, + fontSize = 30.sp + ) + + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.CenterHorizontally) + ) { + ImageBackgroundButton( + paint = painterResource(id = R.drawable.mood_1), + str = stringResource(id = R.string.good), + background2 = background2, + setBackground1 = setBackground2, + setBackground2 = setBackground1 + ) + + ImageBackgroundButton( + paint = painterResource(id = R.drawable.mood_2), + str = stringResource(id = R.string.bad), + background2 = background1, + setBackground1 = setBackground1, + setBackground2 = setBackground2 + ) + } } - BasicButton( - R.string.discard, Modifier.basicButton(), - colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red) - ) { - sessionRecapActions.discardSession() + + Column { + BasicButton( + R.string.save, Modifier.basicButton() + ) { + sessionRecapActions.saveSession() + } + BasicButton( + R.string.discard, Modifier.basicButton(), + colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red) + ) { + sessionRecapActions.discardSession() + } } } } + +@Preview +@Composable +fun SessionRecapScreenPreview() { + SessionRecapScreen( + modifier = Modifier, + sessionRecapActions = SessionRecapActions( + { SessionReport( + studyTime = 100, + ) }, + {}, + {}, + ) + ) +} diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt index c11e28f..bf11b93 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt @@ -24,15 +24,15 @@ class SessionRecapViewModel @Inject constructor( return selectedSessionReport() } - fun saveSession(open: (String, String) -> Unit) { + fun saveSession(open: (String) -> Unit) { sessionDAO.saveSession(getSessionReport()) val newTask = selectedTask().copy(time = selectedTask().time + selectedSessionReport().studyTime) taskDAO.updateTask(newTask) - open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) + open(StudeezDestinations.HOME_SCREEN) } - fun discardSession(open: (String, String) -> Unit) { - open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) + fun discardSession(open: (String) -> Unit) { + open(StudeezDestinations.HOME_SCREEN) } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectScreen.kt index ab2cff4..852c0e5 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectScreen.kt @@ -36,6 +36,8 @@ fun SubjectRoute( navigationBarActions = navigationBarActions, onAddSubject = { viewModel.onAddSubject(open) }, onViewSubject = { viewModel.onViewSubject(it, open) }, + getTaskCount = viewModel::getTaskCount, + getCompletedTaskCount = viewModel::getCompletedTaskCount, getStudyTime = viewModel::getStudyTime, uiState, ) @@ -47,6 +49,8 @@ fun SubjectScreen( navigationBarActions: NavigationBarActions, onAddSubject: () -> Unit, onViewSubject: (Subject) -> Unit, + getTaskCount: (Subject) -> Flow, + getCompletedTaskCount: (Subject) -> Flow, getStudyTime: (Subject) -> Flow, uiState: SubjectUiState, ) { @@ -76,6 +80,8 @@ fun SubjectScreen( SubjectEntry( subject = it, onViewSubject = { onViewSubject(it) }, + getTaskCount = { getTaskCount(it) }, + getCompletedTaskCount = { getCompletedTaskCount(it) }, getStudyTime = { getStudyTime(it) }, ) } @@ -94,13 +100,14 @@ fun SubjectScreenPreview() { navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), onAddSubject = {}, onViewSubject = {}, + getTaskCount = { flowOf() }, + getCompletedTaskCount = { flowOf() }, getStudyTime = { flowOf() }, uiState = SubjectUiState.Succes( listOf( Subject( name = "Test Subject", argb_color = 0xFFFFD200, - taskCount = 5, taskCompletedCount = 2, ) ) ) @@ -115,7 +122,9 @@ fun SubjectScreenLoadingPreview() { navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), onAddSubject = {}, onViewSubject = {}, + getTaskCount = { flowOf() }, + getCompletedTaskCount = { flowOf() }, getStudyTime = { flowOf() }, - uiState = SubjectUiState.Loading + uiState = SubjectUiState.Loading, ) } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectViewModel.kt index c158529..19ba396 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectViewModel.kt @@ -30,6 +30,14 @@ class SubjectViewModel @Inject constructor( open(StudeezDestinations.ADD_SUBJECT_FORM) } + fun getTaskCount(subject: Subject): Flow { + return subjectDAO.getTaskCount(subject) + } + + fun getCompletedTaskCount(subject: Subject): Flow { + return subjectDAO.getCompletedTaskCount(subject) + } + fun getStudyTime(subject: Subject): Flow { return subjectDAO.getStudyTime(subject) } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormScreen.kt index 19e6816..9e787dd 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormScreen.kt @@ -2,20 +2,28 @@ package be.ugent.sel.studeez.screens.subjects.form import androidx.annotation.StringRes import androidx.compose.foundation.layout.Column -import androidx.compose.material.OutlinedTextField +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope 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 be.ugent.sel.studeez.common.composable.BasicButton import be.ugent.sel.studeez.common.composable.DeleteButton +import be.ugent.sel.studeez.common.composable.FormComposable +import be.ugent.sel.studeez.common.composable.LabelledInputField 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.common.ext.generateRandomArgb import be.ugent.sel.studeez.resources +import kotlinx.coroutines.launch import be.ugent.sel.studeez.R.string as AppText @Composable @@ -31,7 +39,7 @@ fun SubjectCreateRoute( uiState = uiState, onConfirm = { viewModel.onCreate(openAndPopUp) }, onNameChange = viewModel::onNameChange, - onColorChange = {}, + onColorChange = viewModel::onColorChange, ) } @@ -42,16 +50,19 @@ fun SubjectEditRoute( viewModel: SubjectEditFormViewModel, ) { val uiState by viewModel.uiState + val coroutineScope = rememberCoroutineScope() SubjectForm( title = AppText.edit_subject, goBack = goBack, uiState = uiState, onConfirm = { viewModel.onEdit(openAndPopUp) }, onNameChange = viewModel::onNameChange, - onColorChange = {}, + onColorChange = viewModel::onColorChange, ) { DeleteButton(text = AppText.delete_subject) { - viewModel.onDelete(openAndPopUp) + coroutineScope.launch { + viewModel.onDelete(openAndPopUp) + } } } } @@ -63,21 +74,21 @@ fun SubjectForm( uiState: SubjectFormUiState, onConfirm: () -> Unit, onNameChange: (String) -> Unit, - onColorChange: (Color) -> Unit, + onColorChange: (Long) -> Unit, extraButton: @Composable () -> Unit = {}, ) { - SecondaryScreenTemplate( + FormComposable( title = resources().getString(title), popUp = goBack, ) { Column { - OutlinedTextField( + LabelledInputField( singleLine = true, value = uiState.name, - onValueChange = onNameChange, - placeholder = { Text(stringResource(id = AppText.name)) }, - modifier = Modifier.fieldModifier(), + onNewValue = onNameChange, + label = AppText.name, ) + ColorPicker(onColorChange, uiState) BasicButton( text = AppText.confirm, modifier = Modifier.basicButton(), @@ -88,6 +99,24 @@ fun SubjectForm( } } +@Composable +fun ColorPicker( + onColorChange: (Long) -> Unit, + uiState: SubjectFormUiState, +) { + Button( + onClick = { onColorChange(Color.generateRandomArgb()) }, + modifier = Modifier.fieldModifier(), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color(uiState.color), + contentColor = Color.White, + ), + shape = RoundedCornerShape(4.dp), + ) { + Text(text = stringResource(id = AppText.regenerate_color)) + } +} + @Preview @Composable fun AddSubjectFormPreview() { diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormUiState.kt index 9fdba01..10a18e8 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormUiState.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormUiState.kt @@ -1,6 +1,9 @@ package be.ugent.sel.studeez.screens.subjects.form +import androidx.compose.ui.graphics.Color +import be.ugent.sel.studeez.common.ext.generateRandomArgb + data class SubjectFormUiState( val name: String = "", - val color: Long = 0xFFFFD200, + val color: Long = Color.generateRandomArgb(), ) \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormViewModel.kt index 533123b..7a1554b 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormViewModel.kt @@ -2,10 +2,13 @@ package be.ugent.sel.studeez.screens.subjects.form import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.graphics.Color +import be.ugent.sel.studeez.common.ext.generateRandomArgb 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.domain.TaskDAO import be.ugent.sel.studeez.navigation.StudeezDestinations import be.ugent.sel.studeez.screens.StudeezViewModel import dagger.hilt.android.lifecycle.HiltViewModel @@ -59,6 +62,7 @@ class SubjectCreateFormViewModel @Inject constructor( @HiltViewModel class SubjectEditFormViewModel @Inject constructor( subjectDAO: SubjectDAO, + private val taskDAO: TaskDAO, selectedSubject: SelectedSubject, logService: LogService, ) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) { @@ -69,17 +73,19 @@ class SubjectEditFormViewModel @Inject constructor( ) ) - fun onDelete(openAndPopUp: (String, String) -> Unit) { - subjectDAO.updateSubject(selectedSubject().copy(archived = true)) + suspend fun onDelete(openAndPopUp: (String, String) -> Unit) { + subjectDAO.archiveSubject(selectedSubject()) openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) } fun onEdit(openAndPopUp: (String, String) -> Unit) { - val newSubject = selectedSubject().copy( - name = name, - argb_color = color, + selectedSubject.set( + selectedSubject().copy( + name = name, + argb_color = color, + ) ) - subjectDAO.updateSubject(newSubject) + subjectDAO.updateSubject(selectedSubject()) 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/form/TaskFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormScreen.kt index 92302ea..79c744d 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormScreen.kt @@ -11,7 +11,7 @@ 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.composable.FormComposable import be.ugent.sel.studeez.common.ext.basicButton import be.ugent.sel.studeez.common.ext.fieldModifier import be.ugent.sel.studeez.resources @@ -62,7 +62,7 @@ fun TaskForm( onNameChange: (String) -> Unit, extraButton: @Composable () -> Unit = {} ) { - SecondaryScreenTemplate( + FormComposable( title = resources().getString(title), popUp = goBack, ) { diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormRoute.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormScreen.kt similarity index 91% rename from app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormRoute.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormScreen.kt index cf8f374..fea01e6 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormRoute.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import be.ugent.sel.studeez.common.composable.DeleteButton import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate +import be.ugent.sel.studeez.common.composable.FormComposable import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo import be.ugent.sel.studeez.R.string as AppText @@ -59,7 +60,10 @@ fun TimerFormScreen( ) { val timerFormScreen = getTimerInfo().accept(GetTimerFormScreen()) - SecondaryScreenTemplate(title = stringResource(id = label), popUp = popUp) { + FormComposable( + title = stringResource(id = label), + popUp = popUp + ) { timerFormScreen(onConfirmClick, extraButton) } } 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 8afe306..9560dd1 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 @@ -1,13 +1,7 @@ package be.ugent.sel.studeez.screens.timer_form.form_screens -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -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.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType import be.ugent.sel.studeez.R @@ -37,58 +31,47 @@ abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) { extraButton: @Composable () -> Unit = {}, ) { - Column( - verticalArrangement = Arrangement.SpaceBetween, - modifier = Modifier - .fillMaxHeight() - .verticalScroll(rememberScrollState()), - ) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - - // Fields that every timer shares (ommited id) - LabeledErrorTextField( - initialValue = timerInfo.name, - label = R.string.name, - errorText = AppText.name_error, - isValid = valids.getValue("name"), - isFirst = firsts.getValue("name"), - keyboardType = KeyboardType.Text, - predicate = { it.isNotBlank() } - ) { correctName -> - timerInfo.name = correctName - } - - LabeledErrorTextField( - initialValue = timerInfo.description, - label = R.string.description, - errorText = AppText.description_error, - isValid = valids.getValue("description"), - isFirst = firsts.getValue("description"), - singleLine= false, - keyboardType = KeyboardType.Text, - predicate = { textPredicate(it) } - ) { correctName -> - timerInfo.description = correctName - } - - ExtraFields() + Column { + // Fields that every timer shares (ommited id) + LabeledErrorTextField( + initialValue = timerInfo.name, + label = R.string.name, + errorText = AppText.name_error, + isValid = valids.getValue("name"), + isFirst = firsts.getValue("name"), + keyboardType = KeyboardType.Text, + predicate = { it.isNotBlank() } + ) { correctName -> + timerInfo.name = correctName } - Column { - BasicButton(R.string.save, Modifier.basicButton()) { - if (valids.all { it.component2().value }) { // All fields are valid - onSaveClick(timerInfo) - } else { - firsts.map { it.component2().value = false } // dont mask error because its not been filled out yet - SnackbarManager.showMessage(AppText.fill_out_error) - } - } - extraButton() + LabeledErrorTextField( + initialValue = timerInfo.description, + label = R.string.description, + errorText = AppText.description_error, + isValid = valids.getValue("description"), + isFirst = firsts.getValue("description"), + singleLine = false, + keyboardType = KeyboardType.Text, + predicate = { textPredicate(it) } + ) { correctName -> + timerInfo.description = correctName } + + ExtraFields() + + BasicButton(R.string.save, Modifier.basicButton()) { + if (valids.all { it.component2().value }) { // All fields are valid + onSaveClick(timerInfo) + } else { + firsts.map { + it.component2().value = false + } // dont mask error because its not been filled out yet + SnackbarManager.showMessage(AppText.fill_out_error) + } + } + extraButton() } } diff --git a/app/src/main/res/drawable/mood_1.xml b/app/src/main/res/drawable/mood_1.xml new file mode 100644 index 0000000..bf009f2 --- /dev/null +++ b/app/src/main/res/drawable/mood_1.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/mood_2.xml b/app/src/main/res/drawable/mood_2.xml new file mode 100644 index 0000000..0fd3daa --- /dev/null +++ b/app/src/main/res/drawable/mood_2.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 303fa29..a5f350e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,6 +46,7 @@ Delete Subject Delete Task View + Regenerate Color 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. @@ -156,4 +157,11 @@ Break Time Number of Repeats + + "Congratulations! You studied: %s" + How did it go? + Good + Bad + +