Merge remote-tracking branch 'origin/development' into better_screens
This commit is contained in:
commit
dc63346e5e
74 changed files with 1568 additions and 1200 deletions
|
@ -2,56 +2,19 @@ package be.ugent.sel.studeez
|
|||
|
||||
import android.content.res.Resources
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
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.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
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 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.navigation.StudeezDestinations
|
||||
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.navigation.StudeezNavGraph
|
||||
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
|
@ -97,205 +60,3 @@ fun resources(): Resources {
|
|||
LocalConfiguration.current
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package be.ugent.sel.studeez.common.composable
|
|||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
@ -48,6 +50,7 @@ fun BasicButton(
|
|||
modifier: Modifier = Modifier,
|
||||
colors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||
border: BorderStroke? = null,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Button(
|
||||
|
@ -56,6 +59,7 @@ fun BasicButton(
|
|||
shape = defaultButtonShape(),
|
||||
colors = colors,
|
||||
border = border,
|
||||
enabled = enabled,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(text),
|
||||
|
@ -74,17 +78,22 @@ fun BasicButtonPreview() {
|
|||
fun StealthButton(
|
||||
@StringRes text: Int,
|
||||
modifier: Modifier = Modifier.card(),
|
||||
enabled: Boolean = true,
|
||||
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(
|
||||
text = text,
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,13 @@ package be.ugent.sel.studeez.common.composable
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
|
@ -23,4 +26,14 @@ fun Headline(
|
|||
fontSize = 34.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DateText(date: String) {
|
||||
Text(
|
||||
text = date,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp,
|
||||
modifier = Modifier.padding(horizontal = 10.dp)
|
||||
)
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package be.ugent.sel.studeez.common.composable
|
||||
|
||||
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.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Email
|
||||
import androidx.compose.material.icons.filled.Lock
|
||||
|
@ -14,10 +14,15 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.PasswordVisualTransformation
|
||||
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.resources
|
||||
import kotlin.math.sin
|
||||
import be.ugent.sel.studeez.R.drawable as AppIcon
|
||||
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
|
||||
fun PasswordField(
|
||||
value: String,
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
package be.ugent.sel.studeez.common.composable.feed
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import be.ugent.sel.studeez.common.composable.BasicTextButton
|
||||
import be.ugent.sel.studeez.common.composable.DateText
|
||||
import be.ugent.sel.studeez.common.composable.Headline
|
||||
import be.ugent.sel.studeez.common.ext.textButton
|
||||
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun Feed(
|
||||
uiState: FeedUiState,
|
||||
continueTask: (String, String) -> Unit,
|
||||
onEmptyFeedHelp: () -> Unit
|
||||
) {
|
||||
when (uiState) {
|
||||
FeedUiState.Loading -> LoadingFeed()
|
||||
is FeedUiState.Succes -> LoadedFeed(
|
||||
uiState = uiState,
|
||||
continueTask = continueTask,
|
||||
onEmptyFeedHelp = onEmptyFeedHelp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoadedFeed(
|
||||
uiState: FeedUiState.Succes,
|
||||
continueTask: (String, String) -> Unit,
|
||||
onEmptyFeedHelp: () -> Unit,
|
||||
) {
|
||||
if (uiState.feedEntries.isEmpty()) EmptyFeed(onEmptyFeedHelp)
|
||||
else FeedWithElements(uiState = uiState, continueTask = continueTask)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoadingFeed() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
CircularProgressIndicator(color = MaterialTheme.colors.onBackground)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FeedWithElements(
|
||||
uiState: FeedUiState.Succes,
|
||||
continueTask: (String, String) -> Unit,
|
||||
) {
|
||||
val feedEntries = uiState.feedEntries
|
||||
LazyColumn {
|
||||
items(feedEntries.toList()) { (date, feedEntries) ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val totalDayStudyTime: Int = feedEntries.sumOf { it.totalStudyTime }
|
||||
DateText(date = date)
|
||||
Text(
|
||||
text = "${HoursMinutesSeconds(totalDayStudyTime)}",
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
feedEntries.forEach { feedEntry ->
|
||||
FeedEntry(feedEntry = feedEntry) {
|
||||
continueTask(feedEntry.subjectId, feedEntry.taskId)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EmptyFeed(onEmptyFeedHelp: () -> Unit) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Headline(text = stringResource(id = AppText.your_feed))
|
||||
|
||||
BasicTextButton(
|
||||
AppText.empty_feed_help_text,
|
||||
Modifier.textButton(),
|
||||
action = onEmptyFeedHelp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun FeedLoadingPreview() {
|
||||
Feed(
|
||||
uiState = FeedUiState.Loading,
|
||||
continueTask = { _, _ -> run {} }, {}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun FeedPreview() {
|
||||
Feed(
|
||||
uiState = FeedUiState.Succes(
|
||||
mapOf(
|
||||
"08 May 2023" to listOf(
|
||||
FeedEntry(
|
||||
argb_color = 0xFFFFD200,
|
||||
subJectName = "Test Subject",
|
||||
taskName = "Test Task",
|
||||
totalStudyTime = 600,
|
||||
),
|
||||
FeedEntry(
|
||||
argb_color = 0xFFFFD200,
|
||||
subJectName = "Test Subject",
|
||||
taskName = "Test Task",
|
||||
totalStudyTime = 20,
|
||||
),
|
||||
),
|
||||
"09 May 2023" to listOf(
|
||||
FeedEntry(
|
||||
argb_color = 0xFFFD1200,
|
||||
subJectName = "Test Subject",
|
||||
taskName = "Test Task",
|
||||
),
|
||||
FeedEntry(
|
||||
argb_color = 0xFFFFD200,
|
||||
subJectName = "Test Subject",
|
||||
taskName = "Test Task",
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
continueTask = { _, _ -> run {} }, {}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package be.ugent.sel.studeez.common.composable.feed
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import be.ugent.sel.studeez.common.composable.StealthButton
|
||||
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||
import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun FeedEntry(
|
||||
feedEntry: FeedEntry,
|
||||
continueWithTask: () -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 10.dp, vertical = 5.dp),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp)
|
||||
.weight(11f)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(feedEntry.argb_color)),
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(0.dp)
|
||||
) {
|
||||
Text(
|
||||
text = feedEntry.subJectName,
|
||||
fontWeight = FontWeight.Bold,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
Text(
|
||||
text = feedEntry.taskName,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
Text(text = HoursMinutesSeconds(feedEntry.totalStudyTime).toString())
|
||||
}
|
||||
}
|
||||
val buttonText: Int =
|
||||
if (feedEntry.isArchived) AppText.deleted else AppText.continue_task
|
||||
StealthButton(
|
||||
text = buttonText,
|
||||
enabled = !feedEntry.isArchived,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp, end = 5.dp)
|
||||
.weight(6f)
|
||||
) {
|
||||
if (!feedEntry.isArchived) {
|
||||
continueWithTask()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun FeedEntryPreview() {
|
||||
FeedEntry(
|
||||
continueWithTask = {},
|
||||
feedEntry = FeedEntry(
|
||||
argb_color = 0xFFFFD200,
|
||||
subJectName = "Test Subject",
|
||||
taskName = "Test Task",
|
||||
totalStudyTime = 20,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun FeedEntryOverflowPreview() {
|
||||
FeedEntry(
|
||||
continueWithTask = {},
|
||||
feedEntry = FeedEntry(
|
||||
argb_color = 0xFFFFD200,
|
||||
subJectName = "Test Subject",
|
||||
taskName = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkk",
|
||||
totalStudyTime = 20,
|
||||
)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package be.ugent.sel.studeez.common.composable.feed
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||
|
||||
sealed interface FeedUiState {
|
||||
object Loading : FeedUiState
|
||||
data class Succes(val feedEntries: Map<String, List<FeedEntry>>) : FeedUiState
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package be.ugent.sel.studeez.common.composable.feed
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import be.ugent.sel.studeez.data.SelectedTask
|
||||
import be.ugent.sel.studeez.domain.FeedDAO
|
||||
import be.ugent.sel.studeez.domain.LogService
|
||||
import be.ugent.sel.studeez.domain.TaskDAO
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class FeedViewModel @Inject constructor(
|
||||
feedDAO: FeedDAO,
|
||||
private val taskDAO: TaskDAO,
|
||||
private val selectedTask: SelectedTask,
|
||||
logService: LogService
|
||||
) : StudeezViewModel(logService) {
|
||||
|
||||
val uiState: StateFlow<FeedUiState> = feedDAO.getFeedEntries()
|
||||
.map { FeedUiState.Succes(it) }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
initialValue = FeedUiState.Loading,
|
||||
started = SharingStarted.Eagerly,
|
||||
)
|
||||
|
||||
fun continueTask(open: (String) -> Unit, subjectId: String, taskId: String) {
|
||||
viewModelScope.launch {
|
||||
val task = taskDAO.getTask(subjectId, taskId)
|
||||
selectedTask.set(task)
|
||||
open(StudeezDestinations.TIMER_SELECTION_SCREEN)
|
||||
}
|
||||
}
|
||||
|
||||
fun onEmptyFeedHelp(open: (String) -> Unit) {
|
||||
open(StudeezDestinations.ADD_SUBJECT_FORM)
|
||||
}
|
||||
}
|
|
@ -1,13 +1,7 @@
|
|||
package be.ugent.sel.studeez.common.composable.tasks
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Card
|
||||
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.filled.List
|
||||
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.draw.clip
|
||||
|
@ -24,16 +20,20 @@ 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.R.string as AppText
|
||||
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.timer_functional.HoursMinutesSeconds
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun SubjectEntry(
|
||||
subject: Subject,
|
||||
onViewSubject: () -> Unit,
|
||||
getStudyTime: () -> Flow<Int>,
|
||||
) {
|
||||
val studytime by getStudyTime().collectAsState(initial = 0)
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -70,7 +70,7 @@ fun SubjectEntry(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = HoursMinutesSeconds(subject.time).toString(),
|
||||
text = HoursMinutesSeconds(studytime).toString(),
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
@ -80,7 +80,7 @@ fun SubjectEntry(
|
|||
imageVector = Icons.Default.List,
|
||||
contentDescription = stringResource(id = AppText.tasks)
|
||||
)
|
||||
Text(text = "0/0") // TODO
|
||||
Text(text = "${subject.taskCompletedCount}/${subject.taskCount}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,9 +104,12 @@ fun SubjectEntryPreview() {
|
|||
subject = Subject(
|
||||
name = "Test Subject",
|
||||
argb_color = 0xFFFFD200,
|
||||
time = 60
|
||||
taskCount = 5,
|
||||
taskCompletedCount = 2,
|
||||
),
|
||||
) {}
|
||||
onViewSubject = {},
|
||||
getStudyTime = { flowOf() }
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
@ -116,7 +119,8 @@ fun OverflowSubjectEntryPreview() {
|
|||
subject = Subject(
|
||||
name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt",
|
||||
argb_color = 0xFFFFD200,
|
||||
time = 60
|
||||
),
|
||||
) {}
|
||||
onViewSubject = {},
|
||||
getStudyTime = { flowOf() }
|
||||
)
|
||||
}
|
|
@ -1,17 +1,7 @@
|
|||
package be.ugent.sel.studeez.common.composable.tasks
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -31,7 +21,8 @@ import be.ugent.sel.studeez.resources
|
|||
fun TaskEntry(
|
||||
task: Task,
|
||||
onCheckTask: (Boolean) -> Unit,
|
||||
onDeleteTask: () -> Unit,
|
||||
onArchiveTask: () -> Unit,
|
||||
onStartTask: () -> Unit
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
|
@ -80,7 +71,7 @@ fun TaskEntry(
|
|||
Box(modifier = Modifier.weight(7f)) {
|
||||
if (task.completed) {
|
||||
IconButton(
|
||||
onClick = onDeleteTask,
|
||||
onClick = onArchiveTask,
|
||||
modifier = Modifier
|
||||
.padding(start = 20.dp)
|
||||
) {
|
||||
|
@ -95,6 +86,7 @@ fun TaskEntry(
|
|||
modifier = Modifier
|
||||
.padding(end = 5.dp),
|
||||
) {
|
||||
onStartTask()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +102,7 @@ fun TaskEntryPreview() {
|
|||
name = "Test Task",
|
||||
completed = false,
|
||||
),
|
||||
{}, {},
|
||||
{}, {}, {}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -122,7 +114,7 @@ fun CompletedTaskEntryPreview() {
|
|||
name = "Test Task",
|
||||
completed = true,
|
||||
),
|
||||
{}, {},
|
||||
{}, {}, {},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -134,6 +126,6 @@ fun OverflowTaskEntryPreview() {
|
|||
name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk",
|
||||
completed = false,
|
||||
),
|
||||
{}, {},
|
||||
{}, {}, {}
|
||||
)
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
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.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
fun Modifier.textButton(): Modifier {
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
package be.ugent.sel.studeez.data
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
||||
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class EditTimerState @Inject constructor(){
|
||||
lateinit var timerInfo: TimerInfo
|
||||
}
|
45
app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt
Normal file
45
app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt
Normal file
|
@ -0,0 +1,45 @@
|
|||
package be.ugent.sel.studeez.data
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
||||
import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Used to cummunicate between viewmodels.
|
||||
*/
|
||||
abstract class SelectedState<T> {
|
||||
abstract var value: T
|
||||
operator fun invoke() = value
|
||||
fun set(newValue: T) {
|
||||
this.value = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class SelectedSessionReport @Inject constructor() : SelectedState<SessionReport>() {
|
||||
override lateinit var value: SessionReport
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class SelectedTask @Inject constructor() : SelectedState<Task>() {
|
||||
override lateinit var value: Task
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class SelectedTimer @Inject constructor() : SelectedState<FunctionalTimer>() {
|
||||
override lateinit var value: FunctionalTimer
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class SelectedSubject @Inject constructor() : SelectedState<Subject>() {
|
||||
override lateinit var value: Subject
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class SelectedTimerInfo @Inject constructor() : SelectedState<TimerInfo>() {
|
||||
override lateinit var value: TimerInfo
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package be.ugent.sel.studeez.data
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Used to communicate the selected subject from the subject overview other screens.
|
||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
||||
*/
|
||||
@Singleton
|
||||
class SelectedSubject @Inject constructor() {
|
||||
private lateinit var subject: Subject
|
||||
operator fun invoke() = subject
|
||||
fun set(subject: Subject) {
|
||||
this.subject = subject
|
||||
}
|
||||
|
||||
fun isSet() = this::subject.isInitialized
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package be.ugent.sel.studeez.data
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.task.Task
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Used to communicate the selected task from the task overview other screens.
|
||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
||||
*/
|
||||
@Singleton
|
||||
class SelectedTask @Inject constructor() {
|
||||
private lateinit var task: Task
|
||||
|
||||
operator fun invoke() = task
|
||||
fun set(task: Task) {
|
||||
this.task = task
|
||||
}
|
||||
|
||||
fun isSet() = this::task.isInitialized
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package be.ugent.sel.studeez.data
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Used to communicate the SelectedTimer from the selection screen to the session screen.
|
||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
||||
*/
|
||||
@Singleton
|
||||
class SelectedTimerState @Inject constructor(){
|
||||
var selectedTimer: FunctionalTimer? = null
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package be.ugent.sel.studeez.data
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Used to communicate the SelectedTimer from the selection screen to the session screen.
|
||||
* Because this is a singleton-class the view-models of both screens observe the same data.
|
||||
*/
|
||||
@Singleton
|
||||
class SessionReportState @Inject constructor(){
|
||||
var sessionReport: SessionReport? = null
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package be.ugent.sel.studeez.data.local.models
|
||||
|
||||
import com.google.firebase.Timestamp
|
||||
|
||||
data class FeedEntry(
|
||||
val argb_color: Long = 0,
|
||||
val subJectName: String = "",
|
||||
val taskName: String = "",
|
||||
val taskId: String = "", // Name of task is not unique
|
||||
val subjectId: String = "",
|
||||
val totalStudyTime: Int = 0,
|
||||
val endTime: Timestamp = Timestamp(0, 0),
|
||||
val isArchived: Boolean = false
|
||||
)
|
|
@ -6,5 +6,7 @@ import com.google.firebase.firestore.DocumentId
|
|||
data class SessionReport(
|
||||
@DocumentId val id: String = "",
|
||||
val studyTime: Int = 0,
|
||||
val endTime: Timestamp = Timestamp(0, 0)
|
||||
val endTime: Timestamp = Timestamp(0, 0),
|
||||
val taskId: String = "",
|
||||
val subjectId: String = ""
|
||||
)
|
|
@ -1,10 +1,22 @@
|
|||
package be.ugent.sel.studeez.data.local.models.task
|
||||
|
||||
import com.google.firebase.firestore.DocumentId
|
||||
import com.google.firebase.firestore.Exclude
|
||||
|
||||
data class Subject(
|
||||
@DocumentId val id: String = "",
|
||||
val name: String = "",
|
||||
val time: Int = 0,
|
||||
val argb_color: Long = 0,
|
||||
)
|
||||
var archived: Boolean = false,
|
||||
@get:Exclude @set:Exclude
|
||||
var taskCount: Int = 0,
|
||||
@get:Exclude @set:Exclude
|
||||
var taskCompletedCount: Int = 0,
|
||||
)
|
||||
|
||||
object SubjectDocument {
|
||||
const val id = "id"
|
||||
const val name = "name"
|
||||
const val archived = "archived"
|
||||
const val argb_color = "argb_color"
|
||||
}
|
|
@ -5,9 +5,10 @@ import com.google.firebase.firestore.DocumentId
|
|||
data class Task(
|
||||
@DocumentId val id: String = "",
|
||||
val name: String = "",
|
||||
val completed: Boolean = false,
|
||||
var completed: Boolean = false,
|
||||
val time: Int = 0,
|
||||
val subjectId: String = "",
|
||||
var archived: Boolean = false,
|
||||
)
|
||||
|
||||
object TaskDocument {
|
||||
|
@ -16,4 +17,5 @@ object TaskDocument {
|
|||
const val completed = "completed"
|
||||
const val time = "time"
|
||||
const val subjectId = "subjectId"
|
||||
const val archived = "archived"
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ package be.ugent.sel.studeez.data.local.models.timer_functional
|
|||
|
||||
class FunctionalPomodoroTimer(
|
||||
private var studyTime: Int,
|
||||
private var breakTime: Int, repeats: Int
|
||||
private var breakTime: Int,
|
||||
val repeats: Int
|
||||
) : FunctionalTimer(studyTime) {
|
||||
|
||||
var breaksRemaining = repeats
|
||||
|
|
|
@ -2,6 +2,7 @@ package be.ugent.sel.studeez.data.local.models.timer_functional
|
|||
|
||||
import be.ugent.sel.studeez.data.local.models.SessionReport
|
||||
import com.google.firebase.Timestamp
|
||||
import com.google.firebase.firestore.DocumentReference
|
||||
|
||||
abstract class FunctionalTimer(initialValue: Int) {
|
||||
var time: Time = Time(initialValue)
|
||||
|
@ -17,10 +18,12 @@ abstract class FunctionalTimer(initialValue: Int) {
|
|||
|
||||
abstract fun hasCurrentCountdownEnded(): Boolean
|
||||
|
||||
fun getSessionReport(): SessionReport {
|
||||
fun getSessionReport(subjectId: String, taskId: String): SessionReport {
|
||||
return SessionReport(
|
||||
studyTime = totalStudyTime,
|
||||
endTime = Timestamp.now()
|
||||
endTime = Timestamp.now(),
|
||||
taskId = taskId,
|
||||
subjectId = subjectId
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class PomodoroTimerInfo(
|
|||
description: String,
|
||||
var studyTime: Int,
|
||||
var breakTime: Int,
|
||||
val repeats: Int,
|
||||
var repeats: Int,
|
||||
id: String = ""
|
||||
): TimerInfo(id, name, description) {
|
||||
|
||||
|
|
|
@ -33,4 +33,7 @@ abstract class DatabaseModule {
|
|||
|
||||
@Binds
|
||||
abstract fun provideTaskDAO(impl: FireBaseTaskDAO): TaskDAO
|
||||
|
||||
@Binds
|
||||
abstract fun provideFeedDAO(impl: FirebaseFeedDAO): FeedDAO
|
||||
}
|
10
app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt
Normal file
10
app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt
Normal file
|
@ -0,0 +1,10 @@
|
|||
package be.ugent.sel.studeez.domain
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface FeedDAO {
|
||||
|
||||
fun getFeedEntries(): Flow<Map<String, List<FeedEntry>>>
|
||||
|
||||
}
|
|
@ -12,4 +12,10 @@ interface SubjectDAO {
|
|||
fun deleteSubject(oldSubject: 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?
|
||||
}
|
|
@ -14,5 +14,5 @@ interface TaskDAO {
|
|||
|
||||
fun deleteTask(oldTask: Task)
|
||||
|
||||
fun toggleTaskCompleted(task: Task, completed: Boolean)
|
||||
suspend fun getTask(subjectId: String, taskId: String): Task
|
||||
}
|
|
@ -1,23 +1,42 @@
|
|||
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.SubjectDocument
|
||||
import be.ugent.sel.studeez.domain.AccountDAO
|
||||
import be.ugent.sel.studeez.domain.SubjectDAO
|
||||
import be.ugent.sel.studeez.domain.TaskDAO
|
||||
import com.google.firebase.firestore.AggregateSource
|
||||
import com.google.firebase.firestore.CollectionReference
|
||||
import com.google.firebase.firestore.FirebaseFirestore
|
||||
import com.google.firebase.firestore.Query
|
||||
import com.google.firebase.firestore.ktx.snapshots
|
||||
import com.google.firebase.firestore.ktx.toObject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import javax.inject.Inject
|
||||
|
||||
class FireBaseSubjectDAO @Inject constructor(
|
||||
private val firestore: FirebaseFirestore,
|
||||
private val auth: AccountDAO,
|
||||
private val taskDAO: TaskDAO,
|
||||
) : SubjectDAO {
|
||||
override fun getSubjects(): Flow<List<Subject>> {
|
||||
return currentUserSubjectsCollection()
|
||||
.subjectNotArchived()
|
||||
.snapshots()
|
||||
.map { it.toObjects(Subject::class.java) }
|
||||
.map { subjects ->
|
||||
subjects.map { subject ->
|
||||
subject.taskCount = getTaskCount(subject)
|
||||
subject.taskCompletedCount = getCompletedTaskCount(subject)
|
||||
subject
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSubject(subjectId: String): Subject? {
|
||||
return currentUserSubjectsCollection().document(subjectId).get().await().toObject()
|
||||
}
|
||||
|
||||
override fun saveSubject(newSubject: Subject) {
|
||||
|
@ -32,8 +51,45 @@ class FireBaseSubjectDAO @Inject constructor(
|
|||
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 =
|
||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
||||
.document(auth.currentUserId)
|
||||
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
||||
|
||||
private fun subjectTasksCollection(subject: Subject): CollectionReference =
|
||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
||||
.document(auth.currentUserId)
|
||||
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
||||
.document(subject.id)
|
||||
.collection(FireBaseCollections.TASK_COLLECTION)
|
||||
|
||||
fun CollectionReference.subjectNotArchived(): Query =
|
||||
this.whereEqualTo(SubjectDocument.archived, false)
|
||||
|
||||
fun Query.subjectNotArchived(): Query =
|
||||
this.whereEqualTo(SubjectDocument.archived, false)
|
||||
}
|
|
@ -7,9 +7,12 @@ import be.ugent.sel.studeez.domain.AccountDAO
|
|||
import be.ugent.sel.studeez.domain.TaskDAO
|
||||
import com.google.firebase.firestore.CollectionReference
|
||||
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.toObject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import javax.inject.Inject
|
||||
|
||||
class FireBaseTaskDAO @Inject constructor(
|
||||
|
@ -18,32 +21,48 @@ class FireBaseTaskDAO @Inject constructor(
|
|||
) : TaskDAO {
|
||||
override fun getTasks(subject: Subject): Flow<List<Task>> {
|
||||
return selectedSubjectTasksCollection(subject.id)
|
||||
.taskNotArchived()
|
||||
.snapshots()
|
||||
.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) {
|
||||
selectedSubjectTasksCollection(newTask.subjectId).add(newTask)
|
||||
}
|
||||
|
||||
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) {
|
||||
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 =
|
||||
firestore.collection(FireBaseCollections.USER_COLLECTION)
|
||||
.document(auth.currentUserId)
|
||||
.collection(FireBaseCollections.SUBJECT_COLLECTION)
|
||||
.document(subjectId)
|
||||
.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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ object StudeezDestinations {
|
|||
// Studying flow
|
||||
const val TIMER_SELECTION_SCREEN = "timer_selection"
|
||||
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_RECAP = "session_recap"
|
||||
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,35 +5,45 @@ import androidx.compose.material.IconButton
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
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 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.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.ext.basicButton
|
||||
import be.ugent.sel.studeez.data.local.models.FeedEntry
|
||||
import be.ugent.sel.studeez.resources
|
||||
|
||||
@Composable
|
||||
fun HomeRoute(
|
||||
open: (String) -> Unit,
|
||||
viewModel: HomeViewModel,
|
||||
drawerActions: DrawerActions,
|
||||
navigationBarActions: NavigationBarActions,
|
||||
feedViewModel: FeedViewModel,
|
||||
) {
|
||||
val feedUiState by feedViewModel.uiState.collectAsState()
|
||||
HomeScreen(
|
||||
onStartSessionClick = { viewModel.onStartSessionClick(open) },
|
||||
drawerActions = drawerActions,
|
||||
open = open,
|
||||
navigationBarActions = navigationBarActions,
|
||||
feedUiState = feedUiState,
|
||||
continueTask = { subjectId, taskId -> feedViewModel.continueTask(open, subjectId, taskId) },
|
||||
onEmptyFeedHelp = { feedViewModel.onEmptyFeedHelp(open) }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
onStartSessionClick: () -> Unit,
|
||||
open: (String) -> Unit,
|
||||
drawerActions: DrawerActions,
|
||||
navigationBarActions: NavigationBarActions
|
||||
navigationBarActions: NavigationBarActions,
|
||||
feedUiState: FeedUiState,
|
||||
continueTask: (String, String) -> Unit,
|
||||
onEmptyFeedHelp: () -> Unit,
|
||||
) {
|
||||
PrimaryScreenTemplate(
|
||||
title = resources().getString(R.string.home),
|
||||
|
@ -41,9 +51,7 @@ fun HomeScreen(
|
|||
navigationBarActions = navigationBarActions,
|
||||
// TODO barAction = { FriendsAction() }
|
||||
) {
|
||||
BasicButton(R.string.start_session, Modifier.basicButton()) {
|
||||
onStartSessionClick()
|
||||
}
|
||||
Feed(feedUiState, continueTask, onEmptyFeedHelp)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,8 +69,40 @@ fun FriendsAction() {
|
|||
@Composable
|
||||
fun HomeScreenPreview() {
|
||||
HomeScreen(
|
||||
onStartSessionClick = {},
|
||||
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 = {}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
package be.ugent.sel.studeez.screens.session
|
||||
|
||||
import be.ugent.sel.studeez.data.SelectedTimerState
|
||||
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.SelectedTimer
|
||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
|
||||
import be.ugent.sel.studeez.domain.LogService
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||
|
@ -11,23 +12,21 @@ import javax.inject.Inject
|
|||
|
||||
@HiltViewModel
|
||||
class SessionViewModel @Inject constructor(
|
||||
private val selectedTimerState: SelectedTimerState,
|
||||
private val sessionReportState: SessionReportState,
|
||||
private val selectedTimer: SelectedTimer,
|
||||
private val sessionReport: SelectedSessionReport,
|
||||
private val selectedTask: SelectedTask,
|
||||
logService: LogService
|
||||
) : StudeezViewModel(logService) {
|
||||
|
||||
private val task : String = "No task selected" // placeholder for tasks implementation
|
||||
|
||||
fun getTimer() : FunctionalTimer {
|
||||
return selectedTimerState.selectedTimer!!
|
||||
fun getTimer(): FunctionalTimer {
|
||||
return selectedTimer()
|
||||
}
|
||||
|
||||
fun getTask(): String {
|
||||
return task
|
||||
return selectedTask().name
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -100,6 +100,8 @@ abstract class AbstractSessionScreen {
|
|||
fontSize = 30.sp
|
||||
)
|
||||
|
||||
MidSection()
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center, modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -126,6 +128,11 @@ abstract class AbstractSessionScreen {
|
|||
@Composable
|
||||
abstract fun motivationString(): String
|
||||
|
||||
@Composable
|
||||
open fun MidSection() {
|
||||
// Default has no midsection, unless overwritten.
|
||||
}
|
||||
|
||||
abstract fun callMediaPlayer()
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,20 @@
|
|||
package be.ugent.sel.studeez.screens.session.sessionScreens
|
||||
|
||||
import android.media.MediaPlayer
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import be.ugent.sel.studeez.R
|
||||
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
|
||||
import be.ugent.sel.studeez.resources
|
||||
|
@ -12,6 +25,37 @@ class BreakSessionScreen(
|
|||
private var mediaplayer: MediaPlayer?
|
||||
): 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
|
||||
override fun motivationString(): String {
|
||||
if (funPomoDoroTimer.isInBreak) {
|
||||
|
@ -22,11 +66,7 @@ class BreakSessionScreen(
|
|||
return resources().getString(AppText.state_done)
|
||||
}
|
||||
|
||||
return resources().getQuantityString(
|
||||
R.plurals.state_focus_remaining,
|
||||
funPomoDoroTimer.breaksRemaining,
|
||||
funPomoDoroTimer.breaksRemaining
|
||||
)
|
||||
return resources().getString(AppText.state_focus)
|
||||
}
|
||||
|
||||
override fun callMediaPlayer() {
|
||||
|
@ -42,4 +82,12 @@ class BreakSessionScreen(
|
|||
mediaplayer?.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun MidsectionPreview() {
|
||||
val funPomoDoroTimer = FunctionalPomodoroTimer(15, 60, 5)
|
||||
val breakSessionScreen = BreakSessionScreen(funPomoDoroTimer, MediaPlayer())
|
||||
breakSessionScreen.MidSection()
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
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.domain.LogService
|
||||
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.screens.StudeezViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
@ -11,19 +13,22 @@ import javax.inject.Inject
|
|||
|
||||
@HiltViewModel
|
||||
class SessionRecapViewModel @Inject constructor(
|
||||
sessionReportState: SessionReportState,
|
||||
private val selectedSessionReport: SelectedSessionReport,
|
||||
private val sessionDAO: SessionDAO,
|
||||
private val taskDAO: TaskDAO,
|
||||
private val selectedTask: SelectedTask,
|
||||
logService: LogService
|
||||
) : StudeezViewModel(logService) {
|
||||
|
||||
private val report: SessionReport = sessionReportState.sessionReport!!
|
||||
|
||||
fun getSessionReport(): SessionReport {
|
||||
return report
|
||||
return selectedSessionReport()
|
||||
}
|
||||
|
||||
fun saveSession(open: (String, String) -> Unit) {
|
||||
sessionDAO.saveSession(getSessionReport())
|
||||
val newTask =
|
||||
selectedTask().copy(time = selectedTask().time + selectedSessionReport().studyTime)
|
||||
taskDAO.updateTask(newTask)
|
||||
open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package be.ugent.sel.studeez.screens.subjects
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton
|
||||
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
|
||||
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
|
||||
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
||||
import be.ugent.sel.studeez.common.composable.tasks.SubjectEntry
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun SubjectRoute(
|
||||
open: (String) -> Unit,
|
||||
viewModel: SubjectViewModel,
|
||||
drawerActions: DrawerActions,
|
||||
navigationBarActions: NavigationBarActions,
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
SubjectScreen(
|
||||
drawerActions = drawerActions,
|
||||
navigationBarActions = navigationBarActions,
|
||||
onAddSubject = { viewModel.onAddSubject(open) },
|
||||
onViewSubject = { viewModel.onViewSubject(it, open) },
|
||||
getStudyTime = viewModel::getStudyTime,
|
||||
uiState,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SubjectScreen(
|
||||
drawerActions: DrawerActions,
|
||||
navigationBarActions: NavigationBarActions,
|
||||
onAddSubject: () -> Unit,
|
||||
onViewSubject: (Subject) -> Unit,
|
||||
getStudyTime: (Subject) -> Flow<Int>,
|
||||
uiState: SubjectUiState,
|
||||
) {
|
||||
PrimaryScreenTemplate(
|
||||
title = stringResource(AppText.my_subjects),
|
||||
drawerActions = drawerActions,
|
||||
navigationBarActions = navigationBarActions,
|
||||
barAction = {},
|
||||
) {
|
||||
when (uiState) {
|
||||
SubjectUiState.Loading -> Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
CircularProgressIndicator(color = MaterialTheme.colors.onBackground)
|
||||
}
|
||||
is SubjectUiState.Succes -> {
|
||||
Column(
|
||||
modifier = Modifier.padding(top = 5.dp)
|
||||
) {
|
||||
NewTaskSubjectButton(onClick = onAddSubject, AppText.new_subject)
|
||||
LazyColumn {
|
||||
items(uiState.subjects) {
|
||||
SubjectEntry(
|
||||
subject = it,
|
||||
onViewSubject = { onViewSubject(it) },
|
||||
getStudyTime = { getStudyTime(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SubjectScreenPreview() {
|
||||
SubjectScreen(
|
||||
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
|
||||
onAddSubject = {},
|
||||
onViewSubject = {},
|
||||
getStudyTime = { flowOf() },
|
||||
uiState = SubjectUiState.Succes(
|
||||
listOf(
|
||||
Subject(
|
||||
name = "Test Subject",
|
||||
argb_color = 0xFFFFD200,
|
||||
taskCount = 5, taskCompletedCount = 2,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SubjectScreenLoadingPreview() {
|
||||
SubjectScreen(
|
||||
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
|
||||
onAddSubject = {},
|
||||
onViewSubject = {},
|
||||
getStudyTime = { flowOf() },
|
||||
uiState = SubjectUiState.Loading
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package be.ugent.sel.studeez.screens.subjects
|
||||
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
|
||||
sealed interface SubjectUiState {
|
||||
object Loading : SubjectUiState
|
||||
data class Succes(val subjects: List<Subject>) : SubjectUiState
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package be.ugent.sel.studeez.screens.tasks
|
||||
package be.ugent.sel.studeez.screens.subjects
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import be.ugent.sel.studeez.data.SelectedSubject
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
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.screens.StudeezViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
@ -16,12 +17,21 @@ class SubjectViewModel @Inject constructor(
|
|||
private val selectedSubject: SelectedSubject,
|
||||
logService: 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)
|
||||
}
|
||||
|
||||
fun getSubjects(): Flow<List<Subject>> {
|
||||
return subjectDAO.getSubjects()
|
||||
fun getStudyTime(subject: Subject): Flow<Int> {
|
||||
return subjectDAO.getStudyTime(subject)
|
||||
}
|
||||
|
||||
fun onViewSubject(subject: Subject, open: (String) -> Unit) {
|
|
@ -1,4 +1,4 @@
|
|||
package be.ugent.sel.studeez.screens.tasks.forms
|
||||
package be.ugent.sel.studeez.screens.subjects.form
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -19,10 +19,10 @@ import be.ugent.sel.studeez.resources
|
|||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun SubjectAddRoute(
|
||||
fun SubjectCreateRoute(
|
||||
goBack: () -> Unit,
|
||||
openAndPopUp: (String, String) -> Unit,
|
||||
viewModel: SubjectFormViewModel,
|
||||
viewModel: SubjectCreateFormViewModel,
|
||||
) {
|
||||
val uiState by viewModel.uiState
|
||||
SubjectForm(
|
||||
|
@ -39,7 +39,7 @@ fun SubjectAddRoute(
|
|||
fun SubjectEditRoute(
|
||||
goBack: () -> Unit,
|
||||
openAndPopUp: (String, String) -> Unit,
|
||||
viewModel: SubjectFormViewModel,
|
||||
viewModel: SubjectEditFormViewModel,
|
||||
) {
|
||||
val uiState by viewModel.uiState
|
||||
SubjectForm(
|
|
@ -1,4 +1,4 @@
|
|||
package be.ugent.sel.studeez.screens.tasks.forms
|
||||
package be.ugent.sel.studeez.screens.subjects.form
|
||||
|
||||
data class SubjectFormUiState(
|
||||
val name: String = "",
|
|
@ -1,5 +1,6 @@
|
|||
package be.ugent.sel.studeez.screens.tasks.forms
|
||||
package be.ugent.sel.studeez.screens.subjects.form
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import be.ugent.sel.studeez.data.SelectedSubject
|
||||
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 javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SubjectFormViewModel @Inject constructor(
|
||||
private val subjectDAO: SubjectDAO,
|
||||
private val selectedSubject: SelectedSubject,
|
||||
abstract class SubjectFormViewModel(
|
||||
protected val subjectDAO: SubjectDAO,
|
||||
protected val selectedSubject: SelectedSubject,
|
||||
logService: LogService,
|
||||
) : StudeezViewModel(logService) {
|
||||
var uiState = mutableStateOf(
|
||||
if (selectedSubject.isSet()) SubjectFormUiState(
|
||||
name = selectedSubject().name,
|
||||
color = selectedSubject().argb_color
|
||||
)
|
||||
else SubjectFormUiState()
|
||||
)
|
||||
private set
|
||||
abstract val uiState: MutableState<SubjectFormUiState>
|
||||
|
||||
private val name: String
|
||||
protected val name: String
|
||||
get() = uiState.value.name
|
||||
|
||||
private val color: Long
|
||||
protected val color: Long
|
||||
get() = uiState.value.color
|
||||
|
||||
fun onNameChange(newValue: String) {
|
||||
|
@ -38,11 +31,15 @@ class SubjectFormViewModel @Inject constructor(
|
|||
fun onColorChange(newValue: Long) {
|
||||
uiState.value = uiState.value.copy(color = newValue)
|
||||
}
|
||||
}
|
||||
|
||||
fun onDelete(openAndPopUp: (String, String) -> Unit) {
|
||||
subjectDAO.deleteSubject(selectedSubject())
|
||||
openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM)
|
||||
}
|
||||
@HiltViewModel
|
||||
class SubjectCreateFormViewModel @Inject constructor(
|
||||
subjectDAO: SubjectDAO,
|
||||
selectedSubject: SelectedSubject,
|
||||
logService: LogService,
|
||||
) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) {
|
||||
override val uiState = mutableStateOf(SubjectFormUiState())
|
||||
|
||||
fun onCreate(openAndPopUp: (String, String) -> Unit) {
|
||||
val newSubject = Subject(
|
||||
|
@ -57,6 +54,25 @@ class SubjectFormViewModel @Inject constructor(
|
|||
// open(StudeezDestinations.TASKS_SCREEN)
|
||||
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) {
|
||||
val newSubject = selectedSubject().copy(
|
|
@ -1,80 +0,0 @@
|
|||
package be.ugent.sel.studeez.screens.tasks
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton
|
||||
import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate
|
||||
import be.ugent.sel.studeez.common.composable.drawer.DrawerActions
|
||||
import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions
|
||||
import be.ugent.sel.studeez.common.composable.tasks.SubjectEntry
|
||||
import be.ugent.sel.studeez.data.local.models.task.Subject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun SubjectRoute(
|
||||
open: (String) -> Unit,
|
||||
viewModel: SubjectViewModel,
|
||||
drawerActions: DrawerActions,
|
||||
navigationBarActions: NavigationBarActions,
|
||||
) {
|
||||
SubjectScreen(
|
||||
drawerActions = drawerActions,
|
||||
navigationBarActions = navigationBarActions,
|
||||
addSubject = { viewModel.addSubject(open) },
|
||||
getSubjects = viewModel::getSubjects,
|
||||
onViewSubject = { viewModel.onViewSubject(it, open) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SubjectScreen(
|
||||
drawerActions: DrawerActions,
|
||||
navigationBarActions: NavigationBarActions,
|
||||
addSubject: () -> Unit,
|
||||
getSubjects: () -> Flow<List<Subject>>,
|
||||
onViewSubject: (Subject) -> Unit,
|
||||
) {
|
||||
PrimaryScreenTemplate(
|
||||
title = stringResource(AppText.my_subjects),
|
||||
drawerActions = drawerActions,
|
||||
navigationBarActions = navigationBarActions,
|
||||
barAction = {},
|
||||
) {
|
||||
val subjects = getSubjects().collectAsState(initial = emptyList())
|
||||
Column(
|
||||
modifier = Modifier.padding(top = 5.dp)
|
||||
) {
|
||||
LazyColumn {
|
||||
items(subjects.value) {
|
||||
SubjectEntry(
|
||||
subject = it,
|
||||
onViewSubject = { onViewSubject(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
NewTaskSubjectButton(onClick = addSubject, AppText.new_subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SubjectScreenPreview() {
|
||||
SubjectScreen(
|
||||
drawerActions = DrawerActions({}, {}, {}, {}, {}),
|
||||
navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}),
|
||||
addSubject = {},
|
||||
getSubjects = { flowOf() },
|
||||
onViewSubject = {},
|
||||
)
|
||||
}
|
|
@ -27,9 +27,10 @@ data class TaskActions(
|
|||
val addTask: () -> Unit,
|
||||
val getSubject: () -> Subject,
|
||||
val getTasks: () -> Flow<List<Task>>,
|
||||
val deleteTask: (Task) -> Unit,
|
||||
val onCheckTask: (Task, Boolean) -> Unit,
|
||||
val editSubject: () -> Unit,
|
||||
val startTask: (Task) -> Unit,
|
||||
val archiveTask: (Task) -> Unit,
|
||||
)
|
||||
|
||||
fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions {
|
||||
|
@ -37,9 +38,10 @@ fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskAction
|
|||
addTask = { viewModel.addTask(open) },
|
||||
getTasks = viewModel::getTasks,
|
||||
getSubject = viewModel::getSelectedSubject,
|
||||
deleteTask = viewModel::deleteTask,
|
||||
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(
|
||||
modifier = Modifier.padding(top = 5.dp)
|
||||
) {
|
||||
NewTaskSubjectButton(onClick = taskActions.addTask, AppText.new_task)
|
||||
LazyColumn {
|
||||
items(tasks.value) {
|
||||
items(tasks.value.filter { !it.completed }) {
|
||||
TaskEntry(
|
||||
task = it,
|
||||
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") },
|
||||
{ flowOf() },
|
||||
{},
|
||||
{ _, _ -> run {} },
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
package be.ugent.sel.studeez.screens.tasks
|
||||
|
||||
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.Task
|
||||
import be.ugent.sel.studeez.domain.LogService
|
||||
import be.ugent.sel.studeez.domain.SubjectDAO
|
||||
import be.ugent.sel.studeez.domain.TaskDAO
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||
|
@ -15,8 +15,8 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class TaskViewModel @Inject constructor(
|
||||
private val taskDAO: TaskDAO,
|
||||
private val subjectDAO: SubjectDAO,
|
||||
private val selectedSubject: SelectedSubject,
|
||||
private val selectedTask: SelectedTask,
|
||||
logService: LogService,
|
||||
) : StudeezViewModel(logService) {
|
||||
fun addTask(open: (String) -> Unit) {
|
||||
|
@ -27,11 +27,6 @@ class TaskViewModel @Inject constructor(
|
|||
return taskDAO.getTasks(selectedSubject())
|
||||
}
|
||||
|
||||
fun deleteSubject(open: (String) -> Unit) {
|
||||
subjectDAO.deleteSubject(selectedSubject())
|
||||
open(StudeezDestinations.SUBJECT_SCREEN)
|
||||
}
|
||||
|
||||
fun getSelectedSubject(): Subject {
|
||||
return selectedSubject()
|
||||
}
|
||||
|
@ -40,11 +35,20 @@ class TaskViewModel @Inject constructor(
|
|||
taskDAO.deleteTask(task)
|
||||
}
|
||||
|
||||
fun archiveTask(task: Task) {
|
||||
taskDAO.updateTask(task.copy(archived = true))
|
||||
}
|
||||
|
||||
fun toggleTaskCompleted(task: Task, completed: Boolean) {
|
||||
taskDAO.toggleTaskCompleted(task, completed)
|
||||
taskDAO.updateTask(task.copy(completed = completed))
|
||||
}
|
||||
|
||||
fun editSubject(open: (String) -> Unit) {
|
||||
open(StudeezDestinations.EDIT_SUBJECT_FORM)
|
||||
}
|
||||
|
||||
fun startTask(task: Task, open: (String) -> Unit) {
|
||||
selectedTask.set(task)
|
||||
open(StudeezDestinations.TIMER_SELECTION_SCREEN)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package be.ugent.sel.studeez.screens.tasks.forms
|
||||
package be.ugent.sel.studeez.screens.tasks.form
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -18,10 +18,10 @@ import be.ugent.sel.studeez.resources
|
|||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
@Composable
|
||||
fun TaskAddRoute(
|
||||
fun TaskCreateRoute(
|
||||
goBack: () -> Unit,
|
||||
openAndPopUp: (String, String) -> Unit,
|
||||
viewModel: TaskFormViewModel,
|
||||
viewModel: TaskCreateFormViewModel,
|
||||
) {
|
||||
val uiState by viewModel.uiState
|
||||
TaskForm(
|
||||
|
@ -37,7 +37,7 @@ fun TaskAddRoute(
|
|||
fun TaskEditRoute(
|
||||
goBack: () -> Unit,
|
||||
openAndPopUp: (String, String) -> Unit,
|
||||
viewModel: TaskFormViewModel,
|
||||
viewModel: TaskEditFormViewModel,
|
||||
) {
|
||||
val uiState by viewModel.uiState
|
||||
TaskForm(
|
|
@ -1,4 +1,4 @@
|
|||
package be.ugent.sel.studeez.screens.tasks.forms
|
||||
package be.ugent.sel.studeez.screens.tasks.form
|
||||
|
||||
data class TaskFormUiState(
|
||||
val name: String = "",
|
|
@ -1,5 +1,6 @@
|
|||
package be.ugent.sel.studeez.screens.tasks.forms
|
||||
package be.ugent.sel.studeez.screens.tasks.form
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import be.ugent.sel.studeez.data.SelectedSubject
|
||||
import be.ugent.sel.studeez.data.SelectedTask
|
||||
|
@ -11,39 +12,55 @@ import be.ugent.sel.studeez.screens.StudeezViewModel
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class TaskFormViewModel @Inject constructor(
|
||||
private val taskDAO: TaskDAO,
|
||||
private val selectedSubject: SelectedSubject,
|
||||
private val selectedTask: SelectedTask,
|
||||
abstract class TaskFormViewModel(
|
||||
protected val taskDAO: TaskDAO,
|
||||
protected val selectedSubject: SelectedSubject,
|
||||
protected val selectedTask: SelectedTask,
|
||||
logService: LogService,
|
||||
) : StudeezViewModel(logService) {
|
||||
var uiState = mutableStateOf(
|
||||
if (selectedTask.isSet()) TaskFormUiState(selectedTask().name) else TaskFormUiState()
|
||||
)
|
||||
private set
|
||||
abstract val uiState: MutableState<TaskFormUiState>
|
||||
|
||||
private val name: String
|
||||
protected val name: String
|
||||
get() = uiState.value.name
|
||||
|
||||
fun onNameChange(newValue: String) {
|
||||
uiState.value = uiState.value.copy(name = newValue)
|
||||
}
|
||||
}
|
||||
|
||||
fun onDelete(openAndPopUp: (String, String) -> Unit) {
|
||||
taskDAO.deleteTask(selectedTask())
|
||||
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM)
|
||||
}
|
||||
@HiltViewModel
|
||||
class TaskCreateFormViewModel @Inject constructor(
|
||||
taskDAO: TaskDAO,
|
||||
selectedSubject: SelectedSubject,
|
||||
selectedTask: SelectedTask,
|
||||
logService: LogService,
|
||||
) : TaskFormViewModel(taskDAO, selectedSubject, selectedTask, logService) {
|
||||
override val uiState = mutableStateOf(TaskFormUiState())
|
||||
|
||||
fun onCreate(openAndPopUp: (String, String) -> Unit) {
|
||||
val newTask = Task(name = name, subjectId = selectedSubject().id)
|
||||
taskDAO.saveTask(newTask)
|
||||
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) {
|
||||
val newTask = Task(name = name)
|
||||
val newTask = selectedTask().copy(name = name)
|
||||
taskDAO.updateTask(newTask)
|
||||
openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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.domain.LogService
|
||||
import be.ugent.sel.studeez.domain.TimerDAO
|
||||
|
@ -9,21 +9,22 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class TimerAddViewModel @Inject constructor(
|
||||
private val editTimerState: EditTimerState,
|
||||
class TimerFormViewModel @Inject constructor(
|
||||
private val selectedTimerInfo: SelectedTimerInfo,
|
||||
private val timerDAO: TimerDAO,
|
||||
logService: LogService
|
||||
) : StudeezViewModel(logService) {
|
||||
|
||||
private val timerInfo: TimerInfo = editTimerState.timerInfo
|
||||
|
||||
fun getTimerInfo(): TimerInfo {
|
||||
return timerInfo
|
||||
return selectedTimerInfo()
|
||||
}
|
||||
|
||||
fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
|
||||
fun editTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
|
||||
timerDAO.updateTimer(timerInfo)
|
||||
goBack()
|
||||
}
|
||||
|
||||
fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) {
|
||||
timerDAO.saveTimer(timerInfo)
|
||||
goBack()
|
||||
}
|
||||
}
|
|
@ -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.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.ext.basicButton
|
||||
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
|
||||
operator fun invoke(onSaveClick: (TimerInfo) -> Unit) {
|
||||
|
@ -50,7 +51,7 @@ abstract class AbstractTimerEditScreen(private val timerInfo: TimerInfo) {
|
|||
LabelledInputField(
|
||||
value = description,
|
||||
onNewValue = { description = it },
|
||||
label = R.string.description,
|
||||
label = AppText.description,
|
||||
singleLine = false
|
||||
)
|
||||
|
|
@ -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.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
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.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.ui.theme.StudeezTheme
|
||||
import be.ugent.sel.studeez.R.string as AppText
|
||||
|
||||
class BreakTimerEditScreen(
|
||||
|
||||
class BreakTimerFormScreen(
|
||||
private val breakTimerInfo: PomodoroTimerInfo
|
||||
): AbstractTimerEditScreen(breakTimerInfo) {
|
||||
): AbstractTimerFormScreen(breakTimerInfo) {
|
||||
|
||||
@Composable
|
||||
override fun ExtraFields() {
|
||||
|
@ -26,8 +25,18 @@ class BreakTimerEditScreen(
|
|||
TimePickerCard(R.string.breakTime, 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
|
||||
|
@ -41,6 +50,6 @@ fun BreakEditScreenPreview() {
|
|||
5
|
||||
)
|
||||
StudeezTheme {
|
||||
BreakTimerEditScreen(pomodoroTimerInfo).invoke(onSaveClick = {})
|
||||
BreakTimerFormScreen(pomodoroTimerInfo).invoke(onSaveClick = {})
|
||||
}
|
||||
}
|
|
@ -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.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.R.string as AppText
|
||||
|
||||
class CustomTimerEditScreen(
|
||||
class CustomTimerFormScreen(
|
||||
private val customTimerInfo: CustomTimerInfo
|
||||
): AbstractTimerEditScreen(customTimerInfo) {
|
||||
): AbstractTimerFormScreen(customTimerInfo) {
|
||||
|
||||
@Composable
|
||||
override fun ExtraFields() {
|
||||
|
@ -29,6 +29,6 @@ class CustomTimerEditScreen(
|
|||
fun CustomEditScreenPreview() {
|
||||
val customTimerInfo = CustomTimerInfo("custom", "my description", 25)
|
||||
StudeezTheme {
|
||||
CustomTimerEditScreen(customTimerInfo).invoke(onSaveClick = {})
|
||||
CustomTimerFormScreen(customTimerInfo).invoke(onSaveClick = {})
|
||||
}
|
||||
}
|
|
@ -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.ui.tooling.preview.Preview
|
||||
import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo
|
||||
import be.ugent.sel.studeez.ui.theme.StudeezTheme
|
||||
|
||||
class EndlessTimerEditScreen(
|
||||
class EndlessTimerFormScreen(
|
||||
endlessTimerInfo: EndlessTimerInfo
|
||||
): AbstractTimerEditScreen(endlessTimerInfo) {
|
||||
): AbstractTimerFormScreen(endlessTimerInfo) {
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
@ -18,6 +18,6 @@ fun EndlessEditScreenPreview() {
|
|||
"My endless timer description",
|
||||
)
|
||||
StudeezTheme {
|
||||
EndlessTimerEditScreen(endlessTimerInfo).invoke(onSaveClick = {})
|
||||
EndlessTimerFormScreen(endlessTimerInfo).invoke(onSaveClick = {})
|
||||
}
|
||||
}
|
|
@ -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.fillMaxWidth
|
||||
|
@ -7,16 +7,18 @@ 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.hilt.navigation.compose.hiltViewModel
|
||||
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
|
||||
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.BREAK
|
||||
import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.ENDLESS
|
||||
|
||||
val defaultTimerInfo: Map<TimerType, TimerInfo> = mapOf(
|
||||
CUSTOM to CustomTimerInfo("", "", 0),
|
||||
BREAK to PomodoroTimerInfo("", "", 0, 0, 0),
|
||||
BREAK to PomodoroTimerInfo("", "", 0, 0, 1),
|
||||
ENDLESS to EndlessTimerInfo("", ""),
|
||||
)
|
||||
|
||||
|
@ -28,13 +30,14 @@ fun TimerTypeSelectScreen(
|
|||
viewModel: TimerTypeSelectViewModel = hiltViewModel()
|
||||
) {
|
||||
|
||||
SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) {
|
||||
SecondaryScreenTemplate(title = stringResource(id = AppText.timer_type_select), popUp = popUp) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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.domain.LogService
|
||||
import be.ugent.sel.studeez.domain.TimerDAO
|
||||
import be.ugent.sel.studeez.navigation.StudeezDestinations
|
||||
import be.ugent.sel.studeez.screens.StudeezViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class TimerTypeSelectViewModel @Inject constructor(
|
||||
private val editTimerState: EditTimerState,
|
||||
private val timerDAO: TimerDAO,
|
||||
private val selectedTimerInfo: SelectedTimerInfo,
|
||||
logService: LogService
|
||||
) : StudeezViewModel(logService) {
|
||||
|
||||
|
||||
fun onTimerTypeChosen(timerInfo: TimerInfo, open: (String) -> Unit) {
|
||||
editTimerState.timerInfo = timerInfo
|
||||
open(StudeezDestinations.TIMER_EDIT_SCREEN)
|
||||
selectedTimerInfo.set(timerInfo)
|
||||
open(StudeezDestinations.ADD_TIMER_SCREEN)
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ data class TimerOverviewActions(
|
|||
val getUserTimers: () -> Flow<List<TimerInfo>>,
|
||||
val getDefaultTimers: () -> List<TimerInfo>,
|
||||
val onEditClick: (TimerInfo) -> Unit,
|
||||
val onAddClick: () -> Unit,
|
||||
val onAddClick: () -> Unit
|
||||
)
|
||||
|
||||
fun getTimerOverviewActions(
|
||||
|
@ -36,7 +36,7 @@ fun getTimerOverviewActions(
|
|||
getUserTimers = viewModel::getUserTimers,
|
||||
getDefaultTimers = viewModel::getDefaultTimers,
|
||||
onEditClick = { viewModel.update(it, open) },
|
||||
onAddClick = { viewModel.create(open) }
|
||||
onAddClick = { viewModel.onAddClick(open) }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -48,14 +48,14 @@ fun TimerOverviewRoute(
|
|||
) {
|
||||
TimerOverviewScreen(
|
||||
timerOverviewActions = getTimerOverviewActions(viewModel, open),
|
||||
drawerActions = drawerActions
|
||||
drawerActions = drawerActions,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TimerOverviewScreen(
|
||||
timerOverviewActions: TimerOverviewActions,
|
||||
drawerActions: DrawerActions
|
||||
drawerActions: DrawerActions,
|
||||
) {
|
||||
|
||||
val timers = timerOverviewActions.getUserTimers().collectAsState(initial = emptyList())
|
||||
|
@ -82,12 +82,13 @@ fun TimerOverviewScreen(
|
|||
items(timers.value) { timerInfo ->
|
||||
TimerEntry(
|
||||
timerInfo = timerInfo,
|
||||
) {
|
||||
StealthButton(
|
||||
text = R.string.edit,
|
||||
onClick = { timerOverviewActions.onEditClick(timerInfo) }
|
||||
)
|
||||
}
|
||||
rightButton = {
|
||||
StealthButton(
|
||||
text = R.string.edit,
|
||||
onClick = { timerOverviewActions.onEditClick(timerInfo) }
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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.domain.ConfigurationService
|
||||
import be.ugent.sel.studeez.domain.LogService
|
||||
|
@ -15,11 +15,11 @@ import javax.inject.Inject
|
|||
class TimerOverviewViewModel @Inject constructor(
|
||||
private val configurationService: ConfigurationService,
|
||||
private val timerDAO: TimerDAO,
|
||||
private val editTimerState: EditTimerState,
|
||||
private val selectedTimerInfo: SelectedTimerInfo,
|
||||
logService: LogService
|
||||
) : StudeezViewModel(logService) {
|
||||
|
||||
fun getUserTimers() : Flow<List<TimerInfo>> {
|
||||
fun getUserTimers(): Flow<List<TimerInfo>> {
|
||||
return timerDAO.getUserTimers()
|
||||
}
|
||||
|
||||
|
@ -27,16 +27,16 @@ class TimerOverviewViewModel @Inject constructor(
|
|||
return configurationService.getDefaultTimers()
|
||||
}
|
||||
|
||||
fun update(timerInfo: TimerInfo, open: (String) -> Unit) {
|
||||
editTimerState.timerInfo = timerInfo
|
||||
fun update(timerInfo: TimerInfo, open: (String) -> Unit) {
|
||||
selectedTimerInfo.set(timerInfo)
|
||||
open(StudeezDestinations.TIMER_EDIT_SCREEN)
|
||||
}
|
||||
|
||||
fun create(open: (String) -> Unit) {
|
||||
open(StudeezDestinations.ADD_TIMER_SCREEN)
|
||||
fun onAddClick(open: (String) -> Unit) {
|
||||
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)
|
||||
|
||||
|
|
|
@ -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",
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
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.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
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.SecondaryScreenTemplate
|
||||
import be.ugent.sel.studeez.common.composable.StealthButton
|
||||
|
@ -99,7 +102,10 @@ fun CustomTimerEntry(
|
|||
)
|
||||
},
|
||||
rightButton = {
|
||||
TimePickerButton(initialSeconds = hms.getTotalSeconds()) { chosenTime ->
|
||||
TimePickerButton(
|
||||
initialSeconds = hms.getTotalSeconds(),
|
||||
modifier = Modifier.padding(horizontal = 5.dp)
|
||||
) { chosenTime ->
|
||||
timerInfo.studyTime = chosenTime
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package be.ugent.sel.studeez.screens.timer_selection
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import be.ugent.sel.studeez.data.SelectedTimerState
|
||||
import be.ugent.sel.studeez.data.SelectedTimer
|
||||
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.domain.LogService
|
||||
import be.ugent.sel.studeez.domain.TimerDAO
|
||||
|
@ -17,18 +16,20 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class TimerSelectionViewModel @Inject constructor(
|
||||
private val timerDAO: TimerDAO,
|
||||
private val selectedTimerState: SelectedTimerState,
|
||||
private val selectedTimer: SelectedTimer,
|
||||
logService: 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()
|
||||
}
|
||||
|
||||
fun startSession(open: (String) -> Unit, timerInfo: TimerInfo) {
|
||||
selectedTimerState.selectedTimer = timerInfo.getFunctionalTimer()
|
||||
selectedTimer.set(timerInfo.getFunctionalTimer())
|
||||
open(StudeezDestinations.SESSION_SCREEN)
|
||||
}
|
||||
}
|
|
@ -29,6 +29,12 @@
|
|||
<string name="home">Home</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 -->
|
||||
<string name="tasks">Tasks</string>
|
||||
<string name="task">Task</string>
|
||||
|
@ -131,8 +137,13 @@
|
|||
<string name="addTimer_studytime_2">" minutes of studytime"</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-->
|
||||
<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="studyTime">Study Time</string>
|
||||
<string name="breakTime">Break Time</string>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package be.ugent.sel.studeez.timer_functional
|
||||
|
||||
import android.media.MediaPlayer
|
||||
import be.ugent.sel.studeez.data.SelectedTimerState
|
||||
import be.ugent.sel.studeez.data.SessionReportState
|
||||
import be.ugent.sel.studeez.data.SelectedSessionReport
|
||||
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.FunctionalEndlessTimer
|
||||
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.SessionViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
@ -18,14 +20,14 @@ import org.mockito.kotlin.mock
|
|||
|
||||
@ExperimentalCoroutinesApi
|
||||
class InvisibleSessionManagerTest {
|
||||
private var timerState: SelectedTimerState = SelectedTimerState()
|
||||
private var selectedTimer: SelectedTimer = SelectedTimer()
|
||||
private lateinit var viewModel: SessionViewModel
|
||||
private var mediaPlayer: MediaPlayer = mock()
|
||||
|
||||
@Test
|
||||
fun InvisibleEndlessTimerTest() = runTest {
|
||||
timerState.selectedTimer = FunctionalEndlessTimer()
|
||||
viewModel = SessionViewModel(timerState, SessionReportState(), mock())
|
||||
selectedTimer.set(FunctionalEndlessTimer())
|
||||
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl())
|
||||
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
|
||||
|
||||
val test = launch {
|
||||
|
@ -46,8 +48,8 @@ class InvisibleSessionManagerTest {
|
|||
val studyTime = 10
|
||||
val breakTime = 5
|
||||
val repeats = 1
|
||||
timerState.selectedTimer = FunctionalPomodoroTimer(studyTime, breakTime, repeats)
|
||||
viewModel = SessionViewModel(timerState, SessionReportState(), mock())
|
||||
selectedTimer.set(FunctionalPomodoroTimer(studyTime, breakTime, repeats))
|
||||
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl())
|
||||
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
|
||||
|
||||
val test = launch {
|
||||
|
@ -79,8 +81,8 @@ class InvisibleSessionManagerTest {
|
|||
|
||||
@Test
|
||||
fun InvisibleCustomTimerTest() = runTest {
|
||||
timerState.selectedTimer = FunctionalCustomTimer(5)
|
||||
viewModel = SessionViewModel(timerState, SessionReportState(), mock())
|
||||
selectedTimer.set(FunctionalCustomTimer(5))
|
||||
viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl())
|
||||
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
|
||||
|
||||
val test = launch {
|
||||
|
|
|
@ -10,7 +10,7 @@ class TimeUnitTest {
|
|||
private val hours = 4
|
||||
private val minutes = 20
|
||||
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
|
||||
fun setup() {
|
||||
|
@ -21,9 +21,9 @@ class TimeUnitTest {
|
|||
fun formatTime() {
|
||||
Assert.assertEquals(
|
||||
HoursMinutesSeconds(
|
||||
hours.toString().padStart(2, '0'),
|
||||
minutes.toString().padStart(2, '0'),
|
||||
seconds.toString().padStart(2, '0'),
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
),
|
||||
time.getAsHMS(),
|
||||
)
|
||||
|
@ -39,7 +39,11 @@ class TimeUnitTest {
|
|||
|
||||
@Test
|
||||
fun minOne() {
|
||||
time.minOne()
|
||||
Assert.assertEquals(
|
||||
(seconds + minutes * 60 + hours * 60 * 60),
|
||||
time.time,
|
||||
)
|
||||
time--
|
||||
Assert.assertEquals(
|
||||
(seconds + minutes * 60 + hours * 60 * 60) - 1,
|
||||
time.time,
|
||||
|
@ -48,7 +52,7 @@ class TimeUnitTest {
|
|||
|
||||
@Test
|
||||
fun plusOne() {
|
||||
time.plusOne()
|
||||
time++
|
||||
Assert.assertEquals(
|
||||
(seconds + minutes * 60 + hours * 60 * 60) + 1,
|
||||
time.time,
|
||||
|
@ -59,7 +63,7 @@ class TimeUnitTest {
|
|||
fun minMultiple() {
|
||||
val n = 10
|
||||
for (i in 1 .. n) {
|
||||
time.minOne()
|
||||
time--
|
||||
}
|
||||
Assert.assertEquals(
|
||||
(seconds + minutes * 60 + hours * 60 * 60) - n,
|
||||
|
@ -71,7 +75,7 @@ class TimeUnitTest {
|
|||
fun plusMultiple() {
|
||||
val n = 10
|
||||
for (i in 1 .. n) {
|
||||
time.plusOne()
|
||||
time++
|
||||
}
|
||||
Assert.assertEquals(
|
||||
(seconds + minutes * 60 + hours * 60 * 60) + n,
|
||||
|
|
Reference in a new issue