diff --git a/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt b/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt index 11454ff..c3d1634 100644 --- a/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt +++ b/app/src/main/java/be/ugent/sel/studeez/StudeezApp.kt @@ -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(), - ) - } - } -} diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt index 73ae1b5..c96994d 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/ButtonComposable.kt @@ -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) ) } diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/TextComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/TextComposable.kt index 1b921a9..25fa3c4 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/TextComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/TextComposable.kt @@ -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) + ) } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt index 47dbb0b..aadcee3 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/TextFieldComposable.kt @@ -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, diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/Feed.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/Feed.kt new file mode 100644 index 0000000..54be2ea --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/Feed.kt @@ -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 {} }, {} + ) +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/FeedEntry.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/FeedEntry.kt new file mode 100644 index 0000000..ff950d6 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/FeedEntry.kt @@ -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, + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/FeedUiState.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/FeedUiState.kt new file mode 100644 index 0000000..1b938ca --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/FeedUiState.kt @@ -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>) : FeedUiState +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/FeedViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/FeedViewModel.kt new file mode 100644 index 0000000..b5e2b1a --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/feed/FeedViewModel.kt @@ -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 = 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/SubjectEntry.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/SubjectEntry.kt index 8655ba3..5db2af3 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/SubjectEntry.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/SubjectEntry.kt @@ -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, ) { + 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() } + ) } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/TaskEntry.kt b/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/TaskEntry.kt index fefb924..35e7a44 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/TaskEntry.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/composable/tasks/TaskEntry.kt @@ -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, ), - {}, {}, + {}, {}, {} ) } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/common/ext/ModifierExt.kt b/app/src/main/java/be/ugent/sel/studeez/common/ext/ModifierExt.kt index 7280ab3..66ade69 100644 --- a/app/src/main/java/be/ugent/sel/studeez/common/ext/ModifierExt.kt +++ b/app/src/main/java/be/ugent/sel/studeez/common/ext/ModifierExt.kt @@ -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 { diff --git a/app/src/main/java/be/ugent/sel/studeez/data/EditTimerState.kt b/app/src/main/java/be/ugent/sel/studeez/data/EditTimerState.kt deleted file mode 100644 index dceec8c..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/data/EditTimerState.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt b/app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt new file mode 100644 index 0000000..c52939f --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt @@ -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 { + abstract var value: T + operator fun invoke() = value + fun set(newValue: T) { + this.value = newValue + } +} + +@Singleton +class SelectedSessionReport @Inject constructor() : SelectedState() { + override lateinit var value: SessionReport +} + +@Singleton +class SelectedTask @Inject constructor() : SelectedState() { + override lateinit var value: Task +} + +@Singleton +class SelectedTimer @Inject constructor() : SelectedState() { + override lateinit var value: FunctionalTimer +} + +@Singleton +class SelectedSubject @Inject constructor() : SelectedState() { + override lateinit var value: Subject +} + +@Singleton +class SelectedTimerInfo @Inject constructor() : SelectedState() { + override lateinit var value: TimerInfo +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/SelectedSubject.kt b/app/src/main/java/be/ugent/sel/studeez/data/SelectedSubject.kt deleted file mode 100644 index fbc7e48..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/data/SelectedSubject.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/SelectedTask.kt b/app/src/main/java/be/ugent/sel/studeez/data/SelectedTask.kt deleted file mode 100644 index 9c3f042..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/data/SelectedTask.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/SelectedTimerState.kt b/app/src/main/java/be/ugent/sel/studeez/data/SelectedTimerState.kt deleted file mode 100644 index f8fcebd..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/data/SelectedTimerState.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/SessionReportState.kt b/app/src/main/java/be/ugent/sel/studeez/data/SessionReportState.kt deleted file mode 100644 index 47770d0..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/data/SessionReportState.kt +++ /dev/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 -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/FeedEntry.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/FeedEntry.kt new file mode 100644 index 0000000..8733c48 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/FeedEntry.kt @@ -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 +) diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/SessionReport.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/SessionReport.kt index 20a44a8..5835538 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/SessionReport.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/SessionReport.kt @@ -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 = "" ) \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Subject.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Subject.kt index e84c2bb..74ebe9f 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Subject.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Subject.kt @@ -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, -) \ No newline at end of file + 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" +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Task.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Task.kt index f2618db..ff2748d 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Task.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/task/Task.kt @@ -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" } diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt index 6d4f868..765fbcd 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalPomodoroTimer.kt @@ -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 diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalTimer.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalTimer.kt index 1f4231a..656f52f 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalTimer.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_functional/FunctionalTimer.kt @@ -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 ) } diff --git a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/PomodoroTimerInfo.kt b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/PomodoroTimerInfo.kt index 6dd6797..7316630 100644 --- a/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/PomodoroTimerInfo.kt +++ b/app/src/main/java/be/ugent/sel/studeez/data/local/models/timer_info/PomodoroTimerInfo.kt @@ -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) { diff --git a/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt b/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt index 7ee4992..4c5fea1 100644 --- a/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt +++ b/app/src/main/java/be/ugent/sel/studeez/di/DatabaseModule.kt @@ -33,4 +33,7 @@ abstract class DatabaseModule { @Binds abstract fun provideTaskDAO(impl: FireBaseTaskDAO): TaskDAO + + @Binds + abstract fun provideFeedDAO(impl: FirebaseFeedDAO): FeedDAO } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt new file mode 100644 index 0000000..2d91781 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt @@ -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>> + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt index 2749fac..bad8106 100644 --- a/app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt +++ b/app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt @@ -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 + + suspend fun getSubject(subjectId: String): Subject? } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt index 0f629ea..8a2dd41 100644 --- a/app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt +++ b/app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt @@ -14,5 +14,5 @@ interface TaskDAO { fun deleteTask(oldTask: Task) - fun toggleTaskCompleted(task: Task, completed: Boolean) + suspend fun getTask(subjectId: String, taskId: String): Task } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSubjectDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSubjectDAO.kt index 7d90fbf..b023986 100644 --- a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSubjectDAO.kt +++ b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseSubjectDAO.kt @@ -1,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> { 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 { + 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) } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseTaskDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseTaskDAO.kt index b8855e6..685b237 100644 --- a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseTaskDAO.kt +++ b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FireBaseTaskDAO.kt @@ -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> { 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) -} \ No newline at end of file + +} + +// 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) diff --git a/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseFeedDAO.kt b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseFeedDAO.kt new file mode 100644 index 0000000..6c445bf --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/domain/implementation/FirebaseFeedDAO.kt @@ -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>> { + 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 = + 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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt index 966b4a1..49856c9 100644 --- a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt +++ b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezDestinations.kt @@ -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" diff --git a/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezNavGraph.kt b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezNavGraph.kt new file mode 100644 index 0000000..37085f1 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/navigation/StudeezNavGraph.kt @@ -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(), + ) + } + } +} + diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeScreen.kt index f02852e..c93527b 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeScreen.kt @@ -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 = {} ) } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeViewModel.kt deleted file mode 100644 index b27f995..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/screens/home/HomeViewModel.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/SessionViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/SessionViewModel.kt index d5e2bab..0be4147 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session/SessionViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/SessionViewModel.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt index 65f5d24..08a8a72 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/AbstractSessionScreen.kt @@ -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() } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt index 8fa45ff..9c59b46 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session/sessionScreens/BreakSessionScreen.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt index 5fb4943..c11e28f 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/session_recap/SessionRecapViewModel.kt @@ -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) } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectScreen.kt new file mode 100644 index 0000000..ab2cff4 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectScreen.kt @@ -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, + 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 + ) +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectUiState.kt new file mode 100644 index 0000000..2e44e27 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectUiState.kt @@ -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) : SubjectUiState +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectViewModel.kt similarity index 60% rename from app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectViewModel.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectViewModel.kt index f1d6071..c158529 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/SubjectViewModel.kt @@ -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 = 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> { - return subjectDAO.getSubjects() + fun getStudyTime(subject: Subject): Flow { + return subjectDAO.getStudyTime(subject) } fun onViewSubject(subject: Subject, open: (String) -> Unit) { diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormScreen.kt similarity index 95% rename from app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormScreen.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormScreen.kt index 74bc7d2..19e6816 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormScreen.kt @@ -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( diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormUiState.kt similarity index 64% rename from app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormUiState.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormUiState.kt index 5418b74..9fdba01 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormUiState.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormUiState.kt @@ -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 = "", diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormViewModel.kt similarity index 64% rename from app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormViewModel.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormViewModel.kt index 68ebd3e..533123b 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/SubjectFormViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/subjects/form/SubjectFormViewModel.kt @@ -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 - 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( diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectScreen.kt deleted file mode 100644 index 15a3925..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/SubjectScreen.kt +++ /dev/null @@ -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>, - 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 = {}, - ) -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskScreen.kt index 67f0e93..516b836 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskScreen.kt @@ -27,9 +27,10 @@ data class TaskActions( val addTask: () -> Unit, val getSubject: () -> Subject, val getTasks: () -> Flow>, - 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 {} }, {}, + {}, + {}, ) ) } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskViewModel.kt index 138d32c..e2adbc1 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/TaskViewModel.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormScreen.kt similarity index 95% rename from app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormScreen.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormScreen.kt index 62b6c6c..92302ea 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormScreen.kt @@ -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( diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormUiState.kt similarity index 53% rename from app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormUiState.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormUiState.kt index d967d59..6156fb7 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormUiState.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormUiState.kt @@ -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 = "", diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormViewModel.kt similarity index 56% rename from app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormViewModel.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormViewModel.kt index 03ad32b..07cba5d 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/tasks/forms/TaskFormViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/tasks/form/TaskFormViewModel.kt @@ -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 - 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) } -} \ No newline at end of file +} diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddScreen.kt deleted file mode 100644 index 7cb6a8f..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddScreen.kt +++ /dev/null @@ -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) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/GetTimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/GetTimerEditScreen.kt deleted file mode 100644 index b22b775..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/GetTimerEditScreen.kt +++ /dev/null @@ -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 { - - 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) - } - - -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditScreen.kt deleted file mode 100644 index 649caf6..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditScreen.kt +++ /dev/null @@ -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) - } - } -} - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditViewModel.kt deleted file mode 100644 index 3258f24..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/TimerEditViewModel.kt +++ /dev/null @@ -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() - } - -} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/GetTimerFormScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/GetTimerFormScreen.kt new file mode 100644 index 0000000..99426e4 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/GetTimerFormScreen.kt @@ -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 { + + 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) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormRoute.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormRoute.kt new file mode 100644 index 0000000..0323dc2 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormRoute.kt @@ -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) + } +} diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormViewModel.kt similarity index 61% rename from app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddViewModel.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormViewModel.kt index 9ab766c..8a0a4d4 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerAddViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/TimerFormViewModel.kt @@ -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() + } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/AbstractTimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt similarity index 91% rename from app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/AbstractTimerEditScreen.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt index 420d734..5f4a17b 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/AbstractTimerEditScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/AbstractTimerFormScreen.kt @@ -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 ) diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/BreakTimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/BreakTimerFormScreen.kt similarity index 57% rename from app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/BreakTimerEditScreen.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/BreakTimerFormScreen.kt index b6104b6..12d07a4 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/BreakTimerEditScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/BreakTimerFormScreen.kt @@ -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 = {}) } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/CustomTimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/CustomTimerFormScreen.kt similarity index 79% rename from app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/CustomTimerEditScreen.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/CustomTimerFormScreen.kt index f3278d5..27c0657 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/CustomTimerEditScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/CustomTimerFormScreen.kt @@ -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 = {}) } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/EndlessTimerEditScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/EndlessTimerFormScreen.kt similarity index 70% rename from app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/EndlessTimerEditScreen.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/EndlessTimerFormScreen.kt index 0e26209..9009fff 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_edit/editScreens/EndlessTimerEditScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/form_screens/EndlessTimerFormScreen.kt @@ -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 = {}) } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/timer_type_select/TimerTypeSelectScreen.kt similarity index 76% rename from app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectScreen.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/timer_form/timer_type_select/TimerTypeSelectScreen.kt index d14bb86..fa8d650 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/timer_type_select/TimerTypeSelectScreen.kt @@ -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 = 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) } } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/timer_type_select/TimerTypeSelectViewModel.kt similarity index 59% rename from app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectViewModel.kt rename to app/src/main/java/be/ugent/sel/studeez/screens/timer_form/timer_type_select/TimerTypeSelectViewModel.kt index 1892833..c3ed2c4 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_add/TimerTypeSelectViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_form/timer_type_select/TimerTypeSelectViewModel.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt index dd3f062..3c25ddf 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewScreen.kt @@ -25,7 +25,7 @@ data class TimerOverviewActions( val getUserTimers: () -> Flow>, val getDefaultTimers: () -> List, 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) } + ) + } + ) } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewViewModel.kt index 77a5a6e..395a155 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/TimerOverviewViewModel.kt @@ -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> { + fun getUserTimers(): Flow> { 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) diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerUiState.kt deleted file mode 100644 index fcfa79a..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerUiState.kt +++ /dev/null @@ -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", -) \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerViewModel.kt deleted file mode 100644 index d507974..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/AddTimerViewModel.kt +++ /dev/null @@ -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) - } -} diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/addTimerScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/addTimerScreen.kt deleted file mode 100644 index dc7bbda..0000000 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_overview/add_timer/addTimerScreen.kt +++ /dev/null @@ -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() - ) - } -} diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionScreen.kt index 2f17e65..d78b4bf 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionScreen.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionScreen.kt @@ -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 } } diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionViewModel.kt index ab42973..c6c6793 100644 --- a/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionViewModel.kt +++ b/app/src/main/java/be/ugent/sel/studeez/screens/timer_selection/TimerSelectionViewModel.kt @@ -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 = mutableStateOf(0) + var customTimerStudyTime: MutableState = mutableStateOf( + HoursMinutesSeconds(1, 0, 0).getTotalSeconds() + ) - fun getAllTimers() : Flow> { + fun getAllTimers(): Flow> { return timerDAO.getAllTimers() } fun startSession(open: (String) -> Unit, timerInfo: TimerInfo) { - selectedTimerState.selectedTimer = timerInfo.getFunctionalTimer() + selectedTimer.set(timerInfo.getFunctionalTimer()) open(StudeezDestinations.SESSION_SCREEN) } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9ebb196..be6b7ef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,12 @@ Home Start session + + Continue + Deleted + This is your feed + Click here to create you first subject and tasks to get started + Tasks Task @@ -131,8 +137,13 @@ " minutes of studytime" How long do you want to study? + + Select Timer Type + Name + Edit Timer + Repeats must be a positive non-zero number Description Study Time Break Time diff --git a/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt b/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt index f9e34c3..54f673d 100644 --- a/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt +++ b/app/src/test/java/be/ugent/sel/studeez/timer_functional/InvisibleSessionManagerTest.kt @@ -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 { diff --git a/app/src/test/java/be/ugent/sel/studeez/timer_functional/TimeUnitTest.kt b/app/src/test/java/be/ugent/sel/studeez/timer_functional/TimeUnitTest.kt index 86fdce8..0d9bf4b 100644 --- a/app/src/test/java/be/ugent/sel/studeez/timer_functional/TimeUnitTest.kt +++ b/app/src/test/java/be/ugent/sel/studeez/timer_functional/TimeUnitTest.kt @@ -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,