resolve conflicts
This commit is contained in:
commit
9bc210c3d0
83 changed files with 1867 additions and 788 deletions
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
|
@ -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">
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -2,6 +2,7 @@ package be.ugent.sel.studeez.common.composable
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
@ -20,6 +21,7 @@ import androidx.compose.material.icons.filled.Add
|
||||||
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.scale
|
||||||
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
|
||||||
|
@ -52,6 +54,7 @@ fun BasicButton(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
colors: ButtonColors = ButtonDefaults.buttonColors(),
|
colors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||||
border: BorderStroke? = null,
|
border: BorderStroke? = null,
|
||||||
|
enabled: Boolean = true,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
|
@ -60,6 +63,7 @@ fun BasicButton(
|
||||||
shape = defaultButtonShape(),
|
shape = defaultButtonShape(),
|
||||||
colors = colors,
|
colors = colors,
|
||||||
border = border,
|
border = border,
|
||||||
|
enabled = enabled,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(text),
|
text = stringResource(text),
|
||||||
|
@ -78,17 +82,22 @@ fun BasicButtonPreview() {
|
||||||
fun StealthButton(
|
fun StealthButton(
|
||||||
@StringRes text: Int,
|
@StringRes text: Int,
|
||||||
modifier: Modifier = Modifier.card(),
|
modifier: Modifier = Modifier.card(),
|
||||||
|
enabled: Boolean = true,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
//val clickablemodifier = if (disabled) Modifier.clickable(indication = null) else modifier
|
||||||
|
val borderColor = if (enabled) MaterialTheme.colors.primary
|
||||||
|
else MaterialTheme.colors.onSurface.copy(alpha = 0.3f)
|
||||||
BasicButton(
|
BasicButton(
|
||||||
text = text,
|
text = text,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
enabled = enabled,
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
backgroundColor = MaterialTheme.colors.surface,
|
backgroundColor = MaterialTheme.colors.surface,
|
||||||
contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.4f)
|
contentColor = borderColor
|
||||||
),
|
),
|
||||||
border = BorderStroke(3.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.4f))
|
border = BorderStroke(2.dp, borderColor)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,10 +3,13 @@ package be.ugent.sel.studeez.common.composable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.Text
|
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.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -23,4 +26,14 @@ fun Headline(
|
||||||
fontSize = 34.sp
|
fontSize = 34.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DateText(date: String) {
|
||||||
|
Text(
|
||||||
|
text = date,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
modifier = Modifier.padding(horizontal = 10.dp)
|
||||||
|
)
|
||||||
}
|
}
|
|
@ -4,7 +4,6 @@ import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
@ -50,7 +49,7 @@ fun LabelledInputField(
|
||||||
value: String,
|
value: String,
|
||||||
onNewValue: (String) -> Unit,
|
onNewValue: (String) -> Unit,
|
||||||
@StringRes label: Int,
|
@StringRes label: Int,
|
||||||
singleLine: Boolean = false
|
singleLine: Boolean = true
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = value,
|
value = value,
|
||||||
|
@ -122,7 +121,9 @@ fun LabeledErrorTextField(
|
||||||
initialValue: String,
|
initialValue: String,
|
||||||
@StringRes label: Int,
|
@StringRes label: Int,
|
||||||
singleLine: Boolean = false,
|
singleLine: Boolean = false,
|
||||||
errorText: Int,
|
isValid: MutableState<Boolean> = remember { mutableStateOf(true) },
|
||||||
|
isFirst: MutableState<Boolean> = remember { mutableStateOf(false) },
|
||||||
|
@StringRes errorText: Int,
|
||||||
keyboardType: KeyboardType,
|
keyboardType: KeyboardType,
|
||||||
predicate: (String) -> Boolean,
|
predicate: (String) -> Boolean,
|
||||||
onNewCorrectValue: (String) -> Unit
|
onNewCorrectValue: (String) -> Unit
|
||||||
|
@ -131,31 +132,28 @@ fun LabeledErrorTextField(
|
||||||
mutableStateOf(initialValue)
|
mutableStateOf(initialValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isValid by remember {
|
|
||||||
mutableStateOf(predicate(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = modifier.fieldModifier(),
|
modifier = modifier.fieldModifier(),
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = { newText ->
|
onValueChange = { newText ->
|
||||||
|
isFirst.value = false
|
||||||
value = newText
|
value = newText
|
||||||
isValid = predicate(value)
|
isValid.value = predicate(value)
|
||||||
if (isValid) {
|
if (isValid.value) {
|
||||||
onNewCorrectValue(newText)
|
onNewCorrectValue(newText)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
singleLine = singleLine,
|
singleLine = singleLine,
|
||||||
label = { Text(text = stringResource(id = label)) },
|
label = { Text(text = stringResource(id = label)) },
|
||||||
isError = !isValid,
|
isError = !isValid.value && !isFirst.value,
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
keyboardType = keyboardType,
|
keyboardType = keyboardType,
|
||||||
imeAction = ImeAction.Done
|
imeAction = ImeAction.Done
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid.value && !isFirst.value) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(start = 16.dp),
|
modifier = Modifier.padding(start = 16.dp),
|
||||||
text = stringResource(id = errorText),
|
text = stringResource(id = errorText),
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable.feed
|
||||||
|
|
||||||
|
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.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import be.ugent.sel.studeez.common.composable.BasicTextButton
|
||||||
|
import be.ugent.sel.studeez.common.composable.DateText
|
||||||
|
import be.ugent.sel.studeez.common.composable.Headline
|
||||||
|
import be.ugent.sel.studeez.common.ext.textButton
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Feed(
|
||||||
|
uiState: FeedUiState,
|
||||||
|
continueTask: (String, String) -> Unit,
|
||||||
|
onEmptyFeedHelp: () -> Unit
|
||||||
|
) {
|
||||||
|
when (uiState) {
|
||||||
|
FeedUiState.Loading -> LoadingFeed()
|
||||||
|
is FeedUiState.Succes -> LoadedFeed(
|
||||||
|
uiState = uiState,
|
||||||
|
continueTask = continueTask,
|
||||||
|
onEmptyFeedHelp = onEmptyFeedHelp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadedFeed(
|
||||||
|
uiState: FeedUiState.Succes,
|
||||||
|
continueTask: (String, String) -> Unit,
|
||||||
|
onEmptyFeedHelp: () -> Unit,
|
||||||
|
) {
|
||||||
|
if (uiState.feedEntries.isEmpty()) EmptyFeed(onEmptyFeedHelp)
|
||||||
|
else FeedWithElements(uiState = uiState, continueTask = continueTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadingFeed() {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight(),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(color = MaterialTheme.colors.onBackground)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FeedWithElements(
|
||||||
|
uiState: FeedUiState.Succes,
|
||||||
|
continueTask: (String, String) -> Unit,
|
||||||
|
) {
|
||||||
|
val feedEntries = uiState.feedEntries
|
||||||
|
LazyColumn {
|
||||||
|
items(feedEntries.toList()) { (date, feedEntries) ->
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
val totalDayStudyTime: Int = feedEntries.sumOf { it.totalStudyTime }
|
||||||
|
DateText(date = date)
|
||||||
|
Text(
|
||||||
|
text = "${HoursMinutesSeconds(totalDayStudyTime)}",
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
feedEntries.forEach { feedEntry ->
|
||||||
|
FeedEntry(feedEntry = feedEntry) {
|
||||||
|
continueTask(feedEntry.subjectId, feedEntry.taskId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EmptyFeed(onEmptyFeedHelp: () -> Unit) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Headline(text = stringResource(id = AppText.your_feed))
|
||||||
|
|
||||||
|
BasicTextButton(
|
||||||
|
AppText.empty_feed_help_text,
|
||||||
|
Modifier.textButton(),
|
||||||
|
action = onEmptyFeedHelp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FeedLoadingPreview() {
|
||||||
|
Feed(
|
||||||
|
uiState = FeedUiState.Loading,
|
||||||
|
continueTask = { _, _ -> run {} }, {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FeedPreview() {
|
||||||
|
Feed(
|
||||||
|
uiState = FeedUiState.Succes(
|
||||||
|
mapOf(
|
||||||
|
"08 May 2023" to listOf(
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
totalStudyTime = 600,
|
||||||
|
),
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
totalStudyTime = 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"09 May 2023" to listOf(
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFD1200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
),
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
continueTask = { _, _ -> run {} }, {}
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable.feed
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Card
|
||||||
|
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.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import be.ugent.sel.studeez.common.composable.StealthButton
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||||
|
import be.ugent.sel.studeez.R.string as AppText
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FeedEntry(
|
||||||
|
feedEntry: FeedEntry,
|
||||||
|
continueWithTask: () -> Unit,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 10.dp, vertical = 5.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 10.dp)
|
||||||
|
.weight(11f)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(20.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color(feedEntry.argb_color)),
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(0.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = feedEntry.subJectName,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = feedEntry.taskName,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(text = HoursMinutesSeconds(feedEntry.totalStudyTime).toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val buttonText: Int =
|
||||||
|
if (feedEntry.isArchived) AppText.deleted else AppText.continue_task
|
||||||
|
StealthButton(
|
||||||
|
text = buttonText,
|
||||||
|
enabled = !feedEntry.isArchived,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 10.dp, end = 5.dp)
|
||||||
|
.weight(6f)
|
||||||
|
) {
|
||||||
|
if (!feedEntry.isArchived) {
|
||||||
|
continueWithTask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FeedEntryPreview() {
|
||||||
|
FeedEntry(
|
||||||
|
continueWithTask = {},
|
||||||
|
feedEntry = FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
totalStudyTime = 20,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun FeedEntryOverflowPreview() {
|
||||||
|
FeedEntry(
|
||||||
|
continueWithTask = {},
|
||||||
|
feedEntry = FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkk",
|
||||||
|
totalStudyTime = 20,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable.feed
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
|
||||||
|
sealed interface FeedUiState {
|
||||||
|
object Loading : FeedUiState
|
||||||
|
data class Succes(val feedEntries: Map<String, List<FeedEntry>>) : FeedUiState
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package be.ugent.sel.studeez.common.composable.feed
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import be.ugent.sel.studeez.data.SelectedTask
|
||||||
|
import be.ugent.sel.studeez.domain.FeedDAO
|
||||||
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
|
import be.ugent.sel.studeez.domain.TaskDAO
|
||||||
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
|
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class FeedViewModel @Inject constructor(
|
||||||
|
feedDAO: FeedDAO,
|
||||||
|
private val taskDAO: TaskDAO,
|
||||||
|
private val selectedTask: SelectedTask,
|
||||||
|
logService: LogService
|
||||||
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
|
val uiState: StateFlow<FeedUiState> = feedDAO.getFeedEntries()
|
||||||
|
.map { FeedUiState.Succes(it) }
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
initialValue = FeedUiState.Loading,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun continueTask(open: (String) -> Unit, subjectId: String, taskId: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val task = taskDAO.getTask(subjectId, taskId)
|
||||||
|
selectedTask.set(task)
|
||||||
|
open(StudeezDestinations.TIMER_SELECTION_SCREEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onEmptyFeedHelp(open: (String) -> Unit) {
|
||||||
|
open(StudeezDestinations.ADD_SUBJECT_FORM)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,24 @@ 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,
|
||||||
|
getTaskCount: () -> Flow<Int>,
|
||||||
|
getCompletedTaskCount: () -> Flow<Int>,
|
||||||
|
getStudyTime: () -> Flow<Int>,
|
||||||
) {
|
) {
|
||||||
|
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()
|
||||||
|
@ -70,7 +74,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 +84,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 = "${completedTaskCount}/${taskCount}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,9 +108,12 @@ fun SubjectEntryPreview() {
|
||||||
subject = Subject(
|
subject = Subject(
|
||||||
name = "Test Subject",
|
name = "Test Subject",
|
||||||
argb_color = 0xFFFFD200,
|
argb_color = 0xFFFFD200,
|
||||||
time = 60
|
|
||||||
),
|
),
|
||||||
) {}
|
onViewSubject = {},
|
||||||
|
getTaskCount = { flowOf() },
|
||||||
|
getCompletedTaskCount = { flowOf() },
|
||||||
|
getStudyTime = { flowOf() },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
|
@ -116,7 +123,10 @@ fun OverflowSubjectEntryPreview() {
|
||||||
subject = Subject(
|
subject = Subject(
|
||||||
name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt",
|
name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt",
|
||||||
argb_color = 0xFFFFD200,
|
argb_color = 0xFFFFD200,
|
||||||
time = 60
|
|
||||||
),
|
),
|
||||||
) {}
|
onViewSubject = {},
|
||||||
|
getTaskCount = { flowOf() },
|
||||||
|
getCompletedTaskCount = { flowOf() },
|
||||||
|
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,8 @@ 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
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -80,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)
|
||||||
) {
|
) {
|
||||||
|
@ -95,6 +86,7 @@ fun TaskEntry(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 5.dp),
|
.padding(end = 5.dp),
|
||||||
) {
|
) {
|
||||||
|
onStartTask()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +102,7 @@ fun TaskEntryPreview() {
|
||||||
name = "Test Task",
|
name = "Test Task",
|
||||||
completed = false,
|
completed = false,
|
||||||
),
|
),
|
||||||
{}, {},
|
{}, {}, {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +114,7 @@ fun CompletedTaskEntryPreview() {
|
||||||
name = "Test Task",
|
name = "Test Task",
|
||||||
completed = true,
|
completed = true,
|
||||||
),
|
),
|
||||||
{}, {},
|
{}, {}, {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +126,6 @@ fun OverflowTaskEntryPreview() {
|
||||||
name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk",
|
name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk",
|
||||||
completed = false,
|
completed = false,
|
||||||
),
|
),
|
||||||
{}, {},
|
{}, {}, {}
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -1,8 +1,12 @@
|
||||||
package be.ugent.sel.studeez.common.ext
|
package be.ugent.sel.studeez.common.ext
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
fun Modifier.textButton(): Modifier {
|
fun Modifier.textButton(): Modifier {
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package be.ugent.sel.studeez.data
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class EditTimerState @Inject constructor(){
|
|
||||||
lateinit var timerInfo: TimerInfo
|
|
||||||
}
|
|
45
app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt
Normal file
45
app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package be.ugent.sel.studeez.data
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||||
|
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.timer_functional.FunctionalTimer
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to cummunicate between viewmodels.
|
||||||
|
*/
|
||||||
|
abstract class SelectedState<T> {
|
||||||
|
abstract var value: T
|
||||||
|
operator fun invoke() = value
|
||||||
|
fun set(newValue: T) {
|
||||||
|
this.value = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SelectedSessionReport @Inject constructor() : SelectedState<SessionReport>() {
|
||||||
|
override lateinit var value: SessionReport
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SelectedTask @Inject constructor() : SelectedState<Task>() {
|
||||||
|
override lateinit var value: Task
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SelectedTimer @Inject constructor() : SelectedState<FunctionalTimer>() {
|
||||||
|
override lateinit var value: FunctionalTimer
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SelectedSubject @Inject constructor() : SelectedState<Subject>() {
|
||||||
|
override lateinit var value: Subject
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SelectedTimerInfo @Inject constructor() : SelectedState<TimerInfo>() {
|
||||||
|
override lateinit var value: TimerInfo
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
package be.ugent.sel.studeez.data
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to communicate the selected subject from the subject overview other screens.
|
|
||||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
class SelectedSubject @Inject constructor() {
|
|
||||||
private lateinit var subject: Subject
|
|
||||||
operator fun invoke() = subject
|
|
||||||
fun set(subject: Subject) {
|
|
||||||
this.subject = subject
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isSet() = this::subject.isInitialized
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package be.ugent.sel.studeez.data
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.task.Task
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to communicate the selected task from the task overview other screens.
|
|
||||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
class SelectedTask @Inject constructor() {
|
|
||||||
private lateinit var task: Task
|
|
||||||
|
|
||||||
operator fun invoke() = task
|
|
||||||
fun set(task: Task) {
|
|
||||||
this.task = task
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isSet() = this::task.isInitialized
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package be.ugent.sel.studeez.data
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to communicate the SelectedTimer from the selection screen to the session screen.
|
|
||||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
class SelectedTimerState @Inject constructor(){
|
|
||||||
var selectedTimer: FunctionalTimer? = null
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package be.ugent.sel.studeez.data
|
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.SessionReport
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to communicate the SelectedTimer from the selection screen to the session screen.
|
|
||||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
class SessionReportState @Inject constructor(){
|
|
||||||
var sessionReport: SessionReport? = null
|
|
||||||
}
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package be.ugent.sel.studeez.data.local.models
|
||||||
|
|
||||||
|
import com.google.firebase.Timestamp
|
||||||
|
|
||||||
|
data class FeedEntry(
|
||||||
|
val argb_color: Long = 0,
|
||||||
|
val subJectName: String = "",
|
||||||
|
val taskName: String = "",
|
||||||
|
val taskId: String = "", // Name of task is not unique
|
||||||
|
val subjectId: String = "",
|
||||||
|
val totalStudyTime: Int = 0,
|
||||||
|
val endTime: Timestamp = Timestamp(0, 0),
|
||||||
|
val isArchived: Boolean = false
|
||||||
|
)
|
|
@ -6,5 +6,7 @@ import com.google.firebase.firestore.DocumentId
|
||||||
data class SessionReport(
|
data class SessionReport(
|
||||||
@DocumentId val id: String = "",
|
@DocumentId val id: String = "",
|
||||||
val studyTime: Int = 0,
|
val studyTime: Int = 0,
|
||||||
val endTime: Timestamp = Timestamp(0, 0)
|
val endTime: Timestamp = Timestamp(0, 0),
|
||||||
|
val taskId: String = "",
|
||||||
|
val subjectId: String = ""
|
||||||
)
|
)
|
|
@ -1,10 +1,18 @@
|
||||||
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
object SubjectDocument {
|
||||||
|
const val id = "id"
|
||||||
|
const val name = "name"
|
||||||
|
const val archived = "archived"
|
||||||
|
const val argb_color = "argb_color"
|
||||||
|
}
|
|
@ -5,9 +5,10 @@ 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,
|
||||||
)
|
)
|
||||||
|
|
||||||
object TaskDocument {
|
object TaskDocument {
|
||||||
|
@ -16,4 +17,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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,17 @@ package be.ugent.sel.studeez.data.local.models.timer_functional
|
||||||
|
|
||||||
class FunctionalPomodoroTimer(
|
class FunctionalPomodoroTimer(
|
||||||
private var studyTime: Int,
|
private var studyTime: Int,
|
||||||
private var breakTime: Int, repeats: Int
|
private var breakTime: 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--
|
||||||
|
|
|
@ -2,6 +2,7 @@ package be.ugent.sel.studeez.data.local.models.timer_functional
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.local.models.SessionReport
|
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||||
import com.google.firebase.Timestamp
|
import com.google.firebase.Timestamp
|
||||||
|
import com.google.firebase.firestore.DocumentReference
|
||||||
|
|
||||||
abstract class FunctionalTimer(initialValue: Int) {
|
abstract class FunctionalTimer(initialValue: Int) {
|
||||||
var time: Time = Time(initialValue)
|
var time: Time = Time(initialValue)
|
||||||
|
@ -17,10 +18,12 @@ abstract class FunctionalTimer(initialValue: Int) {
|
||||||
|
|
||||||
abstract fun hasCurrentCountdownEnded(): Boolean
|
abstract fun hasCurrentCountdownEnded(): Boolean
|
||||||
|
|
||||||
fun getSessionReport(): SessionReport {
|
fun getSessionReport(subjectId: String, taskId: String): SessionReport {
|
||||||
return SessionReport(
|
return SessionReport(
|
||||||
studyTime = totalStudyTime,
|
studyTime = totalStudyTime,
|
||||||
endTime = Timestamp.now()
|
endTime = Timestamp.now(),
|
||||||
|
taskId = taskId,
|
||||||
|
subjectId = subjectId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,4 +36,7 @@ abstract class DatabaseModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun provideTaskDAO(impl: FirebaseTaskDAO): TaskDAO
|
abstract fun provideTaskDAO(impl: FirebaseTaskDAO): TaskDAO
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun provideFeedDAO(impl: FirebaseFeedDAO): FeedDAO
|
||||||
}
|
}
|
10
app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt
Normal file
10
app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package be.ugent.sel.studeez.domain
|
||||||
|
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface FeedDAO {
|
||||||
|
|
||||||
|
fun getFeedEntries(): Flow<Map<String, List<FeedEntry>>>
|
||||||
|
|
||||||
|
}
|
|
@ -12,4 +12,12 @@ interface SubjectDAO {
|
||||||
fun deleteSubject(oldSubject: Subject)
|
fun deleteSubject(oldSubject: Subject)
|
||||||
|
|
||||||
fun updateSubject(newSubject: Subject)
|
fun updateSubject(newSubject: Subject)
|
||||||
|
|
||||||
|
suspend fun archiveSubject(subject: Subject)
|
||||||
|
|
||||||
|
fun getTaskCount(subject: Subject): Flow<Int>
|
||||||
|
fun getCompletedTaskCount(subject: Subject): Flow<Int>
|
||||||
|
fun getStudyTime(subject: Subject): Flow<Int>
|
||||||
|
|
||||||
|
suspend fun getSubject(subjectId: String): Subject?
|
||||||
}
|
}
|
|
@ -14,5 +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
|
||||||
}
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package be.ugent.sel.studeez.domain.implementation
|
||||||
|
|
||||||
|
import android.icu.text.DateFormat
|
||||||
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
|
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||||
|
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||||
|
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||||
|
import be.ugent.sel.studeez.domain.FeedDAO
|
||||||
|
import be.ugent.sel.studeez.domain.SessionDAO
|
||||||
|
import be.ugent.sel.studeez.domain.SubjectDAO
|
||||||
|
import be.ugent.sel.studeez.domain.TaskDAO
|
||||||
|
import com.google.firebase.Timestamp
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class FirebaseFeedDAO @Inject constructor(
|
||||||
|
private val sessionDAO: SessionDAO,
|
||||||
|
private val taskDAO: TaskDAO,
|
||||||
|
private val subjectDAO: SubjectDAO
|
||||||
|
) : FeedDAO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>>> {
|
||||||
|
return sessionDAO.getSessions().map { sessionReports ->
|
||||||
|
sessionReports
|
||||||
|
.map { sessionReport -> sessionToFeedEntry(sessionReport) }
|
||||||
|
.sortedByDescending { it.endTime }
|
||||||
|
.groupBy { getFormattedTime(it) }
|
||||||
|
.mapValues { (_, entries) ->
|
||||||
|
entries
|
||||||
|
.groupBy { it.taskId }
|
||||||
|
.map { fuseFeedEntries(it.component2()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFormattedTime(entry: FeedEntry): String {
|
||||||
|
return DateFormat.getDateInstance().format(entry.endTime.toDate())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Givin a list of entries referencing the same task, in the same day, fuse them into one
|
||||||
|
* feed-entry by adding the studytime and keeping the most recent end-timestamp
|
||||||
|
*/
|
||||||
|
private fun fuseFeedEntries(entries: List<FeedEntry>): FeedEntry =
|
||||||
|
entries.drop(1).fold(entries[0]) { accEntry, newEntry ->
|
||||||
|
accEntry.copy(
|
||||||
|
totalStudyTime = accEntry.totalStudyTime + newEntry.totalStudyTime,
|
||||||
|
endTime = getMostRecent(accEntry.endTime, newEntry.endTime)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMostRecent(t1: Timestamp, t2: Timestamp): Timestamp {
|
||||||
|
return if (t1 < t2) t2 else t1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a sessionReport to a feedEntry. Fetch Task and Subject to get names
|
||||||
|
*/
|
||||||
|
private suspend fun sessionToFeedEntry(sessionReport: SessionReport): FeedEntry {
|
||||||
|
val subjectId: String = sessionReport.subjectId
|
||||||
|
val taskId: String = sessionReport.taskId
|
||||||
|
|
||||||
|
val task: Task = taskDAO.getTask(subjectId, taskId)
|
||||||
|
val subject: Subject = subjectDAO.getSubject(subjectId)!!
|
||||||
|
|
||||||
|
return FeedEntry(
|
||||||
|
argb_color = subject.argb_color,
|
||||||
|
subJectName = subject.name,
|
||||||
|
taskName = task.name,
|
||||||
|
taskId = task.id,
|
||||||
|
subjectId = subject.id,
|
||||||
|
totalStudyTime = sessionReport.studyTime,
|
||||||
|
endTime = sessionReport.endTime,
|
||||||
|
isArchived = task.archived || subject.archived
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,40 @@
|
||||||
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.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 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 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 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,
|
||||||
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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getSubject(subjectId: String): Subject? {
|
||||||
|
return currentUserSubjectsCollection().document(subjectId).get().await().toObject()
|
||||||
|
}
|
||||||
|
|
||||||
override fun saveSubject(newSubject: Subject) {
|
override fun saveSubject(newSubject: Subject) {
|
||||||
currentUserSubjectsCollection().add(newSubject)
|
currentUserSubjectsCollection().add(newSubject)
|
||||||
}
|
}
|
||||||
|
@ -32,8 +47,48 @@ class FirebaseSubjectDAO @Inject constructor(
|
||||||
currentUserSubjectsCollection().document(newSubject.id).set(newSubject)
|
currentUserSubjectsCollection().document(newSubject.id).set(newSubject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun archiveSubject(subject: Subject) {
|
||||||
|
currentUserSubjectsCollection().document(subject.id).update(SubjectDocument.archived, true)
|
||||||
|
currentUserSubjectsCollection().document(subject.id)
|
||||||
|
.collection(FirebaseCollections.TASK_COLLECTION)
|
||||||
|
.taskNotArchived()
|
||||||
|
.get().await()
|
||||||
|
.documents
|
||||||
|
.forEach {
|
||||||
|
it.reference.update(TaskDocument.archived, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTaskCount(subject: Subject): Flow<Int> {
|
||||||
|
return taskDAO.getTasks(subject)
|
||||||
|
.map(List<Task>::count)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCompletedTaskCount(subject: Subject): Flow<Int> {
|
||||||
|
return taskDAO.getTasks(subject)
|
||||||
|
.map { tasks -> tasks.count { it.completed && !it.archived } }
|
||||||
|
}
|
||||||
|
|
||||||
|
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,9 +7,12 @@ 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 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 javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class FirebaseTaskDAO @Inject constructor(
|
class FirebaseTaskDAO @Inject constructor(
|
||||||
|
@ -18,32 +21,47 @@ 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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getTask(subjectId: String, taskId: String): Task {
|
||||||
|
return selectedSubjectTasksCollection(subjectId).document(taskId).get().await().toObject()!!
|
||||||
|
}
|
||||||
|
|
||||||
override fun saveTask(newTask: Task) {
|
override fun saveTask(newTask: Task) {
|
||||||
selectedSubjectTasksCollection(newTask.subjectId).add(newTask)
|
selectedSubjectTasksCollection(newTask.subjectId).add(newTask)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -27,12 +27,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.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.TaskRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.SubjectAddRoute
|
import be.ugent.sel.studeez.screens.tasks.form.TaskCreateRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.SubjectEditRoute
|
import be.ugent.sel.studeez.screens.tasks.form.TaskEditRoute
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.TaskAddRoute
|
|
||||||
import be.ugent.sel.studeez.screens.tasks.forms.TaskEditRoute
|
|
||||||
import be.ugent.sel.studeez.screens.timer_form.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
|
||||||
|
@ -54,6 +54,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 =
|
||||||
|
@ -68,9 +69,10 @@ fun StudeezNavGraph(
|
||||||
composable(StudeezDestinations.HOME_SCREEN) {
|
composable(StudeezDestinations.HOME_SCREEN) {
|
||||||
HomeRoute(
|
HomeRoute(
|
||||||
open,
|
open,
|
||||||
viewModel = hiltViewModel(),
|
|
||||||
drawerActions = drawerActions,
|
drawerActions = drawerActions,
|
||||||
navigationBarActions = navigationBarActions
|
navigationBarActions = navigationBarActions,
|
||||||
|
feedViewModel = hiltViewModel(),
|
||||||
|
viewModel = hiltViewModel()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +86,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(),
|
||||||
|
@ -108,7 +110,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(),
|
||||||
|
@ -203,7 +205,7 @@ fun StudeezNavGraph(
|
||||||
|
|
||||||
composable(StudeezDestinations.SESSION_RECAP) {
|
composable(StudeezDestinations.SESSION_RECAP) {
|
||||||
SessionRecapRoute(
|
SessionRecapRoute(
|
||||||
openAndPopUp = openAndPopUp,
|
clearAndNavigate = clearAndNavigate,
|
||||||
viewModel = hiltViewModel()
|
viewModel = hiltViewModel()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,17 @@ import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
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.PrimaryScreenTemplate
|
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
|
||||||
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
|
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
|
||||||
|
import be.ugent.sel.studeez.common.composable.feed.Feed
|
||||||
|
import be.ugent.sel.studeez.common.composable.feed.FeedUiState
|
||||||
|
import be.ugent.sel.studeez.common.composable.feed.FeedViewModel
|
||||||
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
||||||
import be.ugent.sel.studeez.common.ext.basicButton
|
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||||
import be.ugent.sel.studeez.resources
|
import be.ugent.sel.studeez.resources
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -21,21 +24,27 @@ fun HomeRoute(
|
||||||
viewModel: HomeViewModel,
|
viewModel: HomeViewModel,
|
||||||
drawerActions: DrawerActions,
|
drawerActions: DrawerActions,
|
||||||
navigationBarActions: NavigationBarActions,
|
navigationBarActions: NavigationBarActions,
|
||||||
|
feedViewModel: FeedViewModel,
|
||||||
) {
|
) {
|
||||||
|
val feedUiState by feedViewModel.uiState.collectAsState()
|
||||||
HomeScreen(
|
HomeScreen(
|
||||||
onStartSessionClick = { viewModel.onStartSessionClick(open) },
|
|
||||||
onViewFriendsClick = { viewModel.onViewFriendsClick(open) },
|
onViewFriendsClick = { viewModel.onViewFriendsClick(open) },
|
||||||
drawerActions = drawerActions,
|
drawerActions = drawerActions,
|
||||||
navigationBarActions = navigationBarActions,
|
navigationBarActions = navigationBarActions,
|
||||||
|
feedUiState = feedUiState,
|
||||||
|
continueTask = { subjectId, taskId -> feedViewModel.continueTask(open, subjectId, taskId) },
|
||||||
|
onEmptyFeedHelp = { feedViewModel.onEmptyFeedHelp(open) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(
|
fun HomeScreen(
|
||||||
onStartSessionClick: () -> Unit,
|
|
||||||
onViewFriendsClick: () -> Unit,
|
onViewFriendsClick: () -> Unit,
|
||||||
drawerActions: DrawerActions,
|
drawerActions: DrawerActions,
|
||||||
navigationBarActions: NavigationBarActions
|
navigationBarActions: NavigationBarActions,
|
||||||
|
feedUiState: FeedUiState,
|
||||||
|
continueTask: (String, String) -> Unit,
|
||||||
|
onEmptyFeedHelp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
PrimaryScreenTemplate(
|
PrimaryScreenTemplate(
|
||||||
title = resources().getString(R.string.home),
|
title = resources().getString(R.string.home),
|
||||||
|
@ -43,9 +52,7 @@ fun HomeScreen(
|
||||||
navigationBarActions = navigationBarActions,
|
navigationBarActions = navigationBarActions,
|
||||||
barAction = { FriendsAction(onViewFriendsClick) }
|
barAction = { FriendsAction(onViewFriendsClick) }
|
||||||
) {
|
) {
|
||||||
BasicButton(R.string.start_session, Modifier.basicButton()) {
|
Feed(feedUiState, continueTask, onEmptyFeedHelp)
|
||||||
onStartSessionClick()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,9 +72,40 @@ fun FriendsAction(
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreenPreview() {
|
fun HomeScreenPreview() {
|
||||||
HomeScreen(
|
HomeScreen(
|
||||||
onStartSessionClick = {},
|
|
||||||
onViewFriendsClick = {},
|
onViewFriendsClick = {},
|
||||||
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||||
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {})
|
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
|
||||||
|
feedUiState = FeedUiState.Succes(
|
||||||
|
mapOf(
|
||||||
|
"08 May 2023" to listOf(
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFABD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
totalStudyTime = 600,
|
||||||
|
),
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
totalStudyTime = 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"09 May 2023" to listOf(
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFD1200,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
),
|
||||||
|
FeedEntry(
|
||||||
|
argb_color = 0xFFFF5C89,
|
||||||
|
subJectName = "Test Subject",
|
||||||
|
taskName = "Test Task",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
continueTask = { _, _ -> run {} },
|
||||||
|
onEmptyFeedHelp = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
package be.ugent.sel.studeez.screens.home
|
package be.ugent.sel.studeez.screens.home
|
||||||
|
|
||||||
import be.ugent.sel.studeez.domain.AccountDAO
|
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
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
|
||||||
|
@ -9,15 +7,11 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class HomeViewModel @Inject constructor(
|
class HomeViewModel @Inject constructor(
|
||||||
private val accountDAO: AccountDAO,
|
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
fun onStartSessionClick(open: (String) -> Unit) {
|
|
||||||
open(StudeezDestinations.TIMER_SELECTION_SCREEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onViewFriendsClick(open: (String) -> Unit) {
|
fun onViewFriendsClick(open: (String) -> Unit) {
|
||||||
open(StudeezDestinations.FRIENDS_OVERVIEW_SCREEN)
|
open(StudeezDestinations.FRIENDS_OVERVIEW_SCREEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package be.ugent.sel.studeez.screens.session
|
package be.ugent.sel.studeez.screens.session
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
import android.net.Uri
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
@ -10,9 +14,11 @@ object InvisibleSessionManager {
|
||||||
private var viewModel: SessionViewModel? = null
|
private var viewModel: SessionViewModel? = null
|
||||||
private lateinit var mediaPlayer: MediaPlayer
|
private lateinit var mediaPlayer: MediaPlayer
|
||||||
|
|
||||||
fun setParameters(viewModel: SessionViewModel, mediaplayer: MediaPlayer) {
|
fun setParameters(viewModel: SessionViewModel, context: Context) {
|
||||||
|
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
this.mediaPlayer = MediaPlayer.create(context, uri)
|
||||||
|
this.mediaPlayer.isLooping = false
|
||||||
this.viewModel = viewModel
|
this.viewModel = viewModel
|
||||||
this.mediaPlayer = mediaplayer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateTimer() {
|
suspend fun updateTimer() {
|
||||||
|
|
|
@ -1,33 +1,24 @@
|
||||||
package be.ugent.sel.studeez.screens.session
|
package be.ugent.sel.studeez.screens.session
|
||||||
|
|
||||||
import android.media.MediaPlayer
|
|
||||||
import android.media.RingtoneManager
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
||||||
import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen
|
import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreenComposable
|
||||||
import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreen
|
|
||||||
|
|
||||||
data class SessionActions(
|
data class SessionActions(
|
||||||
val getTimer: () -> FunctionalTimer,
|
val getTimer: () -> FunctionalTimer,
|
||||||
val getTask: () -> String,
|
val getTask: () -> String,
|
||||||
val startMediaPlayer: () -> Unit,
|
|
||||||
val releaseMediaPlayer: () -> Unit,
|
|
||||||
val endSession: () -> Unit
|
val endSession: () -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getSessionActions(
|
private fun getSessionActions(
|
||||||
viewModel: SessionViewModel,
|
viewModel: SessionViewModel,
|
||||||
openAndPopUp: (String, String) -> Unit,
|
openAndPopUp: (String, String) -> Unit,
|
||||||
mediaplayer: MediaPlayer,
|
|
||||||
): SessionActions {
|
): SessionActions {
|
||||||
return SessionActions(
|
return SessionActions(
|
||||||
getTimer = viewModel::getTimer,
|
getTimer = viewModel::getTimer,
|
||||||
getTask = viewModel::getTask,
|
getTask = viewModel::getTask,
|
||||||
endSession = { viewModel.endSession(openAndPopUp) },
|
endSession = { viewModel.endSession(openAndPopUp) },
|
||||||
startMediaPlayer = mediaplayer::start,
|
|
||||||
releaseMediaPlayer = mediaplayer::release,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,20 +28,12 @@ fun SessionRoute(
|
||||||
openAndPopUp: (String, String) -> Unit,
|
openAndPopUp: (String, String) -> Unit,
|
||||||
viewModel: SessionViewModel,
|
viewModel: SessionViewModel,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
|
||||||
val mediaplayer = MediaPlayer.create(context, uri)
|
|
||||||
mediaplayer.isLooping = false
|
|
||||||
|
|
||||||
InvisibleSessionManager.setParameters(
|
InvisibleSessionManager.setParameters(viewModel = viewModel, context = LocalContext.current)
|
||||||
viewModel = viewModel,
|
|
||||||
mediaplayer = mediaplayer
|
|
||||||
)
|
|
||||||
|
|
||||||
val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen(mediaplayer))
|
val soundPlayer = SoundPlayer(LocalContext.current)
|
||||||
|
val sessionActions = getSessionActions(viewModel, openAndPopUp)
|
||||||
|
val sessionScreen = viewModel.getTimer().accept(GetSessionScreenComposable(soundPlayer, open, sessionActions))
|
||||||
|
|
||||||
sessionScreen(
|
sessionScreen()
|
||||||
open = open,
|
|
||||||
sessionActions = getSessionActions(viewModel, openAndPopUp, mediaplayer)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package be.ugent.sel.studeez.screens.session
|
package be.ugent.sel.studeez.screens.session
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.SelectedTimerState
|
import be.ugent.sel.studeez.data.SelectedSessionReport
|
||||||
import be.ugent.sel.studeez.data.SessionReportState
|
import be.ugent.sel.studeez.data.SelectedTask
|
||||||
|
import be.ugent.sel.studeez.data.SelectedTimer
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
|
@ -11,23 +12,21 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SessionViewModel @Inject constructor(
|
class SessionViewModel @Inject constructor(
|
||||||
private val selectedTimerState: SelectedTimerState,
|
private val selectedTimer: SelectedTimer,
|
||||||
private val sessionReportState: SessionReportState,
|
private val sessionReport: SelectedSessionReport,
|
||||||
|
private val selectedTask: SelectedTask,
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
fun getTimer(): FunctionalTimer {
|
||||||
private val task : String = "No task selected" // placeholder for tasks implementation
|
return selectedTimer()
|
||||||
|
|
||||||
fun getTimer() : FunctionalTimer {
|
|
||||||
return selectedTimerState.selectedTimer!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTask(): String {
|
fun getTask(): String {
|
||||||
return task
|
return selectedTask().name
|
||||||
}
|
}
|
||||||
|
|
||||||
fun endSession(openAndPopUp: (String, String) -> Unit) {
|
fun endSession(openAndPopUp: (String, String) -> Unit) {
|
||||||
sessionReportState.sessionReport = getTimer().getSessionReport()
|
sessionReport.set(getTimer().getSessionReport(selectedTask().subjectId, selectedTask().id))
|
||||||
openAndPopUp(StudeezDestinations.SESSION_RECAP, StudeezDestinations.SESSION_SCREEN)
|
openAndPopUp(StudeezDestinations.SESSION_RECAP, StudeezDestinations.SESSION_SCREEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.media.MediaPlayer
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
|
||||||
|
class SoundPlayer(private val context: Context) {
|
||||||
|
|
||||||
|
var oldValue: Boolean = false
|
||||||
|
var mediaPlayer: MediaPlayer = initPlayer()
|
||||||
|
|
||||||
|
fun playOn(newValue: Boolean) {
|
||||||
|
if (oldValue != newValue) {
|
||||||
|
mediaPlayer.start()
|
||||||
|
mediaPlayer.setOnCompletionListener {
|
||||||
|
mediaPlayer = initPlayer()
|
||||||
|
}
|
||||||
|
oldValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun initPlayer(): MediaPlayer {
|
||||||
|
return MediaPlayer.create(
|
||||||
|
context,
|
||||||
|
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,143 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
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.data.local.models.timer_functional.FunctionalEndlessTimer
|
|
||||||
import be.ugent.sel.studeez.screens.session.SessionActions
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
abstract class AbstractSessionScreen {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
operator fun invoke(
|
|
||||||
open: (String) -> Unit,
|
|
||||||
sessionActions: SessionActions,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(10.dp)
|
|
||||||
) {
|
|
||||||
Timer(
|
|
||||||
sessionActions = sessionActions,
|
|
||||||
)
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.Center, modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(50.dp)
|
|
||||||
) {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
sessionActions.releaseMediaPlayer
|
|
||||||
sessionActions.endSession()
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 20.dp)
|
|
||||||
.border(1.dp, Color.Red, RoundedCornerShape(32.dp))
|
|
||||||
.background(Color.Transparent)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "End session",
|
|
||||||
color = Color.Red,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
modifier = Modifier.padding(1.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Timer(
|
|
||||||
sessionActions: SessionActions,
|
|
||||||
) {
|
|
||||||
var tikker by remember { mutableStateOf(false) }
|
|
||||||
LaunchedEffect(tikker) {
|
|
||||||
delay(1.seconds)
|
|
||||||
sessionActions.getTimer().tick()
|
|
||||||
callMediaPlayer()
|
|
||||||
tikker = !tikker
|
|
||||||
}
|
|
||||||
|
|
||||||
val hms = sessionActions.getTimer().getHoursMinutesSeconds()
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = hms.toString(),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(50.dp),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 40.sp,
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = motivationString(),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontWeight = FontWeight.Light,
|
|
||||||
fontSize = 30.sp
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.Center, modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(50.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.background(Color.Blue, RoundedCornerShape(32.dp))
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = sessionActions.getTask(),
|
|
||||||
color = Color.White,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 20.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
abstract fun motivationString(): String
|
|
||||||
|
|
||||||
abstract fun callMediaPlayer()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun TimerPreview() {
|
|
||||||
val sessionScreen = object : AbstractSessionScreen() {
|
|
||||||
@Composable
|
|
||||||
override fun motivationString(): String = "Test"
|
|
||||||
override fun callMediaPlayer() {}
|
|
||||||
|
|
||||||
}
|
|
||||||
sessionScreen.Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}, {}))
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
|
||||||
|
|
||||||
import android.media.MediaPlayer
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
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
|
|
||||||
|
|
||||||
class BreakSessionScreen(
|
|
||||||
private val funPomoDoroTimer: FunctionalPomodoroTimer,
|
|
||||||
private var mediaplayer: MediaPlayer?
|
|
||||||
): AbstractSessionScreen() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun motivationString(): String {
|
|
||||||
if (funPomoDoroTimer.isInBreak) {
|
|
||||||
return resources().getString(AppText.state_take_a_break)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (funPomoDoroTimer.hasEnded()) {
|
|
||||||
return resources().getString(AppText.state_done)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resources().getQuantityString(
|
|
||||||
R.plurals.state_focus_remaining,
|
|
||||||
funPomoDoroTimer.breaksRemaining,
|
|
||||||
funPomoDoroTimer.breaksRemaining
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun callMediaPlayer() {
|
|
||||||
if (funPomoDoroTimer.hasEnded()) {
|
|
||||||
mediaplayer?.let { it: MediaPlayer ->
|
|
||||||
it.setOnCompletionListener {
|
|
||||||
it.release()
|
|
||||||
mediaplayer = null
|
|
||||||
}
|
|
||||||
it.start()
|
|
||||||
}
|
|
||||||
} else if (funPomoDoroTimer.hasCurrentCountdownEnded()) {
|
|
||||||
mediaplayer?.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
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.unit.dp
|
||||||
|
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.screens.session.SessionActions
|
||||||
|
import be.ugent.sel.studeez.screens.session.SoundPlayer
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BreakSessionScreenComposable(
|
||||||
|
open: (String) -> Unit,
|
||||||
|
sessionActions: SessionActions,
|
||||||
|
pomodoroTimer: FunctionalPomodoroTimer,
|
||||||
|
soundPlayer: SoundPlayer,
|
||||||
|
) {
|
||||||
|
SessionScreen(
|
||||||
|
open = open,
|
||||||
|
sessionActions = sessionActions,
|
||||||
|
midSection = { Dots(pomodoroTimer = pomodoroTimer) },
|
||||||
|
callMediaPlayer = { soundPlayer.playOn(pomodoroTimer.hasCurrentCountdownEnded()) },
|
||||||
|
motivationString = { motivationString (pomodoroTimer = pomodoroTimer) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Dots(pomodoroTimer: FunctionalPomodoroTimer): Int {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
if (pomodoroTimer.hasEnded()) {
|
||||||
|
repeat(pomodoroTimer.repeats) {
|
||||||
|
Dot(Color.Green)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat(pomodoroTimer.repeats - pomodoroTimer.breaksRemaining - 1) {
|
||||||
|
Dot(color = Color.DarkGray)
|
||||||
|
}
|
||||||
|
if (!pomodoroTimer.isInBreak) Dot(Color.Green) else Dot(Color.DarkGray)
|
||||||
|
repeat(pomodoroTimer.breaksRemaining) {
|
||||||
|
Dot(color = Color.Gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pomodoroTimer.breaksRemaining
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Dot(color: Color) {
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.padding(5.dp)
|
||||||
|
.size(10.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(color))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun motivationString(pomodoroTimer: FunctionalPomodoroTimer): String {
|
||||||
|
if (pomodoroTimer.isInBreak) {
|
||||||
|
return resources().getString(R.string.state_take_a_break)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pomodoroTimer.hasEnded()) {
|
||||||
|
return resources().getString(R.string.state_done)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources().getString(R.string.state_focus)
|
||||||
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
|
||||||
|
|
||||||
import android.media.MediaPlayer
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
|
|
||||||
import be.ugent.sel.studeez.resources
|
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
|
||||||
|
|
||||||
|
|
||||||
class CustomSessionScreen(
|
|
||||||
private val functionalTimer: FunctionalCustomTimer,
|
|
||||||
private var mediaplayer: MediaPlayer?
|
|
||||||
): AbstractSessionScreen() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun motivationString(): String {
|
|
||||||
if (functionalTimer.hasEnded()) {
|
|
||||||
return resources().getString(AppText.state_done)
|
|
||||||
}
|
|
||||||
return resources().getString(AppText.state_focus)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun callMediaPlayer() {
|
|
||||||
if (functionalTimer.hasEnded()) {
|
|
||||||
mediaplayer?.let { it: MediaPlayer ->
|
|
||||||
it.setOnCompletionListener {
|
|
||||||
it.release()
|
|
||||||
mediaplayer = null
|
|
||||||
}
|
|
||||||
it.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import be.ugent.sel.studeez.R
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
|
||||||
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.screens.session.SessionActions
|
||||||
|
import be.ugent.sel.studeez.screens.session.SoundPlayer
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CustomTimerSessionScreenComposable(
|
||||||
|
open: (String) -> Unit,
|
||||||
|
sessionActions: SessionActions,
|
||||||
|
customTimer: FunctionalCustomTimer,
|
||||||
|
soundPlayer: SoundPlayer
|
||||||
|
) {
|
||||||
|
SessionScreen(
|
||||||
|
open = open,
|
||||||
|
callMediaPlayer = { soundPlayer.playOn(customTimer.hasEnded()) },
|
||||||
|
sessionActions = sessionActions
|
||||||
|
) {
|
||||||
|
motivationString(customTimer = customTimer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun motivationString(customTimer: FunctionalCustomTimer): String {
|
||||||
|
if (customTimer.hasEnded()) {
|
||||||
|
return resources().getString(R.string.state_done)
|
||||||
|
}
|
||||||
|
return resources().getString(R.string.state_focus)
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import be.ugent.sel.studeez.resources
|
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
|
||||||
|
|
||||||
|
|
||||||
class EndlessSessionScreen : AbstractSessionScreen() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun motivationString(): String {
|
|
||||||
return resources().getString(AppText.state_focus)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun callMediaPlayer() {}
|
|
||||||
}
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import be.ugent.sel.studeez.R
|
||||||
|
import be.ugent.sel.studeez.resources
|
||||||
|
import be.ugent.sel.studeez.screens.session.SessionActions
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EndlessTimerSessionScreenComposable(
|
||||||
|
open: (String) -> Unit,
|
||||||
|
sessionActions: SessionActions,
|
||||||
|
) {
|
||||||
|
SessionScreen(
|
||||||
|
open = open,
|
||||||
|
sessionActions = sessionActions
|
||||||
|
) {
|
||||||
|
motivationString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun motivationString(): String {
|
||||||
|
return resources().getString(R.string.state_focus)
|
||||||
|
}
|
|
@ -1,18 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
|
||||||
|
|
||||||
import android.media.MediaPlayer
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimerVisitor
|
|
||||||
|
|
||||||
class GetSessionScreen(private val mediaplayer: MediaPlayer?) : FunctionalTimerVisitor<AbstractSessionScreen> {
|
|
||||||
override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): AbstractSessionScreen =
|
|
||||||
CustomSessionScreen(functionalCustomTimer, mediaplayer)
|
|
||||||
|
|
||||||
override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): AbstractSessionScreen =
|
|
||||||
EndlessSessionScreen()
|
|
||||||
|
|
||||||
override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): AbstractSessionScreen =
|
|
||||||
BreakSessionScreen(functionalPomodoroTimer, mediaplayer)
|
|
||||||
}
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimerVisitor
|
||||||
|
import be.ugent.sel.studeez.screens.session.SessionActions
|
||||||
|
import be.ugent.sel.studeez.screens.session.SoundPlayer
|
||||||
|
|
||||||
|
class GetSessionScreenComposable(
|
||||||
|
private val soundPlayer: SoundPlayer,
|
||||||
|
private val open: (String) -> Unit,
|
||||||
|
private val sessionActions: SessionActions
|
||||||
|
) :
|
||||||
|
FunctionalTimerVisitor<@Composable () -> Unit> {
|
||||||
|
|
||||||
|
override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): @Composable () -> Unit {
|
||||||
|
return { CustomTimerSessionScreenComposable(
|
||||||
|
open = open,
|
||||||
|
sessionActions = sessionActions,
|
||||||
|
soundPlayer = soundPlayer,
|
||||||
|
customTimer = functionalCustomTimer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): @Composable () -> Unit {
|
||||||
|
return {
|
||||||
|
EndlessTimerSessionScreenComposable(
|
||||||
|
open = open,
|
||||||
|
sessionActions = sessionActions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): @Composable () -> Unit {
|
||||||
|
return {
|
||||||
|
BreakSessionScreenComposable(
|
||||||
|
open = open,
|
||||||
|
sessionActions = sessionActions,
|
||||||
|
soundPlayer = soundPlayer,
|
||||||
|
pomodoroTimer = functionalPomodoroTimer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import be.ugent.sel.studeez.screens.session.SessionActions
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SessionScreen(
|
||||||
|
open: (String) -> Unit,
|
||||||
|
sessionActions: SessionActions,
|
||||||
|
callMediaPlayer: () -> Unit = {},
|
||||||
|
midSection: @Composable () -> Int = {0},
|
||||||
|
motivationString: @Composable () -> String,
|
||||||
|
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(10.dp)
|
||||||
|
) {
|
||||||
|
Timer(
|
||||||
|
sessionActions = sessionActions,
|
||||||
|
callMediaPlayer = callMediaPlayer,
|
||||||
|
motivationString = motivationString,
|
||||||
|
MidSection = midSection
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center, modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(50.dp)
|
||||||
|
) {
|
||||||
|
EndSessionButton(sessionActions = sessionActions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EndSessionButton(sessionActions: SessionActions) {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
sessionActions.endSession()
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 20.dp)
|
||||||
|
.border(1.dp, Color.Red, RoundedCornerShape(32.dp))
|
||||||
|
.background(Color.Transparent)
|
||||||
|
) {
|
||||||
|
EndsessionText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EndsessionText() {
|
||||||
|
Text(
|
||||||
|
text = "End session",
|
||||||
|
color = Color.Red,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
modifier = Modifier.padding(1.dp)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||||
|
import be.ugent.sel.studeez.screens.session.SessionActions
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Timer(
|
||||||
|
sessionActions: SessionActions,
|
||||||
|
callMediaPlayer: () -> Unit,
|
||||||
|
motivationString: @Composable () -> String,
|
||||||
|
MidSection: @Composable () -> Int
|
||||||
|
) {
|
||||||
|
var tikker by remember { mutableStateOf(false) }
|
||||||
|
LaunchedEffect(tikker) {
|
||||||
|
delay(1.seconds)
|
||||||
|
sessionActions.getTimer().tick()
|
||||||
|
callMediaPlayer()
|
||||||
|
tikker = !tikker
|
||||||
|
}
|
||||||
|
|
||||||
|
val hms = sessionActions.getTimer().getHoursMinutesSeconds()
|
||||||
|
Column {
|
||||||
|
|
||||||
|
TimerClock(hms)
|
||||||
|
MotivationText(text = motivationString())
|
||||||
|
MidSection()
|
||||||
|
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center, modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(50.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.background(Color.Blue, RoundedCornerShape(32.dp))
|
||||||
|
) {
|
||||||
|
TaskText(taskName = sessionActions.getTask())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TimerClock(hms: HoursMinutesSeconds) {
|
||||||
|
Text(
|
||||||
|
text = hms.toString(),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(50.dp),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 40.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MotivationText(text: String) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
fontSize = 30.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TaskText(taskName: String) {
|
||||||
|
Text(
|
||||||
|
text = taskName,
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(vertical = 4.dp, horizontal = 20.dp)
|
||||||
|
)
|
||||||
|
}
|
|
@ -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,21 +58,88 @@ 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,
|
||||||
|
|
||||||
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(),
|
Column {
|
||||||
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red)
|
BasicButton(
|
||||||
) {
|
R.string.save, Modifier.basicButton()
|
||||||
sessionRecapActions.discardSession()
|
) {
|
||||||
|
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,
|
||||||
|
) },
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -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.SessionReportState
|
import be.ugent.sel.studeez.data.SelectedSessionReport
|
||||||
|
import be.ugent.sel.studeez.data.SelectedTask
|
||||||
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
|
||||||
|
@ -11,23 +13,26 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SessionRecapViewModel @Inject constructor(
|
class SessionRecapViewModel @Inject constructor(
|
||||||
sessionReportState: SessionReportState,
|
private val selectedSessionReport: SelectedSessionReport,
|
||||||
private val sessionDAO: SessionDAO,
|
private val sessionDAO: SessionDAO,
|
||||||
|
private val taskDAO: TaskDAO,
|
||||||
|
private val selectedTask: SelectedTask,
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
private val report: SessionReport = sessionReportState.sessionReport!!
|
|
||||||
|
|
||||||
fun getSessionReport(): SessionReport {
|
fun getSessionReport(): SessionReport {
|
||||||
return report
|
return selectedSessionReport()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveSession(open: (String, String) -> Unit) {
|
fun saveSession(open: (String) -> Unit) {
|
||||||
sessionDAO.saveSession(getSessionReport())
|
sessionDAO.saveSession(getSessionReport())
|
||||||
open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP)
|
val newTask =
|
||||||
|
selectedTask().copy(time = selectedTask().time + selectedSessionReport().studyTime)
|
||||||
|
taskDAO.updateTask(newTask)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
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) },
|
||||||
|
getTaskCount = viewModel::getTaskCount,
|
||||||
|
getCompletedTaskCount = viewModel::getCompletedTaskCount,
|
||||||
|
getStudyTime = viewModel::getStudyTime,
|
||||||
|
uiState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SubjectScreen(
|
||||||
|
drawerActions: DrawerActions,
|
||||||
|
navigationBarActions: NavigationBarActions,
|
||||||
|
onAddSubject: () -> Unit,
|
||||||
|
onViewSubject: (Subject) -> Unit,
|
||||||
|
getTaskCount: (Subject) -> Flow<Int>,
|
||||||
|
getCompletedTaskCount: (Subject) -> Flow<Int>,
|
||||||
|
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) },
|
||||||
|
getTaskCount = { getTaskCount(it) },
|
||||||
|
getCompletedTaskCount = { getCompletedTaskCount(it) },
|
||||||
|
getStudyTime = { getStudyTime(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SubjectScreenPreview() {
|
||||||
|
SubjectScreen(
|
||||||
|
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||||
|
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
|
||||||
|
onAddSubject = {},
|
||||||
|
onViewSubject = {},
|
||||||
|
getTaskCount = { flowOf() },
|
||||||
|
getCompletedTaskCount = { flowOf() },
|
||||||
|
getStudyTime = { flowOf() },
|
||||||
|
uiState = SubjectUiState.Succes(
|
||||||
|
listOf(
|
||||||
|
Subject(
|
||||||
|
name = "Test Subject",
|
||||||
|
argb_color = 0xFFFFD200,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SubjectScreenLoadingPreview() {
|
||||||
|
SubjectScreen(
|
||||||
|
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||||
|
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
|
||||||
|
onAddSubject = {},
|
||||||
|
onViewSubject = {},
|
||||||
|
getTaskCount = { flowOf() },
|
||||||
|
getCompletedTaskCount = { flowOf() },
|
||||||
|
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,29 @@ 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 getTaskCount(subject: Subject): Flow<Int> {
|
||||||
return subjectDAO.getSubjects()
|
return subjectDAO.getTaskCount(subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCompletedTaskCount(subject: Subject): Flow<Int> {
|
||||||
|
return subjectDAO.getCompletedTaskCount(subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStudyTime(subject: Subject): Flow<Int> {
|
||||||
|
return subjectDAO.getStudyTime(subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewSubject(subject: Subject, open: (String) -> Unit) {
|
fun onViewSubject(subject: Subject, open: (String) -> Unit) {
|
|
@ -1,28 +1,36 @@
|
||||||
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
|
||||||
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
|
||||||
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(
|
||||||
|
@ -31,7 +39,7 @@ fun SubjectAddRoute(
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
onConfirm = { viewModel.onCreate(openAndPopUp) },
|
onConfirm = { viewModel.onCreate(openAndPopUp) },
|
||||||
onNameChange = viewModel::onNameChange,
|
onNameChange = viewModel::onNameChange,
|
||||||
onColorChange = {},
|
onColorChange = viewModel::onColorChange,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,19 +47,22 @@ 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
|
||||||
|
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) {
|
||||||
viewModel.onDelete(openAndPopUp)
|
coroutineScope.launch {
|
||||||
|
viewModel.onDelete(openAndPopUp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
|
@ -0,0 +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 = Color.generateRandomArgb(),
|
||||||
|
)
|
|
@ -1,34 +1,30 @@
|
||||||
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 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
|
||||||
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 +34,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,13 +57,35 @@ 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,
|
||||||
|
private val taskDAO: TaskDAO,
|
||||||
|
selectedSubject: SelectedSubject,
|
||||||
|
logService: LogService,
|
||||||
|
) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) {
|
||||||
|
override val uiState = mutableStateOf(
|
||||||
|
SubjectFormUiState(
|
||||||
|
name = selectedSubject().name,
|
||||||
|
color = selectedSubject().argb_color
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun onDelete(openAndPopUp: (String, String) -> Unit) {
|
||||||
|
subjectDAO.archiveSubject(selectedSubject())
|
||||||
|
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(
|
||||||
name = name,
|
selectedSubject().copy(
|
||||||
argb_color = color,
|
name = name,
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,9 +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 archiveTask: (Task) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions {
|
fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions {
|
||||||
|
@ -37,9 +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) },
|
||||||
|
archiveTask = viewModel::archiveTask
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,16 +71,25 @@ fun TaskScreen(
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(top = 5.dp)
|
modifier = Modifier.padding(top = 5.dp)
|
||||||
) {
|
) {
|
||||||
|
NewTaskSubjectButton(onClick = taskActions.addTask, AppText.new_task)
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
items(tasks.value) {
|
items(tasks.value.filter { !it.completed }) {
|
||||||
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) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
items(tasks.value.filter { it.completed }) {
|
||||||
|
TaskEntry(
|
||||||
|
task = it,
|
||||||
|
onCheckTask = { isChecked -> taskActions.onCheckTask(it, isChecked) },
|
||||||
|
onArchiveTask = { taskActions.archiveTask(it) },
|
||||||
|
onStartTask = { taskActions.startTask(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NewTaskSubjectButton(onClick = taskActions.addTask, AppText.new_task)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,9 +116,10 @@ fun TaskScreenPreview() {
|
||||||
{},
|
{},
|
||||||
{ Subject(name = "Test Subject") },
|
{ Subject(name = "Test Subject") },
|
||||||
{ flowOf() },
|
{ flowOf() },
|
||||||
{},
|
|
||||||
{ _, _ -> run {} },
|
{ _, _ -> run {} },
|
||||||
{},
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package be.ugent.sel.studeez.screens.tasks
|
package be.ugent.sel.studeez.screens.tasks
|
||||||
|
|
||||||
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.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
|
||||||
|
@ -15,8 +15,8 @@ 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,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
fun addTask(open: (String) -> Unit) {
|
fun addTask(open: (String) -> Unit) {
|
||||||
|
@ -27,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()
|
||||||
}
|
}
|
||||||
|
@ -40,11 +35,20 @@ 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) {
|
||||||
open(StudeezDestinations.EDIT_SUBJECT_FORM)
|
open(StudeezDestinations.EDIT_SUBJECT_FORM)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startTask(task: Task, open: (String) -> Unit) {
|
||||||
|
selectedTask.set(task)
|
||||||
|
open(StudeezDestinations.TIMER_SELECTION_SCREEN)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
@ -11,17 +11,17 @@ 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
|
||||||
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(
|
||||||
|
@ -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,
|
||||||
) {
|
) {
|
|
@ -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,39 +12,55 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.tasks.forms
|
|
||||||
|
|
||||||
data class SubjectFormUiState(
|
|
||||||
val name: String = "",
|
|
||||||
val color: Long = 0xFFFFD200,
|
|
||||||
)
|
|
|
@ -1,42 +0,0 @@
|
||||||
package be.ugent.sel.studeez.screens.timer_form
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
|
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
|
|
||||||
import be.ugent.sel.studeez.R.string as AppText
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TimerAddRoute(
|
|
||||||
popUp: () -> Unit,
|
|
||||||
viewModel: TimerFormViewModel
|
|
||||||
) {
|
|
||||||
TimerFormScreen(popUp = popUp, getTimerInfo = viewModel::getTimerInfo, AppText.add_timer) {
|
|
||||||
viewModel.saveTimer(it, goBack = popUp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TimerEditRoute(
|
|
||||||
popUp: () -> Unit,
|
|
||||||
viewModel: TimerFormViewModel
|
|
||||||
) {
|
|
||||||
TimerFormScreen(popUp = popUp, getTimerInfo = viewModel::getTimerInfo, AppText.edit_timer) {
|
|
||||||
viewModel.editTimer(it, goBack = popUp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TimerFormScreen(
|
|
||||||
popUp: () -> Unit,
|
|
||||||
getTimerInfo: () -> TimerInfo,
|
|
||||||
@StringRes label: Int,
|
|
||||||
onConfirmClick: (TimerInfo) -> Unit
|
|
||||||
) {
|
|
||||||
val timerFormScreen = getTimerInfo().accept(GetTimerFormScreen())
|
|
||||||
|
|
||||||
SecondaryScreenTemplate(title = stringResource(id = label), popUp = popUp) {
|
|
||||||
timerFormScreen(onConfirmClick)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package be.ugent.sel.studeez.screens.timer_form
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
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
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TimerAddRoute(
|
||||||
|
popUp: () -> Unit,
|
||||||
|
viewModel: TimerFormViewModel
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
TimerFormScreen(
|
||||||
|
popUp = popUp,
|
||||||
|
getTimerInfo = viewModel::getTimerInfo,
|
||||||
|
extraButton= { },
|
||||||
|
AppText.add_timer
|
||||||
|
) {
|
||||||
|
viewModel.saveTimer(it, goBack = {popUp(); popUp()})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TimerEditRoute(
|
||||||
|
popUp: () -> Unit,
|
||||||
|
viewModel: TimerFormViewModel
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun deleteButton() {
|
||||||
|
DeleteButton(text = AppText.delete_timer) {
|
||||||
|
viewModel.deleteTimer(viewModel.getTimerInfo(), popUp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimerFormScreen(
|
||||||
|
popUp = popUp,
|
||||||
|
getTimerInfo = viewModel::getTimerInfo,
|
||||||
|
extraButton= { deleteButton() },
|
||||||
|
AppText.edit_timer
|
||||||
|
) {
|
||||||
|
viewModel.editTimer(it, goBack = popUp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TimerFormScreen(
|
||||||
|
popUp: () -> Unit,
|
||||||
|
getTimerInfo: () -> TimerInfo,
|
||||||
|
extraButton: @Composable () -> Unit,
|
||||||
|
@StringRes label: Int,
|
||||||
|
onConfirmClick: (TimerInfo) -> Unit
|
||||||
|
) {
|
||||||
|
val timerFormScreen = getTimerInfo().accept(GetTimerFormScreen())
|
||||||
|
|
||||||
|
FormComposable(
|
||||||
|
title = stringResource(id = label),
|
||||||
|
popUp = popUp
|
||||||
|
) {
|
||||||
|
timerFormScreen(onConfirmClick, extraButton)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package be.ugent.sel.studeez.screens.timer_form
|
package be.ugent.sel.studeez.screens.timer_form
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.EditTimerState
|
import be.ugent.sel.studeez.data.SelectedTimerInfo
|
||||||
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.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
import be.ugent.sel.studeez.domain.TimerDAO
|
import be.ugent.sel.studeez.domain.TimerDAO
|
||||||
|
@ -10,15 +10,12 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class TimerFormViewModel @Inject constructor(
|
class TimerFormViewModel @Inject constructor(
|
||||||
private val editTimerState: EditTimerState,
|
private val selectedTimerInfo: SelectedTimerInfo,
|
||||||
private val timerDAO: TimerDAO,
|
private val timerDAO: TimerDAO,
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
private val timerInfo: TimerInfo = editTimerState.timerInfo
|
|
||||||
|
|
||||||
fun getTimerInfo(): TimerInfo {
|
fun getTimerInfo(): TimerInfo {
|
||||||
return timerInfo
|
return selectedTimerInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun editTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
|
fun editTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
|
||||||
|
@ -26,6 +23,11 @@ class TimerFormViewModel @Inject constructor(
|
||||||
goBack()
|
goBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
|
||||||
|
timerDAO.deleteTimer(timerInfo)
|
||||||
|
goBack()
|
||||||
|
}
|
||||||
|
|
||||||
fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
|
fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
|
||||||
timerDAO.saveTimer(timerInfo)
|
timerDAO.saveTimer(timerInfo)
|
||||||
goBack()
|
goBack()
|
||||||
|
|
|
@ -1,69 +1,84 @@
|
||||||
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.runtime.*
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
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.LabelledInputField
|
import be.ugent.sel.studeez.common.composable.LabeledErrorTextField
|
||||||
import be.ugent.sel.studeez.common.ext.basicButton
|
import be.ugent.sel.studeez.common.ext.basicButton
|
||||||
|
import be.ugent.sel.studeez.common.snackbar.SnackbarManager
|
||||||
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
|
||||||
|
|
||||||
abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) {
|
abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) {
|
||||||
|
|
||||||
|
protected val valids = mutableMapOf(
|
||||||
|
"name" to mutableStateOf(textPredicate(timerInfo.name)),
|
||||||
|
"description" to mutableStateOf(textPredicate(timerInfo.description))
|
||||||
|
)
|
||||||
|
|
||||||
|
protected val firsts = mutableMapOf(
|
||||||
|
"name" to mutableStateOf(true),
|
||||||
|
"description" to mutableStateOf(true)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
operator fun invoke(onSaveClick: (TimerInfo) -> Unit) {
|
operator fun invoke(
|
||||||
|
onSaveClick: (TimerInfo) -> Unit,
|
||||||
|
extraButton: @Composable () -> Unit = {},
|
||||||
|
) {
|
||||||
|
|
||||||
var name by remember { mutableStateOf(timerInfo.name) }
|
Column {
|
||||||
var description by remember { mutableStateOf(timerInfo.description) }
|
|
||||||
|
|
||||||
// This shall rerun whenever name and description change
|
|
||||||
timerInfo.name = name
|
|
||||||
timerInfo.description = description
|
|
||||||
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.SpaceBetween,
|
|
||||||
modifier = Modifier.fillMaxHeight().verticalScroll(rememberScrollState()),
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
|
|
||||||
// Fields that every timer shares (ommited id)
|
|
||||||
LabelledInputField(
|
|
||||||
value = name,
|
|
||||||
onNewValue = { name = it },
|
|
||||||
label = R.string.name
|
|
||||||
)
|
|
||||||
|
|
||||||
LabelledInputField(
|
|
||||||
value = description,
|
|
||||||
onNewValue = { description = it },
|
|
||||||
label = AppText.description,
|
|
||||||
singleLine = false
|
|
||||||
)
|
|
||||||
|
|
||||||
ExtraFields()
|
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
BasicButton(R.string.save, Modifier.basicButton()) {
|
BasicButton(R.string.save, Modifier.basicButton()) {
|
||||||
onSaveClick(timerInfo)
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun textPredicate(text: String): Boolean {
|
||||||
|
return text.isNotBlank()
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
open fun ExtraFields() {
|
open fun ExtraFields() {
|
||||||
// By default no extra fields, unless overwritten by subclass.
|
// By default no extra fields, unless overwritten by subclass.
|
||||||
|
|
|
@ -15,6 +15,8 @@ class BreakTimerFormScreen(
|
||||||
private val breakTimerInfo: PomodoroTimerInfo
|
private val breakTimerInfo: PomodoroTimerInfo
|
||||||
): AbstractTimerFormScreen(breakTimerInfo) {
|
): AbstractTimerFormScreen(breakTimerInfo) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ExtraFields() {
|
override fun ExtraFields() {
|
||||||
// If the user presses the OK button on the timepicker, the time in the button should change
|
// If the user presses the OK button on the timepicker, the time in the button should change
|
||||||
|
@ -26,12 +28,17 @@ class BreakTimerFormScreen(
|
||||||
breakTimerInfo.breakTime = newTime
|
breakTimerInfo.breakTime = newTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
valids["repeats"] = remember {mutableStateOf(true)}
|
||||||
|
firsts["repeats"] = remember { mutableStateOf(true) }
|
||||||
|
|
||||||
LabeledErrorTextField(
|
LabeledErrorTextField(
|
||||||
initialValue = breakTimerInfo.repeats.toString(),
|
initialValue = breakTimerInfo.repeats.toString(),
|
||||||
label = R.string.repeats,
|
label = R.string.repeats,
|
||||||
errorText = AppText.repeats_error,
|
errorText = AppText.repeats_error,
|
||||||
|
isValid = valids.getValue("repeats"),
|
||||||
|
isFirst = firsts.getValue("repeats"),
|
||||||
keyboardType = KeyboardType.Decimal,
|
keyboardType = KeyboardType.Decimal,
|
||||||
predicate = { it.matches(Regex("[1-9]+\\d*")) }
|
predicate = { isNumber(it) }
|
||||||
) { correctlyTypedInt ->
|
) { correctlyTypedInt ->
|
||||||
breakTimerInfo.repeats = correctlyTypedInt.toInt()
|
breakTimerInfo.repeats = correctlyTypedInt.toInt()
|
||||||
}
|
}
|
||||||
|
@ -39,6 +46,10 @@ class BreakTimerFormScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isNumber(text: String): Boolean {
|
||||||
|
return text.matches(Regex("[1-9]+\\d*"))
|
||||||
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun BreakEditScreenPreview() {
|
fun BreakEditScreenPreview() {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package be.ugent.sel.studeez.screens.timer_form.timer_type_select
|
package be.ugent.sel.studeez.screens.timer_form.timer_type_select
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.material.Button
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.Text
|
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.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
|
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_info.*
|
import be.ugent.sel.studeez.data.local.models.timer_info.*
|
||||||
|
@ -37,7 +37,10 @@ fun TimerTypeSelectScreen(
|
||||||
) {
|
) {
|
||||||
TimerType.values().forEach { timerType ->
|
TimerType.values().forEach { timerType ->
|
||||||
val default: TimerInfo = defaultTimerInfo.getValue(timerType)
|
val default: TimerInfo = defaultTimerInfo.getValue(timerType)
|
||||||
Button(onClick = { viewModel.onTimerTypeChosen(default, open) }) {
|
Button(
|
||||||
|
onClick = { viewModel.onTimerTypeChosen(default, open) },
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(5.dp)
|
||||||
|
) {
|
||||||
Text(text = timerType.name)
|
Text(text = timerType.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package be.ugent.sel.studeez.screens.timer_form.timer_type_select
|
package be.ugent.sel.studeez.screens.timer_form.timer_type_select
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.EditTimerState
|
import be.ugent.sel.studeez.data.SelectedTimerInfo
|
||||||
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.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||||
|
@ -10,13 +10,13 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class TimerTypeSelectViewModel @Inject constructor(
|
class TimerTypeSelectViewModel @Inject constructor(
|
||||||
private val editTimerState: EditTimerState,
|
private val selectedTimerInfo: SelectedTimerInfo,
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
|
|
||||||
fun onTimerTypeChosen(timerInfo: TimerInfo, open: (String) -> Unit) {
|
fun onTimerTypeChosen(timerInfo: TimerInfo, open: (String) -> Unit) {
|
||||||
editTimerState.timerInfo = timerInfo
|
selectedTimerInfo.set(timerInfo)
|
||||||
open(StudeezDestinations.ADD_TIMER_SCREEN)
|
open(StudeezDestinations.ADD_TIMER_SCREEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -82,12 +82,13 @@ fun TimerOverviewScreen(
|
||||||
items(timers.value) { timerInfo ->
|
items(timers.value) { timerInfo ->
|
||||||
TimerEntry(
|
TimerEntry(
|
||||||
timerInfo = timerInfo,
|
timerInfo = timerInfo,
|
||||||
) {
|
rightButton = {
|
||||||
StealthButton(
|
StealthButton(
|
||||||
text = R.string.edit,
|
text = R.string.edit,
|
||||||
onClick = { timerOverviewActions.onEditClick(timerInfo) }
|
onClick = { timerOverviewActions.onEditClick(timerInfo) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package be.ugent.sel.studeez.screens.timer_overview
|
package be.ugent.sel.studeez.screens.timer_overview
|
||||||
|
|
||||||
import be.ugent.sel.studeez.data.EditTimerState
|
import be.ugent.sel.studeez.data.SelectedTimerInfo
|
||||||
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.domain.ConfigurationService
|
import be.ugent.sel.studeez.domain.ConfigurationService
|
||||||
import be.ugent.sel.studeez.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
|
@ -15,11 +15,11 @@ import javax.inject.Inject
|
||||||
class TimerOverviewViewModel @Inject constructor(
|
class TimerOverviewViewModel @Inject constructor(
|
||||||
private val configurationService: ConfigurationService,
|
private val configurationService: ConfigurationService,
|
||||||
private val timerDAO: TimerDAO,
|
private val timerDAO: TimerDAO,
|
||||||
private val editTimerState: EditTimerState,
|
private val selectedTimerInfo: SelectedTimerInfo,
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
fun getUserTimers() : Flow<List<TimerInfo>> {
|
fun getUserTimers(): Flow<List<TimerInfo>> {
|
||||||
return timerDAO.getUserTimers()
|
return timerDAO.getUserTimers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ class TimerOverviewViewModel @Inject constructor(
|
||||||
return configurationService.getDefaultTimers()
|
return configurationService.getDefaultTimers()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(timerInfo: TimerInfo, open: (String) -> Unit) {
|
fun update(timerInfo: TimerInfo, open: (String) -> Unit) {
|
||||||
editTimerState.timerInfo = timerInfo
|
selectedTimerInfo.set(timerInfo)
|
||||||
open(StudeezDestinations.TIMER_EDIT_SCREEN)
|
open(StudeezDestinations.TIMER_EDIT_SCREEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class TimerOverviewViewModel @Inject constructor(
|
||||||
open(StudeezDestinations.TIMER_TYPE_CHOOSING_SCREEN)
|
open(StudeezDestinations.TIMER_TYPE_CHOOSING_SCREEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(timerInfo: TimerInfo) =timerDAO.deleteTimer(timerInfo)
|
fun delete(timerInfo: TimerInfo) = timerDAO.deleteTimer(timerInfo)
|
||||||
|
|
||||||
fun save(timerInfo: TimerInfo) = timerDAO.saveTimer(timerInfo)
|
fun save(timerInfo: TimerInfo) = timerDAO.saveTimer(timerInfo)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package be.ugent.sel.studeez.screens.timer_selection
|
package be.ugent.sel.studeez.screens.timer_selection
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
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.R
|
import be.ugent.sel.studeez.R
|
||||||
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
|
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
|
||||||
import be.ugent.sel.studeez.common.composable.StealthButton
|
import be.ugent.sel.studeez.common.composable.StealthButton
|
||||||
|
@ -99,7 +102,10 @@ fun CustomTimerEntry(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
rightButton = {
|
rightButton = {
|
||||||
TimePickerButton(initialSeconds = hms.getTotalSeconds()) { chosenTime ->
|
TimePickerButton(
|
||||||
|
initialSeconds = hms.getTotalSeconds(),
|
||||||
|
modifier = Modifier.padding(horizontal = 5.dp)
|
||||||
|
) { chosenTime ->
|
||||||
timerInfo.studyTime = chosenTime
|
timerInfo.studyTime = chosenTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package be.ugent.sel.studeez.screens.timer_selection
|
package be.ugent.sel.studeez.screens.timer_selection
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import be.ugent.sel.studeez.data.SelectedTimer
|
||||||
import be.ugent.sel.studeez.data.SelectedTimerState
|
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||||
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.domain.LogService
|
import be.ugent.sel.studeez.domain.LogService
|
||||||
import be.ugent.sel.studeez.domain.TimerDAO
|
import be.ugent.sel.studeez.domain.TimerDAO
|
||||||
|
@ -17,18 +16,20 @@ import javax.inject.Inject
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class TimerSelectionViewModel @Inject constructor(
|
class TimerSelectionViewModel @Inject constructor(
|
||||||
private val timerDAO: TimerDAO,
|
private val timerDAO: TimerDAO,
|
||||||
private val selectedTimerState: SelectedTimerState,
|
private val selectedTimer: SelectedTimer,
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) : StudeezViewModel(logService) {
|
) : StudeezViewModel(logService) {
|
||||||
|
|
||||||
var customTimerStudyTime: MutableState<Int> = mutableStateOf(0)
|
var customTimerStudyTime: MutableState<Int> = mutableStateOf(
|
||||||
|
HoursMinutesSeconds(1, 0, 0).getTotalSeconds()
|
||||||
|
)
|
||||||
|
|
||||||
fun getAllTimers() : Flow<List<TimerInfo>> {
|
fun getAllTimers(): Flow<List<TimerInfo>> {
|
||||||
return timerDAO.getAllTimers()
|
return timerDAO.getAllTimers()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startSession(open: (String) -> Unit, timerInfo: TimerInfo) {
|
fun startSession(open: (String) -> Unit, timerInfo: TimerInfo) {
|
||||||
selectedTimerState.selectedTimer = timerInfo.getFunctionalTimer()
|
selectedTimer.set(timerInfo.getFunctionalTimer())
|
||||||
open(StudeezDestinations.SESSION_SCREEN)
|
open(StudeezDestinations.SESSION_SCREEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
5
app/src/main/res/drawable/mood_1.xml
Normal file
5
app/src/main/res/drawable/mood_1.xml
Normal 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>
|
5
app/src/main/res/drawable/mood_2.xml
Normal file
5
app/src/main/res/drawable/mood_2.xml
Normal 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>
|
|
@ -30,6 +30,12 @@
|
||||||
<string name="home">Home</string>
|
<string name="home">Home</string>
|
||||||
<string name="start_session">Start session</string>
|
<string name="start_session">Start session</string>
|
||||||
|
|
||||||
|
<!-- Feed-->
|
||||||
|
<string name="continue_task">Continue</string>
|
||||||
|
<string name="deleted">Deleted</string>
|
||||||
|
<string name="your_feed">This is your feed</string>
|
||||||
|
<string name="empty_feed_help_text">Click here to create you first subject and tasks to get started</string>
|
||||||
|
|
||||||
<!-- Tasks -->
|
<!-- Tasks -->
|
||||||
<string name="tasks">Tasks</string>
|
<string name="tasks">Tasks</string>
|
||||||
<string name="task">Task</string>
|
<string name="task">Task</string>
|
||||||
|
@ -41,6 +47,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. -->
|
||||||
|
@ -65,8 +72,15 @@
|
||||||
|
|
||||||
<!-- Timers -->
|
<!-- Timers -->
|
||||||
<string name="timers">Timers</string>
|
<string name="timers">Timers</string>
|
||||||
|
<string name="delete_timer">Delete Timer</string>
|
||||||
<string name="edit">Edit</string>
|
<string name="edit">Edit</string>
|
||||||
<string name="add_timer">Add timer</string>
|
<string name="add_timer">Add timer</string>
|
||||||
|
|
||||||
|
<string name="name_error">Name should not be blank</string>
|
||||||
|
<string name="description_error">Description should not be blank</string>
|
||||||
|
<string name="fill_out_error">Fill out all the fields correctly!</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="pick_time">Select time</string>
|
<string name="pick_time">Select time</string>
|
||||||
<string name="state_focus">Focus!</string>
|
<string name="state_focus">Focus!</string>
|
||||||
<plurals name="state_focus_remaining">
|
<plurals name="state_focus_remaining">
|
||||||
|
@ -154,4 +168,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>
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package be.ugent.sel.studeez.timer_functional
|
package be.ugent.sel.studeez.timer_functional
|
||||||
|
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import be.ugent.sel.studeez.data.SelectedTimerState
|
import be.ugent.sel.studeez.data.SelectedSessionReport
|
||||||
import be.ugent.sel.studeez.data.SessionReportState
|
import be.ugent.sel.studeez.data.SelectedTimer
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
|
||||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
|
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
|
||||||
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.domain.LogService
|
||||||
|
import be.ugent.sel.studeez.domain.implementation.LogServiceImpl
|
||||||
import be.ugent.sel.studeez.screens.session.InvisibleSessionManager
|
import be.ugent.sel.studeez.screens.session.InvisibleSessionManager
|
||||||
import be.ugent.sel.studeez.screens.session.SessionViewModel
|
import be.ugent.sel.studeez.screens.session.SessionViewModel
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
@ -18,14 +20,14 @@ import org.mockito.kotlin.mock
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
class InvisibleSessionManagerTest {
|
class InvisibleSessionManagerTest {
|
||||||
private var timerState: SelectedTimerState = SelectedTimerState()
|
private var selectedTimer: SelectedTimer = SelectedTimer()
|
||||||
private lateinit var viewModel: SessionViewModel
|
private lateinit var viewModel: SessionViewModel
|
||||||
private var mediaPlayer: MediaPlayer = mock()
|
private var mediaPlayer: MediaPlayer = mock()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun InvisibleEndlessTimerTest() = runTest {
|
fun InvisibleEndlessTimerTest() = runTest {
|
||||||
timerState.selectedTimer = FunctionalEndlessTimer()
|
selectedTimer.set(FunctionalEndlessTimer())
|
||||||
viewModel = SessionViewModel(timerState, SessionReportState(), mock())
|
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl())
|
||||||
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
|
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
|
||||||
|
|
||||||
val test = launch {
|
val test = launch {
|
||||||
|
@ -46,8 +48,8 @@ class InvisibleSessionManagerTest {
|
||||||
val studyTime = 10
|
val studyTime = 10
|
||||||
val breakTime = 5
|
val breakTime = 5
|
||||||
val repeats = 1
|
val repeats = 1
|
||||||
timerState.selectedTimer = FunctionalPomodoroTimer(studyTime, breakTime, repeats)
|
selectedTimer.set(FunctionalPomodoroTimer(studyTime, breakTime, repeats))
|
||||||
viewModel = SessionViewModel(timerState, SessionReportState(), mock())
|
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl())
|
||||||
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
|
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
|
||||||
|
|
||||||
val test = launch {
|
val test = launch {
|
||||||
|
@ -79,8 +81,8 @@ class InvisibleSessionManagerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun InvisibleCustomTimerTest() = runTest {
|
fun InvisibleCustomTimerTest() = runTest {
|
||||||
timerState.selectedTimer = FunctionalCustomTimer(5)
|
selectedTimer.set(FunctionalCustomTimer(5))
|
||||||
viewModel = SessionViewModel(timerState, SessionReportState(), mock())
|
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl())
|
||||||
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
|
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
|
||||||
|
|
||||||
val test = launch {
|
val test = launch {
|
||||||
|
|
Reference in a new issue