solve merge issues

This commit is contained in:
lbarraga 2023-05-15 21:43:02 +02:00
commit 61f6e2ac25
26 changed files with 364 additions and 154 deletions

1
.idea/misc.xml generated
View file

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">

View file

@ -123,9 +123,6 @@ dependencies {
implementation 'com.google.firebase:firebase-firestore-ktx' implementation 'com.google.firebase:firebase-firestore-ktx'
implementation 'com.google.firebase:firebase-perf-ktx' implementation 'com.google.firebase:firebase-perf-ktx'
implementation 'com.google.firebase:firebase-config-ktx' implementation 'com.google.firebase:firebase-config-ktx'
// Colorpicker
implementation 'com.github.skydoves:colorpicker-compose:1.0.2'
} }
// Allow references to generate code // Allow references to generate code

View file

@ -2,7 +2,6 @@ package be.ugent.sel.studeez.common.composable
import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.updateTransition import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.FloatingActionButton import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon import androidx.compose.material.Icon

View file

@ -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()
}
}
}

View file

@ -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)
)
)
}

View file

@ -31,9 +31,13 @@ import be.ugent.sel.studeez.R.string as AppText
fun SubjectEntry( fun SubjectEntry(
subject: Subject, subject: Subject,
onViewSubject: () -> Unit, onViewSubject: () -> Unit,
getTaskCount: () -> Flow<Int>,
getCompletedTaskCount: () -> Flow<Int>,
getStudyTime: () -> Flow<Int>, getStudyTime: () -> Flow<Int>,
) { ) {
val studytime by getStudyTime().collectAsState(initial = 0) val studytime by getStudyTime().collectAsState(initial = 0)
val taskCount by getTaskCount().collectAsState(initial = 0)
val completedTaskCount by getCompletedTaskCount().collectAsState(initial = 0)
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -80,7 +84,7 @@ fun SubjectEntry(
imageVector = Icons.Default.List, imageVector = Icons.Default.List,
contentDescription = stringResource(id = AppText.tasks) contentDescription = stringResource(id = AppText.tasks)
) )
Text(text = "${subject.taskCompletedCount}/${subject.taskCount}") Text(text = "${completedTaskCount}/${taskCount}")
} }
} }
} }
@ -104,11 +108,11 @@ fun SubjectEntryPreview() {
subject = Subject( subject = Subject(
name = "Test Subject", name = "Test Subject",
argb_color = 0xFFFFD200, argb_color = 0xFFFFD200,
taskCount = 5,
taskCompletedCount = 2,
), ),
onViewSubject = {}, onViewSubject = {},
getStudyTime = { flowOf() } getTaskCount = { flowOf() },
getCompletedTaskCount = { flowOf() },
getStudyTime = { flowOf() },
) )
} }
@ -121,6 +125,8 @@ fun OverflowSubjectEntryPreview() {
argb_color = 0xFFFFD200, argb_color = 0xFFFFD200,
), ),
onViewSubject = {}, onViewSubject = {},
getStudyTime = { flowOf() } getTaskCount = { flowOf() },
getCompletedTaskCount = { flowOf() },
getStudyTime = { flowOf() },
) )
} }

View file

@ -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
}

View file

@ -8,10 +8,6 @@ data class Subject(
val name: String = "", val name: String = "",
val argb_color: Long = 0, val argb_color: Long = 0,
var archived: Boolean = false, var archived: Boolean = false,
@get:Exclude @set:Exclude
var taskCount: Int = 0,
@get:Exclude @set:Exclude
var taskCompletedCount: Int = 0,
) )
object SubjectDocument { object SubjectDocument {

View file

@ -6,14 +6,13 @@ class FunctionalPomodoroTimer(
val repeats: Int val repeats: Int
) : FunctionalTimer(studyTime) { ) : FunctionalTimer(studyTime) {
var breaksRemaining = repeats var breaksRemaining = repeats - 1
var isInBreak = false var isInBreak = false
override fun tick() { override fun tick() {
if (hasEnded()) { if (hasEnded()) {
return return
} }
if (hasCurrentCountdownEnded()) { if (hasCurrentCountdownEnded()) {
if (isInBreak) { if (isInBreak) {
breaksRemaining-- breaksRemaining--

View file

@ -13,8 +13,10 @@ interface SubjectDAO {
fun updateSubject(newSubject: Subject) fun updateSubject(newSubject: Subject)
suspend fun getTaskCount(subject: Subject): Int suspend fun archiveSubject(subject: Subject)
suspend fun getCompletedTaskCount(subject: Subject): Int
fun getTaskCount(subject: Subject): Flow<Int>
fun getCompletedTaskCount(subject: Subject): Flow<Int>
fun getStudyTime(subject: Subject): Flow<Int> fun getStudyTime(subject: Subject): Flow<Int>
suspend fun getSubject(subjectId: String): Subject? suspend fun getSubject(subjectId: String): Subject?

View file

@ -1,11 +1,13 @@
package be.ugent.sel.studeez.domain.implementation 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.Subject
import be.ugent.sel.studeez.data.local.models.task.SubjectDocument 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.AccountDAO
import be.ugent.sel.studeez.domain.SubjectDAO 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.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.Query
@ -15,6 +17,7 @@ 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
import kotlin.collections.count
class FireBaseSubjectDAO @Inject constructor( class FireBaseSubjectDAO @Inject constructor(
private val firestore: FirebaseFirestore, private val firestore: FirebaseFirestore,
@ -26,13 +29,6 @@ class FireBaseSubjectDAO @Inject constructor(
.subjectNotArchived() .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? {
@ -51,23 +47,26 @@ class FireBaseSubjectDAO @Inject constructor(
currentUserSubjectsCollection().document(newSubject.id).set(newSubject) currentUserSubjectsCollection().document(newSubject.id).set(newSubject)
} }
override suspend fun getTaskCount(subject: Subject): Int { override suspend fun archiveSubject(subject: Subject) {
return subjectTasksCollection(subject) currentUserSubjectsCollection().document(subject.id).update(SubjectDocument.archived, true)
currentUserSubjectsCollection().document(subject.id)
.collection(FireBaseCollections.TASK_COLLECTION)
.taskNotArchived() .taskNotArchived()
.count() .get().await()
.get(AggregateSource.SERVER) .documents
.await() .forEach {
.count.toInt() it.reference.update(TaskDocument.archived, true)
}
} }
override suspend fun getCompletedTaskCount(subject: Subject): Int { override fun getTaskCount(subject: Subject): Flow<Int> {
return subjectTasksCollection(subject) return taskDAO.getTasks(subject)
.taskNotArchived() .map(List<Task>::count)
.taskNotCompleted() }
.count()
.get(AggregateSource.SERVER) override fun getCompletedTaskCount(subject: Subject): Flow<Int> {
.await() return taskDAO.getTasks(subject)
.count.toInt() .map { tasks -> tasks.count { it.completed && !it.archived } }
} }
override fun getStudyTime(subject: Subject): Flow<Int> { override fun getStudyTime(subject: Subject): Flow<Int> {

View file

@ -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.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.subjects.SubjectRoute 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.SubjectCreateRoute
import be.ugent.sel.studeez.screens.subjects.form.SubjectEditRoute 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.TaskCreateRoute
import be.ugent.sel.studeez.screens.tasks.form.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
@ -51,6 +51,7 @@ fun StudeezNavGraph(
val open: (String) -> Unit = { appState.navigate(it) } val open: (String) -> Unit = { appState.navigate(it) }
val openAndPopUp: (String, String) -> Unit = val openAndPopUp: (String, String) -> Unit =
{ route, popUp -> appState.navigateAndPopUp(route, popUp) } { route, popUp -> appState.navigateAndPopUp(route, popUp) }
val clearAndNavigate: (route: String) -> Unit = { route -> appState.clearAndNavigate(route) }
val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp) val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp)
val navigationBarActions: NavigationBarActions = val navigationBarActions: NavigationBarActions =
@ -200,7 +201,7 @@ fun StudeezNavGraph(
composable(StudeezDestinations.SESSION_RECAP) { composable(StudeezDestinations.SESSION_RECAP) {
SessionRecapRoute( SessionRecapRoute(
openAndPopUp = openAndPopUp, clearAndNavigate = clearAndNavigate,
viewModel = hiltViewModel() viewModel = hiltViewModel()
) )
} }

View file

@ -4,18 +4,13 @@ import android.media.MediaPlayer
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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
import androidx.compose.ui.graphics.Color 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp 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.data.local.models.timer_functional.FunctionalPomodoroTimer
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
@ -23,7 +18,7 @@ import be.ugent.sel.studeez.R.string as AppText
class BreakSessionScreen( class BreakSessionScreen(
private val funPomoDoroTimer: FunctionalPomodoroTimer, private val funPomoDoroTimer: FunctionalPomodoroTimer,
private var mediaplayer: MediaPlayer? private var mediaplayer: MediaPlayer?
): AbstractSessionScreen() { ) : AbstractSessionScreen() {
@Composable @Composable
override fun MidSection() { override fun MidSection() {
@ -37,23 +32,31 @@ class BreakSessionScreen(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
) { ) {
repeat(funPomoDoroTimer.repeats - funPomoDoroTimer.breaksRemaining) { if (funPomoDoroTimer.hasEnded()) {
repeat(funPomoDoroTimer.repeats) {
Dot(Color.Green)
}
} else {
repeat(funPomoDoroTimer.repeats - funPomoDoroTimer.breaksRemaining - 1) {
Dot(color = Color.DarkGray) Dot(color = Color.DarkGray)
} }
if (!funPomoDoroTimer.isInBreak) Dot(Color.Green) else Dot(Color.DarkGray) if (!funPomoDoroTimer.isInBreak) Dot(Color.Green) else Dot(Color.DarkGray)
repeat(funPomoDoroTimer.breaksRemaining - 1) { repeat(funPomoDoroTimer.breaksRemaining) {
Dot(color = Color.Gray) Dot(color = Color.Gray)
} }
} }
} }
}
@Composable @Composable
private fun Dot(color: Color) { private fun Dot(color: Color) {
Box(modifier = Modifier Box(
modifier = Modifier
.padding(5.dp) .padding(5.dp)
.size(10.dp) .size(10.dp)
.clip(CircleShape) .clip(CircleShape)
.background(color)) .background(color)
)
} }
@Composable @Composable

View file

@ -1,13 +1,24 @@
package be.ugent.sel.studeez.screens.session_recap 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.ButtonDefaults
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable 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.Modifier
import androidx.compose.ui.graphics.Color 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.R
import be.ugent.sel.studeez.common.composable.BasicButton 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.common.ext.basicButton
import be.ugent.sel.studeez.data.local.models.SessionReport import be.ugent.sel.studeez.data.local.models.SessionReport
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
@ -21,24 +32,24 @@ data class SessionRecapActions(
fun getSessionRecapActions( fun getSessionRecapActions(
viewModel: SessionRecapViewModel, viewModel: SessionRecapViewModel,
openAndPopUp: (String, String) -> Unit, clearAndNavigate: (String) -> Unit,
): SessionRecapActions { ): SessionRecapActions {
return SessionRecapActions( return SessionRecapActions(
viewModel::getSessionReport, viewModel::getSessionReport,
{viewModel.saveSession(openAndPopUp)}, { viewModel.saveSession(clearAndNavigate) },
{viewModel.discardSession(openAndPopUp)} { viewModel.discardSession(clearAndNavigate) }
) )
} }
@Composable @Composable
fun SessionRecapRoute( fun SessionRecapRoute(
openAndPopUp: (String, String) -> Unit, clearAndNavigate: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: SessionRecapViewModel, viewModel: SessionRecapViewModel,
) { ) {
SessionRecapScreen( SessionRecapScreen(
modifier = modifier, modifier = modifier,
getSessionRecapActions(viewModel, openAndPopUp) getSessionRecapActions(viewModel, clearAndNavigate)
) )
} }
@ -47,11 +58,62 @@ fun SessionRecapScreen(modifier: Modifier, sessionRecapActions: SessionRecapActi
val sessionReport: SessionReport = sessionRecapActions.getSessionReport() val sessionReport: SessionReport = sessionRecapActions.getSessionReport()
val studyTime: Int = sessionReport.studyTime val studyTime: Int = sessionReport.studyTime
val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS() val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS()
val (background1, setBackground1) = remember { mutableStateOf(Color.Transparent) }
val (background2, setBackground2) = remember { mutableStateOf(Color.Transparent) }
Column( Column(
modifier = modifier 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,
)
Column(
modifier = Modifier.fillMaxWidth()
) {
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
)
}
}
Column {
BasicButton( BasicButton(
R.string.save, Modifier.basicButton() R.string.save, Modifier.basicButton()
) { ) {
@ -64,4 +126,20 @@ fun SessionRecapScreen(modifier: Modifier, sessionRecapActions: SessionRecapActi
sessionRecapActions.discardSession() sessionRecapActions.discardSession()
} }
} }
}
}
@Preview
@Composable
fun SessionRecapScreenPreview() {
SessionRecapScreen(
modifier = Modifier,
sessionRecapActions = SessionRecapActions(
{ SessionReport(
studyTime = 100,
) },
{},
{},
)
)
} }

View file

@ -24,15 +24,15 @@ class SessionRecapViewModel @Inject constructor(
return selectedSessionReport() return selectedSessionReport()
} }
fun saveSession(open: (String, String) -> Unit) { fun saveSession(open: (String) -> Unit) {
sessionDAO.saveSession(getSessionReport()) sessionDAO.saveSession(getSessionReport())
val newTask = val newTask =
selectedTask().copy(time = selectedTask().time + selectedSessionReport().studyTime) selectedTask().copy(time = selectedTask().time + selectedSessionReport().studyTime)
taskDAO.updateTask(newTask) taskDAO.updateTask(newTask)
open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) open(StudeezDestinations.HOME_SCREEN)
} }
fun discardSession(open: (String, String) -> Unit) { fun discardSession(open: (String) -> Unit) {
open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) open(StudeezDestinations.HOME_SCREEN)
} }
} }

View file

@ -36,6 +36,8 @@ fun SubjectRoute(
navigationBarActions = navigationBarActions, navigationBarActions = navigationBarActions,
onAddSubject = { viewModel.onAddSubject(open) }, onAddSubject = { viewModel.onAddSubject(open) },
onViewSubject = { viewModel.onViewSubject(it, open) }, onViewSubject = { viewModel.onViewSubject(it, open) },
getTaskCount = viewModel::getTaskCount,
getCompletedTaskCount = viewModel::getCompletedTaskCount,
getStudyTime = viewModel::getStudyTime, getStudyTime = viewModel::getStudyTime,
uiState, uiState,
) )
@ -47,6 +49,8 @@ fun SubjectScreen(
navigationBarActions: NavigationBarActions, navigationBarActions: NavigationBarActions,
onAddSubject: () -> Unit, onAddSubject: () -> Unit,
onViewSubject: (Subject) -> Unit, onViewSubject: (Subject) -> Unit,
getTaskCount: (Subject) -> Flow<Int>,
getCompletedTaskCount: (Subject) -> Flow<Int>,
getStudyTime: (Subject) -> Flow<Int>, getStudyTime: (Subject) -> Flow<Int>,
uiState: SubjectUiState, uiState: SubjectUiState,
) { ) {
@ -76,6 +80,8 @@ fun SubjectScreen(
SubjectEntry( SubjectEntry(
subject = it, subject = it,
onViewSubject = { onViewSubject(it) }, onViewSubject = { onViewSubject(it) },
getTaskCount = { getTaskCount(it) },
getCompletedTaskCount = { getCompletedTaskCount(it) },
getStudyTime = { getStudyTime(it) }, getStudyTime = { getStudyTime(it) },
) )
} }
@ -94,13 +100,14 @@ fun SubjectScreenPreview() {
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
onAddSubject = {}, onAddSubject = {},
onViewSubject = {}, onViewSubject = {},
getTaskCount = { flowOf() },
getCompletedTaskCount = { flowOf() },
getStudyTime = { flowOf() }, getStudyTime = { flowOf() },
uiState = SubjectUiState.Succes( uiState = SubjectUiState.Succes(
listOf( listOf(
Subject( Subject(
name = "Test Subject", name = "Test Subject",
argb_color = 0xFFFFD200, argb_color = 0xFFFFD200,
taskCount = 5, taskCompletedCount = 2,
) )
) )
) )
@ -115,7 +122,9 @@ fun SubjectScreenLoadingPreview() {
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
onAddSubject = {}, onAddSubject = {},
onViewSubject = {}, onViewSubject = {},
getTaskCount = { flowOf() },
getCompletedTaskCount = { flowOf() },
getStudyTime = { flowOf() }, getStudyTime = { flowOf() },
uiState = SubjectUiState.Loading uiState = SubjectUiState.Loading,
) )
} }

View file

@ -30,6 +30,14 @@ class SubjectViewModel @Inject constructor(
open(StudeezDestinations.ADD_SUBJECT_FORM) open(StudeezDestinations.ADD_SUBJECT_FORM)
} }
fun getTaskCount(subject: Subject): Flow<Int> {
return subjectDAO.getTaskCount(subject)
}
fun getCompletedTaskCount(subject: Subject): Flow<Int> {
return subjectDAO.getCompletedTaskCount(subject)
}
fun getStudyTime(subject: Subject): Flow<Int> { fun getStudyTime(subject: Subject): Flow<Int> {
return subjectDAO.getStudyTime(subject) return subjectDAO.getStudyTime(subject)
} }

View file

@ -2,20 +2,28 @@ 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
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.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview 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.BasicButton
import be.ugent.sel.studeez.common.composable.DeleteButton 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.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.ext.basicButton import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.ext.fieldModifier import be.ugent.sel.studeez.common.ext.fieldModifier
import be.ugent.sel.studeez.common.ext.generateRandomArgb
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
import kotlinx.coroutines.launch
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
@Composable @Composable
@ -31,7 +39,7 @@ fun SubjectCreateRoute(
uiState = uiState, uiState = uiState,
onConfirm = { viewModel.onCreate(openAndPopUp) }, onConfirm = { viewModel.onCreate(openAndPopUp) },
onNameChange = viewModel::onNameChange, onNameChange = viewModel::onNameChange,
onColorChange = {}, onColorChange = viewModel::onColorChange,
) )
} }
@ -42,18 +50,21 @@ fun SubjectEditRoute(
viewModel: SubjectEditFormViewModel, viewModel: SubjectEditFormViewModel,
) { ) {
val uiState by viewModel.uiState val uiState by viewModel.uiState
val coroutineScope = rememberCoroutineScope()
SubjectForm( SubjectForm(
title = AppText.edit_subject, title = AppText.edit_subject,
goBack = goBack, goBack = goBack,
uiState = uiState, uiState = uiState,
onConfirm = { viewModel.onEdit(openAndPopUp) }, onConfirm = { viewModel.onEdit(openAndPopUp) },
onNameChange = viewModel::onNameChange, onNameChange = viewModel::onNameChange,
onColorChange = {}, onColorChange = viewModel::onColorChange,
) { ) {
DeleteButton(text = AppText.delete_subject) { DeleteButton(text = AppText.delete_subject) {
coroutineScope.launch {
viewModel.onDelete(openAndPopUp) viewModel.onDelete(openAndPopUp)
} }
} }
}
} }
@Composable @Composable
@ -63,21 +74,21 @@ fun SubjectForm(
uiState: SubjectFormUiState, uiState: SubjectFormUiState,
onConfirm: () -> Unit, onConfirm: () -> Unit,
onNameChange: (String) -> Unit, onNameChange: (String) -> Unit,
onColorChange: (Color) -> Unit, onColorChange: (Long) -> Unit,
extraButton: @Composable () -> Unit = {}, extraButton: @Composable () -> Unit = {},
) { ) {
SecondaryScreenTemplate( FormComposable(
title = resources().getString(title), title = resources().getString(title),
popUp = goBack, popUp = goBack,
) { ) {
Column { Column {
OutlinedTextField( LabelledInputField(
singleLine = true, singleLine = true,
value = uiState.name, value = uiState.name,
onValueChange = onNameChange, onNewValue = onNameChange,
placeholder = { Text(stringResource(id = AppText.name)) }, label = AppText.name,
modifier = Modifier.fieldModifier(),
) )
ColorPicker(onColorChange, uiState)
BasicButton( BasicButton(
text = AppText.confirm, text = AppText.confirm,
modifier = Modifier.basicButton(), 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 @Preview
@Composable @Composable
fun AddSubjectFormPreview() { fun AddSubjectFormPreview() {

View file

@ -1,6 +1,9 @@
package be.ugent.sel.studeez.screens.subjects.form 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( data class SubjectFormUiState(
val name: String = "", val name: String = "",
val color: Long = 0xFFFFD200, val color: Long = Color.generateRandomArgb(),
) )

View file

@ -2,10 +2,13 @@ package be.ugent.sel.studeez.screens.subjects.form
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf 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.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
import be.ugent.sel.studeez.domain.SubjectDAO 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.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
@ -59,6 +62,7 @@ class SubjectCreateFormViewModel @Inject constructor(
@HiltViewModel @HiltViewModel
class SubjectEditFormViewModel @Inject constructor( class SubjectEditFormViewModel @Inject constructor(
subjectDAO: SubjectDAO, subjectDAO: SubjectDAO,
private val taskDAO: TaskDAO,
selectedSubject: SelectedSubject, selectedSubject: SelectedSubject,
logService: LogService, logService: LogService,
) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) { ) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) {
@ -69,17 +73,19 @@ class SubjectEditFormViewModel @Inject constructor(
) )
) )
fun onDelete(openAndPopUp: (String, String) -> Unit) { suspend fun onDelete(openAndPopUp: (String, String) -> Unit) {
subjectDAO.updateSubject(selectedSubject().copy(archived = true)) subjectDAO.archiveSubject(selectedSubject())
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM)
} }
fun onEdit(openAndPopUp: (String, String) -> Unit) { fun onEdit(openAndPopUp: (String, String) -> Unit) {
val newSubject = selectedSubject().copy( selectedSubject.set(
selectedSubject().copy(
name = name, name = name,
argb_color = color, argb_color = color,
) )
subjectDAO.updateSubject(newSubject) )
subjectDAO.updateSubject(selectedSubject())
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM)
} }
} }

View file

@ -11,7 +11,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.common.composable.BasicButton import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.DeleteButton 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.basicButton
import be.ugent.sel.studeez.common.ext.fieldModifier import be.ugent.sel.studeez.common.ext.fieldModifier
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
@ -62,7 +62,7 @@ fun TaskForm(
onNameChange: (String) -> Unit, onNameChange: (String) -> Unit,
extraButton: @Composable () -> Unit = {} extraButton: @Composable () -> Unit = {}
) { ) {
SecondaryScreenTemplate( FormComposable(
title = resources().getString(title), title = resources().getString(title),
popUp = goBack, popUp = goBack,
) { ) {

View file

@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import be.ugent.sel.studeez.common.composable.DeleteButton import be.ugent.sel.studeez.common.composable.DeleteButton
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate 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.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
@ -59,7 +60,10 @@ fun TimerFormScreen(
) { ) {
val timerFormScreen = getTimerInfo().accept(GetTimerFormScreen()) val timerFormScreen = getTimerInfo().accept(GetTimerFormScreen())
SecondaryScreenTemplate(title = stringResource(id = label), popUp = popUp) { FormComposable(
title = stringResource(id = label),
popUp = popUp
) {
timerFormScreen(onConfirmClick, extraButton) timerFormScreen(onConfirmClick, extraButton)
} }
} }

View file

@ -1,13 +1,7 @@
package be.ugent.sel.studeez.screens.timer_form.form_screens 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.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.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import be.ugent.sel.studeez.R import be.ugent.sel.studeez.R
@ -37,16 +31,7 @@ abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) {
extraButton: @Composable () -> Unit = {}, extraButton: @Composable () -> Unit = {},
) { ) {
Column( Column {
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxHeight()
.verticalScroll(rememberScrollState()),
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Fields that every timer shares (ommited id) // Fields that every timer shares (ommited id)
LabeledErrorTextField( LabeledErrorTextField(
@ -67,7 +52,7 @@ abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) {
errorText = AppText.description_error, errorText = AppText.description_error,
isValid = valids.getValue("description"), isValid = valids.getValue("description"),
isFirst = firsts.getValue("description"), isFirst = firsts.getValue("description"),
singleLine= false, singleLine = false,
keyboardType = KeyboardType.Text, keyboardType = KeyboardType.Text,
predicate = { textPredicate(it) } predicate = { textPredicate(it) }
) { correctName -> ) { correctName ->
@ -76,21 +61,19 @@ abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) {
ExtraFields() ExtraFields()
}
Column {
BasicButton(R.string.save, Modifier.basicButton()) { BasicButton(R.string.save, Modifier.basicButton()) {
if (valids.all { it.component2().value }) { // All fields are valid if (valids.all { it.component2().value }) { // All fields are valid
onSaveClick(timerInfo) onSaveClick(timerInfo)
} else { } else {
firsts.map { it.component2().value = false } // dont mask error because its not been filled out yet firsts.map {
it.component2().value = false
} // dont mask error because its not been filled out yet
SnackbarManager.showMessage(AppText.fill_out_error) SnackbarManager.showMessage(AppText.fill_out_error)
} }
} }
extraButton() extraButton()
} }
} }
}
private fun textPredicate(text: String): Boolean { private fun textPredicate(text: String): Boolean {
return text.isNotBlank() return text.isNotBlank()

View file

@ -0,0 +1,5 @@
<vector android:height="75dp" android:tint="#999999"
android:viewportHeight="24" android:viewportWidth="24"
android:width="75dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,17.5c2.33,0 4.31,-1.46 5.11,-3.5L6.89,14c0.8,2.04 2.78,3.5 5.11,3.5z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="75dp" android:tint="#999999"
android:viewportHeight="24" android:viewportWidth="24"
android:width="75dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,14c-2.33,0 -4.31,1.46 -5.11,3.5h10.22c-0.8,-2.04 -2.78,-3.5 -5.11,-3.5z"/>
</vector>

View file

@ -46,6 +46,7 @@
<string name="delete_subject">Delete Subject</string> <string name="delete_subject">Delete Subject</string>
<string name="delete_task">Delete Task</string> <string name="delete_task">Delete Task</string>
<string name="view_tasks">View</string> <string name="view_tasks">View</string>
<string name="regenerate_color">Regenerate Color</string>
<!-- Sessions --> <!-- Sessions -->
<string name="sessions_temp_description">Looks like you found the sessions screen! In here, your upcoming studying sessions with friends will be listed. You can accept invites or edit your own.</string> <!-- TODO Remove this description line once implemented. --> <string name="sessions_temp_description">Looks like you found the sessions screen! In here, your upcoming studying sessions with friends will be listed. You can accept invites or edit your own.</string> <!-- TODO Remove this description line once implemented. -->
@ -156,4 +157,11 @@
<string name="breakTime">Break Time</string> <string name="breakTime">Break Time</string>
<string name="repeats">Number of Repeats</string> <string name="repeats">Number of Repeats</string>
<!-- Session Recap -->
<string name="congrats">"Congratulations! You studied: %s"</string>
<string name="how_did_it_go">How did it go?</string>
<string name="good">Good</string>
<string name="bad">Bad</string>
</resources> </resources>