Merge remote-tracking branch 'origin/development' into better_screens

This commit is contained in:
Rune Dyselinck 2023-05-10 15:35:44 +02:00
commit dc63346e5e
74 changed files with 1568 additions and 1200 deletions

View file

@ -2,56 +2,19 @@ package be.ugent.sel.studeez
import android.content.res.Resources import android.content.res.Resources
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme import androidx.compose.material.*
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldState
import androidx.compose.material.Snackbar
import androidx.compose.material.SnackbarHost
import androidx.compose.material.Surface
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel
import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel
import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions
import be.ugent.sel.studeez.common.snackbar.SnackbarManager import be.ugent.sel.studeez.common.snackbar.SnackbarManager
import be.ugent.sel.studeez.navigation.StudeezDestinations import be.ugent.sel.studeez.navigation.StudeezNavGraph
import be.ugent.sel.studeez.screens.home.HomeRoute
import be.ugent.sel.studeez.screens.log_in.LoginRoute
import be.ugent.sel.studeez.screens.profile.EditProfileRoute
import be.ugent.sel.studeez.screens.profile.ProfileRoute
import be.ugent.sel.studeez.screens.session.SessionRoute
import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute
import be.ugent.sel.studeez.screens.sessions.SessionsRoute
import be.ugent.sel.studeez.screens.settings.SettingsRoute
import be.ugent.sel.studeez.screens.sign_up.SignUpRoute
import be.ugent.sel.studeez.screens.splash.SplashRoute
import be.ugent.sel.studeez.screens.tasks.SubjectRoute
import be.ugent.sel.studeez.screens.tasks.TaskRoute
import be.ugent.sel.studeez.screens.tasks.forms.SubjectAddRoute
import be.ugent.sel.studeez.screens.tasks.forms.SubjectEditRoute
import be.ugent.sel.studeez.screens.tasks.forms.TaskAddRoute
import be.ugent.sel.studeez.screens.tasks.forms.TaskEditRoute
import be.ugent.sel.studeez.screens.timer_edit.TimerEditRoute
import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute
import be.ugent.sel.studeez.screens.timer_overview.add_timer.AddTimerRoute
import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -97,205 +60,3 @@ fun resources(): Resources {
LocalConfiguration.current LocalConfiguration.current
return LocalContext.current.resources return LocalContext.current.resources
} }
@Composable
fun StudeezNavGraph(
appState: StudeezAppstate,
modifier: Modifier = Modifier,
) {
val drawerViewModel: DrawerViewModel = hiltViewModel()
val navBarViewModel: NavigationBarViewModel = hiltViewModel()
val backStackEntry by appState.navController.currentBackStackEntryAsState()
val getCurrentScreen: () -> String? = { backStackEntry?.destination?.route }
val goBack: () -> Unit = { appState.popUp() }
val open: (String) -> Unit = { appState.navigate(it) }
val openAndPopUp: (String, String) -> Unit =
{ route, popUp -> appState.navigateAndPopUp(route, popUp) }
val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp)
val navigationBarActions: NavigationBarActions =
getNavigationBarActions(navBarViewModel, open, getCurrentScreen)
NavHost(
navController = appState.navController,
startDestination = StudeezDestinations.SPLASH_SCREEN,
modifier = modifier,
) {
// NavBar
composable(StudeezDestinations.HOME_SCREEN) {
HomeRoute(
open,
viewModel = hiltViewModel(),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
)
}
composable(StudeezDestinations.SUBJECT_SCREEN) {
SubjectRoute(
open = open,
viewModel = hiltViewModel(),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
)
}
composable(StudeezDestinations.ADD_SUBJECT_FORM) {
SubjectAddRoute(
goBack = goBack,
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.EDIT_SUBJECT_FORM) {
SubjectEditRoute(
goBack = goBack,
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.TASKS_SCREEN) {
TaskRoute(
goBack = goBack,
open = open,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.ADD_TASK_FORM) {
TaskAddRoute(
goBack = goBack,
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.EDIT_TASK_FORM) {
TaskEditRoute(
goBack = goBack,
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SESSIONS_SCREEN) {
SessionsRoute(
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
)
}
composable(StudeezDestinations.PROFILE_SCREEN) {
ProfileRoute(
open,
viewModel = hiltViewModel(),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
)
}
// Drawer
composable(StudeezDestinations.TIMER_SCREEN) {
TimerOverviewRoute(
viewModel = hiltViewModel(),
drawerActions = drawerActions,
open = open
)
}
composable(StudeezDestinations.SETTINGS_SCREEN) {
SettingsRoute(
drawerActions = drawerActions
)
}
// Login flow
composable(StudeezDestinations.SPLASH_SCREEN) {
SplashRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.LOGIN_SCREEN) {
LoginRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SIGN_UP_SCREEN) {
SignUpRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
// Studying flow
composable(StudeezDestinations.TIMER_SELECTION_SCREEN) {
TimerSelectionRoute(
open,
goBack,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SESSION_SCREEN) {
SessionRoute(
open,
openAndPopUp,
viewModel = hiltViewModel()
)
}
composable(StudeezDestinations.SESSION_RECAP) {
SessionRecapRoute(
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel()
)
}
composable(StudeezDestinations.ADD_TIMER_SCREEN) {
AddTimerRoute(
open = open,
goBack = goBack,
viewModel = hiltViewModel()
)
}
composable(StudeezDestinations.TIMER_EDIT_SCREEN) {
TimerEditRoute(
open = open,
popUp = goBack,
viewModel = hiltViewModel()
)
}
// Friends flow
composable(StudeezDestinations.SEARCH_FRIENDS_SCREEN) {
// TODO
}
// Create & edit screens
composable(StudeezDestinations.CREATE_TASK_SCREEN) {
// TODO
}
composable(StudeezDestinations.CREATE_SESSION_SCREEN) {
// TODO
}
composable(StudeezDestinations.EDIT_PROFILE_SCREEN) {
EditProfileRoute(
goBack,
openAndPopUp,
viewModel = hiltViewModel(),
)
}
}
}

View file

@ -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
@ -48,6 +50,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(
@ -56,6 +59,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),
@ -74,17 +78,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)
) )
} }

View file

@ -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
@ -24,3 +27,13 @@ fun Headline(
) )
} }
} }
@Composable
fun DateText(date: String) {
Text(
text = date,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
modifier = Modifier.padding(horizontal = 10.dp)
)
}

View file

@ -1,11 +1,11 @@
package be.ugent.sel.studeez.common.composable package be.ugent.sel.studeez.common.composable
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column
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.Icon import androidx.compose.material.*
import androidx.compose.material.IconButton
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Email import androidx.compose.material.icons.filled.Email
import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Lock
@ -14,10 +14,15 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.common.ext.fieldModifier import be.ugent.sel.studeez.common.ext.fieldModifier
import be.ugent.sel.studeez.resources
import kotlin.math.sin
import be.ugent.sel.studeez.R.drawable as AppIcon import be.ugent.sel.studeez.R.drawable as AppIcon
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
@ -85,6 +90,86 @@ fun EmailField(
) )
} }
@Composable
fun LabeledNumberInputField(
value: Int,
onNewValue: (Int) -> Unit,
@StringRes label: Int,
singleLine: Boolean = false
) {
var number by remember { mutableStateOf(value) }
OutlinedTextField(
value = number.toString(),
singleLine = singleLine,
label = { Text(resources().getString(label)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
onValueChange = {typedInt ->
val isNumber = typedInt.matches(Regex("[1-9]+\\d*]"))
if (isNumber) {
number = typedInt.toInt()
onNewValue(typedInt.toInt())
}
}
)
}
@Composable
fun LabeledErrorTextField(
modifier: Modifier = Modifier,
initialValue: String,
@StringRes label: Int,
singleLine: Boolean = false,
errorText: Int,
keyboardType: KeyboardType,
predicate: (String) -> Boolean,
onNewCorrectValue: (String) -> Unit
) {
var value by remember {
mutableStateOf(initialValue)
}
var isValid by remember {
mutableStateOf(predicate(value))
}
Column {
OutlinedTextField(
modifier = modifier.fieldModifier(),
value = value,
onValueChange = { newText ->
value = newText
isValid = predicate(value)
if (isValid) {
onNewCorrectValue(newText)
}
},
singleLine = singleLine,
label = { Text(text = stringResource(id = label)) },
isError = !isValid,
keyboardOptions = KeyboardOptions(
keyboardType = keyboardType,
imeAction = ImeAction.Done
)
)
if (!isValid) {
Text(
modifier = Modifier.padding(start = 16.dp),
text = stringResource(id = errorText),
color = MaterialTheme.colors.error
)
}
}
}
@Preview(showBackground = true)
@Composable
fun IntInputPreview() {
LabeledNumberInputField(value = 1, onNewValue = {}, label = AppText.email)
}
@Composable @Composable
fun PasswordField( fun PasswordField(
value: String, value: String,

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +1,7 @@
package be.ugent.sel.studeez.common.composable.tasks package be.ugent.sel.studeez.common.composable.tasks
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Card import androidx.compose.material.Card
import androidx.compose.material.Icon import androidx.compose.material.Icon
@ -15,6 +9,8 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.List import androidx.compose.material.icons.filled.List
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -24,16 +20,20 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.R.string as AppText
import be.ugent.sel.studeez.common.composable.StealthButton import be.ugent.sel.studeez.common.composable.StealthButton
import be.ugent.sel.studeez.data.local.models.task.Subject import be.ugent.sel.studeez.data.local.models.task.Subject
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import be.ugent.sel.studeez.R.string as AppText
@Composable @Composable
fun SubjectEntry( fun SubjectEntry(
subject: Subject, subject: Subject,
onViewSubject: () -> Unit, onViewSubject: () -> Unit,
getStudyTime: () -> Flow<Int>,
) { ) {
val studytime by getStudyTime().collectAsState(initial = 0)
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -70,7 +70,7 @@ fun SubjectEntry(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Text( Text(
text = HoursMinutesSeconds(subject.time).toString(), text = HoursMinutesSeconds(studytime).toString(),
) )
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@ -80,7 +80,7 @@ fun SubjectEntry(
imageVector = Icons.Default.List, imageVector = Icons.Default.List,
contentDescription = stringResource(id = AppText.tasks) contentDescription = stringResource(id = AppText.tasks)
) )
Text(text = "0/0") // TODO Text(text = "${subject.taskCompletedCount}/${subject.taskCount}")
} }
} }
} }
@ -104,9 +104,12 @@ fun SubjectEntryPreview() {
subject = Subject( subject = Subject(
name = "Test Subject", name = "Test Subject",
argb_color = 0xFFFFD200, argb_color = 0xFFFFD200,
time = 60 taskCount = 5,
taskCompletedCount = 2,
), ),
) {} onViewSubject = {},
getStudyTime = { flowOf() }
)
} }
@Preview @Preview
@ -116,7 +119,8 @@ fun OverflowSubjectEntryPreview() {
subject = Subject( subject = Subject(
name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt", name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt",
argb_color = 0xFFFFD200, argb_color = 0xFFFFD200,
time = 60
), ),
) {} onViewSubject = {},
getStudyTime = { flowOf() }
)
} }

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = ""
) )

View file

@ -1,10 +1,22 @@
package be.ugent.sel.studeez.data.local.models.task package be.ugent.sel.studeez.data.local.models.task
import com.google.firebase.firestore.DocumentId import com.google.firebase.firestore.DocumentId
import com.google.firebase.firestore.Exclude
data class Subject( data class Subject(
@DocumentId val id: String = "", @DocumentId val id: String = "",
val name: String = "", val name: String = "",
val time: Int = 0,
val argb_color: Long = 0, val argb_color: Long = 0,
var archived: Boolean = false,
@get:Exclude @set:Exclude
var taskCount: Int = 0,
@get:Exclude @set:Exclude
var taskCompletedCount: Int = 0,
) )
object SubjectDocument {
const val id = "id"
const val name = "name"
const val archived = "archived"
const val argb_color = "argb_color"
}

View file

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

View file

@ -2,7 +2,8 @@ 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

View file

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

View file

@ -9,7 +9,7 @@ class PomodoroTimerInfo(
description: String, description: String,
var studyTime: Int, var studyTime: Int,
var breakTime: Int, var breakTime: Int,
val repeats: Int, var repeats: Int,
id: String = "" id: String = ""
): TimerInfo(id, name, description) { ): TimerInfo(id, name, description) {

View file

@ -33,4 +33,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
} }

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

View file

@ -12,4 +12,10 @@ interface SubjectDAO {
fun deleteSubject(oldSubject: Subject) fun deleteSubject(oldSubject: Subject)
fun updateSubject(newSubject: Subject) fun updateSubject(newSubject: Subject)
suspend fun getTaskCount(subject: Subject): Int
suspend fun getCompletedTaskCount(subject: Subject): Int
fun getStudyTime(subject: Subject): Flow<Int>
suspend fun getSubject(subjectId: String): Subject?
} }

View file

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

View file

@ -1,23 +1,42 @@
package be.ugent.sel.studeez.domain.implementation package be.ugent.sel.studeez.domain.implementation
import be.ugent.sel.studeez.data.local.models.task.Subject import be.ugent.sel.studeez.data.local.models.task.Subject
import be.ugent.sel.studeez.data.local.models.task.SubjectDocument
import be.ugent.sel.studeez.domain.AccountDAO import be.ugent.sel.studeez.domain.AccountDAO
import be.ugent.sel.studeez.domain.SubjectDAO import be.ugent.sel.studeez.domain.SubjectDAO
import be.ugent.sel.studeez.domain.TaskDAO
import com.google.firebase.firestore.AggregateSource
import com.google.firebase.firestore.CollectionReference import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.Query
import com.google.firebase.firestore.ktx.snapshots import com.google.firebase.firestore.ktx.snapshots
import com.google.firebase.firestore.ktx.toObject
import 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 FireBaseSubjectDAO @Inject constructor( class FireBaseSubjectDAO @Inject constructor(
private val firestore: FirebaseFirestore, private val firestore: FirebaseFirestore,
private val auth: AccountDAO, private val auth: AccountDAO,
private val taskDAO: TaskDAO,
) : SubjectDAO { ) : SubjectDAO {
override fun getSubjects(): Flow<List<Subject>> { override fun getSubjects(): Flow<List<Subject>> {
return currentUserSubjectsCollection() return currentUserSubjectsCollection()
.subjectNotArchived()
.snapshots() .snapshots()
.map { it.toObjects(Subject::class.java) } .map { it.toObjects(Subject::class.java) }
.map { subjects ->
subjects.map { subject ->
subject.taskCount = getTaskCount(subject)
subject.taskCompletedCount = getCompletedTaskCount(subject)
subject
}
}
}
override suspend fun getSubject(subjectId: String): Subject? {
return currentUserSubjectsCollection().document(subjectId).get().await().toObject()
} }
override fun saveSubject(newSubject: Subject) { override fun saveSubject(newSubject: Subject) {
@ -32,8 +51,45 @@ class FireBaseSubjectDAO @Inject constructor(
currentUserSubjectsCollection().document(newSubject.id).set(newSubject) currentUserSubjectsCollection().document(newSubject.id).set(newSubject)
} }
override suspend fun getTaskCount(subject: Subject): Int {
return subjectTasksCollection(subject)
.taskNotArchived()
.count()
.get(AggregateSource.SERVER)
.await()
.count.toInt()
}
override suspend fun getCompletedTaskCount(subject: Subject): Int {
return subjectTasksCollection(subject)
.taskNotArchived()
.taskNotCompleted()
.count()
.get(AggregateSource.SERVER)
.await()
.count.toInt()
}
override fun getStudyTime(subject: Subject): Flow<Int> {
return taskDAO.getTasks(subject)
.map { tasks -> tasks.sumOf { it.time } }
}
private fun currentUserSubjectsCollection(): CollectionReference = private fun currentUserSubjectsCollection(): CollectionReference =
firestore.collection(FireBaseCollections.USER_COLLECTION) firestore.collection(FireBaseCollections.USER_COLLECTION)
.document(auth.currentUserId) .document(auth.currentUserId)
.collection(FireBaseCollections.SUBJECT_COLLECTION) .collection(FireBaseCollections.SUBJECT_COLLECTION)
private fun subjectTasksCollection(subject: Subject): CollectionReference =
firestore.collection(FireBaseCollections.USER_COLLECTION)
.document(auth.currentUserId)
.collection(FireBaseCollections.SUBJECT_COLLECTION)
.document(subject.id)
.collection(FireBaseCollections.TASK_COLLECTION)
fun CollectionReference.subjectNotArchived(): Query =
this.whereEqualTo(SubjectDocument.archived, false)
fun Query.subjectNotArchived(): Query =
this.whereEqualTo(SubjectDocument.archived, false)
} }

View file

@ -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,48 @@ 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)

View file

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

View file

@ -19,6 +19,7 @@ object StudeezDestinations {
// Studying flow // Studying flow
const val TIMER_SELECTION_SCREEN = "timer_selection" const val TIMER_SELECTION_SCREEN = "timer_selection"
const val TIMER_EDIT_SCREEN = "timer_edit" const val TIMER_EDIT_SCREEN = "timer_edit"
const val TIMER_TYPE_CHOOSING_SCREEN = "timer_type_choosing_screen"
const val SESSION_SCREEN = "session" const val SESSION_SCREEN = "session"
const val SESSION_RECAP = "session_recap" const val SESSION_RECAP = "session_recap"

View file

@ -0,0 +1,245 @@
package be.ugent.sel.studeez.navigation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import be.ugent.sel.studeez.StudeezAppstate
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel
import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel
import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions
import be.ugent.sel.studeez.screens.home.HomeRoute
import be.ugent.sel.studeez.screens.log_in.LoginRoute
import be.ugent.sel.studeez.screens.profile.EditProfileRoute
import be.ugent.sel.studeez.screens.profile.ProfileRoute
import be.ugent.sel.studeez.screens.session.SessionRoute
import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute
import be.ugent.sel.studeez.screens.sessions.SessionsRoute
import be.ugent.sel.studeez.screens.settings.SettingsRoute
import be.ugent.sel.studeez.screens.sign_up.SignUpRoute
import be.ugent.sel.studeez.screens.splash.SplashRoute
import be.ugent.sel.studeez.screens.subjects.SubjectRoute
import be.ugent.sel.studeez.screens.tasks.TaskRoute
import be.ugent.sel.studeez.screens.subjects.form.SubjectCreateRoute
import be.ugent.sel.studeez.screens.subjects.form.SubjectEditRoute
import be.ugent.sel.studeez.screens.tasks.form.TaskCreateRoute
import be.ugent.sel.studeez.screens.tasks.form.TaskEditRoute
import be.ugent.sel.studeez.screens.timer_form.TimerAddRoute
import be.ugent.sel.studeez.screens.timer_form.TimerEditRoute
import be.ugent.sel.studeez.screens.timer_form.timer_type_select.TimerTypeSelectScreen
import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute
import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute
@Composable
fun StudeezNavGraph(
appState: StudeezAppstate,
modifier: Modifier = Modifier,
) {
val drawerViewModel: DrawerViewModel = hiltViewModel()
val navBarViewModel: NavigationBarViewModel = hiltViewModel()
val backStackEntry by appState.navController.currentBackStackEntryAsState()
val getCurrentScreen: () -> String? = { backStackEntry?.destination?.route }
val goBack: () -> Unit = { appState.popUp() }
val open: (String) -> Unit = { appState.navigate(it) }
val openAndPopUp: (String, String) -> Unit =
{ route, popUp -> appState.navigateAndPopUp(route, popUp) }
val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp)
val navigationBarActions: NavigationBarActions =
getNavigationBarActions(navBarViewModel, open, getCurrentScreen)
NavHost(
navController = appState.navController,
startDestination = StudeezDestinations.SPLASH_SCREEN,
modifier = modifier,
) {
// NavBar
composable(StudeezDestinations.HOME_SCREEN) {
HomeRoute(
open,
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
feedViewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SUBJECT_SCREEN) {
SubjectRoute(
open = open,
viewModel = hiltViewModel(),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
)
}
composable(StudeezDestinations.ADD_SUBJECT_FORM) {
SubjectCreateRoute(
goBack = goBack,
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.EDIT_SUBJECT_FORM) {
SubjectEditRoute(
goBack = goBack,
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.TASKS_SCREEN) {
TaskRoute(
goBack = goBack,
open = open,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.ADD_TASK_FORM) {
TaskCreateRoute(
goBack = goBack,
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.EDIT_TASK_FORM) {
TaskEditRoute(
goBack = goBack,
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SESSIONS_SCREEN) {
SessionsRoute(
drawerActions = drawerActions,
navigationBarActions = navigationBarActions
)
}
composable(StudeezDestinations.PROFILE_SCREEN) {
ProfileRoute(
open,
viewModel = hiltViewModel(),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
)
}
// Drawer
composable(StudeezDestinations.TIMER_SCREEN) {
TimerOverviewRoute(
viewModel = hiltViewModel(),
drawerActions = drawerActions,
open = open
)
}
composable(StudeezDestinations.SETTINGS_SCREEN) {
SettingsRoute(
drawerActions = drawerActions
)
}
// Login flow
composable(StudeezDestinations.SPLASH_SCREEN) {
SplashRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.LOGIN_SCREEN) {
LoginRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.SIGN_UP_SCREEN) {
SignUpRoute(
openAndPopUp,
viewModel = hiltViewModel(),
)
}
// Studying flow
composable(StudeezDestinations.TIMER_SELECTION_SCREEN) {
TimerSelectionRoute(
open,
goBack,
viewModel = hiltViewModel(),
)
}
composable(StudeezDestinations.TIMER_TYPE_CHOOSING_SCREEN) {
TimerTypeSelectScreen(
open = open,
popUp = goBack
)
}
composable(StudeezDestinations.SESSION_SCREEN) {
SessionRoute(
open,
openAndPopUp,
viewModel = hiltViewModel()
)
}
composable(StudeezDestinations.SESSION_RECAP) {
SessionRecapRoute(
openAndPopUp = openAndPopUp,
viewModel = hiltViewModel()
)
}
composable(StudeezDestinations.ADD_TIMER_SCREEN) {
TimerAddRoute(
popUp = goBack,
viewModel = hiltViewModel()
)
}
composable(StudeezDestinations.TIMER_EDIT_SCREEN) {
TimerEditRoute(
popUp = goBack,
viewModel = hiltViewModel()
)
}
// Friends flow
composable(StudeezDestinations.SEARCH_FRIENDS_SCREEN) {
// TODO
}
// Create & edit screens
composable(StudeezDestinations.CREATE_TASK_SCREEN) {
// TODO
}
composable(StudeezDestinations.CREATE_SESSION_SCREEN) {
// TODO
}
composable(StudeezDestinations.EDIT_PROFILE_SCREEN) {
EditProfileRoute(
goBack,
openAndPopUp,
viewModel = hiltViewModel(),
)
}
}
}

View file

@ -5,35 +5,45 @@ 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
fun HomeRoute( fun HomeRoute(
open: (String) -> Unit, open: (String) -> Unit,
viewModel: HomeViewModel,
drawerActions: DrawerActions, drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions, navigationBarActions: NavigationBarActions,
feedViewModel: FeedViewModel,
) { ) {
val feedUiState by feedViewModel.uiState.collectAsState()
HomeScreen( HomeScreen(
onStartSessionClick = { viewModel.onStartSessionClick(open) },
drawerActions = drawerActions, drawerActions = drawerActions,
open = open,
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, open: (String) -> 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),
@ -41,9 +51,7 @@ fun HomeScreen(
navigationBarActions = navigationBarActions, navigationBarActions = navigationBarActions,
// TODO barAction = { FriendsAction() } // TODO barAction = { FriendsAction() }
) { ) {
BasicButton(R.string.start_session, Modifier.basicButton()) { Feed(feedUiState, continueTask, onEmptyFeedHelp)
onStartSessionClick()
}
} }
} }
@ -61,8 +69,40 @@ fun FriendsAction() {
@Composable @Composable
fun HomeScreenPreview() { fun HomeScreenPreview() {
HomeScreen( HomeScreen(
onStartSessionClick = {},
drawerActions = DrawerActions({}, {}, {}, {}, {}), drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}) navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
open = {},
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 = {}
) )
} }

View file

@ -1,19 +0,0 @@
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.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor(
private val accountDAO: AccountDAO,
logService: LogService
) : StudeezViewModel(logService) {
fun onStartSessionClick(open: (String) -> Unit) {
open(StudeezDestinations.TIMER_SELECTION_SCREEN)
}
}

View file

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

View file

@ -100,6 +100,8 @@ abstract class AbstractSessionScreen {
fontSize = 30.sp fontSize = 30.sp
) )
MidSection()
Box( Box(
contentAlignment = Alignment.Center, modifier = Modifier contentAlignment = Alignment.Center, modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -126,6 +128,11 @@ abstract class AbstractSessionScreen {
@Composable @Composable
abstract fun motivationString(): String abstract fun motivationString(): String
@Composable
open fun MidSection() {
// Default has no midsection, unless overwritten.
}
abstract fun callMediaPlayer() abstract fun callMediaPlayer()
} }

View file

@ -1,7 +1,20 @@
package be.ugent.sel.studeez.screens.session.sessionScreens package be.ugent.sel.studeez.screens.session.sessionScreens
import android.media.MediaPlayer import android.media.MediaPlayer
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import be.ugent.sel.studeez.R import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
@ -12,6 +25,37 @@ class BreakSessionScreen(
private var mediaplayer: MediaPlayer? private var mediaplayer: MediaPlayer?
): AbstractSessionScreen() { ): AbstractSessionScreen() {
@Composable
override fun MidSection() {
Dots()
}
@Composable
fun Dots() {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
repeat(funPomoDoroTimer.repeats - funPomoDoroTimer.breaksRemaining) {
Dot(color = Color.DarkGray)
}
if (!funPomoDoroTimer.isInBreak) Dot(Color.Green) else Dot(Color.DarkGray)
repeat(funPomoDoroTimer.breaksRemaining - 1) {
Dot(color = Color.Gray)
}
}
}
@Composable
private fun Dot(color: Color) {
Box(modifier = Modifier
.padding(5.dp)
.size(10.dp)
.clip(CircleShape)
.background(color))
}
@Composable @Composable
override fun motivationString(): String { override fun motivationString(): String {
if (funPomoDoroTimer.isInBreak) { if (funPomoDoroTimer.isInBreak) {
@ -22,11 +66,7 @@ class BreakSessionScreen(
return resources().getString(AppText.state_done) return resources().getString(AppText.state_done)
} }
return resources().getQuantityString( return resources().getString(AppText.state_focus)
R.plurals.state_focus_remaining,
funPomoDoroTimer.breaksRemaining,
funPomoDoroTimer.breaksRemaining
)
} }
override fun callMediaPlayer() { override fun callMediaPlayer() {
@ -43,3 +83,11 @@ class BreakSessionScreen(
} }
} }
} }
@Preview
@Composable
fun MidsectionPreview() {
val funPomoDoroTimer = FunctionalPomodoroTimer(15, 60, 5)
val breakSessionScreen = BreakSessionScreen(funPomoDoroTimer, MediaPlayer())
breakSessionScreen.MidSection()
}

View file

@ -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,19 +13,22 @@ 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, String) -> Unit) {
sessionDAO.saveSession(getSessionReport()) sessionDAO.saveSession(getSessionReport())
val newTask =
selectedTask().copy(time = selectedTask().time + selectedSessionReport().studyTime)
taskDAO.updateTask(newTask)
open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP)
} }

View file

@ -0,0 +1,121 @@
package be.ugent.sel.studeez.screens.subjects
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
import be.ugent.sel.studeez.common.composable.tasks.SubjectEntry
import be.ugent.sel.studeez.data.local.models.task.Subject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import be.ugent.sel.studeez.R.string as AppText
@Composable
fun SubjectRoute(
open: (String) -> Unit,
viewModel: SubjectViewModel,
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions,
) {
val uiState by viewModel.uiState.collectAsState()
SubjectScreen(
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
onAddSubject = { viewModel.onAddSubject(open) },
onViewSubject = { viewModel.onViewSubject(it, open) },
getStudyTime = viewModel::getStudyTime,
uiState,
)
}
@Composable
fun SubjectScreen(
drawerActions: DrawerActions,
navigationBarActions: NavigationBarActions,
onAddSubject: () -> Unit,
onViewSubject: (Subject) -> Unit,
getStudyTime: (Subject) -> Flow<Int>,
uiState: SubjectUiState,
) {
PrimaryScreenTemplate(
title = stringResource(AppText.my_subjects),
drawerActions = drawerActions,
navigationBarActions = navigationBarActions,
barAction = {},
) {
when (uiState) {
SubjectUiState.Loading -> Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(color = MaterialTheme.colors.onBackground)
}
is SubjectUiState.Succes -> {
Column(
modifier = Modifier.padding(top = 5.dp)
) {
NewTaskSubjectButton(onClick = onAddSubject, AppText.new_subject)
LazyColumn {
items(uiState.subjects) {
SubjectEntry(
subject = it,
onViewSubject = { onViewSubject(it) },
getStudyTime = { getStudyTime(it) },
)
}
}
}
}
}
}
}
@Preview
@Composable
fun SubjectScreenPreview() {
SubjectScreen(
drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
onAddSubject = {},
onViewSubject = {},
getStudyTime = { flowOf() },
uiState = SubjectUiState.Succes(
listOf(
Subject(
name = "Test Subject",
argb_color = 0xFFFFD200,
taskCount = 5, taskCompletedCount = 2,
)
)
)
)
}
@Preview
@Composable
fun SubjectScreenLoadingPreview() {
SubjectScreen(
drawerActions = DrawerActions({}, {}, {}, {}, {}),
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
onAddSubject = {},
onViewSubject = {},
getStudyTime = { flowOf() },
uiState = SubjectUiState.Loading
)
}

View file

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

View file

@ -1,5 +1,6 @@
package be.ugent.sel.studeez.screens.tasks package be.ugent.sel.studeez.screens.subjects
import androidx.lifecycle.viewModelScope
import be.ugent.sel.studeez.data.SelectedSubject import be.ugent.sel.studeez.data.SelectedSubject
import be.ugent.sel.studeez.data.local.models.task.Subject import be.ugent.sel.studeez.data.local.models.task.Subject
import be.ugent.sel.studeez.domain.LogService import be.ugent.sel.studeez.domain.LogService
@ -7,7 +8,7 @@ import be.ugent.sel.studeez.domain.SubjectDAO
import be.ugent.sel.studeez.navigation.StudeezDestinations import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.StudeezViewModel import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.*
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -16,12 +17,21 @@ class SubjectViewModel @Inject constructor(
private val selectedSubject: SelectedSubject, private val selectedSubject: SelectedSubject,
logService: LogService, logService: LogService,
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
fun addSubject(open: (String) -> Unit) {
val uiState: StateFlow<SubjectUiState> = subjectDAO.getSubjects()
.map { SubjectUiState.Succes(it) }
.stateIn(
scope = viewModelScope,
initialValue = SubjectUiState.Loading,
started = SharingStarted.Eagerly,
)
fun onAddSubject(open: (String) -> Unit) {
open(StudeezDestinations.ADD_SUBJECT_FORM) open(StudeezDestinations.ADD_SUBJECT_FORM)
} }
fun getSubjects(): Flow<List<Subject>> { fun getStudyTime(subject: Subject): Flow<Int> {
return subjectDAO.getSubjects() return subjectDAO.getStudyTime(subject)
} }
fun onViewSubject(subject: Subject, open: (String) -> Unit) { fun onViewSubject(subject: Subject, open: (String) -> Unit) {

View file

@ -1,4 +1,4 @@
package be.ugent.sel.studeez.screens.tasks.forms package be.ugent.sel.studeez.screens.subjects.form
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -19,10 +19,10 @@ import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
@Composable @Composable
fun SubjectAddRoute( fun SubjectCreateRoute(
goBack: () -> Unit, goBack: () -> Unit,
openAndPopUp: (String, String) -> Unit, openAndPopUp: (String, String) -> Unit,
viewModel: SubjectFormViewModel, viewModel: SubjectCreateFormViewModel,
) { ) {
val uiState by viewModel.uiState val uiState by viewModel.uiState
SubjectForm( SubjectForm(
@ -39,7 +39,7 @@ fun SubjectAddRoute(
fun SubjectEditRoute( fun SubjectEditRoute(
goBack: () -> Unit, goBack: () -> Unit,
openAndPopUp: (String, String) -> Unit, openAndPopUp: (String, String) -> Unit,
viewModel: SubjectFormViewModel, viewModel: SubjectEditFormViewModel,
) { ) {
val uiState by viewModel.uiState val uiState by viewModel.uiState
SubjectForm( SubjectForm(

View file

@ -1,4 +1,4 @@
package be.ugent.sel.studeez.screens.tasks.forms package be.ugent.sel.studeez.screens.subjects.form
data class SubjectFormUiState( data class SubjectFormUiState(
val name: String = "", val name: String = "",

View file

@ -1,5 +1,6 @@
package be.ugent.sel.studeez.screens.tasks.forms package be.ugent.sel.studeez.screens.subjects.form
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import be.ugent.sel.studeez.data.SelectedSubject import be.ugent.sel.studeez.data.SelectedSubject
import be.ugent.sel.studeez.data.local.models.task.Subject import be.ugent.sel.studeez.data.local.models.task.Subject
@ -10,25 +11,17 @@ import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel abstract class SubjectFormViewModel(
class SubjectFormViewModel @Inject constructor( protected val subjectDAO: SubjectDAO,
private val subjectDAO: SubjectDAO, protected val selectedSubject: SelectedSubject,
private val selectedSubject: SelectedSubject,
logService: LogService, logService: LogService,
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
var uiState = mutableStateOf( abstract val uiState: MutableState<SubjectFormUiState>
if (selectedSubject.isSet()) SubjectFormUiState(
name = selectedSubject().name,
color = selectedSubject().argb_color
)
else SubjectFormUiState()
)
private set
private val name: String protected val name: String
get() = uiState.value.name get() = uiState.value.name
private val color: Long protected val color: Long
get() = uiState.value.color get() = uiState.value.color
fun onNameChange(newValue: String) { fun onNameChange(newValue: String) {
@ -38,11 +31,15 @@ class SubjectFormViewModel @Inject constructor(
fun onColorChange(newValue: Long) { fun onColorChange(newValue: Long) {
uiState.value = uiState.value.copy(color = newValue) uiState.value = uiState.value.copy(color = newValue)
} }
}
fun onDelete(openAndPopUp: (String, String) -> Unit) { @HiltViewModel
subjectDAO.deleteSubject(selectedSubject()) class SubjectCreateFormViewModel @Inject constructor(
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) subjectDAO: SubjectDAO,
} selectedSubject: SelectedSubject,
logService: LogService,
) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) {
override val uiState = mutableStateOf(SubjectFormUiState())
fun onCreate(openAndPopUp: (String, String) -> Unit) { fun onCreate(openAndPopUp: (String, String) -> Unit) {
val newSubject = Subject( val newSubject = Subject(
@ -57,6 +54,25 @@ class SubjectFormViewModel @Inject constructor(
// open(StudeezDestinations.TASKS_SCREEN) // open(StudeezDestinations.TASKS_SCREEN)
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.ADD_SUBJECT_FORM) openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.ADD_SUBJECT_FORM)
} }
}
@HiltViewModel
class SubjectEditFormViewModel @Inject constructor(
subjectDAO: SubjectDAO,
selectedSubject: SelectedSubject,
logService: LogService,
) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) {
override val uiState = mutableStateOf(
SubjectFormUiState(
name = selectedSubject().name,
color = selectedSubject().argb_color
)
)
fun onDelete(openAndPopUp: (String, String) -> Unit) {
subjectDAO.updateSubject(selectedSubject().copy(archived = true))
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM)
}
fun onEdit(openAndPopUp: (String, String) -> Unit) { fun onEdit(openAndPopUp: (String, String) -> Unit) {
val newSubject = selectedSubject().copy( val newSubject = selectedSubject().copy(

View file

@ -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 = {},
)
}

View file

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

View file

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

View file

@ -1,4 +1,4 @@
package be.ugent.sel.studeez.screens.tasks.forms package be.ugent.sel.studeez.screens.tasks.form
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -18,10 +18,10 @@ import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
@Composable @Composable
fun TaskAddRoute( fun TaskCreateRoute(
goBack: () -> Unit, goBack: () -> Unit,
openAndPopUp: (String, String) -> Unit, openAndPopUp: (String, String) -> Unit,
viewModel: TaskFormViewModel, viewModel: TaskCreateFormViewModel,
) { ) {
val uiState by viewModel.uiState val uiState by viewModel.uiState
TaskForm( TaskForm(
@ -37,7 +37,7 @@ fun TaskAddRoute(
fun TaskEditRoute( fun TaskEditRoute(
goBack: () -> Unit, goBack: () -> Unit,
openAndPopUp: (String, String) -> Unit, openAndPopUp: (String, String) -> Unit,
viewModel: TaskFormViewModel, viewModel: TaskEditFormViewModel,
) { ) {
val uiState by viewModel.uiState val uiState by viewModel.uiState
TaskForm( TaskForm(

View file

@ -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 = "",

View file

@ -1,5 +1,6 @@
package be.ugent.sel.studeez.screens.tasks.forms package be.ugent.sel.studeez.screens.tasks.form
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import be.ugent.sel.studeez.data.SelectedSubject import be.ugent.sel.studeez.data.SelectedSubject
import be.ugent.sel.studeez.data.SelectedTask import be.ugent.sel.studeez.data.SelectedTask
@ -11,38 +12,54 @@ import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel abstract class TaskFormViewModel(
class TaskFormViewModel @Inject constructor( protected val taskDAO: TaskDAO,
private val taskDAO: TaskDAO, protected val selectedSubject: SelectedSubject,
private val selectedSubject: SelectedSubject, protected val selectedTask: SelectedTask,
private val selectedTask: SelectedTask,
logService: LogService, logService: LogService,
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
var uiState = mutableStateOf( abstract val uiState: MutableState<TaskFormUiState>
if (selectedTask.isSet()) TaskFormUiState(selectedTask().name) else TaskFormUiState()
)
private set
private val name: String protected val name: String
get() = uiState.value.name get() = uiState.value.name
fun onNameChange(newValue: String) { fun onNameChange(newValue: String) {
uiState.value = uiState.value.copy(name = newValue) uiState.value = uiState.value.copy(name = newValue)
} }
}
fun onDelete(openAndPopUp: (String, String) -> Unit) { @HiltViewModel
taskDAO.deleteTask(selectedTask()) class TaskCreateFormViewModel @Inject constructor(
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM) taskDAO: TaskDAO,
} selectedSubject: SelectedSubject,
selectedTask: SelectedTask,
logService: LogService,
) : TaskFormViewModel(taskDAO, selectedSubject, selectedTask, logService) {
override val uiState = mutableStateOf(TaskFormUiState())
fun onCreate(openAndPopUp: (String, String) -> Unit) { fun onCreate(openAndPopUp: (String, String) -> Unit) {
val newTask = Task(name = name, subjectId = selectedSubject().id) val newTask = Task(name = name, subjectId = selectedSubject().id)
taskDAO.saveTask(newTask) taskDAO.saveTask(newTask)
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.ADD_TASK_FORM) openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.ADD_TASK_FORM)
} }
}
@HiltViewModel
class TaskEditFormViewModel @Inject constructor(
taskDAO: TaskDAO,
selectedSubject: SelectedSubject,
selectedTask: SelectedTask,
logService: LogService,
) : TaskFormViewModel(taskDAO, selectedSubject, selectedTask, logService) {
override val uiState = mutableStateOf(TaskFormUiState())
fun onDelete(openAndPopUp: (String, String) -> Unit) {
taskDAO.deleteTask(selectedTask())
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM)
}
fun onEdit(openAndPopUp: (String, String) -> Unit) { fun onEdit(openAndPopUp: (String, String) -> Unit) {
val newTask = Task(name = name) val newTask = selectedTask().copy(name = name)
taskDAO.updateTask(newTask) taskDAO.updateTask(newTask)
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM) openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM)
} }

View file

@ -1,44 +0,0 @@
package be.ugent.sel.studeez.screens.timer_add
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.screens.timer_edit.GetTimerEditScreen
import be.ugent.sel.studeez.screens.timer_edit.TimerEditViewModel
import be.ugent.sel.studeez.screens.timer_edit.editScreens.AbstractTimerEditScreen
import be.ugent.sel.studeez.ui.theme.StudeezTheme
data class TimerEditActions(
val getTimerInfo: () -> TimerInfo,
val saveTimer: (TimerInfo, () -> Unit) -> Unit
)
fun getTimerEditActions(
viewModel: TimerEditViewModel,
open: (String) -> Unit
): TimerEditActions {
return TimerEditActions(
getTimerInfo = viewModel::getTimerInfo,
saveTimer = viewModel::saveTimer
)
}
@Composable
fun TimerEditRoute(
open: (String) -> Unit,
popUp: () -> Unit,
viewModel: TimerEditViewModel,
) {
val timerEditActions = getTimerEditActions(viewModel, open)
SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) {
val timerEditScreen = timerEditActions.getTimerInfo().accept(GetTimerEditScreen())
timerEditScreen { timerInfo ->
timerEditActions.saveTimer(timerInfo, popUp)
}
}
}

View file

@ -1,27 +0,0 @@
package be.ugent.sel.studeez.screens.timer_edit
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfoVisitor
import be.ugent.sel.studeez.screens.timer_edit.editScreens.AbstractTimerEditScreen
import be.ugent.sel.studeez.screens.timer_edit.editScreens.BreakTimerEditScreen
import be.ugent.sel.studeez.screens.timer_edit.editScreens.CustomTimerEditScreen
import be.ugent.sel.studeez.screens.timer_edit.editScreens.EndlessTimerEditScreen
class GetTimerEditScreen: TimerInfoVisitor<AbstractTimerEditScreen> {
override fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): AbstractTimerEditScreen {
return CustomTimerEditScreen(customTimerInfo)
}
override fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo): AbstractTimerEditScreen {
return EndlessTimerEditScreen(endlessTimerInfo)
}
override fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo): AbstractTimerEditScreen {
return BreakTimerEditScreen(pomodoroTimerInfo)
}
}

View file

@ -1,65 +0,0 @@
package be.ugent.sel.studeez.screens.timer_edit
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.screens.timer_edit.editScreens.AbstractTimerEditScreen
import be.ugent.sel.studeez.ui.theme.StudeezTheme
data class TimerEditActions(
val getTimerInfo: () -> TimerInfo,
val saveTimer: (TimerInfo, () -> Unit) -> Unit
)
fun getTimerEditActions(
viewModel: TimerEditViewModel,
open: (String) -> Unit
): TimerEditActions {
return TimerEditActions(
getTimerInfo = viewModel::getTimerInfo,
saveTimer = viewModel::saveTimer
)
}
@Composable
fun TimerEditRoute(
open: (String) -> Unit,
popUp: () -> Unit,
viewModel: TimerEditViewModel,
) {
val timerEditActions = getTimerEditActions(viewModel, open)
SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) {
val timerEditScreen = timerEditActions.getTimerInfo().accept(GetTimerEditScreen())
timerEditScreen { timerInfo ->
timerEditActions.saveTimer(timerInfo, popUp)
}
}
}

View file

@ -1,29 +0,0 @@
package be.ugent.sel.studeez.screens.timer_edit
import be.ugent.sel.studeez.data.EditTimerState
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.TimerDAO
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class TimerEditViewModel @Inject constructor(
private val editTimerState: EditTimerState,
private val timerDAO: TimerDAO,
logService: LogService
) : StudeezViewModel(logService) {
private val timerInfo: TimerInfo = editTimerState.timerInfo
fun getTimerInfo(): TimerInfo {
return timerInfo
}
fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
timerDAO.updateTimer(timerInfo)
goBack()
}
}

View file

@ -0,0 +1,27 @@
package be.ugent.sel.studeez.screens.timer_form
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfoVisitor
import be.ugent.sel.studeez.screens.timer_form.form_screens.AbstractTimerFormScreen
import be.ugent.sel.studeez.screens.timer_form.form_screens.BreakTimerFormScreen
import be.ugent.sel.studeez.screens.timer_form.form_screens.CustomTimerFormScreen
import be.ugent.sel.studeez.screens.timer_form.form_screens.EndlessTimerFormScreen
class GetTimerFormScreen: TimerInfoVisitor<AbstractTimerFormScreen> {
override fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): AbstractTimerFormScreen {
return CustomTimerFormScreen(customTimerInfo)
}
override fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo): AbstractTimerFormScreen {
return EndlessTimerFormScreen(endlessTimerInfo)
}
override fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo): AbstractTimerFormScreen {
return BreakTimerFormScreen(pomodoroTimerInfo)
}
}

View file

@ -0,0 +1,42 @@
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)
}
}

View file

@ -1,6 +1,6 @@
package be.ugent.sel.studeez.screens.timer_add 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
@ -9,21 +9,22 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class TimerAddViewModel @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 saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) { fun editTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
timerDAO.updateTimer(timerInfo) timerDAO.updateTimer(timerInfo)
goBack() goBack()
} }
fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
timerDAO.saveTimer(timerInfo)
goBack()
}
} }

View file

@ -1,4 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens package be.ugent.sel.studeez.screens.timer_form.form_screens
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -18,8 +18,9 @@ import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.LabelledInputField import be.ugent.sel.studeez.common.composable.LabelledInputField
import be.ugent.sel.studeez.common.ext.basicButton import be.ugent.sel.studeez.common.ext.basicButton
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
abstract class AbstractTimerEditScreen(private val timerInfo: TimerInfo) { abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) {
@Composable @Composable
operator fun invoke(onSaveClick: (TimerInfo) -> Unit) { operator fun invoke(onSaveClick: (TimerInfo) -> Unit) {
@ -50,7 +51,7 @@ abstract class AbstractTimerEditScreen(private val timerInfo: TimerInfo) {
LabelledInputField( LabelledInputField(
value = description, value = description,
onNewValue = { description = it }, onNewValue = { description = it },
label = R.string.description, label = AppText.description,
singleLine = false singleLine = false
) )

View file

@ -1,20 +1,19 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens package be.ugent.sel.studeez.screens.timer_form.form_screens
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.text.input.KeyboardType
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.TimePickerButton import be.ugent.sel.studeez.common.composable.LabeledErrorTextField
import be.ugent.sel.studeez.common.composable.TimePickerCard import be.ugent.sel.studeez.common.composable.TimePickerCard
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
import be.ugent.sel.studeez.data.local.models.timer_functional.Time
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
import be.ugent.sel.studeez.R.string as AppText
class BreakTimerEditScreen(
class BreakTimerFormScreen(
private val breakTimerInfo: PomodoroTimerInfo private val breakTimerInfo: PomodoroTimerInfo
): AbstractTimerEditScreen(breakTimerInfo) { ): AbstractTimerFormScreen(breakTimerInfo) {
@Composable @Composable
override fun ExtraFields() { override fun ExtraFields() {
@ -26,8 +25,18 @@ class BreakTimerEditScreen(
TimePickerCard(R.string.breakTime, breakTimerInfo.breakTime) { newTime -> TimePickerCard(R.string.breakTime, breakTimerInfo.breakTime) { newTime ->
breakTimerInfo.breakTime = newTime breakTimerInfo.breakTime = newTime
} }
}
LabeledErrorTextField(
initialValue = breakTimerInfo.repeats.toString(),
label = R.string.repeats,
errorText = AppText.repeats_error,
keyboardType = KeyboardType.Decimal,
predicate = { it.matches(Regex("[1-9]+\\d*")) }
) { correctlyTypedInt ->
breakTimerInfo.repeats = correctlyTypedInt.toInt()
}
}
} }
@Preview @Preview
@ -41,6 +50,6 @@ fun BreakEditScreenPreview() {
5 5
) )
StudeezTheme { StudeezTheme {
BreakTimerEditScreen(pomodoroTimerInfo).invoke(onSaveClick = {}) BreakTimerFormScreen(pomodoroTimerInfo).invoke(onSaveClick = {})
} }
} }

View file

@ -1,4 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens package be.ugent.sel.studeez.screens.timer_form.form_screens
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -7,9 +7,9 @@ import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
import be.ugent.sel.studeez.R.string as AppText import be.ugent.sel.studeez.R.string as AppText
class CustomTimerEditScreen( class CustomTimerFormScreen(
private val customTimerInfo: CustomTimerInfo private val customTimerInfo: CustomTimerInfo
): AbstractTimerEditScreen(customTimerInfo) { ): AbstractTimerFormScreen(customTimerInfo) {
@Composable @Composable
override fun ExtraFields() { override fun ExtraFields() {
@ -29,6 +29,6 @@ class CustomTimerEditScreen(
fun CustomEditScreenPreview() { fun CustomEditScreenPreview() {
val customTimerInfo = CustomTimerInfo("custom", "my description", 25) val customTimerInfo = CustomTimerInfo("custom", "my description", 25)
StudeezTheme { StudeezTheme {
CustomTimerEditScreen(customTimerInfo).invoke(onSaveClick = {}) CustomTimerFormScreen(customTimerInfo).invoke(onSaveClick = {})
} }
} }

View file

@ -1,13 +1,13 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens package be.ugent.sel.studeez.screens.timer_form.form_screens
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo
import be.ugent.sel.studeez.ui.theme.StudeezTheme import be.ugent.sel.studeez.ui.theme.StudeezTheme
class EndlessTimerEditScreen( class EndlessTimerFormScreen(
endlessTimerInfo: EndlessTimerInfo endlessTimerInfo: EndlessTimerInfo
): AbstractTimerEditScreen(endlessTimerInfo) { ): AbstractTimerFormScreen(endlessTimerInfo) {
} }
@Preview @Preview
@ -18,6 +18,6 @@ fun EndlessEditScreenPreview() {
"My endless timer description", "My endless timer description",
) )
StudeezTheme { StudeezTheme {
EndlessTimerEditScreen(endlessTimerInfo).invoke(onSaveClick = {}) EndlessTimerFormScreen(endlessTimerInfo).invoke(onSaveClick = {})
} }
} }

View file

@ -1,4 +1,4 @@
package be.ugent.sel.studeez.screens.timer_add package be.ugent.sel.studeez.screens.timer_form.timer_type_select
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -7,16 +7,18 @@ 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.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.*
import be.ugent.sel.studeez.R.string as AppText
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.CUSTOM import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.CUSTOM
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.BREAK import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.BREAK
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.ENDLESS import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.ENDLESS
val defaultTimerInfo: Map<TimerType, TimerInfo> = mapOf( val defaultTimerInfo: Map<TimerType, TimerInfo> = mapOf(
CUSTOM to CustomTimerInfo("", "", 0), CUSTOM to CustomTimerInfo("", "", 0),
BREAK to PomodoroTimerInfo("", "", 0, 0, 0), BREAK to PomodoroTimerInfo("", "", 0, 0, 1),
ENDLESS to EndlessTimerInfo("", ""), ENDLESS to EndlessTimerInfo("", ""),
) )
@ -28,13 +30,14 @@ fun TimerTypeSelectScreen(
viewModel: TimerTypeSelectViewModel = hiltViewModel() viewModel: TimerTypeSelectViewModel = hiltViewModel()
) { ) {
SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) { SecondaryScreenTemplate(title = stringResource(id = AppText.timer_type_select), popUp = popUp) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
TimerType.values().forEach { timerType -> TimerType.values().forEach { timerType ->
Button(onClick = { viewModel.onTimerTypeChosen(defaultTimerInfo[timerType]!!, open) }) { val default: TimerInfo = defaultTimerInfo.getValue(timerType)
Button(onClick = { viewModel.onTimerTypeChosen(default, open) }) {
Text(text = timerType.name) Text(text = timerType.name)
} }
} }

View file

@ -1,25 +1,22 @@
package be.ugent.sel.studeez.screens.timer_add 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.domain.TimerDAO
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 javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class TimerTypeSelectViewModel @Inject constructor( class TimerTypeSelectViewModel @Inject constructor(
private val editTimerState: EditTimerState, private val selectedTimerInfo: SelectedTimerInfo,
private val timerDAO: TimerDAO,
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.TIMER_EDIT_SCREEN) open(StudeezDestinations.ADD_TIMER_SCREEN)
} }
} }

View file

@ -25,7 +25,7 @@ data class TimerOverviewActions(
val getUserTimers: () -> Flow<List<TimerInfo>>, val getUserTimers: () -> Flow<List<TimerInfo>>,
val getDefaultTimers: () -> List<TimerInfo>, val getDefaultTimers: () -> List<TimerInfo>,
val onEditClick: (TimerInfo) -> Unit, val onEditClick: (TimerInfo) -> Unit,
val onAddClick: () -> Unit, val onAddClick: () -> Unit
) )
fun getTimerOverviewActions( fun getTimerOverviewActions(
@ -36,7 +36,7 @@ fun getTimerOverviewActions(
getUserTimers = viewModel::getUserTimers, getUserTimers = viewModel::getUserTimers,
getDefaultTimers = viewModel::getDefaultTimers, getDefaultTimers = viewModel::getDefaultTimers,
onEditClick = { viewModel.update(it, open) }, onEditClick = { viewModel.update(it, open) },
onAddClick = { viewModel.create(open) } onAddClick = { viewModel.onAddClick(open) }
) )
} }
@ -48,14 +48,14 @@ fun TimerOverviewRoute(
) { ) {
TimerOverviewScreen( TimerOverviewScreen(
timerOverviewActions = getTimerOverviewActions(viewModel, open), timerOverviewActions = getTimerOverviewActions(viewModel, open),
drawerActions = drawerActions drawerActions = drawerActions,
) )
} }
@Composable @Composable
fun TimerOverviewScreen( fun TimerOverviewScreen(
timerOverviewActions: TimerOverviewActions, timerOverviewActions: TimerOverviewActions,
drawerActions: DrawerActions drawerActions: DrawerActions,
) { ) {
val timers = timerOverviewActions.getUserTimers().collectAsState(initial = emptyList()) val timers = timerOverviewActions.getUserTimers().collectAsState(initial = emptyList())
@ -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) }
) )
} }
)
} }

View file

@ -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,16 +27,16 @@ 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)
} }
fun create(open: (String) -> Unit) { fun onAddClick(open: (String) -> Unit) {
open(StudeezDestinations.ADD_TIMER_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)

View file

@ -1,12 +0,0 @@
package be.ugent.sel.studeez.screens.timer_overview.add_timer
data class AddTimerUiState(
val studyTimeHours: Int = 1,
val studyTimeMinutes: Int = 0,
val withBreaks: Boolean = false,
val breakTimeMinutes: Int = 5,
val breakTimeHours: Int = 0,
val repeats: Int = 1,
val name: String = "Timer",
val description: String = "Long study session",
)

View file

@ -1,91 +0,0 @@
package be.ugent.sel.studeez.screens.timer_overview.add_timer
import androidx.compose.runtime.mutableStateOf
import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo
import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo
import be.ugent.sel.studeez.domain.LogService
import be.ugent.sel.studeez.domain.TimerDAO
import be.ugent.sel.studeez.screens.StudeezViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class AddTimerViewModel @Inject constructor(
logService: LogService,
private val timerDAO: TimerDAO,
): StudeezViewModel(logService) {
var uiState = mutableStateOf(AddTimerUiState())
private set
private val studyTimeHours
get() = uiState.value.studyTimeHours
private val studyTimeMinutes
get() = uiState.value.studyTimeMinutes
private val breakTimeHours
get() = uiState.value.breakTimeHours
private val breakTimeMinutes
get() = uiState.value.breakTimeMinutes
private val repeats
get() = uiState.value.repeats
private val name
get() = uiState.value.name
private val description
get() = uiState.value.description
fun onStudyTimeHoursChange(newValue: Int) {
uiState.value = uiState.value.copy(studyTimeHours = newValue)
}
fun onStudyTimeMinutesChange(newValue: Int) {
uiState.value = uiState.value.copy(studyTimeMinutes = newValue)
}
fun onWithBreaksChange() {
uiState.value = uiState.value.copy(withBreaks = !uiState.value.withBreaks)
}
fun onBreakTimeHourChange(newValue: Int) {
uiState.value = uiState.value.copy(breakTimeHours = newValue)
}
fun onBreakTimeMinutesChange(newValue: Int) {
uiState.value = uiState.value.copy(breakTimeMinutes = newValue)
}
fun onRepeatsChange(newValue: Int) {
uiState.value = uiState.value.copy(repeats = newValue)
}
fun addTimer() {
if (uiState.value.withBreaks) {
timerDAO.saveTimer(PomodoroTimerInfo(
name = uiState.value.name,
description = uiState.value.description,
studyTime = studyTimeHours * 60 * 60 + studyTimeMinutes * 60,
breakTime = breakTimeHours * 60 * 60 + breakTimeMinutes * 60,
repeats = repeats
))
} else {
timerDAO.saveTimer(CustomTimerInfo(
name = uiState.value.name,
description = uiState.value.description,
studyTime = studyTimeHours * 60 * 60 + studyTimeMinutes * 60
))
}
}
fun onNameChange(newValue: String) {
uiState.value = uiState.value.copy(name = newValue)
}
fun onDescriptionChange(newValue: String) {
uiState.value = uiState.value.copy(description = newValue)
}
}

View file

@ -1,274 +0,0 @@
package be.ugent.sel.studeez.screens.timer_overview.add_timer
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicButton
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.composable.navbar.BasicTimePicker
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.ui.theme.StudeezTheme
data class AddTimerActions(
val open: (String) -> Unit,
val goBack: () -> Unit,
val onStudyTimeHoursChange: (Int) -> Unit,
val onStudyTimeMinutesChange: (Int) -> Unit,
val onBreakTimeHourChange: (Int) -> Unit,
val onBreakTimeMinutesChange: (Int) -> Unit,
val onRepeatsChange: (Int) -> Unit,
val onWithBreaksChange: () -> Unit,
val addTimer: () -> Unit,
val onNameChange: (String) -> Unit,
val onDescriptionChange: (String) -> Unit,
)
fun getAddTimerActions(
open: (String) -> Unit,
goBack: () -> Unit,
viewModel: AddTimerViewModel,
): AddTimerActions {
return AddTimerActions(
open = open,
goBack = goBack,
onWithBreaksChange = viewModel::onWithBreaksChange,
onStudyTimeHoursChange = viewModel::onStudyTimeHoursChange,
onStudyTimeMinutesChange = viewModel::onStudyTimeMinutesChange,
onBreakTimeHourChange = viewModel::onBreakTimeHourChange,
onBreakTimeMinutesChange = viewModel::onBreakTimeMinutesChange,
onRepeatsChange = viewModel::onRepeatsChange,
addTimer = viewModel::addTimer,
onNameChange = viewModel::onNameChange,
onDescriptionChange = viewModel::onDescriptionChange
)
}
@Composable
fun AddTimerRoute(
open: (String) -> Unit,
goBack: () -> Unit,
viewModel: AddTimerViewModel,
) {
val uiState by viewModel.uiState
AddTimerScreen(
addTimerActions = getAddTimerActions(
open = open,
goBack = goBack,
viewModel = viewModel,
),
uiState = uiState
)
}
@Composable
fun AddTimerScreen(
addTimerActions: AddTimerActions,
uiState: AddTimerUiState,
) {
val mStudyTimePicker = BasicTimePicker(
onHoursChange = addTimerActions.onStudyTimeHoursChange,
onMinutesChange = addTimerActions.onStudyTimeMinutesChange,
Hours = uiState.studyTimeHours,
Minutes = uiState.studyTimeMinutes
)
val mBreakTimePicker = BasicTimePicker(
onHoursChange = addTimerActions.onBreakTimeHourChange,
onMinutesChange = addTimerActions.onBreakTimeMinutesChange,
Hours = uiState.breakTimeHours,
Minutes = uiState.breakTimeMinutes
)
SecondaryScreenTemplate(
title = resources().getString(R.string.add_timer),
popUp = addTimerActions.goBack
) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
item {
Row(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = stringResource(R.string.addTimer_question),
textAlign = TextAlign.Center
)
}
}
item {
Text(
text = uiState.studyTimeHours.toString() + stringResource(R.string.addTimer_studytime_1) + uiState.studyTimeMinutes + stringResource(
R.string.addTimer_studytime_2)
)
}
item {
Button(
onClick = {
mStudyTimePicker.show()
},
) {
Text(
text = stringResource(R.string.addTimer_timepicker),
)
}
}
item {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.addTimer_break_question),
)
Checkbox(
checked = uiState.withBreaks,
onCheckedChange = { addTimerActions.onWithBreaksChange() }
)
}
}
if (uiState.withBreaks) {
item {
Text(
text = if (uiState.repeats == 1) uiState.repeats.toString() + stringResource(
R.string.addTimer_break_1)
else uiState.repeats.toString() + stringResource(
R.string.addTimer_break_s)
)
TextField(
value = uiState.repeats.toString(),
onValueChange = { it: String ->
it.toIntOrNull()?.let { it1 ->
addTimerActions.onRepeatsChange(
kotlin.math.abs(it1)
)
}
}
)
}
item {
Text(
text = uiState.breakTimeHours.toString() + stringResource(R.string.breakTime_1) + uiState.breakTimeMinutes + stringResource(
R.string.breakTime_2)
)
}
item {
Button(
onClick = {
mBreakTimePicker.show()
},
) {
Text(
text = stringResource(R.string.addTimer_timepicker),
)
}
}
}
item {
Text(
text = stringResource(R.string.addTimer_name)
)
}
item {
TextField(
value = uiState.name,
onValueChange = { addTimerActions.onNameChange(it) }
)
}
item {
if (uiState.name == "") {
Text(
text = stringResource(R.string.addTimer_name_error),
color = Color.Red
)
}
}
item {
Text(
text = stringResource(R.string.addTimer_description)
)
}
item {
TextField(
value = uiState.description,
onValueChange = { addTimerActions.onDescriptionChange(it) }
)
}
item {
if (uiState.description == "") {
Text(
text = stringResource(R.string.addTimer_description_error),
color = Color.Red
)
}
}
item {
Row(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.Center
) {
BasicButton(
text = R.string.add_timer,
modifier = Modifier,
onClick = {
if (uiState.description != "" && uiState.name != "") {
addTimerActions.addTimer()
addTimerActions.open(StudeezDestinations.TIMER_SCREEN)
}
}
)
}
}
}
}
}
@Preview
@Composable
fun AddTimerScreenPreview() { StudeezTheme {
AddTimerScreen(
addTimerActions = AddTimerActions({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}),
uiState = AddTimerUiState()
)
}
}

View file

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

View file

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

View file

@ -29,6 +29,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>
@ -131,8 +137,13 @@
<string name="addTimer_studytime_2">" minutes of studytime"</string> <string name="addTimer_studytime_2">" minutes of studytime"</string>
<string name="addTimer_question">How long do you want to study?</string> <string name="addTimer_question">How long do you want to study?</string>
<!-- Timer Type Select -->
<string name="timer_type_select">Select Timer Type</string>
<!-- Edit Timer--> <!-- Edit Timer-->
<string name="name">Name</string> <string name="name">Name</string>
<string name="edit_timer">Edit Timer</string>
<string name="repeats_error">Repeats must be a positive non-zero number</string>
<string name="description">Description</string> <string name="description">Description</string>
<string name="studyTime">Study Time</string> <string name="studyTime">Study Time</string>
<string name="breakTime">Break Time</string> <string name="breakTime">Break Time</string>

View file

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

View file

@ -10,7 +10,7 @@ class TimeUnitTest {
private val hours = 4 private val hours = 4
private val minutes = 20 private val minutes = 20
private val seconds = 39 private val seconds = 39
private val time: Time = Time(seconds + minutes * 60 + hours * 60 * 60) private var time: Time = Time(seconds + minutes * 60 + hours * 60 * 60)
@Before @Before
fun setup() { fun setup() {
@ -21,9 +21,9 @@ class TimeUnitTest {
fun formatTime() { fun formatTime() {
Assert.assertEquals( Assert.assertEquals(
HoursMinutesSeconds( HoursMinutesSeconds(
hours.toString().padStart(2, '0'), hours,
minutes.toString().padStart(2, '0'), minutes,
seconds.toString().padStart(2, '0'), seconds,
), ),
time.getAsHMS(), time.getAsHMS(),
) )
@ -39,7 +39,11 @@ class TimeUnitTest {
@Test @Test
fun minOne() { fun minOne() {
time.minOne() Assert.assertEquals(
(seconds + minutes * 60 + hours * 60 * 60),
time.time,
)
time--
Assert.assertEquals( Assert.assertEquals(
(seconds + minutes * 60 + hours * 60 * 60) - 1, (seconds + minutes * 60 + hours * 60 * 60) - 1,
time.time, time.time,
@ -48,7 +52,7 @@ class TimeUnitTest {
@Test @Test
fun plusOne() { fun plusOne() {
time.plusOne() time++
Assert.assertEquals( Assert.assertEquals(
(seconds + minutes * 60 + hours * 60 * 60) + 1, (seconds + minutes * 60 + hours * 60 * 60) + 1,
time.time, time.time,
@ -59,7 +63,7 @@ class TimeUnitTest {
fun minMultiple() { fun minMultiple() {
val n = 10 val n = 10
for (i in 1 .. n) { for (i in 1 .. n) {
time.minOne() time--
} }
Assert.assertEquals( Assert.assertEquals(
(seconds + minutes * 60 + hours * 60 * 60) - n, (seconds + minutes * 60 + hours * 60 * 60) - n,
@ -71,7 +75,7 @@ class TimeUnitTest {
fun plusMultiple() { fun plusMultiple() {
val n = 10 val n = 10
for (i in 1 .. n) { for (i in 1 .. n) {
time.plusOne() time++
} }
Assert.assertEquals( Assert.assertEquals(
(seconds + minutes * 60 + hours * 60 * 60) + n, (seconds + minutes * 60 + hours * 60 * 60) + n,