Merge remote-tracking branch 'origin/development' into better_screens
This commit is contained in:
		
						commit
						dc63346e5e
					
				
					 74 changed files with 1568 additions and 1200 deletions
				
			
		|  | @ -2,56 +2,19 @@ package be.ugent.sel.studeez | ||||||
| 
 | 
 | ||||||
| import android.content.res.Resources | import android.content.res.Resources | ||||||
| import androidx.compose.foundation.layout.padding | import androidx.compose.foundation.layout.padding | ||||||
| import androidx.compose.material.MaterialTheme | import androidx.compose.material.* | ||||||
| import androidx.compose.material.Scaffold |  | ||||||
| import androidx.compose.material.ScaffoldState |  | ||||||
| import androidx.compose.material.Snackbar |  | ||||||
| import androidx.compose.material.SnackbarHost |  | ||||||
| import androidx.compose.material.Surface |  | ||||||
| import androidx.compose.material.rememberScaffoldState |  | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.ReadOnlyComposable | import androidx.compose.runtime.ReadOnlyComposable | ||||||
| import androidx.compose.runtime.getValue |  | ||||||
| import androidx.compose.runtime.remember | import androidx.compose.runtime.remember | ||||||
| import androidx.compose.runtime.rememberCoroutineScope | import androidx.compose.runtime.rememberCoroutineScope | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.platform.LocalConfiguration | import androidx.compose.ui.platform.LocalConfiguration | ||||||
| import androidx.compose.ui.platform.LocalContext | import androidx.compose.ui.platform.LocalContext | ||||||
| import androidx.compose.ui.unit.dp | import androidx.compose.ui.unit.dp | ||||||
| import androidx.hilt.navigation.compose.hiltViewModel |  | ||||||
| import androidx.navigation.NavHostController | import androidx.navigation.NavHostController | ||||||
| import androidx.navigation.compose.NavHost |  | ||||||
| import androidx.navigation.compose.composable |  | ||||||
| import androidx.navigation.compose.currentBackStackEntryAsState |  | ||||||
| import androidx.navigation.compose.rememberNavController | import androidx.navigation.compose.rememberNavController | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel |  | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions |  | ||||||
| import be.ugent.sel.studeez.common.snackbar.SnackbarManager | import be.ugent.sel.studeez.common.snackbar.SnackbarManager | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | import be.ugent.sel.studeez.navigation.StudeezNavGraph | ||||||
| import be.ugent.sel.studeez.screens.home.HomeRoute |  | ||||||
| import be.ugent.sel.studeez.screens.log_in.LoginRoute |  | ||||||
| import be.ugent.sel.studeez.screens.profile.EditProfileRoute |  | ||||||
| import be.ugent.sel.studeez.screens.profile.ProfileRoute |  | ||||||
| import be.ugent.sel.studeez.screens.session.SessionRoute |  | ||||||
| import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute |  | ||||||
| import be.ugent.sel.studeez.screens.sessions.SessionsRoute |  | ||||||
| import be.ugent.sel.studeez.screens.settings.SettingsRoute |  | ||||||
| import be.ugent.sel.studeez.screens.sign_up.SignUpRoute |  | ||||||
| import be.ugent.sel.studeez.screens.splash.SplashRoute |  | ||||||
| import be.ugent.sel.studeez.screens.tasks.SubjectRoute |  | ||||||
| import be.ugent.sel.studeez.screens.tasks.TaskRoute |  | ||||||
| import be.ugent.sel.studeez.screens.tasks.forms.SubjectAddRoute |  | ||||||
| import be.ugent.sel.studeez.screens.tasks.forms.SubjectEditRoute |  | ||||||
| import be.ugent.sel.studeez.screens.tasks.forms.TaskAddRoute |  | ||||||
| import be.ugent.sel.studeez.screens.tasks.forms.TaskEditRoute |  | ||||||
| import be.ugent.sel.studeez.screens.timer_edit.TimerEditRoute |  | ||||||
| import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute |  | ||||||
| import be.ugent.sel.studeez.screens.timer_overview.add_timer.AddTimerRoute |  | ||||||
| import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute |  | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| 
 | 
 | ||||||
|  | @ -97,205 +60,3 @@ fun resources(): Resources { | ||||||
|     LocalConfiguration.current |     LocalConfiguration.current | ||||||
|     return LocalContext.current.resources |     return LocalContext.current.resources | ||||||
| } | } | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun StudeezNavGraph( |  | ||||||
|     appState: StudeezAppstate, |  | ||||||
|     modifier: Modifier = Modifier, |  | ||||||
| ) { |  | ||||||
|     val drawerViewModel: DrawerViewModel = hiltViewModel() |  | ||||||
|     val navBarViewModel: NavigationBarViewModel = hiltViewModel() |  | ||||||
| 
 |  | ||||||
|     val backStackEntry by appState.navController.currentBackStackEntryAsState() |  | ||||||
|     val getCurrentScreen: () -> String? = { backStackEntry?.destination?.route } |  | ||||||
| 
 |  | ||||||
|     val goBack: () -> Unit = { appState.popUp() } |  | ||||||
|     val open: (String) -> Unit = { appState.navigate(it) } |  | ||||||
|     val openAndPopUp: (String, String) -> Unit = |  | ||||||
|         { route, popUp -> appState.navigateAndPopUp(route, popUp) } |  | ||||||
| 
 |  | ||||||
|     val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp) |  | ||||||
|     val navigationBarActions: NavigationBarActions = |  | ||||||
|         getNavigationBarActions(navBarViewModel, open, getCurrentScreen) |  | ||||||
| 
 |  | ||||||
|     NavHost( |  | ||||||
|         navController = appState.navController, |  | ||||||
|         startDestination = StudeezDestinations.SPLASH_SCREEN, |  | ||||||
|         modifier = modifier, |  | ||||||
|     ) { |  | ||||||
|         // NavBar |  | ||||||
|         composable(StudeezDestinations.HOME_SCREEN) { |  | ||||||
|             HomeRoute( |  | ||||||
|                 open, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|                 drawerActions = drawerActions, |  | ||||||
|                 navigationBarActions = navigationBarActions |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.SUBJECT_SCREEN) { |  | ||||||
|             SubjectRoute( |  | ||||||
|                 open = open, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|                 drawerActions = drawerActions, |  | ||||||
|                 navigationBarActions = navigationBarActions, |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.ADD_SUBJECT_FORM) { |  | ||||||
|             SubjectAddRoute( |  | ||||||
|                 goBack = goBack, |  | ||||||
|                 openAndPopUp = openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.EDIT_SUBJECT_FORM) { |  | ||||||
|             SubjectEditRoute( |  | ||||||
|                 goBack = goBack, |  | ||||||
|                 openAndPopUp = openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.TASKS_SCREEN) { |  | ||||||
|             TaskRoute( |  | ||||||
|                 goBack = goBack, |  | ||||||
|                 open = open, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.ADD_TASK_FORM) { |  | ||||||
|             TaskAddRoute( |  | ||||||
|                 goBack = goBack, |  | ||||||
|                 openAndPopUp = openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.EDIT_TASK_FORM) { |  | ||||||
|             TaskEditRoute( |  | ||||||
|                 goBack = goBack, |  | ||||||
|                 openAndPopUp = openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.SESSIONS_SCREEN) { |  | ||||||
|             SessionsRoute( |  | ||||||
|                 drawerActions = drawerActions, |  | ||||||
|                 navigationBarActions = navigationBarActions |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.PROFILE_SCREEN) { |  | ||||||
|             ProfileRoute( |  | ||||||
|                 open, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|                 drawerActions = drawerActions, |  | ||||||
|                 navigationBarActions = navigationBarActions, |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Drawer |  | ||||||
|         composable(StudeezDestinations.TIMER_SCREEN) { |  | ||||||
|             TimerOverviewRoute( |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|                 drawerActions = drawerActions, |  | ||||||
|                 open = open |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.SETTINGS_SCREEN) { |  | ||||||
|             SettingsRoute( |  | ||||||
|                 drawerActions = drawerActions |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Login flow |  | ||||||
|         composable(StudeezDestinations.SPLASH_SCREEN) { |  | ||||||
|             SplashRoute( |  | ||||||
|                 openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.LOGIN_SCREEN) { |  | ||||||
|             LoginRoute( |  | ||||||
|                 openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.SIGN_UP_SCREEN) { |  | ||||||
|             SignUpRoute( |  | ||||||
|                 openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Studying flow |  | ||||||
|         composable(StudeezDestinations.TIMER_SELECTION_SCREEN) { |  | ||||||
|             TimerSelectionRoute( |  | ||||||
|                 open, |  | ||||||
|                 goBack, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.SESSION_SCREEN) { |  | ||||||
|             SessionRoute( |  | ||||||
|                 open, |  | ||||||
|                 openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel() |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.SESSION_RECAP) { |  | ||||||
|             SessionRecapRoute( |  | ||||||
|                 openAndPopUp = openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel() |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.ADD_TIMER_SCREEN) { |  | ||||||
|             AddTimerRoute( |  | ||||||
|                 open = open, |  | ||||||
|                 goBack = goBack, |  | ||||||
|                 viewModel = hiltViewModel() |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.TIMER_EDIT_SCREEN) { |  | ||||||
|             TimerEditRoute( |  | ||||||
|                 open = open, |  | ||||||
|                 popUp = goBack, |  | ||||||
|                 viewModel = hiltViewModel() |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Friends flow |  | ||||||
|         composable(StudeezDestinations.SEARCH_FRIENDS_SCREEN) { |  | ||||||
|             // TODO |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Create & edit screens |  | ||||||
|         composable(StudeezDestinations.CREATE_TASK_SCREEN) { |  | ||||||
|             // TODO |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.CREATE_SESSION_SCREEN) { |  | ||||||
|             // TODO |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composable(StudeezDestinations.EDIT_PROFILE_SCREEN) { |  | ||||||
|             EditProfileRoute( |  | ||||||
|                 goBack, |  | ||||||
|                 openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel(), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package be.ugent.sel.studeez.common.composable | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.StringRes | import androidx.annotation.StringRes | ||||||
| import androidx.compose.foundation.BorderStroke | import androidx.compose.foundation.BorderStroke | ||||||
|  | import androidx.compose.foundation.clickable | ||||||
| import androidx.compose.foundation.layout.Arrangement | import androidx.compose.foundation.layout.Arrangement | ||||||
| import androidx.compose.foundation.layout.Row | import androidx.compose.foundation.layout.Row | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | @ -20,6 +21,7 @@ import androidx.compose.material.icons.filled.Add | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.Alignment | import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.draw.scale | ||||||
| import androidx.compose.ui.graphics.Color | import androidx.compose.ui.graphics.Color | ||||||
| import androidx.compose.ui.res.stringResource | import androidx.compose.ui.res.stringResource | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | @ -48,6 +50,7 @@ fun BasicButton( | ||||||
|     modifier: Modifier = Modifier, |     modifier: Modifier = Modifier, | ||||||
|     colors: ButtonColors = ButtonDefaults.buttonColors(), |     colors: ButtonColors = ButtonDefaults.buttonColors(), | ||||||
|     border: BorderStroke? = null, |     border: BorderStroke? = null, | ||||||
|  |     enabled: Boolean = true, | ||||||
|     onClick: () -> Unit, |     onClick: () -> Unit, | ||||||
| ) { | ) { | ||||||
|     Button( |     Button( | ||||||
|  | @ -56,6 +59,7 @@ fun BasicButton( | ||||||
|         shape = defaultButtonShape(), |         shape = defaultButtonShape(), | ||||||
|         colors = colors, |         colors = colors, | ||||||
|         border = border, |         border = border, | ||||||
|  |         enabled = enabled, | ||||||
|     ) { |     ) { | ||||||
|         Text( |         Text( | ||||||
|             text = stringResource(text), |             text = stringResource(text), | ||||||
|  | @ -74,17 +78,22 @@ fun BasicButtonPreview() { | ||||||
| fun StealthButton( | fun StealthButton( | ||||||
|     @StringRes text: Int, |     @StringRes text: Int, | ||||||
|     modifier: Modifier = Modifier.card(), |     modifier: Modifier = Modifier.card(), | ||||||
|  |     enabled: Boolean = true, | ||||||
|     onClick: () -> Unit, |     onClick: () -> Unit, | ||||||
| ) { | ) { | ||||||
|  |     //val clickablemodifier = if (disabled) Modifier.clickable(indication = null) else modifier | ||||||
|  |     val borderColor = if (enabled) MaterialTheme.colors.primary | ||||||
|  |                       else MaterialTheme.colors.onSurface.copy(alpha = 0.3f) | ||||||
|     BasicButton( |     BasicButton( | ||||||
|         text = text, |         text = text, | ||||||
|         onClick = onClick, |         onClick = onClick, | ||||||
|         modifier = modifier, |         modifier = modifier, | ||||||
|  |         enabled = enabled, | ||||||
|         colors = ButtonDefaults.buttonColors( |         colors = ButtonDefaults.buttonColors( | ||||||
|             backgroundColor = MaterialTheme.colors.surface, |             backgroundColor = MaterialTheme.colors.surface, | ||||||
|             contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.4f) |             contentColor = borderColor | ||||||
|         ), |         ), | ||||||
|         border = BorderStroke(3.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.4f)) |         border = BorderStroke(2.dp, borderColor) | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,10 +3,13 @@ package be.ugent.sel.studeez.common.composable | ||||||
| import androidx.compose.foundation.layout.Arrangement | import androidx.compose.foundation.layout.Arrangement | ||||||
| import androidx.compose.foundation.layout.Row | import androidx.compose.foundation.layout.Row | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
| import androidx.compose.material.Text | import androidx.compose.material.Text | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.Alignment | import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.text.font.FontWeight | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
| import androidx.compose.ui.unit.sp | import androidx.compose.ui.unit.sp | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
|  | @ -24,3 +27,13 @@ fun Headline( | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun DateText(date: String) { | ||||||
|  |     Text( | ||||||
|  |         text = date, | ||||||
|  |         fontWeight = FontWeight.Bold, | ||||||
|  |         fontSize = 20.sp, | ||||||
|  |         modifier = Modifier.padding(horizontal = 10.dp) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| package be.ugent.sel.studeez.common.composable | package be.ugent.sel.studeez.common.composable | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.StringRes | import androidx.annotation.StringRes | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
|  | import androidx.compose.foundation.text.KeyboardActions | ||||||
| import androidx.compose.foundation.text.KeyboardOptions | import androidx.compose.foundation.text.KeyboardOptions | ||||||
| import androidx.compose.material.Icon | import androidx.compose.material.* | ||||||
| import androidx.compose.material.IconButton |  | ||||||
| import androidx.compose.material.OutlinedTextField |  | ||||||
| import androidx.compose.material.Text |  | ||||||
| import androidx.compose.material.icons.Icons | import androidx.compose.material.icons.Icons | ||||||
| import androidx.compose.material.icons.filled.Email | import androidx.compose.material.icons.filled.Email | ||||||
| import androidx.compose.material.icons.filled.Lock | import androidx.compose.material.icons.filled.Lock | ||||||
|  | @ -14,10 +14,15 @@ import androidx.compose.runtime.* | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.res.painterResource | import androidx.compose.ui.res.painterResource | ||||||
| import androidx.compose.ui.res.stringResource | import androidx.compose.ui.res.stringResource | ||||||
|  | import androidx.compose.ui.text.input.ImeAction | ||||||
| import androidx.compose.ui.text.input.KeyboardType | import androidx.compose.ui.text.input.KeyboardType | ||||||
| import androidx.compose.ui.text.input.PasswordVisualTransformation | import androidx.compose.ui.text.input.PasswordVisualTransformation | ||||||
| import androidx.compose.ui.text.input.VisualTransformation | import androidx.compose.ui.text.input.VisualTransformation | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
| import be.ugent.sel.studeez.common.ext.fieldModifier | import be.ugent.sel.studeez.common.ext.fieldModifier | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
|  | import kotlin.math.sin | ||||||
| import be.ugent.sel.studeez.R.drawable as AppIcon | import be.ugent.sel.studeez.R.drawable as AppIcon | ||||||
| import be.ugent.sel.studeez.R.string as AppText | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
|  | @ -85,6 +90,86 @@ fun EmailField( | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @Composable | ||||||
|  | fun LabeledNumberInputField( | ||||||
|  |     value: Int, | ||||||
|  |     onNewValue: (Int) -> Unit, | ||||||
|  |     @StringRes label: Int, | ||||||
|  |     singleLine: Boolean = false | ||||||
|  | ) { | ||||||
|  |     var number by remember { mutableStateOf(value) } | ||||||
|  |     OutlinedTextField( | ||||||
|  |         value = number.toString(), | ||||||
|  |         singleLine = singleLine, | ||||||
|  |         label = { Text(resources().getString(label)) }, | ||||||
|  |         keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), | ||||||
|  |         onValueChange = {typedInt -> | ||||||
|  |             val isNumber = typedInt.matches(Regex("[1-9]+\\d*]")) | ||||||
|  |             if (isNumber) { | ||||||
|  |                 number = typedInt.toInt() | ||||||
|  |                 onNewValue(typedInt.toInt()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun LabeledErrorTextField( | ||||||
|  |     modifier: Modifier = Modifier, | ||||||
|  |     initialValue: String, | ||||||
|  |     @StringRes label: Int, | ||||||
|  |     singleLine: Boolean = false, | ||||||
|  |     errorText: Int, | ||||||
|  |     keyboardType: KeyboardType, | ||||||
|  |     predicate: (String) -> Boolean, | ||||||
|  |     onNewCorrectValue: (String) -> Unit | ||||||
|  | ) { | ||||||
|  |     var value by remember { | ||||||
|  |         mutableStateOf(initialValue) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var isValid by remember { | ||||||
|  |         mutableStateOf(predicate(value)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Column { | ||||||
|  |         OutlinedTextField( | ||||||
|  |             modifier = modifier.fieldModifier(), | ||||||
|  |             value = value, | ||||||
|  |             onValueChange = { newText -> | ||||||
|  |                 value = newText | ||||||
|  |                 isValid = predicate(value) | ||||||
|  |                 if (isValid) { | ||||||
|  |                     onNewCorrectValue(newText) | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             singleLine = singleLine, | ||||||
|  |             label = { Text(text = stringResource(id = label)) }, | ||||||
|  |             isError = !isValid, | ||||||
|  |             keyboardOptions = KeyboardOptions( | ||||||
|  |                 keyboardType = keyboardType, | ||||||
|  |                 imeAction = ImeAction.Done | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         if (!isValid) { | ||||||
|  |             Text( | ||||||
|  |                 modifier = Modifier.padding(start = 16.dp), | ||||||
|  |                 text = stringResource(id = errorText), | ||||||
|  |                 color = MaterialTheme.colors.error | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |  @Preview(showBackground = true) | ||||||
|  |  @Composable | ||||||
|  |  fun IntInputPreview() { | ||||||
|  |      LabeledNumberInputField(value = 1, onNewValue = {}, label = AppText.email) | ||||||
|  |  } | ||||||
|  | 
 | ||||||
| @Composable | @Composable | ||||||
| fun PasswordField( | fun PasswordField( | ||||||
|     value: String, |     value: String, | ||||||
|  |  | ||||||
|  | @ -0,0 +1,160 @@ | ||||||
|  | package be.ugent.sel.studeez.common.composable.feed | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.layout.* | ||||||
|  | import androidx.compose.foundation.lazy.LazyColumn | ||||||
|  | import androidx.compose.foundation.lazy.items | ||||||
|  | import androidx.compose.material.CircularProgressIndicator | ||||||
|  | import androidx.compose.material.MaterialTheme | ||||||
|  | import androidx.compose.material.Text | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.Alignment | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import androidx.compose.ui.text.font.FontWeight | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import androidx.compose.ui.unit.sp | ||||||
|  | import be.ugent.sel.studeez.common.composable.BasicTextButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.DateText | ||||||
|  | import be.ugent.sel.studeez.common.composable.Headline | ||||||
|  | import be.ugent.sel.studeez.common.ext.textButton | ||||||
|  | import be.ugent.sel.studeez.data.local.models.FeedEntry | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun Feed( | ||||||
|  |     uiState: FeedUiState, | ||||||
|  |     continueTask: (String, String) -> Unit, | ||||||
|  |     onEmptyFeedHelp: () -> Unit | ||||||
|  | ) { | ||||||
|  |     when (uiState) { | ||||||
|  |         FeedUiState.Loading -> LoadingFeed() | ||||||
|  |         is FeedUiState.Succes -> LoadedFeed( | ||||||
|  |             uiState = uiState, | ||||||
|  |             continueTask = continueTask, | ||||||
|  |             onEmptyFeedHelp = onEmptyFeedHelp | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun LoadedFeed( | ||||||
|  |     uiState: FeedUiState.Succes, | ||||||
|  |     continueTask: (String, String) -> Unit, | ||||||
|  |     onEmptyFeedHelp: () -> Unit, | ||||||
|  | ) { | ||||||
|  |     if (uiState.feedEntries.isEmpty()) EmptyFeed(onEmptyFeedHelp) | ||||||
|  |     else FeedWithElements(uiState = uiState, continueTask = continueTask) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun LoadingFeed() { | ||||||
|  |     Column( | ||||||
|  |         modifier = Modifier | ||||||
|  |             .fillMaxWidth() | ||||||
|  |             .fillMaxHeight(), | ||||||
|  |         verticalArrangement = Arrangement.Center, | ||||||
|  |         horizontalAlignment = Alignment.CenterHorizontally | ||||||
|  |     ) { | ||||||
|  |         CircularProgressIndicator(color = MaterialTheme.colors.onBackground) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun FeedWithElements( | ||||||
|  |     uiState: FeedUiState.Succes, | ||||||
|  |     continueTask: (String, String) -> Unit, | ||||||
|  | ) { | ||||||
|  |     val feedEntries = uiState.feedEntries | ||||||
|  |     LazyColumn { | ||||||
|  |         items(feedEntries.toList()) { (date, feedEntries) -> | ||||||
|  |             Row( | ||||||
|  |                 horizontalArrangement = Arrangement.SpaceBetween, | ||||||
|  |                 modifier = Modifier | ||||||
|  |                     .fillMaxWidth() | ||||||
|  |                     .padding(10.dp), | ||||||
|  |                 verticalAlignment = Alignment.CenterVertically | ||||||
|  |             ) { | ||||||
|  |                 val totalDayStudyTime: Int = feedEntries.sumOf { it.totalStudyTime } | ||||||
|  |                 DateText(date = date) | ||||||
|  |                 Text( | ||||||
|  |                     text = "${HoursMinutesSeconds(totalDayStudyTime)}", | ||||||
|  |                     fontSize = 15.sp, | ||||||
|  |                     fontWeight = FontWeight.Bold | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             feedEntries.forEach { feedEntry -> | ||||||
|  |                 FeedEntry(feedEntry = feedEntry) { | ||||||
|  |                     continueTask(feedEntry.subjectId, feedEntry.taskId) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Spacer(modifier = Modifier.height(20.dp)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun EmptyFeed(onEmptyFeedHelp: () -> Unit) { | ||||||
|  |     Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { | ||||||
|  |         Column( | ||||||
|  |             horizontalAlignment = Alignment.CenterHorizontally, | ||||||
|  |             modifier = Modifier.fillMaxWidth() | ||||||
|  |         ) { | ||||||
|  |             Headline(text = stringResource(id = AppText.your_feed)) | ||||||
|  | 
 | ||||||
|  |             BasicTextButton( | ||||||
|  |                 AppText.empty_feed_help_text, | ||||||
|  |                 Modifier.textButton(), | ||||||
|  |                 action = onEmptyFeedHelp, | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun FeedLoadingPreview() { | ||||||
|  |     Feed( | ||||||
|  |         uiState = FeedUiState.Loading, | ||||||
|  |         continueTask = { _, _ -> run {} }, {} | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun FeedPreview() { | ||||||
|  |     Feed( | ||||||
|  |         uiState = FeedUiState.Succes( | ||||||
|  |             mapOf( | ||||||
|  |                 "08 May 2023" to listOf( | ||||||
|  |                     FeedEntry( | ||||||
|  |                         argb_color = 0xFFFFD200, | ||||||
|  |                         subJectName = "Test Subject", | ||||||
|  |                         taskName = "Test Task", | ||||||
|  |                         totalStudyTime = 600, | ||||||
|  |                     ), | ||||||
|  |                     FeedEntry( | ||||||
|  |                         argb_color = 0xFFFFD200, | ||||||
|  |                         subJectName = "Test Subject", | ||||||
|  |                         taskName = "Test Task", | ||||||
|  |                         totalStudyTime = 20, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 "09 May 2023" to listOf( | ||||||
|  |                     FeedEntry( | ||||||
|  |                         argb_color = 0xFFFD1200, | ||||||
|  |                         subJectName = "Test Subject", | ||||||
|  |                         taskName = "Test Task", | ||||||
|  |                     ), | ||||||
|  |                     FeedEntry( | ||||||
|  |                         argb_color = 0xFFFFD200, | ||||||
|  |                         subJectName = "Test Subject", | ||||||
|  |                         taskName = "Test Task", | ||||||
|  |                     ), | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |         ), | ||||||
|  |         continueTask = { _, _ -> run {} }, {} | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,116 @@ | ||||||
|  | package be.ugent.sel.studeez.common.composable.feed | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.background | ||||||
|  | import androidx.compose.foundation.layout.* | ||||||
|  | import androidx.compose.foundation.shape.CircleShape | ||||||
|  | import androidx.compose.material.Card | ||||||
|  | import androidx.compose.material.Text | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.Alignment | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.draw.clip | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import androidx.compose.ui.text.font.FontWeight | ||||||
|  | import androidx.compose.ui.text.style.TextOverflow | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import be.ugent.sel.studeez.common.composable.StealthButton | ||||||
|  | import be.ugent.sel.studeez.data.local.models.FeedEntry | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun FeedEntry( | ||||||
|  |     feedEntry: FeedEntry, | ||||||
|  |     continueWithTask: () -> Unit, | ||||||
|  | ) { | ||||||
|  |     Card( | ||||||
|  |         modifier = Modifier | ||||||
|  |             .fillMaxWidth() | ||||||
|  |             .padding(horizontal = 10.dp, vertical = 5.dp), | ||||||
|  |     ) { | ||||||
|  |         Row( | ||||||
|  |             horizontalArrangement = Arrangement.SpaceBetween, | ||||||
|  |             verticalAlignment = Alignment.CenterVertically, | ||||||
|  |         ) { | ||||||
|  |             Row( | ||||||
|  |                 horizontalArrangement = Arrangement.spacedBy(8.dp), | ||||||
|  |                 verticalAlignment = Alignment.CenterVertically, | ||||||
|  |                 modifier = Modifier | ||||||
|  |                     .padding(start = 10.dp) | ||||||
|  |                     .weight(11f) | ||||||
|  |             ) { | ||||||
|  |                 Box( | ||||||
|  |                     modifier = Modifier | ||||||
|  |                         .size(20.dp) | ||||||
|  |                         .clip(CircleShape) | ||||||
|  |                         .background(Color(feedEntry.argb_color)), | ||||||
|  |                 ) | ||||||
|  |                 Row( | ||||||
|  |                     modifier = Modifier.fillMaxWidth(), | ||||||
|  |                     horizontalArrangement = Arrangement.SpaceBetween, | ||||||
|  |                     verticalAlignment = Alignment.CenterVertically, | ||||||
|  |                 ) { | ||||||
|  |                     Column( | ||||||
|  |                         verticalArrangement = Arrangement.spacedBy(0.dp) | ||||||
|  |                     ) { | ||||||
|  |                         Text( | ||||||
|  |                             text = feedEntry.subJectName, | ||||||
|  |                             fontWeight = FontWeight.Bold, | ||||||
|  |                             overflow = TextOverflow.Ellipsis, | ||||||
|  |                             maxLines = 1, | ||||||
|  |                         ) | ||||||
|  |                         Text( | ||||||
|  |                             text = feedEntry.taskName, | ||||||
|  |                             overflow = TextOverflow.Ellipsis, | ||||||
|  |                             maxLines = 1, | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                     Text(text = HoursMinutesSeconds(feedEntry.totalStudyTime).toString()) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             val buttonText: Int = | ||||||
|  |                 if (feedEntry.isArchived) AppText.deleted else AppText.continue_task | ||||||
|  |             StealthButton( | ||||||
|  |                 text = buttonText, | ||||||
|  |                 enabled = !feedEntry.isArchived, | ||||||
|  |                 modifier = Modifier | ||||||
|  |                     .padding(start = 10.dp, end = 5.dp) | ||||||
|  |                     .weight(6f) | ||||||
|  |             ) { | ||||||
|  |                 if (!feedEntry.isArchived) { | ||||||
|  |                     continueWithTask() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun FeedEntryPreview() { | ||||||
|  |     FeedEntry( | ||||||
|  |         continueWithTask = {}, | ||||||
|  |         feedEntry = FeedEntry( | ||||||
|  |             argb_color = 0xFFFFD200, | ||||||
|  |             subJectName = "Test Subject", | ||||||
|  |             taskName = "Test Task", | ||||||
|  |             totalStudyTime = 20, | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun FeedEntryOverflowPreview() { | ||||||
|  |     FeedEntry( | ||||||
|  |         continueWithTask = {}, | ||||||
|  |         feedEntry = FeedEntry( | ||||||
|  |             argb_color = 0xFFFFD200, | ||||||
|  |             subJectName = "Test Subject", | ||||||
|  |             taskName = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkk", | ||||||
|  |             totalStudyTime = 20, | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | package be.ugent.sel.studeez.common.composable.feed | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.FeedEntry | ||||||
|  | 
 | ||||||
|  | sealed interface FeedUiState { | ||||||
|  |     object Loading : FeedUiState | ||||||
|  |     data class Succes(val feedEntries: Map<String, List<FeedEntry>>) : FeedUiState | ||||||
|  | } | ||||||
|  | @ -0,0 +1,45 @@ | ||||||
|  | package be.ugent.sel.studeez.common.composable.feed | ||||||
|  | 
 | ||||||
|  | import androidx.lifecycle.viewModelScope | ||||||
|  | import be.ugent.sel.studeez.data.SelectedTask | ||||||
|  | import be.ugent.sel.studeez.domain.FeedDAO | ||||||
|  | import be.ugent.sel.studeez.domain.LogService | ||||||
|  | import be.ugent.sel.studeez.domain.TaskDAO | ||||||
|  | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
|  | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
|  | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
|  | import kotlinx.coroutines.flow.SharingStarted | ||||||
|  | import kotlinx.coroutines.flow.StateFlow | ||||||
|  | import kotlinx.coroutines.flow.map | ||||||
|  | import kotlinx.coroutines.flow.stateIn | ||||||
|  | import kotlinx.coroutines.launch | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | @HiltViewModel | ||||||
|  | class FeedViewModel @Inject constructor( | ||||||
|  |     feedDAO: FeedDAO, | ||||||
|  |     private val taskDAO: TaskDAO, | ||||||
|  |     private val selectedTask: SelectedTask, | ||||||
|  |     logService: LogService | ||||||
|  | ) : StudeezViewModel(logService) { | ||||||
|  | 
 | ||||||
|  |     val uiState: StateFlow<FeedUiState> = feedDAO.getFeedEntries() | ||||||
|  |         .map { FeedUiState.Succes(it) } | ||||||
|  |         .stateIn( | ||||||
|  |             scope = viewModelScope, | ||||||
|  |             initialValue = FeedUiState.Loading, | ||||||
|  |             started = SharingStarted.Eagerly, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     fun continueTask(open: (String) -> Unit, subjectId: String, taskId: String) { | ||||||
|  |         viewModelScope.launch { | ||||||
|  |             val task = taskDAO.getTask(subjectId, taskId) | ||||||
|  |             selectedTask.set(task) | ||||||
|  |             open(StudeezDestinations.TIMER_SELECTION_SCREEN) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun onEmptyFeedHelp(open: (String) -> Unit) { | ||||||
|  |         open(StudeezDestinations.ADD_SUBJECT_FORM) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,13 +1,7 @@ | ||||||
| package be.ugent.sel.studeez.common.composable.tasks | package be.ugent.sel.studeez.common.composable.tasks | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.background | import androidx.compose.foundation.background | ||||||
| import androidx.compose.foundation.layout.Arrangement | import androidx.compose.foundation.layout.* | ||||||
| import androidx.compose.foundation.layout.Box |  | ||||||
| import androidx.compose.foundation.layout.Column |  | ||||||
| import androidx.compose.foundation.layout.Row |  | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth |  | ||||||
| import androidx.compose.foundation.layout.padding |  | ||||||
| import androidx.compose.foundation.layout.size |  | ||||||
| import androidx.compose.foundation.shape.CircleShape | import androidx.compose.foundation.shape.CircleShape | ||||||
| import androidx.compose.material.Card | import androidx.compose.material.Card | ||||||
| import androidx.compose.material.Icon | import androidx.compose.material.Icon | ||||||
|  | @ -15,6 +9,8 @@ import androidx.compose.material.Text | ||||||
| import androidx.compose.material.icons.Icons | import androidx.compose.material.icons.Icons | ||||||
| import androidx.compose.material.icons.filled.List | import androidx.compose.material.icons.filled.List | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.collectAsState | ||||||
|  | import androidx.compose.runtime.getValue | ||||||
| import androidx.compose.ui.Alignment | import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.draw.clip | import androidx.compose.ui.draw.clip | ||||||
|  | @ -24,16 +20,20 @@ import androidx.compose.ui.text.font.FontWeight | ||||||
| import androidx.compose.ui.text.style.TextOverflow | import androidx.compose.ui.text.style.TextOverflow | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import androidx.compose.ui.unit.dp | import androidx.compose.ui.unit.dp | ||||||
| import be.ugent.sel.studeez.R.string as AppText |  | ||||||
| import be.ugent.sel.studeez.common.composable.StealthButton | import be.ugent.sel.studeez.common.composable.StealthButton | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Subject | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.flowOf | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun SubjectEntry( | fun SubjectEntry( | ||||||
|     subject: Subject, |     subject: Subject, | ||||||
|     onViewSubject: () -> Unit, |     onViewSubject: () -> Unit, | ||||||
|  |     getStudyTime: () -> Flow<Int>, | ||||||
| ) { | ) { | ||||||
|  |     val studytime by getStudyTime().collectAsState(initial = 0) | ||||||
|     Card( |     Card( | ||||||
|         modifier = Modifier |         modifier = Modifier | ||||||
|             .fillMaxWidth() |             .fillMaxWidth() | ||||||
|  | @ -70,7 +70,7 @@ fun SubjectEntry( | ||||||
|                         verticalAlignment = Alignment.CenterVertically, |                         verticalAlignment = Alignment.CenterVertically, | ||||||
|                     ) { |                     ) { | ||||||
|                         Text( |                         Text( | ||||||
|                             text = HoursMinutesSeconds(subject.time).toString(), |                             text = HoursMinutesSeconds(studytime).toString(), | ||||||
|                         ) |                         ) | ||||||
|                         Row( |                         Row( | ||||||
|                             verticalAlignment = Alignment.CenterVertically, |                             verticalAlignment = Alignment.CenterVertically, | ||||||
|  | @ -80,7 +80,7 @@ fun SubjectEntry( | ||||||
|                                 imageVector = Icons.Default.List, |                                 imageVector = Icons.Default.List, | ||||||
|                                 contentDescription = stringResource(id = AppText.tasks) |                                 contentDescription = stringResource(id = AppText.tasks) | ||||||
|                             ) |                             ) | ||||||
|                             Text(text = "0/0") // TODO |                             Text(text = "${subject.taskCompletedCount}/${subject.taskCount}") | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -104,9 +104,12 @@ fun SubjectEntryPreview() { | ||||||
|         subject = Subject( |         subject = Subject( | ||||||
|             name = "Test Subject", |             name = "Test Subject", | ||||||
|             argb_color = 0xFFFFD200, |             argb_color = 0xFFFFD200, | ||||||
|             time = 60 |             taskCount = 5, | ||||||
|  |             taskCompletedCount = 2, | ||||||
|         ), |         ), | ||||||
|     ) {} |         onViewSubject = {}, | ||||||
|  |         getStudyTime = { flowOf() } | ||||||
|  |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Preview | @Preview | ||||||
|  | @ -116,7 +119,8 @@ fun OverflowSubjectEntryPreview() { | ||||||
|         subject = Subject( |         subject = Subject( | ||||||
|             name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt", |             name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt", | ||||||
|             argb_color = 0xFFFFD200, |             argb_color = 0xFFFFD200, | ||||||
|             time = 60 |  | ||||||
|         ), |         ), | ||||||
|     ) {} |         onViewSubject = {}, | ||||||
|  |         getStudyTime = { flowOf() } | ||||||
|  |     ) | ||||||
| } | } | ||||||
|  | @ -1,17 +1,7 @@ | ||||||
| package be.ugent.sel.studeez.common.composable.tasks | package be.ugent.sel.studeez.common.composable.tasks | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.Arrangement | import androidx.compose.foundation.layout.* | ||||||
| import androidx.compose.foundation.layout.Box | import androidx.compose.material.* | ||||||
| import androidx.compose.foundation.layout.Row |  | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth |  | ||||||
| import androidx.compose.foundation.layout.padding |  | ||||||
| import androidx.compose.material.Card |  | ||||||
| import androidx.compose.material.Checkbox |  | ||||||
| import androidx.compose.material.CheckboxDefaults |  | ||||||
| import androidx.compose.material.Icon |  | ||||||
| import androidx.compose.material.IconButton |  | ||||||
| import androidx.compose.material.MaterialTheme |  | ||||||
| import androidx.compose.material.Text |  | ||||||
| import androidx.compose.material.icons.Icons | import androidx.compose.material.icons.Icons | ||||||
| import androidx.compose.material.icons.filled.Delete | import androidx.compose.material.icons.filled.Delete | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
|  | @ -31,7 +21,8 @@ import be.ugent.sel.studeez.resources | ||||||
| fun TaskEntry( | fun TaskEntry( | ||||||
|     task: Task, |     task: Task, | ||||||
|     onCheckTask: (Boolean) -> Unit, |     onCheckTask: (Boolean) -> Unit, | ||||||
|     onDeleteTask: () -> Unit, |     onArchiveTask: () -> Unit, | ||||||
|  |     onStartTask: () -> Unit | ||||||
| ) { | ) { | ||||||
|     Card( |     Card( | ||||||
|         modifier = Modifier |         modifier = Modifier | ||||||
|  | @ -80,7 +71,7 @@ fun TaskEntry( | ||||||
|             Box(modifier = Modifier.weight(7f)) { |             Box(modifier = Modifier.weight(7f)) { | ||||||
|                 if (task.completed) { |                 if (task.completed) { | ||||||
|                     IconButton( |                     IconButton( | ||||||
|                         onClick = onDeleteTask, |                         onClick = onArchiveTask, | ||||||
|                         modifier = Modifier |                         modifier = Modifier | ||||||
|                             .padding(start = 20.dp) |                             .padding(start = 20.dp) | ||||||
|                     ) { |                     ) { | ||||||
|  | @ -95,6 +86,7 @@ fun TaskEntry( | ||||||
|                         modifier = Modifier |                         modifier = Modifier | ||||||
|                             .padding(end = 5.dp), |                             .padding(end = 5.dp), | ||||||
|                     ) { |                     ) { | ||||||
|  |                         onStartTask() | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -110,7 +102,7 @@ fun TaskEntryPreview() { | ||||||
|             name = "Test Task", |             name = "Test Task", | ||||||
|             completed = false, |             completed = false, | ||||||
|         ), |         ), | ||||||
|         {}, {}, |         {}, {}, {} | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -122,7 +114,7 @@ fun CompletedTaskEntryPreview() { | ||||||
|             name = "Test Task", |             name = "Test Task", | ||||||
|             completed = true, |             completed = true, | ||||||
|         ), |         ), | ||||||
|         {}, {}, |         {}, {}, {}, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -134,6 +126,6 @@ fun OverflowTaskEntryPreview() { | ||||||
|             name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk", |             name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk", | ||||||
|             completed = false, |             completed = false, | ||||||
|         ), |         ), | ||||||
|         {}, {}, |         {}, {}, {} | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  | @ -1,8 +1,12 @@ | ||||||
| package be.ugent.sel.studeez.common.ext | package be.ugent.sel.studeez.common.ext | ||||||
| 
 | 
 | ||||||
|  | import androidx.compose.foundation.clickable | ||||||
|  | import androidx.compose.foundation.interaction.MutableInteractionSource | ||||||
| import androidx.compose.foundation.layout.* | import androidx.compose.foundation.layout.* | ||||||
|  | import androidx.compose.runtime.remember | ||||||
| import androidx.compose.ui.Alignment | import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.composed | ||||||
| import androidx.compose.ui.unit.dp | import androidx.compose.ui.unit.dp | ||||||
| 
 | 
 | ||||||
| fun Modifier.textButton(): Modifier { | fun Modifier.textButton(): Modifier { | ||||||
|  |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.data |  | ||||||
| 
 |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo |  | ||||||
| import javax.inject.Inject |  | ||||||
| import javax.inject.Singleton |  | ||||||
| 
 |  | ||||||
| @Singleton |  | ||||||
| class EditTimerState @Inject constructor(){ |  | ||||||
|     lateinit var timerInfo: TimerInfo |  | ||||||
| } |  | ||||||
							
								
								
									
										45
									
								
								app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								app/src/main/java/be/ugent/sel/studeez/data/SelectedState.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | ||||||
|  | package be.ugent.sel.studeez.data | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.SessionReport | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Task | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
|  | import javax.inject.Inject | ||||||
|  | import javax.inject.Singleton | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Used to cummunicate between viewmodels. | ||||||
|  |  */ | ||||||
|  | abstract class SelectedState<T> { | ||||||
|  |     abstract var value: T | ||||||
|  |     operator fun invoke() = value | ||||||
|  |     fun set(newValue: T) { | ||||||
|  |         this.value = newValue | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Singleton | ||||||
|  | class SelectedSessionReport @Inject constructor() : SelectedState<SessionReport>() { | ||||||
|  |     override lateinit var value: SessionReport | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Singleton | ||||||
|  | class SelectedTask @Inject constructor() : SelectedState<Task>() { | ||||||
|  |     override lateinit var value: Task | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Singleton | ||||||
|  | class SelectedTimer @Inject constructor() : SelectedState<FunctionalTimer>() { | ||||||
|  |     override lateinit var value: FunctionalTimer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Singleton | ||||||
|  | class SelectedSubject @Inject constructor() : SelectedState<Subject>() { | ||||||
|  |     override lateinit var value: Subject | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Singleton | ||||||
|  | class SelectedTimerInfo @Inject constructor() : SelectedState<TimerInfo>() { | ||||||
|  |     override lateinit var value: TimerInfo | ||||||
|  | } | ||||||
|  | @ -1,20 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.data |  | ||||||
| 
 |  | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Subject |  | ||||||
| import javax.inject.Inject |  | ||||||
| import javax.inject.Singleton |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Used to communicate the selected subject from the subject overview other screens. |  | ||||||
|  * Because this is a singleton-class the view-models of both screens observe the same data. |  | ||||||
|  */ |  | ||||||
| @Singleton |  | ||||||
| class SelectedSubject @Inject constructor() { |  | ||||||
|     private lateinit var subject: Subject |  | ||||||
|     operator fun invoke() = subject |  | ||||||
|     fun set(subject: Subject) { |  | ||||||
|         this.subject = subject |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun isSet() = this::subject.isInitialized |  | ||||||
| } |  | ||||||
|  | @ -1,21 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.data |  | ||||||
| 
 |  | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Task |  | ||||||
| import javax.inject.Inject |  | ||||||
| import javax.inject.Singleton |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Used to communicate the selected task from the task overview other screens. |  | ||||||
|  * Because this is a singleton-class the view-models of both screens observe the same data. |  | ||||||
|  */ |  | ||||||
| @Singleton |  | ||||||
| class SelectedTask @Inject constructor() { |  | ||||||
|     private lateinit var task: Task |  | ||||||
| 
 |  | ||||||
|     operator fun invoke() = task |  | ||||||
|     fun set(task: Task) { |  | ||||||
|         this.task = task |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun isSet() = this::task.isInitialized |  | ||||||
| } |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.data |  | ||||||
| 
 |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer |  | ||||||
| import javax.inject.Inject |  | ||||||
| import javax.inject.Singleton |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Used to communicate the SelectedTimer from the selection screen to the session screen. |  | ||||||
|  * Because this is a singleton-class the view-models of both screens observe the same data. |  | ||||||
|  */ |  | ||||||
| @Singleton |  | ||||||
| class SelectedTimerState @Inject constructor(){ |  | ||||||
|     var selectedTimer: FunctionalTimer? = null |  | ||||||
| } |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.data |  | ||||||
| 
 |  | ||||||
| import be.ugent.sel.studeez.data.local.models.SessionReport |  | ||||||
| import javax.inject.Inject |  | ||||||
| import javax.inject.Singleton |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Used to communicate the SelectedTimer from the selection screen to the session screen. |  | ||||||
|  * Because this is a singleton-class the view-models of both screens observe the same data. |  | ||||||
|  */ |  | ||||||
| @Singleton |  | ||||||
| class SessionReportState @Inject constructor(){ |  | ||||||
|     var sessionReport: SessionReport? = null |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | package be.ugent.sel.studeez.data.local.models | ||||||
|  | 
 | ||||||
|  | import com.google.firebase.Timestamp | ||||||
|  | 
 | ||||||
|  | data class FeedEntry( | ||||||
|  |     val argb_color: Long = 0, | ||||||
|  |     val subJectName: String = "", | ||||||
|  |     val taskName: String = "", | ||||||
|  |     val taskId: String = "", // Name of task is not unique | ||||||
|  |     val subjectId: String = "", | ||||||
|  |     val totalStudyTime: Int = 0, | ||||||
|  |     val endTime: Timestamp = Timestamp(0, 0), | ||||||
|  |     val isArchived: Boolean = false | ||||||
|  | ) | ||||||
|  | @ -6,5 +6,7 @@ import com.google.firebase.firestore.DocumentId | ||||||
| data class SessionReport( | data class SessionReport( | ||||||
|     @DocumentId val id: String = "", |     @DocumentId val id: String = "", | ||||||
|     val studyTime: Int = 0, |     val studyTime: Int = 0, | ||||||
|     val endTime: Timestamp = Timestamp(0, 0) |     val endTime: Timestamp = Timestamp(0, 0), | ||||||
|  |     val taskId: String = "", | ||||||
|  |     val subjectId: String = "" | ||||||
| ) | ) | ||||||
|  | @ -1,10 +1,22 @@ | ||||||
| package be.ugent.sel.studeez.data.local.models.task | package be.ugent.sel.studeez.data.local.models.task | ||||||
| 
 | 
 | ||||||
| import com.google.firebase.firestore.DocumentId | import com.google.firebase.firestore.DocumentId | ||||||
|  | import com.google.firebase.firestore.Exclude | ||||||
| 
 | 
 | ||||||
| data class Subject( | data class Subject( | ||||||
|     @DocumentId val id: String = "", |     @DocumentId val id: String = "", | ||||||
|     val name: String = "", |     val name: String = "", | ||||||
|     val time: Int = 0, |  | ||||||
|     val argb_color: Long = 0, |     val argb_color: Long = 0, | ||||||
|  |     var archived: Boolean = false, | ||||||
|  |     @get:Exclude @set:Exclude | ||||||
|  |     var taskCount: Int = 0, | ||||||
|  |     @get:Exclude @set:Exclude | ||||||
|  |     var taskCompletedCount: Int = 0, | ||||||
| ) | ) | ||||||
|  | 
 | ||||||
|  | object SubjectDocument { | ||||||
|  |     const val id = "id" | ||||||
|  |     const val name = "name" | ||||||
|  |     const val archived = "archived" | ||||||
|  |     const val argb_color = "argb_color" | ||||||
|  | } | ||||||
|  | @ -5,9 +5,10 @@ import com.google.firebase.firestore.DocumentId | ||||||
| data class Task( | data class Task( | ||||||
|     @DocumentId val id: String = "", |     @DocumentId val id: String = "", | ||||||
|     val name: String = "", |     val name: String = "", | ||||||
|     val completed: Boolean = false, |     var completed: Boolean = false, | ||||||
|     val time: Int = 0, |     val time: Int = 0, | ||||||
|     val subjectId: String = "", |     val subjectId: String = "", | ||||||
|  |     var archived: Boolean = false, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| object TaskDocument { | object TaskDocument { | ||||||
|  | @ -16,4 +17,5 @@ object TaskDocument { | ||||||
|     const val completed = "completed" |     const val completed = "completed" | ||||||
|     const val time = "time" |     const val time = "time" | ||||||
|     const val subjectId = "subjectId" |     const val subjectId = "subjectId" | ||||||
|  |     const val archived = "archived" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,8 @@ package be.ugent.sel.studeez.data.local.models.timer_functional | ||||||
| 
 | 
 | ||||||
| class FunctionalPomodoroTimer( | class FunctionalPomodoroTimer( | ||||||
|     private var studyTime: Int, |     private var studyTime: Int, | ||||||
|     private var breakTime: Int, repeats: Int |     private var breakTime: Int, | ||||||
|  |     val repeats: Int | ||||||
| ) : FunctionalTimer(studyTime) { | ) : FunctionalTimer(studyTime) { | ||||||
| 
 | 
 | ||||||
|     var breaksRemaining = repeats |     var breaksRemaining = repeats | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package be.ugent.sel.studeez.data.local.models.timer_functional | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.data.local.models.SessionReport | import be.ugent.sel.studeez.data.local.models.SessionReport | ||||||
| import com.google.firebase.Timestamp | import com.google.firebase.Timestamp | ||||||
|  | import com.google.firebase.firestore.DocumentReference | ||||||
| 
 | 
 | ||||||
| abstract class FunctionalTimer(initialValue: Int) { | abstract class FunctionalTimer(initialValue: Int) { | ||||||
|     var time: Time = Time(initialValue) |     var time: Time = Time(initialValue) | ||||||
|  | @ -17,10 +18,12 @@ abstract class FunctionalTimer(initialValue: Int) { | ||||||
| 
 | 
 | ||||||
|     abstract fun hasCurrentCountdownEnded(): Boolean |     abstract fun hasCurrentCountdownEnded(): Boolean | ||||||
| 
 | 
 | ||||||
|     fun getSessionReport(): SessionReport { |     fun getSessionReport(subjectId: String, taskId: String): SessionReport { | ||||||
|         return SessionReport( |         return SessionReport( | ||||||
|             studyTime = totalStudyTime, |             studyTime = totalStudyTime, | ||||||
|             endTime = Timestamp.now() |             endTime = Timestamp.now(), | ||||||
|  |             taskId = taskId, | ||||||
|  |             subjectId = subjectId | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ class PomodoroTimerInfo( | ||||||
|     description: String, |     description: String, | ||||||
|     var studyTime: Int, |     var studyTime: Int, | ||||||
|     var breakTime: Int, |     var breakTime: Int, | ||||||
|     val repeats: Int, |     var repeats: Int, | ||||||
|     id: String = "" |     id: String = "" | ||||||
| ):  TimerInfo(id, name, description) { | ):  TimerInfo(id, name, description) { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -33,4 +33,7 @@ abstract class DatabaseModule { | ||||||
| 
 | 
 | ||||||
|     @Binds |     @Binds | ||||||
|     abstract fun provideTaskDAO(impl: FireBaseTaskDAO): TaskDAO |     abstract fun provideTaskDAO(impl: FireBaseTaskDAO): TaskDAO | ||||||
|  | 
 | ||||||
|  |     @Binds | ||||||
|  |     abstract fun provideFeedDAO(impl: FirebaseFeedDAO): FeedDAO | ||||||
| } | } | ||||||
							
								
								
									
										10
									
								
								app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/src/main/java/be/ugent/sel/studeez/domain/FeedDAO.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | package be.ugent.sel.studeez.domain | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.FeedEntry | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | 
 | ||||||
|  | interface FeedDAO { | ||||||
|  | 
 | ||||||
|  |     fun getFeedEntries(): Flow<Map<String, List<FeedEntry>>> | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -12,4 +12,10 @@ interface SubjectDAO { | ||||||
|     fun deleteSubject(oldSubject: Subject) |     fun deleteSubject(oldSubject: Subject) | ||||||
| 
 | 
 | ||||||
|     fun updateSubject(newSubject: Subject) |     fun updateSubject(newSubject: Subject) | ||||||
|  | 
 | ||||||
|  |     suspend fun getTaskCount(subject: Subject): Int | ||||||
|  |     suspend fun getCompletedTaskCount(subject: Subject): Int | ||||||
|  |     fun getStudyTime(subject: Subject): Flow<Int> | ||||||
|  | 
 | ||||||
|  |     suspend fun getSubject(subjectId: String): Subject? | ||||||
| } | } | ||||||
|  | @ -14,5 +14,5 @@ interface TaskDAO { | ||||||
| 
 | 
 | ||||||
|     fun deleteTask(oldTask: Task) |     fun deleteTask(oldTask: Task) | ||||||
| 
 | 
 | ||||||
|     fun toggleTaskCompleted(task: Task, completed: Boolean) |     suspend fun getTask(subjectId: String, taskId: String): Task | ||||||
| } | } | ||||||
|  | @ -1,23 +1,42 @@ | ||||||
| package be.ugent.sel.studeez.domain.implementation | package be.ugent.sel.studeez.domain.implementation | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Subject | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.SubjectDocument | ||||||
| import be.ugent.sel.studeez.domain.AccountDAO | import be.ugent.sel.studeez.domain.AccountDAO | ||||||
| import be.ugent.sel.studeez.domain.SubjectDAO | import be.ugent.sel.studeez.domain.SubjectDAO | ||||||
|  | import be.ugent.sel.studeez.domain.TaskDAO | ||||||
|  | import com.google.firebase.firestore.AggregateSource | ||||||
| import com.google.firebase.firestore.CollectionReference | import com.google.firebase.firestore.CollectionReference | ||||||
| import com.google.firebase.firestore.FirebaseFirestore | import com.google.firebase.firestore.FirebaseFirestore | ||||||
|  | import com.google.firebase.firestore.Query | ||||||
| import com.google.firebase.firestore.ktx.snapshots | import com.google.firebase.firestore.ktx.snapshots | ||||||
|  | import com.google.firebase.firestore.ktx.toObject | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
| import kotlinx.coroutines.flow.map | import kotlinx.coroutines.flow.map | ||||||
|  | import kotlinx.coroutines.tasks.await | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| class FireBaseSubjectDAO @Inject constructor( | class FireBaseSubjectDAO @Inject constructor( | ||||||
|     private val firestore: FirebaseFirestore, |     private val firestore: FirebaseFirestore, | ||||||
|     private val auth: AccountDAO, |     private val auth: AccountDAO, | ||||||
|  |     private val taskDAO: TaskDAO, | ||||||
| ) : SubjectDAO { | ) : SubjectDAO { | ||||||
|     override fun getSubjects(): Flow<List<Subject>> { |     override fun getSubjects(): Flow<List<Subject>> { | ||||||
|         return currentUserSubjectsCollection() |         return currentUserSubjectsCollection() | ||||||
|  |             .subjectNotArchived() | ||||||
|             .snapshots() |             .snapshots() | ||||||
|             .map { it.toObjects(Subject::class.java) } |             .map { it.toObjects(Subject::class.java) } | ||||||
|  |             .map { subjects -> | ||||||
|  |                 subjects.map { subject -> | ||||||
|  |                     subject.taskCount = getTaskCount(subject) | ||||||
|  |                     subject.taskCompletedCount = getCompletedTaskCount(subject) | ||||||
|  |                     subject | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override suspend fun getSubject(subjectId: String): Subject? { | ||||||
|  |         return currentUserSubjectsCollection().document(subjectId).get().await().toObject() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun saveSubject(newSubject: Subject) { |     override fun saveSubject(newSubject: Subject) { | ||||||
|  | @ -32,8 +51,45 @@ class FireBaseSubjectDAO @Inject constructor( | ||||||
|         currentUserSubjectsCollection().document(newSubject.id).set(newSubject) |         currentUserSubjectsCollection().document(newSubject.id).set(newSubject) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     override suspend fun getTaskCount(subject: Subject): Int { | ||||||
|  |         return subjectTasksCollection(subject) | ||||||
|  |             .taskNotArchived() | ||||||
|  |             .count() | ||||||
|  |             .get(AggregateSource.SERVER) | ||||||
|  |             .await() | ||||||
|  |             .count.toInt() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override suspend fun getCompletedTaskCount(subject: Subject): Int { | ||||||
|  |         return subjectTasksCollection(subject) | ||||||
|  |             .taskNotArchived() | ||||||
|  |             .taskNotCompleted() | ||||||
|  |             .count() | ||||||
|  |             .get(AggregateSource.SERVER) | ||||||
|  |             .await() | ||||||
|  |             .count.toInt() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun getStudyTime(subject: Subject): Flow<Int> { | ||||||
|  |         return taskDAO.getTasks(subject) | ||||||
|  |             .map { tasks -> tasks.sumOf { it.time } } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private fun currentUserSubjectsCollection(): CollectionReference = |     private fun currentUserSubjectsCollection(): CollectionReference = | ||||||
|         firestore.collection(FireBaseCollections.USER_COLLECTION) |         firestore.collection(FireBaseCollections.USER_COLLECTION) | ||||||
|             .document(auth.currentUserId) |             .document(auth.currentUserId) | ||||||
|             .collection(FireBaseCollections.SUBJECT_COLLECTION) |             .collection(FireBaseCollections.SUBJECT_COLLECTION) | ||||||
|  | 
 | ||||||
|  |     private fun subjectTasksCollection(subject: Subject): CollectionReference = | ||||||
|  |         firestore.collection(FireBaseCollections.USER_COLLECTION) | ||||||
|  |             .document(auth.currentUserId) | ||||||
|  |             .collection(FireBaseCollections.SUBJECT_COLLECTION) | ||||||
|  |             .document(subject.id) | ||||||
|  |             .collection(FireBaseCollections.TASK_COLLECTION) | ||||||
|  | 
 | ||||||
|  |     fun CollectionReference.subjectNotArchived(): Query = | ||||||
|  |         this.whereEqualTo(SubjectDocument.archived, false) | ||||||
|  | 
 | ||||||
|  |     fun Query.subjectNotArchived(): Query = | ||||||
|  |         this.whereEqualTo(SubjectDocument.archived, false) | ||||||
| } | } | ||||||
|  | @ -7,9 +7,12 @@ import be.ugent.sel.studeez.domain.AccountDAO | ||||||
| import be.ugent.sel.studeez.domain.TaskDAO | import be.ugent.sel.studeez.domain.TaskDAO | ||||||
| import com.google.firebase.firestore.CollectionReference | import com.google.firebase.firestore.CollectionReference | ||||||
| import com.google.firebase.firestore.FirebaseFirestore | import com.google.firebase.firestore.FirebaseFirestore | ||||||
|  | import com.google.firebase.firestore.Query | ||||||
| import com.google.firebase.firestore.ktx.snapshots | import com.google.firebase.firestore.ktx.snapshots | ||||||
|  | import com.google.firebase.firestore.ktx.toObject | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
| import kotlinx.coroutines.flow.map | import kotlinx.coroutines.flow.map | ||||||
|  | import kotlinx.coroutines.tasks.await | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| class FireBaseTaskDAO @Inject constructor( | class FireBaseTaskDAO @Inject constructor( | ||||||
|  | @ -18,32 +21,48 @@ class FireBaseTaskDAO @Inject constructor( | ||||||
| ) : TaskDAO { | ) : TaskDAO { | ||||||
|     override fun getTasks(subject: Subject): Flow<List<Task>> { |     override fun getTasks(subject: Subject): Flow<List<Task>> { | ||||||
|         return selectedSubjectTasksCollection(subject.id) |         return selectedSubjectTasksCollection(subject.id) | ||||||
|  |             .taskNotArchived() | ||||||
|             .snapshots() |             .snapshots() | ||||||
|             .map { it.toObjects(Task::class.java) } |             .map { it.toObjects(Task::class.java) } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     override suspend fun getTask(subjectId: String, taskId: String): Task { | ||||||
|  |         return selectedSubjectTasksCollection(subjectId).document(taskId).get().await().toObject()!! | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     override fun saveTask(newTask: Task) { |     override fun saveTask(newTask: Task) { | ||||||
|         selectedSubjectTasksCollection(newTask.subjectId).add(newTask) |         selectedSubjectTasksCollection(newTask.subjectId).add(newTask) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun updateTask(newTask: Task) { |     override fun updateTask(newTask: Task) { | ||||||
|         selectedSubjectTasksCollection(newTask.id).document(newTask.id).set(newTask) |         selectedSubjectTasksCollection(newTask.subjectId) | ||||||
|  |             .document(newTask.id) | ||||||
|  |             .set(newTask) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun deleteTask(oldTask: Task) { |     override fun deleteTask(oldTask: Task) { | ||||||
|         selectedSubjectTasksCollection(oldTask.subjectId).document(oldTask.id).delete() |         selectedSubjectTasksCollection(oldTask.subjectId).document(oldTask.id).delete() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun toggleTaskCompleted(task: Task, completed: Boolean) { |  | ||||||
|         selectedSubjectTasksCollection(task.subjectId) |  | ||||||
|             .document(task.id) |  | ||||||
|             .update(TaskDocument.completed, completed) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private fun selectedSubjectTasksCollection(subjectId: String): CollectionReference = |     private fun selectedSubjectTasksCollection(subjectId: String): CollectionReference = | ||||||
|         firestore.collection(FireBaseCollections.USER_COLLECTION) |         firestore.collection(FireBaseCollections.USER_COLLECTION) | ||||||
|             .document(auth.currentUserId) |             .document(auth.currentUserId) | ||||||
|             .collection(FireBaseCollections.SUBJECT_COLLECTION) |             .collection(FireBaseCollections.SUBJECT_COLLECTION) | ||||||
|             .document(subjectId) |             .document(subjectId) | ||||||
|             .collection(FireBaseCollections.TASK_COLLECTION) |             .collection(FireBaseCollections.TASK_COLLECTION) | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // Extend CollectionReference and Query with some filters | ||||||
|  | 
 | ||||||
|  | fun CollectionReference.taskNotArchived(): Query = | ||||||
|  |     this.whereEqualTo(TaskDocument.archived, false) | ||||||
|  | 
 | ||||||
|  | fun Query.taskNotArchived(): Query = | ||||||
|  |     this.whereEqualTo(TaskDocument.archived, false) | ||||||
|  | 
 | ||||||
|  | fun CollectionReference.taskNotCompleted(): Query = | ||||||
|  |     this.whereEqualTo(TaskDocument.completed, true) | ||||||
|  | 
 | ||||||
|  | fun Query.taskNotCompleted(): Query = | ||||||
|  |     this.whereEqualTo(TaskDocument.completed, true) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,81 @@ | ||||||
|  | package be.ugent.sel.studeez.domain.implementation | ||||||
|  | 
 | ||||||
|  | import android.icu.text.DateFormat | ||||||
|  | import be.ugent.sel.studeez.data.local.models.FeedEntry | ||||||
|  | import be.ugent.sel.studeez.data.local.models.SessionReport | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Task | ||||||
|  | import be.ugent.sel.studeez.domain.FeedDAO | ||||||
|  | import be.ugent.sel.studeez.domain.SessionDAO | ||||||
|  | import be.ugent.sel.studeez.domain.SubjectDAO | ||||||
|  | import be.ugent.sel.studeez.domain.TaskDAO | ||||||
|  | import com.google.firebase.Timestamp | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.map | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | class FirebaseFeedDAO @Inject constructor( | ||||||
|  |     private val sessionDAO: SessionDAO, | ||||||
|  |     private val taskDAO: TaskDAO, | ||||||
|  |     private val subjectDAO: SubjectDAO | ||||||
|  | ) : FeedDAO { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      *  Return a map as with key the day and value a list of feedentries for that day. | ||||||
|  |      */ | ||||||
|  |     override fun getFeedEntries(): Flow<Map<String, List<FeedEntry>>> { | ||||||
|  |         return sessionDAO.getSessions().map { sessionReports -> | ||||||
|  |             sessionReports | ||||||
|  |                 .map { sessionReport -> sessionToFeedEntry(sessionReport) } | ||||||
|  |                 .sortedByDescending { it.endTime } | ||||||
|  |                 .groupBy { getFormattedTime(it) } | ||||||
|  |                 .mapValues { (_, entries) -> | ||||||
|  |                     entries | ||||||
|  |                         .groupBy { it.taskId } | ||||||
|  |                         .map { fuseFeedEntries(it.component2()) } | ||||||
|  |                 } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun getFormattedTime(entry: FeedEntry): String { | ||||||
|  |         return DateFormat.getDateInstance().format(entry.endTime.toDate()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Givin a list of entries referencing the same task, in the same day, fuse them into one | ||||||
|  |      * feed-entry by adding the studytime and keeping the most recent end-timestamp | ||||||
|  |      */ | ||||||
|  |     private fun fuseFeedEntries(entries: List<FeedEntry>): FeedEntry = | ||||||
|  |         entries.drop(1).fold(entries[0]) { accEntry, newEntry -> | ||||||
|  |             accEntry.copy( | ||||||
|  |                 totalStudyTime = accEntry.totalStudyTime + newEntry.totalStudyTime, | ||||||
|  |                 endTime = getMostRecent(accEntry.endTime, newEntry.endTime) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     private fun getMostRecent(t1: Timestamp, t2: Timestamp): Timestamp { | ||||||
|  |         return if (t1 < t2) t2 else t1 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Convert a sessionReport to a feedEntry. Fetch Task and Subject to get names | ||||||
|  |      */ | ||||||
|  |     private suspend fun sessionToFeedEntry(sessionReport: SessionReport): FeedEntry { | ||||||
|  |         val subjectId: String = sessionReport.subjectId | ||||||
|  |         val taskId: String = sessionReport.taskId | ||||||
|  | 
 | ||||||
|  |         val task: Task = taskDAO.getTask(subjectId, taskId) | ||||||
|  |         val subject: Subject = subjectDAO.getSubject(subjectId)!! | ||||||
|  | 
 | ||||||
|  |         return FeedEntry( | ||||||
|  |             argb_color = subject.argb_color, | ||||||
|  |             subJectName = subject.name, | ||||||
|  |             taskName = task.name, | ||||||
|  |             taskId = task.id, | ||||||
|  |             subjectId = subject.id, | ||||||
|  |             totalStudyTime = sessionReport.studyTime, | ||||||
|  |             endTime = sessionReport.endTime, | ||||||
|  |             isArchived = task.archived || subject.archived | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -19,6 +19,7 @@ object StudeezDestinations { | ||||||
|     // Studying flow |     // Studying flow | ||||||
|     const val TIMER_SELECTION_SCREEN = "timer_selection" |     const val TIMER_SELECTION_SCREEN = "timer_selection" | ||||||
|     const val TIMER_EDIT_SCREEN = "timer_edit" |     const val TIMER_EDIT_SCREEN = "timer_edit" | ||||||
|  |     const val TIMER_TYPE_CHOOSING_SCREEN = "timer_type_choosing_screen" | ||||||
|     const val SESSION_SCREEN = "session" |     const val SESSION_SCREEN = "session" | ||||||
|     const val SESSION_RECAP = "session_recap" |     const val SESSION_RECAP = "session_recap" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,245 @@ | ||||||
|  | package be.ugent.sel.studeez.navigation | ||||||
|  | 
 | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.getValue | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.hilt.navigation.compose.hiltViewModel | ||||||
|  | import androidx.navigation.compose.NavHost | ||||||
|  | import androidx.navigation.compose.composable | ||||||
|  | import androidx.navigation.compose.currentBackStackEntryAsState | ||||||
|  | import be.ugent.sel.studeez.StudeezAppstate | ||||||
|  | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
|  | import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel | ||||||
|  | import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions | ||||||
|  | import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | ||||||
|  | import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel | ||||||
|  | import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions | ||||||
|  | import be.ugent.sel.studeez.screens.home.HomeRoute | ||||||
|  | import be.ugent.sel.studeez.screens.log_in.LoginRoute | ||||||
|  | import be.ugent.sel.studeez.screens.profile.EditProfileRoute | ||||||
|  | import be.ugent.sel.studeez.screens.profile.ProfileRoute | ||||||
|  | import be.ugent.sel.studeez.screens.session.SessionRoute | ||||||
|  | import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute | ||||||
|  | import be.ugent.sel.studeez.screens.sessions.SessionsRoute | ||||||
|  | import be.ugent.sel.studeez.screens.settings.SettingsRoute | ||||||
|  | import be.ugent.sel.studeez.screens.sign_up.SignUpRoute | ||||||
|  | import be.ugent.sel.studeez.screens.splash.SplashRoute | ||||||
|  | import be.ugent.sel.studeez.screens.subjects.SubjectRoute | ||||||
|  | import be.ugent.sel.studeez.screens.tasks.TaskRoute | ||||||
|  | import be.ugent.sel.studeez.screens.subjects.form.SubjectCreateRoute | ||||||
|  | import be.ugent.sel.studeez.screens.subjects.form.SubjectEditRoute | ||||||
|  | import be.ugent.sel.studeez.screens.tasks.form.TaskCreateRoute | ||||||
|  | import be.ugent.sel.studeez.screens.tasks.form.TaskEditRoute | ||||||
|  | import be.ugent.sel.studeez.screens.timer_form.TimerAddRoute | ||||||
|  | import be.ugent.sel.studeez.screens.timer_form.TimerEditRoute | ||||||
|  | import be.ugent.sel.studeez.screens.timer_form.timer_type_select.TimerTypeSelectScreen | ||||||
|  | import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute | ||||||
|  | import be.ugent.sel.studeez.screens.timer_selection.TimerSelectionRoute | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun StudeezNavGraph( | ||||||
|  |     appState: StudeezAppstate, | ||||||
|  |     modifier: Modifier = Modifier, | ||||||
|  | ) { | ||||||
|  |     val drawerViewModel: DrawerViewModel = hiltViewModel() | ||||||
|  |     val navBarViewModel: NavigationBarViewModel = hiltViewModel() | ||||||
|  | 
 | ||||||
|  |     val backStackEntry by appState.navController.currentBackStackEntryAsState() | ||||||
|  |     val getCurrentScreen: () -> String? = { backStackEntry?.destination?.route } | ||||||
|  | 
 | ||||||
|  |     val goBack: () -> Unit = { appState.popUp() } | ||||||
|  |     val open: (String) -> Unit = { appState.navigate(it) } | ||||||
|  |     val openAndPopUp: (String, String) -> Unit = | ||||||
|  |         { route, popUp -> appState.navigateAndPopUp(route, popUp) } | ||||||
|  | 
 | ||||||
|  |     val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp) | ||||||
|  |     val navigationBarActions: NavigationBarActions = | ||||||
|  |         getNavigationBarActions(navBarViewModel, open, getCurrentScreen) | ||||||
|  | 
 | ||||||
|  |     NavHost( | ||||||
|  |         navController = appState.navController, | ||||||
|  |         startDestination = StudeezDestinations.SPLASH_SCREEN, | ||||||
|  |         modifier = modifier, | ||||||
|  |     ) { | ||||||
|  |         // NavBar | ||||||
|  |         composable(StudeezDestinations.HOME_SCREEN) { | ||||||
|  |             HomeRoute( | ||||||
|  |                 open, | ||||||
|  |                 drawerActions = drawerActions, | ||||||
|  |                 navigationBarActions = navigationBarActions, | ||||||
|  |                 feedViewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.SUBJECT_SCREEN) { | ||||||
|  |             SubjectRoute( | ||||||
|  |                 open = open, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |                 drawerActions = drawerActions, | ||||||
|  |                 navigationBarActions = navigationBarActions, | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.ADD_SUBJECT_FORM) { | ||||||
|  |             SubjectCreateRoute( | ||||||
|  |                 goBack = goBack, | ||||||
|  |                 openAndPopUp = openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.EDIT_SUBJECT_FORM) { | ||||||
|  |             SubjectEditRoute( | ||||||
|  |                 goBack = goBack, | ||||||
|  |                 openAndPopUp = openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.TASKS_SCREEN) { | ||||||
|  |             TaskRoute( | ||||||
|  |                 goBack = goBack, | ||||||
|  |                 open = open, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.ADD_TASK_FORM) { | ||||||
|  |             TaskCreateRoute( | ||||||
|  |                 goBack = goBack, | ||||||
|  |                 openAndPopUp = openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.EDIT_TASK_FORM) { | ||||||
|  |             TaskEditRoute( | ||||||
|  |                 goBack = goBack, | ||||||
|  |                 openAndPopUp = openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.SESSIONS_SCREEN) { | ||||||
|  |             SessionsRoute( | ||||||
|  |                 drawerActions = drawerActions, | ||||||
|  |                 navigationBarActions = navigationBarActions | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.PROFILE_SCREEN) { | ||||||
|  |             ProfileRoute( | ||||||
|  |                 open, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |                 drawerActions = drawerActions, | ||||||
|  |                 navigationBarActions = navigationBarActions, | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Drawer | ||||||
|  |         composable(StudeezDestinations.TIMER_SCREEN) { | ||||||
|  |             TimerOverviewRoute( | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |                 drawerActions = drawerActions, | ||||||
|  |                 open = open | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.SETTINGS_SCREEN) { | ||||||
|  |             SettingsRoute( | ||||||
|  |                 drawerActions = drawerActions | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Login flow | ||||||
|  |         composable(StudeezDestinations.SPLASH_SCREEN) { | ||||||
|  |             SplashRoute( | ||||||
|  |                 openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.LOGIN_SCREEN) { | ||||||
|  |             LoginRoute( | ||||||
|  |                 openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.SIGN_UP_SCREEN) { | ||||||
|  |             SignUpRoute( | ||||||
|  |                 openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Studying flow | ||||||
|  |         composable(StudeezDestinations.TIMER_SELECTION_SCREEN) { | ||||||
|  |             TimerSelectionRoute( | ||||||
|  |                 open, | ||||||
|  |                 goBack, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.TIMER_TYPE_CHOOSING_SCREEN) { | ||||||
|  |             TimerTypeSelectScreen( | ||||||
|  |                 open = open, | ||||||
|  |                 popUp = goBack | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.SESSION_SCREEN) { | ||||||
|  |             SessionRoute( | ||||||
|  |                 open, | ||||||
|  |                 openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.SESSION_RECAP) { | ||||||
|  |             SessionRecapRoute( | ||||||
|  |                 openAndPopUp = openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.ADD_TIMER_SCREEN) { | ||||||
|  |             TimerAddRoute( | ||||||
|  |                 popUp = goBack, | ||||||
|  |                 viewModel = hiltViewModel() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.TIMER_EDIT_SCREEN) { | ||||||
|  |             TimerEditRoute( | ||||||
|  |                 popUp = goBack, | ||||||
|  |                 viewModel = hiltViewModel() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Friends flow | ||||||
|  |         composable(StudeezDestinations.SEARCH_FRIENDS_SCREEN) { | ||||||
|  |             // TODO | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Create & edit screens | ||||||
|  |         composable(StudeezDestinations.CREATE_TASK_SCREEN) { | ||||||
|  |             // TODO | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.CREATE_SESSION_SCREEN) { | ||||||
|  |             // TODO | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.EDIT_PROFILE_SCREEN) { | ||||||
|  |             EditProfileRoute( | ||||||
|  |                 goBack, | ||||||
|  |                 openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -5,35 +5,45 @@ import androidx.compose.material.IconButton | ||||||
| import androidx.compose.material.icons.Icons | import androidx.compose.material.icons.Icons | ||||||
| import androidx.compose.material.icons.filled.Person | import androidx.compose.material.icons.filled.Person | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.runtime.collectAsState | ||||||
|  | import androidx.compose.runtime.getValue | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.composable.BasicButton |  | ||||||
| import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
|  | import be.ugent.sel.studeez.common.composable.feed.Feed | ||||||
|  | import be.ugent.sel.studeez.common.composable.feed.FeedUiState | ||||||
|  | import be.ugent.sel.studeez.common.composable.feed.FeedViewModel | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | ||||||
| import be.ugent.sel.studeez.common.ext.basicButton | import be.ugent.sel.studeez.data.local.models.FeedEntry | ||||||
| import be.ugent.sel.studeez.resources | import be.ugent.sel.studeez.resources | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun HomeRoute( | fun HomeRoute( | ||||||
|     open: (String) -> Unit, |     open: (String) -> Unit, | ||||||
|     viewModel: HomeViewModel, |  | ||||||
|     drawerActions: DrawerActions, |     drawerActions: DrawerActions, | ||||||
|     navigationBarActions: NavigationBarActions, |     navigationBarActions: NavigationBarActions, | ||||||
|  |     feedViewModel: FeedViewModel, | ||||||
| ) { | ) { | ||||||
|  |     val feedUiState by feedViewModel.uiState.collectAsState() | ||||||
|     HomeScreen( |     HomeScreen( | ||||||
|         onStartSessionClick = { viewModel.onStartSessionClick(open) }, |  | ||||||
|         drawerActions = drawerActions, |         drawerActions = drawerActions, | ||||||
|  |         open = open, | ||||||
|         navigationBarActions = navigationBarActions, |         navigationBarActions = navigationBarActions, | ||||||
|  |         feedUiState = feedUiState, | ||||||
|  |         continueTask = { subjectId, taskId -> feedViewModel.continueTask(open, subjectId, taskId) }, | ||||||
|  |         onEmptyFeedHelp = { feedViewModel.onEmptyFeedHelp(open) } | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun HomeScreen( | fun HomeScreen( | ||||||
|     onStartSessionClick: () -> Unit, |     open: (String) -> Unit, | ||||||
|     drawerActions: DrawerActions, |     drawerActions: DrawerActions, | ||||||
|     navigationBarActions: NavigationBarActions |     navigationBarActions: NavigationBarActions, | ||||||
|  |     feedUiState: FeedUiState, | ||||||
|  |     continueTask: (String, String) -> Unit, | ||||||
|  |     onEmptyFeedHelp: () -> Unit, | ||||||
| ) { | ) { | ||||||
|     PrimaryScreenTemplate( |     PrimaryScreenTemplate( | ||||||
|         title = resources().getString(R.string.home), |         title = resources().getString(R.string.home), | ||||||
|  | @ -41,9 +51,7 @@ fun HomeScreen( | ||||||
|         navigationBarActions = navigationBarActions, |         navigationBarActions = navigationBarActions, | ||||||
|         // TODO barAction = { FriendsAction() } |         // TODO barAction = { FriendsAction() } | ||||||
|     ) { |     ) { | ||||||
|         BasicButton(R.string.start_session, Modifier.basicButton()) { |         Feed(feedUiState, continueTask, onEmptyFeedHelp) | ||||||
|             onStartSessionClick() |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -61,8 +69,40 @@ fun FriendsAction() { | ||||||
| @Composable | @Composable | ||||||
| fun HomeScreenPreview() { | fun HomeScreenPreview() { | ||||||
|     HomeScreen( |     HomeScreen( | ||||||
|         onStartSessionClick = {}, |  | ||||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}), |         drawerActions = DrawerActions({}, {}, {}, {}, {}), | ||||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}) |         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), | ||||||
|  |         open = {}, | ||||||
|  |         feedUiState = FeedUiState.Succes( | ||||||
|  |             mapOf( | ||||||
|  |                 "08 May 2023" to listOf( | ||||||
|  |                     FeedEntry( | ||||||
|  |                         argb_color = 0xFFABD200, | ||||||
|  |                         subJectName = "Test Subject", | ||||||
|  |                         taskName = "Test Task", | ||||||
|  |                         totalStudyTime = 600, | ||||||
|  |                     ), | ||||||
|  |                     FeedEntry( | ||||||
|  |                         argb_color = 0xFFFFD200, | ||||||
|  |                         subJectName = "Test Subject", | ||||||
|  |                         taskName = "Test Task", | ||||||
|  |                         totalStudyTime = 20, | ||||||
|  |                     ), | ||||||
|  |                 ), | ||||||
|  |                 "09 May 2023" to listOf( | ||||||
|  |                     FeedEntry( | ||||||
|  |                         argb_color = 0xFFFD1200, | ||||||
|  |                         subJectName = "Test Subject", | ||||||
|  |                         taskName = "Test Task", | ||||||
|  |                     ), | ||||||
|  |                     FeedEntry( | ||||||
|  |                         argb_color = 0xFFFF5C89, | ||||||
|  |                         subJectName = "Test Subject", | ||||||
|  |                         taskName = "Test Task", | ||||||
|  |                     ), | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |         ), | ||||||
|  |         continueTask = { _, _ -> run {} }, | ||||||
|  |         onEmptyFeedHelp = {} | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,19 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.home |  | ||||||
| 
 |  | ||||||
| import be.ugent.sel.studeez.domain.AccountDAO |  | ||||||
| import be.ugent.sel.studeez.domain.LogService |  | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations |  | ||||||
| import be.ugent.sel.studeez.screens.StudeezViewModel |  | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel |  | ||||||
| import javax.inject.Inject |  | ||||||
| 
 |  | ||||||
| @HiltViewModel |  | ||||||
| class HomeViewModel @Inject constructor( |  | ||||||
|     private val accountDAO: AccountDAO, |  | ||||||
|     logService: LogService |  | ||||||
| ) : StudeezViewModel(logService) { |  | ||||||
| 
 |  | ||||||
|     fun onStartSessionClick(open: (String) -> Unit) { |  | ||||||
|         open(StudeezDestinations.TIMER_SELECTION_SCREEN) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| package be.ugent.sel.studeez.screens.session | package be.ugent.sel.studeez.screens.session | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.data.SelectedTimerState | import be.ugent.sel.studeez.data.SelectedSessionReport | ||||||
| import be.ugent.sel.studeez.data.SessionReportState | import be.ugent.sel.studeez.data.SelectedTask | ||||||
|  | import be.ugent.sel.studeez.data.SelectedTimer | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
|  | @ -11,23 +12,21 @@ import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| @HiltViewModel | @HiltViewModel | ||||||
| class SessionViewModel @Inject constructor( | class SessionViewModel @Inject constructor( | ||||||
|     private val selectedTimerState: SelectedTimerState, |     private val selectedTimer: SelectedTimer, | ||||||
|     private val sessionReportState: SessionReportState, |     private val sessionReport: SelectedSessionReport, | ||||||
|  |     private val selectedTask: SelectedTask, | ||||||
|     logService: LogService |     logService: LogService | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
| 
 |     fun getTimer(): FunctionalTimer { | ||||||
|     private val task : String = "No task selected" // placeholder for tasks implementation |         return selectedTimer() | ||||||
| 
 |  | ||||||
|     fun getTimer() : FunctionalTimer { |  | ||||||
|         return selectedTimerState.selectedTimer!! |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun getTask(): String { |     fun getTask(): String { | ||||||
|         return task |         return selectedTask().name | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun endSession(openAndPopUp: (String, String) -> Unit) { |     fun endSession(openAndPopUp: (String, String) -> Unit) { | ||||||
|         sessionReportState.sessionReport = getTimer().getSessionReport() |         sessionReport.set(getTimer().getSessionReport(selectedTask().subjectId, selectedTask().id)) | ||||||
|         openAndPopUp(StudeezDestinations.SESSION_RECAP, StudeezDestinations.SESSION_SCREEN) |         openAndPopUp(StudeezDestinations.SESSION_RECAP, StudeezDestinations.SESSION_SCREEN) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -100,6 +100,8 @@ abstract class AbstractSessionScreen { | ||||||
|                 fontSize = 30.sp |                 fontSize = 30.sp | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|  |             MidSection() | ||||||
|  | 
 | ||||||
|             Box( |             Box( | ||||||
|                 contentAlignment = Alignment.Center, modifier = Modifier |                 contentAlignment = Alignment.Center, modifier = Modifier | ||||||
|                     .fillMaxWidth() |                     .fillMaxWidth() | ||||||
|  | @ -126,6 +128,11 @@ abstract class AbstractSessionScreen { | ||||||
|     @Composable |     @Composable | ||||||
|     abstract fun motivationString(): String |     abstract fun motivationString(): String | ||||||
| 
 | 
 | ||||||
|  |     @Composable | ||||||
|  |     open fun MidSection() { | ||||||
|  |         // Default has no midsection, unless overwritten. | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     abstract fun callMediaPlayer() |     abstract fun callMediaPlayer() | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,20 @@ | ||||||
| package be.ugent.sel.studeez.screens.session.sessionScreens | package be.ugent.sel.studeez.screens.session.sessionScreens | ||||||
| 
 | 
 | ||||||
| import android.media.MediaPlayer | import android.media.MediaPlayer | ||||||
|  | import androidx.compose.foundation.background | ||||||
|  | import androidx.compose.foundation.layout.* | ||||||
|  | import androidx.compose.foundation.shape.CircleShape | ||||||
|  | import androidx.compose.material.Text | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.Alignment | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.draw.clip | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import androidx.compose.ui.text.font.FontWeight | ||||||
|  | import androidx.compose.ui.text.style.TextAlign | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import androidx.compose.ui.unit.sp | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer | import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer | ||||||
| import be.ugent.sel.studeez.resources | import be.ugent.sel.studeez.resources | ||||||
|  | @ -12,6 +25,37 @@ class BreakSessionScreen( | ||||||
|     private var mediaplayer: MediaPlayer? |     private var mediaplayer: MediaPlayer? | ||||||
| ): AbstractSessionScreen() { | ): AbstractSessionScreen() { | ||||||
| 
 | 
 | ||||||
|  |     @Composable | ||||||
|  |     override fun MidSection() { | ||||||
|  |         Dots() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Composable | ||||||
|  |     fun Dots() { | ||||||
|  |         Row( | ||||||
|  |             modifier = Modifier.fillMaxWidth(), | ||||||
|  |             verticalAlignment = Alignment.CenterVertically, | ||||||
|  |             horizontalArrangement = Arrangement.Center, | ||||||
|  |         ) { | ||||||
|  |             repeat(funPomoDoroTimer.repeats - funPomoDoroTimer.breaksRemaining) { | ||||||
|  |                 Dot(color = Color.DarkGray) | ||||||
|  |             } | ||||||
|  |             if (!funPomoDoroTimer.isInBreak) Dot(Color.Green) else Dot(Color.DarkGray) | ||||||
|  |             repeat(funPomoDoroTimer.breaksRemaining - 1) { | ||||||
|  |                 Dot(color = Color.Gray) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Composable | ||||||
|  |     private fun Dot(color: Color) { | ||||||
|  |         Box(modifier = Modifier | ||||||
|  |             .padding(5.dp) | ||||||
|  |             .size(10.dp) | ||||||
|  |             .clip(CircleShape) | ||||||
|  |             .background(color)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Composable |     @Composable | ||||||
|     override fun motivationString(): String { |     override fun motivationString(): String { | ||||||
|         if (funPomoDoroTimer.isInBreak) { |         if (funPomoDoroTimer.isInBreak) { | ||||||
|  | @ -22,11 +66,7 @@ class BreakSessionScreen( | ||||||
|             return resources().getString(AppText.state_done) |             return resources().getString(AppText.state_done) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return resources().getQuantityString( |         return resources().getString(AppText.state_focus) | ||||||
|             R.plurals.state_focus_remaining, |  | ||||||
|             funPomoDoroTimer.breaksRemaining, |  | ||||||
|             funPomoDoroTimer.breaksRemaining |  | ||||||
|         ) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun callMediaPlayer() { |     override fun callMediaPlayer() { | ||||||
|  | @ -43,3 +83,11 @@ class BreakSessionScreen( | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun MidsectionPreview() { | ||||||
|  |     val funPomoDoroTimer = FunctionalPomodoroTimer(15, 60, 5) | ||||||
|  |     val breakSessionScreen = BreakSessionScreen(funPomoDoroTimer, MediaPlayer()) | ||||||
|  |     breakSessionScreen.MidSection() | ||||||
|  | } | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| package be.ugent.sel.studeez.screens.session_recap | package be.ugent.sel.studeez.screens.session_recap | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.data.SessionReportState | import be.ugent.sel.studeez.data.SelectedSessionReport | ||||||
|  | import be.ugent.sel.studeez.data.SelectedTask | ||||||
| import be.ugent.sel.studeez.data.local.models.SessionReport | import be.ugent.sel.studeez.data.local.models.SessionReport | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
| import be.ugent.sel.studeez.domain.SessionDAO | import be.ugent.sel.studeez.domain.SessionDAO | ||||||
|  | import be.ugent.sel.studeez.domain.TaskDAO | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
| import be.ugent.sel.studeez.screens.StudeezViewModel | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
|  | @ -11,19 +13,22 @@ import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| @HiltViewModel | @HiltViewModel | ||||||
| class SessionRecapViewModel @Inject constructor( | class SessionRecapViewModel @Inject constructor( | ||||||
|     sessionReportState: SessionReportState, |     private val selectedSessionReport: SelectedSessionReport, | ||||||
|     private val sessionDAO: SessionDAO, |     private val sessionDAO: SessionDAO, | ||||||
|  |     private val taskDAO: TaskDAO, | ||||||
|  |     private val selectedTask: SelectedTask, | ||||||
|     logService: LogService |     logService: LogService | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
| 
 | 
 | ||||||
|     private val report: SessionReport = sessionReportState.sessionReport!! |  | ||||||
| 
 |  | ||||||
|     fun getSessionReport(): SessionReport { |     fun getSessionReport(): SessionReport { | ||||||
|         return report |         return selectedSessionReport() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun saveSession(open: (String, String) -> Unit) { |     fun saveSession(open: (String, String) -> Unit) { | ||||||
|         sessionDAO.saveSession(getSessionReport()) |         sessionDAO.saveSession(getSessionReport()) | ||||||
|  |         val newTask = | ||||||
|  |             selectedTask().copy(time = selectedTask().time + selectedSessionReport().studyTime) | ||||||
|  |         taskDAO.updateTask(newTask) | ||||||
|         open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) |         open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,121 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.subjects | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.layout.* | ||||||
|  | import androidx.compose.foundation.lazy.LazyColumn | ||||||
|  | import androidx.compose.foundation.lazy.items | ||||||
|  | import androidx.compose.material.CircularProgressIndicator | ||||||
|  | import androidx.compose.material.MaterialTheme | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.collectAsState | ||||||
|  | import androidx.compose.runtime.getValue | ||||||
|  | import androidx.compose.ui.Alignment | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | ||||||
|  | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
|  | import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | ||||||
|  | import be.ugent.sel.studeez.common.composable.tasks.SubjectEntry | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.flowOf | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SubjectRoute( | ||||||
|  |     open: (String) -> Unit, | ||||||
|  |     viewModel: SubjectViewModel, | ||||||
|  |     drawerActions: DrawerActions, | ||||||
|  |     navigationBarActions: NavigationBarActions, | ||||||
|  | ) { | ||||||
|  |     val uiState by viewModel.uiState.collectAsState() | ||||||
|  |     SubjectScreen( | ||||||
|  |         drawerActions = drawerActions, | ||||||
|  |         navigationBarActions = navigationBarActions, | ||||||
|  |         onAddSubject = { viewModel.onAddSubject(open) }, | ||||||
|  |         onViewSubject = { viewModel.onViewSubject(it, open) }, | ||||||
|  |         getStudyTime = viewModel::getStudyTime, | ||||||
|  |         uiState, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SubjectScreen( | ||||||
|  |     drawerActions: DrawerActions, | ||||||
|  |     navigationBarActions: NavigationBarActions, | ||||||
|  |     onAddSubject: () -> Unit, | ||||||
|  |     onViewSubject: (Subject) -> Unit, | ||||||
|  |     getStudyTime: (Subject) -> Flow<Int>, | ||||||
|  |     uiState: SubjectUiState, | ||||||
|  | ) { | ||||||
|  |     PrimaryScreenTemplate( | ||||||
|  |         title = stringResource(AppText.my_subjects), | ||||||
|  |         drawerActions = drawerActions, | ||||||
|  |         navigationBarActions = navigationBarActions, | ||||||
|  |         barAction = {}, | ||||||
|  |     ) { | ||||||
|  |         when (uiState) { | ||||||
|  |             SubjectUiState.Loading -> Column( | ||||||
|  |                 modifier = Modifier | ||||||
|  |                     .fillMaxWidth() | ||||||
|  |                     .fillMaxHeight(), | ||||||
|  |                 verticalArrangement = Arrangement.Center, | ||||||
|  |                 horizontalAlignment = Alignment.CenterHorizontally | ||||||
|  |             ) { | ||||||
|  |                 CircularProgressIndicator(color = MaterialTheme.colors.onBackground) | ||||||
|  |             } | ||||||
|  |             is SubjectUiState.Succes -> { | ||||||
|  |                 Column( | ||||||
|  |                     modifier = Modifier.padding(top = 5.dp) | ||||||
|  |                 ) { | ||||||
|  |                     NewTaskSubjectButton(onClick = onAddSubject, AppText.new_subject) | ||||||
|  |                     LazyColumn { | ||||||
|  |                         items(uiState.subjects) { | ||||||
|  |                             SubjectEntry( | ||||||
|  |                                 subject = it, | ||||||
|  |                                 onViewSubject = { onViewSubject(it) }, | ||||||
|  |                                 getStudyTime = { getStudyTime(it) }, | ||||||
|  |                             ) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun SubjectScreenPreview() { | ||||||
|  |     SubjectScreen( | ||||||
|  |         drawerActions = DrawerActions({}, {}, {}, {}, {}), | ||||||
|  |         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), | ||||||
|  |         onAddSubject = {}, | ||||||
|  |         onViewSubject = {}, | ||||||
|  |         getStudyTime = { flowOf() }, | ||||||
|  |         uiState = SubjectUiState.Succes( | ||||||
|  |             listOf( | ||||||
|  |                 Subject( | ||||||
|  |                     name = "Test Subject", | ||||||
|  |                     argb_color = 0xFFFFD200, | ||||||
|  |                     taskCount = 5, taskCompletedCount = 2, | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun SubjectScreenLoadingPreview() { | ||||||
|  |     SubjectScreen( | ||||||
|  |         drawerActions = DrawerActions({}, {}, {}, {}, {}), | ||||||
|  |         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), | ||||||
|  |         onAddSubject = {}, | ||||||
|  |         onViewSubject = {}, | ||||||
|  |         getStudyTime = { flowOf() }, | ||||||
|  |         uiState = SubjectUiState.Loading | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.subjects | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | 
 | ||||||
|  | sealed interface SubjectUiState { | ||||||
|  |     object Loading : SubjectUiState | ||||||
|  |     data class Succes(val subjects: List<Subject>) : SubjectUiState | ||||||
|  | } | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.screens.tasks | package be.ugent.sel.studeez.screens.subjects | ||||||
| 
 | 
 | ||||||
|  | import androidx.lifecycle.viewModelScope | ||||||
| import be.ugent.sel.studeez.data.SelectedSubject | import be.ugent.sel.studeez.data.SelectedSubject | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Subject | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
|  | @ -7,7 +8,7 @@ import be.ugent.sel.studeez.domain.SubjectDAO | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
| import be.ugent.sel.studeez.screens.StudeezViewModel | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.* | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| @HiltViewModel | @HiltViewModel | ||||||
|  | @ -16,12 +17,21 @@ class SubjectViewModel @Inject constructor( | ||||||
|     private val selectedSubject: SelectedSubject, |     private val selectedSubject: SelectedSubject, | ||||||
|     logService: LogService, |     logService: LogService, | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
|     fun addSubject(open: (String) -> Unit) { | 
 | ||||||
|  |     val uiState: StateFlow<SubjectUiState> = subjectDAO.getSubjects() | ||||||
|  |         .map { SubjectUiState.Succes(it) } | ||||||
|  |         .stateIn( | ||||||
|  |             scope = viewModelScope, | ||||||
|  |             initialValue = SubjectUiState.Loading, | ||||||
|  |             started = SharingStarted.Eagerly, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     fun onAddSubject(open: (String) -> Unit) { | ||||||
|         open(StudeezDestinations.ADD_SUBJECT_FORM) |         open(StudeezDestinations.ADD_SUBJECT_FORM) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun getSubjects(): Flow<List<Subject>> { |     fun getStudyTime(subject: Subject): Flow<Int> { | ||||||
|         return subjectDAO.getSubjects() |         return subjectDAO.getStudyTime(subject) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun onViewSubject(subject: Subject, open: (String) -> Unit) { |     fun onViewSubject(subject: Subject, open: (String) -> Unit) { | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package be.ugent.sel.studeez.screens.tasks.forms | package be.ugent.sel.studeez.screens.subjects.form | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.StringRes | import androidx.annotation.StringRes | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.compose.foundation.layout.Column | ||||||
|  | @ -19,10 +19,10 @@ import be.ugent.sel.studeez.resources | ||||||
| import be.ugent.sel.studeez.R.string as AppText | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun SubjectAddRoute( | fun SubjectCreateRoute( | ||||||
|     goBack: () -> Unit, |     goBack: () -> Unit, | ||||||
|     openAndPopUp: (String, String) -> Unit, |     openAndPopUp: (String, String) -> Unit, | ||||||
|     viewModel: SubjectFormViewModel, |     viewModel: SubjectCreateFormViewModel, | ||||||
| ) { | ) { | ||||||
|     val uiState by viewModel.uiState |     val uiState by viewModel.uiState | ||||||
|     SubjectForm( |     SubjectForm( | ||||||
|  | @ -39,7 +39,7 @@ fun SubjectAddRoute( | ||||||
| fun SubjectEditRoute( | fun SubjectEditRoute( | ||||||
|     goBack: () -> Unit, |     goBack: () -> Unit, | ||||||
|     openAndPopUp: (String, String) -> Unit, |     openAndPopUp: (String, String) -> Unit, | ||||||
|     viewModel: SubjectFormViewModel, |     viewModel: SubjectEditFormViewModel, | ||||||
| ) { | ) { | ||||||
|     val uiState by viewModel.uiState |     val uiState by viewModel.uiState | ||||||
|     SubjectForm( |     SubjectForm( | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package be.ugent.sel.studeez.screens.tasks.forms | package be.ugent.sel.studeez.screens.subjects.form | ||||||
| 
 | 
 | ||||||
| data class SubjectFormUiState( | data class SubjectFormUiState( | ||||||
|     val name: String = "", |     val name: String = "", | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.screens.tasks.forms | package be.ugent.sel.studeez.screens.subjects.form | ||||||
| 
 | 
 | ||||||
|  | import androidx.compose.runtime.MutableState | ||||||
| import androidx.compose.runtime.mutableStateOf | import androidx.compose.runtime.mutableStateOf | ||||||
| import be.ugent.sel.studeez.data.SelectedSubject | import be.ugent.sel.studeez.data.SelectedSubject | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Subject | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | @ -10,25 +11,17 @@ import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| @HiltViewModel | abstract class SubjectFormViewModel( | ||||||
| class SubjectFormViewModel @Inject constructor( |     protected val subjectDAO: SubjectDAO, | ||||||
|     private val subjectDAO: SubjectDAO, |     protected val selectedSubject: SelectedSubject, | ||||||
|     private val selectedSubject: SelectedSubject, |  | ||||||
|     logService: LogService, |     logService: LogService, | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
|     var uiState = mutableStateOf( |     abstract val uiState: MutableState<SubjectFormUiState> | ||||||
|         if (selectedSubject.isSet()) SubjectFormUiState( |  | ||||||
|             name = selectedSubject().name, |  | ||||||
|             color = selectedSubject().argb_color |  | ||||||
|         ) |  | ||||||
|         else SubjectFormUiState() |  | ||||||
|     ) |  | ||||||
|         private set |  | ||||||
| 
 | 
 | ||||||
|     private val name: String |     protected val name: String | ||||||
|         get() = uiState.value.name |         get() = uiState.value.name | ||||||
| 
 | 
 | ||||||
|     private val color: Long |     protected val color: Long | ||||||
|         get() = uiState.value.color |         get() = uiState.value.color | ||||||
| 
 | 
 | ||||||
|     fun onNameChange(newValue: String) { |     fun onNameChange(newValue: String) { | ||||||
|  | @ -38,11 +31,15 @@ class SubjectFormViewModel @Inject constructor( | ||||||
|     fun onColorChange(newValue: Long) { |     fun onColorChange(newValue: Long) { | ||||||
|         uiState.value = uiState.value.copy(color = newValue) |         uiState.value = uiState.value.copy(color = newValue) | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     fun onDelete(openAndPopUp: (String, String) -> Unit) { | @HiltViewModel | ||||||
|         subjectDAO.deleteSubject(selectedSubject()) | class SubjectCreateFormViewModel @Inject constructor( | ||||||
|         openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) |     subjectDAO: SubjectDAO, | ||||||
|     } |     selectedSubject: SelectedSubject, | ||||||
|  |     logService: LogService, | ||||||
|  | ) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) { | ||||||
|  |     override val uiState = mutableStateOf(SubjectFormUiState()) | ||||||
| 
 | 
 | ||||||
|     fun onCreate(openAndPopUp: (String, String) -> Unit) { |     fun onCreate(openAndPopUp: (String, String) -> Unit) { | ||||||
|         val newSubject = Subject( |         val newSubject = Subject( | ||||||
|  | @ -57,6 +54,25 @@ class SubjectFormViewModel @Inject constructor( | ||||||
| //        open(StudeezDestinations.TASKS_SCREEN) | //        open(StudeezDestinations.TASKS_SCREEN) | ||||||
|         openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.ADD_SUBJECT_FORM) |         openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.ADD_SUBJECT_FORM) | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @HiltViewModel | ||||||
|  | class SubjectEditFormViewModel @Inject constructor( | ||||||
|  |     subjectDAO: SubjectDAO, | ||||||
|  |     selectedSubject: SelectedSubject, | ||||||
|  |     logService: LogService, | ||||||
|  | ) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) { | ||||||
|  |     override val uiState = mutableStateOf( | ||||||
|  |         SubjectFormUiState( | ||||||
|  |             name = selectedSubject().name, | ||||||
|  |             color = selectedSubject().argb_color | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     fun onDelete(openAndPopUp: (String, String) -> Unit) { | ||||||
|  |         subjectDAO.updateSubject(selectedSubject().copy(archived = true)) | ||||||
|  |         openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     fun onEdit(openAndPopUp: (String, String) -> Unit) { |     fun onEdit(openAndPopUp: (String, String) -> Unit) { | ||||||
|         val newSubject = selectedSubject().copy( |         val newSubject = selectedSubject().copy( | ||||||
|  | @ -1,80 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.tasks |  | ||||||
| 
 |  | ||||||
| import androidx.compose.foundation.layout.Column |  | ||||||
| import androidx.compose.foundation.layout.padding |  | ||||||
| import androidx.compose.foundation.lazy.LazyColumn |  | ||||||
| import androidx.compose.foundation.lazy.items |  | ||||||
| import androidx.compose.runtime.Composable |  | ||||||
| import androidx.compose.runtime.collectAsState |  | ||||||
| import androidx.compose.ui.Modifier |  | ||||||
| import androidx.compose.ui.res.stringResource |  | ||||||
| import androidx.compose.ui.tooling.preview.Preview |  | ||||||
| import androidx.compose.ui.unit.dp |  | ||||||
| import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton |  | ||||||
| import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate |  | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.tasks.SubjectEntry |  | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Subject |  | ||||||
| import kotlinx.coroutines.flow.Flow |  | ||||||
| import kotlinx.coroutines.flow.flowOf |  | ||||||
| import be.ugent.sel.studeez.R.string as AppText |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun SubjectRoute( |  | ||||||
|     open: (String) -> Unit, |  | ||||||
|     viewModel: SubjectViewModel, |  | ||||||
|     drawerActions: DrawerActions, |  | ||||||
|     navigationBarActions: NavigationBarActions, |  | ||||||
| ) { |  | ||||||
|     SubjectScreen( |  | ||||||
|         drawerActions = drawerActions, |  | ||||||
|         navigationBarActions = navigationBarActions, |  | ||||||
|         addSubject = { viewModel.addSubject(open) }, |  | ||||||
|         getSubjects = viewModel::getSubjects, |  | ||||||
|         onViewSubject = { viewModel.onViewSubject(it, open) }, |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun SubjectScreen( |  | ||||||
|     drawerActions: DrawerActions, |  | ||||||
|     navigationBarActions: NavigationBarActions, |  | ||||||
|     addSubject: () -> Unit, |  | ||||||
|     getSubjects: () -> Flow<List<Subject>>, |  | ||||||
|     onViewSubject: (Subject) -> Unit, |  | ||||||
| ) { |  | ||||||
|     PrimaryScreenTemplate( |  | ||||||
|         title = stringResource(AppText.my_subjects), |  | ||||||
|         drawerActions = drawerActions, |  | ||||||
|         navigationBarActions = navigationBarActions, |  | ||||||
|         barAction = {}, |  | ||||||
|     ) { |  | ||||||
|         val subjects = getSubjects().collectAsState(initial = emptyList()) |  | ||||||
|         Column( |  | ||||||
|             modifier = Modifier.padding(top = 5.dp) |  | ||||||
|         ) { |  | ||||||
|             LazyColumn { |  | ||||||
|                 items(subjects.value) { |  | ||||||
|                     SubjectEntry( |  | ||||||
|                         subject = it, |  | ||||||
|                         onViewSubject = { onViewSubject(it) }, |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             NewTaskSubjectButton(onClick = addSubject, AppText.new_subject) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Preview |  | ||||||
| @Composable |  | ||||||
| fun SubjectScreenPreview() { |  | ||||||
|     SubjectScreen( |  | ||||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}), |  | ||||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), |  | ||||||
|         addSubject = {}, |  | ||||||
|         getSubjects = { flowOf() }, |  | ||||||
|         onViewSubject = {}, |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  | @ -27,9 +27,10 @@ data class TaskActions( | ||||||
|     val addTask: () -> Unit, |     val addTask: () -> Unit, | ||||||
|     val getSubject: () -> Subject, |     val getSubject: () -> Subject, | ||||||
|     val getTasks: () -> Flow<List<Task>>, |     val getTasks: () -> Flow<List<Task>>, | ||||||
|     val deleteTask: (Task) -> Unit, |  | ||||||
|     val onCheckTask: (Task, Boolean) -> Unit, |     val onCheckTask: (Task, Boolean) -> Unit, | ||||||
|     val editSubject: () -> Unit, |     val editSubject: () -> Unit, | ||||||
|  |     val startTask: (Task) -> Unit, | ||||||
|  |     val archiveTask: (Task) -> Unit, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions { | fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions { | ||||||
|  | @ -37,9 +38,10 @@ fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskAction | ||||||
|         addTask = { viewModel.addTask(open) }, |         addTask = { viewModel.addTask(open) }, | ||||||
|         getTasks = viewModel::getTasks, |         getTasks = viewModel::getTasks, | ||||||
|         getSubject = viewModel::getSelectedSubject, |         getSubject = viewModel::getSelectedSubject, | ||||||
|         deleteTask = viewModel::deleteTask, |  | ||||||
|         onCheckTask = { task, isChecked -> viewModel.toggleTaskCompleted(task, isChecked) }, |         onCheckTask = { task, isChecked -> viewModel.toggleTaskCompleted(task, isChecked) }, | ||||||
|         editSubject = { viewModel.editSubject(open) } |         editSubject = { viewModel.editSubject(open) }, | ||||||
|  |         startTask = { task -> viewModel.startTask(task, open) }, | ||||||
|  |         archiveTask = viewModel::archiveTask | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -69,16 +71,25 @@ fun TaskScreen( | ||||||
|         Column( |         Column( | ||||||
|             modifier = Modifier.padding(top = 5.dp) |             modifier = Modifier.padding(top = 5.dp) | ||||||
|         ) { |         ) { | ||||||
|  |             NewTaskSubjectButton(onClick = taskActions.addTask, AppText.new_task) | ||||||
|             LazyColumn { |             LazyColumn { | ||||||
|                 items(tasks.value) { |                 items(tasks.value.filter { !it.completed }) { | ||||||
|                     TaskEntry( |                     TaskEntry( | ||||||
|                         task = it, |                         task = it, | ||||||
|                         onCheckTask = { isChecked -> taskActions.onCheckTask(it, isChecked) }, |                         onCheckTask = { isChecked -> taskActions.onCheckTask(it, isChecked) }, | ||||||
|                         onDeleteTask = { taskActions.deleteTask(it) }, |                         onArchiveTask = { taskActions.archiveTask(it) }, | ||||||
|  |                         onStartTask = { taskActions.startTask(it) } | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |                 items(tasks.value.filter { it.completed }) { | ||||||
|  |                     TaskEntry( | ||||||
|  |                         task = it, | ||||||
|  |                         onCheckTask = { isChecked -> taskActions.onCheckTask(it, isChecked) }, | ||||||
|  |                         onArchiveTask = { taskActions.archiveTask(it) }, | ||||||
|  |                         onStartTask = { taskActions.startTask(it) } | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             NewTaskSubjectButton(onClick = taskActions.addTask, AppText.new_task) |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -105,9 +116,10 @@ fun TaskScreenPreview() { | ||||||
|             {}, |             {}, | ||||||
|             { Subject(name = "Test Subject") }, |             { Subject(name = "Test Subject") }, | ||||||
|             { flowOf() }, |             { flowOf() }, | ||||||
|             {}, |  | ||||||
|             { _, _ -> run {} }, |             { _, _ -> run {} }, | ||||||
|             {}, |             {}, | ||||||
|  |             {}, | ||||||
|  |             {}, | ||||||
|         ) |         ) | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  | @ -1,10 +1,10 @@ | ||||||
| package be.ugent.sel.studeez.screens.tasks | package be.ugent.sel.studeez.screens.tasks | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.data.SelectedSubject | import be.ugent.sel.studeez.data.SelectedSubject | ||||||
|  | import be.ugent.sel.studeez.data.SelectedTask | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Subject | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Task | import be.ugent.sel.studeez.data.local.models.task.Task | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
| import be.ugent.sel.studeez.domain.SubjectDAO |  | ||||||
| import be.ugent.sel.studeez.domain.TaskDAO | import be.ugent.sel.studeez.domain.TaskDAO | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
| import be.ugent.sel.studeez.screens.StudeezViewModel | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
|  | @ -15,8 +15,8 @@ import javax.inject.Inject | ||||||
| @HiltViewModel | @HiltViewModel | ||||||
| class TaskViewModel @Inject constructor( | class TaskViewModel @Inject constructor( | ||||||
|     private val taskDAO: TaskDAO, |     private val taskDAO: TaskDAO, | ||||||
|     private val subjectDAO: SubjectDAO, |  | ||||||
|     private val selectedSubject: SelectedSubject, |     private val selectedSubject: SelectedSubject, | ||||||
|  |     private val selectedTask: SelectedTask, | ||||||
|     logService: LogService, |     logService: LogService, | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
|     fun addTask(open: (String) -> Unit) { |     fun addTask(open: (String) -> Unit) { | ||||||
|  | @ -27,11 +27,6 @@ class TaskViewModel @Inject constructor( | ||||||
|         return taskDAO.getTasks(selectedSubject()) |         return taskDAO.getTasks(selectedSubject()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun deleteSubject(open: (String) -> Unit) { |  | ||||||
|         subjectDAO.deleteSubject(selectedSubject()) |  | ||||||
|         open(StudeezDestinations.SUBJECT_SCREEN) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun getSelectedSubject(): Subject { |     fun getSelectedSubject(): Subject { | ||||||
|         return selectedSubject() |         return selectedSubject() | ||||||
|     } |     } | ||||||
|  | @ -40,11 +35,20 @@ class TaskViewModel @Inject constructor( | ||||||
|         taskDAO.deleteTask(task) |         taskDAO.deleteTask(task) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     fun archiveTask(task: Task) { | ||||||
|  |         taskDAO.updateTask(task.copy(archived = true)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fun toggleTaskCompleted(task: Task, completed: Boolean) { |     fun toggleTaskCompleted(task: Task, completed: Boolean) { | ||||||
|         taskDAO.toggleTaskCompleted(task, completed) |         taskDAO.updateTask(task.copy(completed = completed)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun editSubject(open: (String) -> Unit) { |     fun editSubject(open: (String) -> Unit) { | ||||||
|         open(StudeezDestinations.EDIT_SUBJECT_FORM) |         open(StudeezDestinations.EDIT_SUBJECT_FORM) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     fun startTask(task: Task, open: (String) -> Unit) { | ||||||
|  |         selectedTask.set(task) | ||||||
|  |         open(StudeezDestinations.TIMER_SELECTION_SCREEN) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package be.ugent.sel.studeez.screens.tasks.forms | package be.ugent.sel.studeez.screens.tasks.form | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.StringRes | import androidx.annotation.StringRes | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.compose.foundation.layout.Column | ||||||
|  | @ -18,10 +18,10 @@ import be.ugent.sel.studeez.resources | ||||||
| import be.ugent.sel.studeez.R.string as AppText | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun TaskAddRoute( | fun TaskCreateRoute( | ||||||
|     goBack: () -> Unit, |     goBack: () -> Unit, | ||||||
|     openAndPopUp: (String, String) -> Unit, |     openAndPopUp: (String, String) -> Unit, | ||||||
|     viewModel: TaskFormViewModel, |     viewModel: TaskCreateFormViewModel, | ||||||
| ) { | ) { | ||||||
|     val uiState by viewModel.uiState |     val uiState by viewModel.uiState | ||||||
|     TaskForm( |     TaskForm( | ||||||
|  | @ -37,7 +37,7 @@ fun TaskAddRoute( | ||||||
| fun TaskEditRoute( | fun TaskEditRoute( | ||||||
|     goBack: () -> Unit, |     goBack: () -> Unit, | ||||||
|     openAndPopUp: (String, String) -> Unit, |     openAndPopUp: (String, String) -> Unit, | ||||||
|     viewModel: TaskFormViewModel, |     viewModel: TaskEditFormViewModel, | ||||||
| ) { | ) { | ||||||
|     val uiState by viewModel.uiState |     val uiState by viewModel.uiState | ||||||
|     TaskForm( |     TaskForm( | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package be.ugent.sel.studeez.screens.tasks.forms | package be.ugent.sel.studeez.screens.tasks.form | ||||||
| 
 | 
 | ||||||
| data class TaskFormUiState( | data class TaskFormUiState( | ||||||
|     val name: String = "", |     val name: String = "", | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.screens.tasks.forms | package be.ugent.sel.studeez.screens.tasks.form | ||||||
| 
 | 
 | ||||||
|  | import androidx.compose.runtime.MutableState | ||||||
| import androidx.compose.runtime.mutableStateOf | import androidx.compose.runtime.mutableStateOf | ||||||
| import be.ugent.sel.studeez.data.SelectedSubject | import be.ugent.sel.studeez.data.SelectedSubject | ||||||
| import be.ugent.sel.studeez.data.SelectedTask | import be.ugent.sel.studeez.data.SelectedTask | ||||||
|  | @ -11,38 +12,54 @@ import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| @HiltViewModel | abstract class TaskFormViewModel( | ||||||
| class TaskFormViewModel @Inject constructor( |     protected val taskDAO: TaskDAO, | ||||||
|     private val taskDAO: TaskDAO, |     protected val selectedSubject: SelectedSubject, | ||||||
|     private val selectedSubject: SelectedSubject, |     protected val selectedTask: SelectedTask, | ||||||
|     private val selectedTask: SelectedTask, |  | ||||||
|     logService: LogService, |     logService: LogService, | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
|     var uiState = mutableStateOf( |     abstract val uiState: MutableState<TaskFormUiState> | ||||||
|         if (selectedTask.isSet()) TaskFormUiState(selectedTask().name) else TaskFormUiState() |  | ||||||
|     ) |  | ||||||
|         private set |  | ||||||
| 
 | 
 | ||||||
|     private val name: String |     protected val name: String | ||||||
|         get() = uiState.value.name |         get() = uiState.value.name | ||||||
| 
 | 
 | ||||||
|     fun onNameChange(newValue: String) { |     fun onNameChange(newValue: String) { | ||||||
|         uiState.value = uiState.value.copy(name = newValue) |         uiState.value = uiState.value.copy(name = newValue) | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     fun onDelete(openAndPopUp: (String, String) -> Unit) { | @HiltViewModel | ||||||
|         taskDAO.deleteTask(selectedTask()) | class TaskCreateFormViewModel @Inject constructor( | ||||||
|         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM) |     taskDAO: TaskDAO, | ||||||
|     } |     selectedSubject: SelectedSubject, | ||||||
|  |     selectedTask: SelectedTask, | ||||||
|  |     logService: LogService, | ||||||
|  | ) : TaskFormViewModel(taskDAO, selectedSubject, selectedTask, logService) { | ||||||
|  |     override val uiState = mutableStateOf(TaskFormUiState()) | ||||||
| 
 | 
 | ||||||
|     fun onCreate(openAndPopUp: (String, String) -> Unit) { |     fun onCreate(openAndPopUp: (String, String) -> Unit) { | ||||||
|         val newTask = Task(name = name, subjectId = selectedSubject().id) |         val newTask = Task(name = name, subjectId = selectedSubject().id) | ||||||
|         taskDAO.saveTask(newTask) |         taskDAO.saveTask(newTask) | ||||||
|         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.ADD_TASK_FORM) |         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.ADD_TASK_FORM) | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @HiltViewModel | ||||||
|  | class TaskEditFormViewModel @Inject constructor( | ||||||
|  |     taskDAO: TaskDAO, | ||||||
|  |     selectedSubject: SelectedSubject, | ||||||
|  |     selectedTask: SelectedTask, | ||||||
|  |     logService: LogService, | ||||||
|  | ) : TaskFormViewModel(taskDAO, selectedSubject, selectedTask, logService) { | ||||||
|  |     override val uiState = mutableStateOf(TaskFormUiState()) | ||||||
|  | 
 | ||||||
|  |     fun onDelete(openAndPopUp: (String, String) -> Unit) { | ||||||
|  |         taskDAO.deleteTask(selectedTask()) | ||||||
|  |         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     fun onEdit(openAndPopUp: (String, String) -> Unit) { |     fun onEdit(openAndPopUp: (String, String) -> Unit) { | ||||||
|         val newTask = Task(name = name) |         val newTask = selectedTask().copy(name = name) | ||||||
|         taskDAO.updateTask(newTask) |         taskDAO.updateTask(newTask) | ||||||
|         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM) |         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM) | ||||||
|     } |     } | ||||||
|  | @ -1,44 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_add |  | ||||||
| 
 |  | ||||||
| import androidx.compose.runtime.Composable |  | ||||||
| import androidx.compose.runtime.getValue |  | ||||||
| import androidx.compose.ui.tooling.preview.Preview |  | ||||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo |  | ||||||
| import be.ugent.sel.studeez.screens.timer_edit.GetTimerEditScreen |  | ||||||
| import be.ugent.sel.studeez.screens.timer_edit.TimerEditViewModel |  | ||||||
| import be.ugent.sel.studeez.screens.timer_edit.editScreens.AbstractTimerEditScreen |  | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme |  | ||||||
| 
 |  | ||||||
| data class TimerEditActions( |  | ||||||
|     val getTimerInfo: () -> TimerInfo, |  | ||||||
|     val saveTimer: (TimerInfo, () -> Unit) -> Unit |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| fun getTimerEditActions( |  | ||||||
|     viewModel: TimerEditViewModel, |  | ||||||
|     open: (String) -> Unit |  | ||||||
| ): TimerEditActions { |  | ||||||
|     return TimerEditActions( |  | ||||||
|         getTimerInfo = viewModel::getTimerInfo, |  | ||||||
|         saveTimer = viewModel::saveTimer |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun TimerEditRoute( |  | ||||||
|     open: (String) -> Unit, |  | ||||||
|     popUp: () -> Unit, |  | ||||||
|     viewModel: TimerEditViewModel, |  | ||||||
| ) { |  | ||||||
| 
 |  | ||||||
|     val timerEditActions = getTimerEditActions(viewModel, open) |  | ||||||
| 
 |  | ||||||
|     SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) { |  | ||||||
| 
 |  | ||||||
|         val timerEditScreen = timerEditActions.getTimerInfo().accept(GetTimerEditScreen()) |  | ||||||
|         timerEditScreen { timerInfo -> |  | ||||||
|             timerEditActions.saveTimer(timerInfo, popUp) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,27 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_edit |  | ||||||
| 
 |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfoVisitor |  | ||||||
| import be.ugent.sel.studeez.screens.timer_edit.editScreens.AbstractTimerEditScreen |  | ||||||
| import be.ugent.sel.studeez.screens.timer_edit.editScreens.BreakTimerEditScreen |  | ||||||
| import be.ugent.sel.studeez.screens.timer_edit.editScreens.CustomTimerEditScreen |  | ||||||
| import be.ugent.sel.studeez.screens.timer_edit.editScreens.EndlessTimerEditScreen |  | ||||||
| 
 |  | ||||||
| class GetTimerEditScreen: TimerInfoVisitor<AbstractTimerEditScreen> { |  | ||||||
| 
 |  | ||||||
|     override fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): AbstractTimerEditScreen { |  | ||||||
|         return CustomTimerEditScreen(customTimerInfo) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo): AbstractTimerEditScreen { |  | ||||||
|         return EndlessTimerEditScreen(endlessTimerInfo) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo): AbstractTimerEditScreen { |  | ||||||
|         return BreakTimerEditScreen(pomodoroTimerInfo) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,65 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_edit |  | ||||||
| 
 |  | ||||||
| import androidx.compose.runtime.Composable |  | ||||||
| import androidx.compose.runtime.getValue |  | ||||||
| import androidx.compose.ui.tooling.preview.Preview |  | ||||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo |  | ||||||
| import be.ugent.sel.studeez.screens.timer_edit.editScreens.AbstractTimerEditScreen |  | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme |  | ||||||
| 
 |  | ||||||
| data class TimerEditActions( |  | ||||||
|     val getTimerInfo: () -> TimerInfo, |  | ||||||
|     val saveTimer: (TimerInfo, () -> Unit) -> Unit |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| fun getTimerEditActions( |  | ||||||
|     viewModel: TimerEditViewModel, |  | ||||||
|     open: (String) -> Unit |  | ||||||
| ): TimerEditActions { |  | ||||||
|     return TimerEditActions( |  | ||||||
|         getTimerInfo = viewModel::getTimerInfo, |  | ||||||
|         saveTimer = viewModel::saveTimer |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun TimerEditRoute( |  | ||||||
|     open: (String) -> Unit, |  | ||||||
|     popUp: () -> Unit, |  | ||||||
|     viewModel: TimerEditViewModel, |  | ||||||
| ) { |  | ||||||
| 
 |  | ||||||
|     val timerEditActions = getTimerEditActions(viewModel, open) |  | ||||||
| 
 |  | ||||||
|     SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) { |  | ||||||
| 
 |  | ||||||
|         val timerEditScreen = timerEditActions.getTimerInfo().accept(GetTimerEditScreen()) |  | ||||||
|         timerEditScreen { timerInfo -> |  | ||||||
|             timerEditActions.saveTimer(timerInfo, popUp) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|  | @ -1,29 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_edit |  | ||||||
| 
 |  | ||||||
| import be.ugent.sel.studeez.data.EditTimerState |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo |  | ||||||
| import be.ugent.sel.studeez.domain.LogService |  | ||||||
| import be.ugent.sel.studeez.domain.TimerDAO |  | ||||||
| import be.ugent.sel.studeez.screens.StudeezViewModel |  | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel |  | ||||||
| import javax.inject.Inject |  | ||||||
| 
 |  | ||||||
| @HiltViewModel |  | ||||||
| class TimerEditViewModel @Inject constructor( |  | ||||||
|     private val editTimerState: EditTimerState, |  | ||||||
|     private val timerDAO: TimerDAO, |  | ||||||
|     logService: LogService |  | ||||||
| ) : StudeezViewModel(logService) { |  | ||||||
| 
 |  | ||||||
|     private val timerInfo: TimerInfo = editTimerState.timerInfo |  | ||||||
| 
 |  | ||||||
|     fun getTimerInfo(): TimerInfo { |  | ||||||
|         return timerInfo |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) { |  | ||||||
|         timerDAO.updateTimer(timerInfo) |  | ||||||
|         goBack() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,27 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.timer_form | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfoVisitor | ||||||
|  | import be.ugent.sel.studeez.screens.timer_form.form_screens.AbstractTimerFormScreen | ||||||
|  | import be.ugent.sel.studeez.screens.timer_form.form_screens.BreakTimerFormScreen | ||||||
|  | import be.ugent.sel.studeez.screens.timer_form.form_screens.CustomTimerFormScreen | ||||||
|  | import be.ugent.sel.studeez.screens.timer_form.form_screens.EndlessTimerFormScreen | ||||||
|  | 
 | ||||||
|  | class GetTimerFormScreen: TimerInfoVisitor<AbstractTimerFormScreen> { | ||||||
|  | 
 | ||||||
|  |     override fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): AbstractTimerFormScreen { | ||||||
|  |         return CustomTimerFormScreen(customTimerInfo) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo): AbstractTimerFormScreen { | ||||||
|  |         return EndlessTimerFormScreen(endlessTimerInfo) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo): AbstractTimerFormScreen { | ||||||
|  |         return BreakTimerFormScreen(pomodoroTimerInfo) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.timer_form | ||||||
|  | 
 | ||||||
|  | import androidx.annotation.StringRes | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TimerAddRoute( | ||||||
|  |     popUp: () -> Unit, | ||||||
|  |     viewModel: TimerFormViewModel | ||||||
|  | ) { | ||||||
|  |     TimerFormScreen(popUp = popUp, getTimerInfo = viewModel::getTimerInfo, AppText.add_timer) { | ||||||
|  |         viewModel.saveTimer(it, goBack = popUp) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TimerEditRoute( | ||||||
|  |     popUp: () -> Unit, | ||||||
|  |     viewModel: TimerFormViewModel | ||||||
|  | ) { | ||||||
|  |     TimerFormScreen(popUp = popUp, getTimerInfo = viewModel::getTimerInfo, AppText.edit_timer) { | ||||||
|  |         viewModel.editTimer(it, goBack = popUp) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TimerFormScreen( | ||||||
|  |     popUp: () -> Unit, | ||||||
|  |     getTimerInfo: () -> TimerInfo, | ||||||
|  |     @StringRes label: Int, | ||||||
|  |     onConfirmClick: (TimerInfo) -> Unit | ||||||
|  | ) { | ||||||
|  |     val timerFormScreen = getTimerInfo().accept(GetTimerFormScreen()) | ||||||
|  | 
 | ||||||
|  |     SecondaryScreenTemplate(title = stringResource(id = label), popUp = popUp) { | ||||||
|  |         timerFormScreen(onConfirmClick) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_add | package be.ugent.sel.studeez.screens.timer_form | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.data.EditTimerState | import be.ugent.sel.studeez.data.SelectedTimerInfo | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
| import be.ugent.sel.studeez.domain.TimerDAO | import be.ugent.sel.studeez.domain.TimerDAO | ||||||
|  | @ -9,21 +9,22 @@ import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| @HiltViewModel | @HiltViewModel | ||||||
| class TimerAddViewModel @Inject constructor( | class TimerFormViewModel @Inject constructor( | ||||||
|     private val editTimerState: EditTimerState, |     private val selectedTimerInfo: SelectedTimerInfo, | ||||||
|     private val timerDAO: TimerDAO, |     private val timerDAO: TimerDAO, | ||||||
|     logService: LogService |     logService: LogService | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
| 
 |  | ||||||
|     private val timerInfo: TimerInfo = editTimerState.timerInfo |  | ||||||
| 
 |  | ||||||
|     fun getTimerInfo(): TimerInfo { |     fun getTimerInfo(): TimerInfo { | ||||||
|         return timerInfo |         return selectedTimerInfo() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) { |     fun editTimer(timerInfo: TimerInfo, goBack: () -> Unit) { | ||||||
|         timerDAO.updateTimer(timerInfo) |         timerDAO.updateTimer(timerInfo) | ||||||
|         goBack() |         goBack() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) { | ||||||
|  |         timerDAO.saveTimer(timerInfo) | ||||||
|  |         goBack() | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_edit.editScreens | package be.ugent.sel.studeez.screens.timer_form.form_screens | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.Arrangement | import androidx.compose.foundation.layout.Arrangement | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.compose.foundation.layout.Column | ||||||
|  | @ -18,8 +18,9 @@ import be.ugent.sel.studeez.common.composable.BasicButton | ||||||
| import be.ugent.sel.studeez.common.composable.LabelledInputField | import be.ugent.sel.studeez.common.composable.LabelledInputField | ||||||
| import be.ugent.sel.studeez.common.ext.basicButton | import be.ugent.sel.studeez.common.ext.basicButton | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| abstract class AbstractTimerEditScreen(private val timerInfo: TimerInfo) { | abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) { | ||||||
| 
 | 
 | ||||||
|     @Composable |     @Composable | ||||||
|     operator fun invoke(onSaveClick: (TimerInfo) -> Unit) { |     operator fun invoke(onSaveClick: (TimerInfo) -> Unit) { | ||||||
|  | @ -50,7 +51,7 @@ abstract class AbstractTimerEditScreen(private val timerInfo: TimerInfo) { | ||||||
|                 LabelledInputField( |                 LabelledInputField( | ||||||
|                     value = description, |                     value = description, | ||||||
|                     onNewValue = { description = it }, |                     onNewValue = { description = it }, | ||||||
|                     label = R.string.description, |                     label = AppText.description, | ||||||
|                     singleLine = false |                     singleLine = false | ||||||
|                 ) |                 ) | ||||||
| 
 | 
 | ||||||
|  | @ -1,20 +1,19 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_edit.editScreens | package be.ugent.sel.studeez.screens.timer_form.form_screens | ||||||
| 
 | 
 | ||||||
| import androidx.compose.runtime.* | import androidx.compose.runtime.* | ||||||
|  | import androidx.compose.ui.text.input.KeyboardType | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.composable.TimePickerButton | import be.ugent.sel.studeez.common.composable.LabeledErrorTextField | ||||||
| import be.ugent.sel.studeez.common.composable.TimePickerCard | import be.ugent.sel.studeez.common.composable.TimePickerCard | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.Time |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo | import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| class BreakTimerEditScreen( | 
 | ||||||
|  | class BreakTimerFormScreen( | ||||||
|     private val breakTimerInfo: PomodoroTimerInfo |     private val breakTimerInfo: PomodoroTimerInfo | ||||||
| ): AbstractTimerEditScreen(breakTimerInfo) { | ): AbstractTimerFormScreen(breakTimerInfo) { | ||||||
| 
 | 
 | ||||||
|     @Composable |     @Composable | ||||||
|     override fun ExtraFields() { |     override fun ExtraFields() { | ||||||
|  | @ -26,8 +25,18 @@ class BreakTimerEditScreen( | ||||||
|         TimePickerCard(R.string.breakTime, breakTimerInfo.breakTime) { newTime -> |         TimePickerCard(R.string.breakTime, breakTimerInfo.breakTime) { newTime -> | ||||||
|             breakTimerInfo.breakTime = newTime |             breakTimerInfo.breakTime = newTime | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         LabeledErrorTextField( | ||||||
|  |             initialValue = breakTimerInfo.repeats.toString(), | ||||||
|  |             label = R.string.repeats, | ||||||
|  |             errorText = AppText.repeats_error, | ||||||
|  |             keyboardType = KeyboardType.Decimal, | ||||||
|  |             predicate = { it.matches(Regex("[1-9]+\\d*")) } | ||||||
|  |         ) { correctlyTypedInt -> | ||||||
|  |             breakTimerInfo.repeats = correctlyTypedInt.toInt() | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Preview | @Preview | ||||||
|  | @ -41,6 +50,6 @@ fun BreakEditScreenPreview() { | ||||||
|         5 |         5 | ||||||
|     ) |     ) | ||||||
|     StudeezTheme { |     StudeezTheme { | ||||||
|         BreakTimerEditScreen(pomodoroTimerInfo).invoke(onSaveClick = {}) |         BreakTimerFormScreen(pomodoroTimerInfo).invoke(onSaveClick = {}) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_edit.editScreens | package be.ugent.sel.studeez.screens.timer_form.form_screens | ||||||
| 
 | 
 | ||||||
| import androidx.compose.runtime.* | import androidx.compose.runtime.* | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | @ -7,9 +7,9 @@ import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
| import be.ugent.sel.studeez.R.string as AppText | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| class CustomTimerEditScreen( | class CustomTimerFormScreen( | ||||||
|     private val customTimerInfo: CustomTimerInfo |     private val customTimerInfo: CustomTimerInfo | ||||||
|     ): AbstractTimerEditScreen(customTimerInfo) { |     ): AbstractTimerFormScreen(customTimerInfo) { | ||||||
| 
 | 
 | ||||||
|     @Composable |     @Composable | ||||||
|     override fun ExtraFields() { |     override fun ExtraFields() { | ||||||
|  | @ -29,6 +29,6 @@ class CustomTimerEditScreen( | ||||||
| fun CustomEditScreenPreview() { | fun CustomEditScreenPreview() { | ||||||
|     val customTimerInfo = CustomTimerInfo("custom", "my description", 25) |     val customTimerInfo = CustomTimerInfo("custom", "my description", 25) | ||||||
|     StudeezTheme { |     StudeezTheme { | ||||||
|         CustomTimerEditScreen(customTimerInfo).invoke(onSaveClick = {}) |         CustomTimerFormScreen(customTimerInfo).invoke(onSaveClick = {}) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1,13 +1,13 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_edit.editScreens | package be.ugent.sel.studeez.screens.timer_form.form_screens | ||||||
| 
 | 
 | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo | import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
| 
 | 
 | ||||||
| class EndlessTimerEditScreen( | class EndlessTimerFormScreen( | ||||||
|     endlessTimerInfo: EndlessTimerInfo |     endlessTimerInfo: EndlessTimerInfo | ||||||
| ): AbstractTimerEditScreen(endlessTimerInfo) { | ): AbstractTimerFormScreen(endlessTimerInfo) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Preview | @Preview | ||||||
|  | @ -18,6 +18,6 @@ fun EndlessEditScreenPreview() { | ||||||
|         "My endless timer description", |         "My endless timer description", | ||||||
|     ) |     ) | ||||||
|     StudeezTheme { |     StudeezTheme { | ||||||
|         EndlessTimerEditScreen(endlessTimerInfo).invoke(onSaveClick = {}) |         EndlessTimerFormScreen(endlessTimerInfo).invoke(onSaveClick = {}) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_add | package be.ugent.sel.studeez.screens.timer_form.timer_type_select | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.compose.foundation.layout.Column | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | @ -7,16 +7,18 @@ import androidx.compose.material.Text | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.Alignment | import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
| import androidx.hilt.navigation.compose.hiltViewModel | import androidx.hilt.navigation.compose.hiltViewModel | ||||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.* | import be.ugent.sel.studeez.data.local.models.timer_info.* | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.CUSTOM | import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.CUSTOM | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.BREAK | import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.BREAK | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.ENDLESS | import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.ENDLESS | ||||||
| 
 | 
 | ||||||
| val defaultTimerInfo: Map<TimerType, TimerInfo> = mapOf( | val defaultTimerInfo: Map<TimerType, TimerInfo> = mapOf( | ||||||
|     CUSTOM to CustomTimerInfo("", "", 0), |     CUSTOM to CustomTimerInfo("", "", 0), | ||||||
|     BREAK to PomodoroTimerInfo("", "", 0, 0, 0), |     BREAK to PomodoroTimerInfo("", "", 0, 0, 1), | ||||||
|     ENDLESS to EndlessTimerInfo("", ""), |     ENDLESS to EndlessTimerInfo("", ""), | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -28,13 +30,14 @@ fun TimerTypeSelectScreen( | ||||||
|     viewModel: TimerTypeSelectViewModel = hiltViewModel() |     viewModel: TimerTypeSelectViewModel = hiltViewModel() | ||||||
| ) { | ) { | ||||||
| 
 | 
 | ||||||
|     SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) { |     SecondaryScreenTemplate(title = stringResource(id = AppText.timer_type_select), popUp = popUp) { | ||||||
|         Column( |         Column( | ||||||
|             horizontalAlignment = Alignment.CenterHorizontally, |             horizontalAlignment = Alignment.CenterHorizontally, | ||||||
|             modifier = Modifier.fillMaxWidth() |             modifier = Modifier.fillMaxWidth() | ||||||
|         ) { |         ) { | ||||||
|             TimerType.values().forEach { timerType -> |             TimerType.values().forEach { timerType -> | ||||||
|                 Button(onClick = { viewModel.onTimerTypeChosen(defaultTimerInfo[timerType]!!, open) }) { |                 val default: TimerInfo = defaultTimerInfo.getValue(timerType) | ||||||
|  |                 Button(onClick = { viewModel.onTimerTypeChosen(default, open) }) { | ||||||
|                     Text(text = timerType.name) |                     Text(text = timerType.name) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | @ -1,25 +1,22 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_add | package be.ugent.sel.studeez.screens.timer_form.timer_type_select | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.data.EditTimerState | import be.ugent.sel.studeez.data.SelectedTimerInfo | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
| import be.ugent.sel.studeez.domain.TimerDAO |  | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
| import be.ugent.sel.studeez.screens.StudeezViewModel | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
| import kotlinx.coroutines.flow.Flow |  | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| @HiltViewModel | @HiltViewModel | ||||||
| class TimerTypeSelectViewModel @Inject constructor( | class TimerTypeSelectViewModel @Inject constructor( | ||||||
|     private val editTimerState: EditTimerState, |     private val selectedTimerInfo: SelectedTimerInfo, | ||||||
|     private val timerDAO: TimerDAO, |  | ||||||
|     logService: LogService |     logService: LogService | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     fun onTimerTypeChosen(timerInfo: TimerInfo, open: (String) -> Unit) { |     fun onTimerTypeChosen(timerInfo: TimerInfo, open: (String) -> Unit) { | ||||||
|         editTimerState.timerInfo = timerInfo |         selectedTimerInfo.set(timerInfo) | ||||||
|         open(StudeezDestinations.TIMER_EDIT_SCREEN) |         open(StudeezDestinations.ADD_TIMER_SCREEN) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -25,7 +25,7 @@ data class TimerOverviewActions( | ||||||
|     val getUserTimers: () -> Flow<List<TimerInfo>>, |     val getUserTimers: () -> Flow<List<TimerInfo>>, | ||||||
|     val getDefaultTimers: () -> List<TimerInfo>, |     val getDefaultTimers: () -> List<TimerInfo>, | ||||||
|     val onEditClick: (TimerInfo) -> Unit, |     val onEditClick: (TimerInfo) -> Unit, | ||||||
|     val onAddClick: () -> Unit, |     val onAddClick: () -> Unit | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| fun getTimerOverviewActions( | fun getTimerOverviewActions( | ||||||
|  | @ -36,7 +36,7 @@ fun getTimerOverviewActions( | ||||||
|         getUserTimers = viewModel::getUserTimers, |         getUserTimers = viewModel::getUserTimers, | ||||||
|         getDefaultTimers = viewModel::getDefaultTimers, |         getDefaultTimers = viewModel::getDefaultTimers, | ||||||
|         onEditClick = { viewModel.update(it, open) }, |         onEditClick = { viewModel.update(it, open) }, | ||||||
|         onAddClick = { viewModel.create(open) } |         onAddClick = { viewModel.onAddClick(open) } | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -48,14 +48,14 @@ fun TimerOverviewRoute( | ||||||
| ) { | ) { | ||||||
|     TimerOverviewScreen( |     TimerOverviewScreen( | ||||||
|         timerOverviewActions = getTimerOverviewActions(viewModel, open), |         timerOverviewActions = getTimerOverviewActions(viewModel, open), | ||||||
|         drawerActions = drawerActions |         drawerActions = drawerActions, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun TimerOverviewScreen( | fun TimerOverviewScreen( | ||||||
|     timerOverviewActions: TimerOverviewActions, |     timerOverviewActions: TimerOverviewActions, | ||||||
|     drawerActions: DrawerActions |     drawerActions: DrawerActions, | ||||||
| ) { | ) { | ||||||
| 
 | 
 | ||||||
|     val timers = timerOverviewActions.getUserTimers().collectAsState(initial = emptyList()) |     val timers = timerOverviewActions.getUserTimers().collectAsState(initial = emptyList()) | ||||||
|  | @ -82,12 +82,13 @@ fun TimerOverviewScreen( | ||||||
|                 items(timers.value) { timerInfo -> |                 items(timers.value) { timerInfo -> | ||||||
|                     TimerEntry( |                     TimerEntry( | ||||||
|                         timerInfo = timerInfo, |                         timerInfo = timerInfo, | ||||||
|                     ) { |                         rightButton = { | ||||||
|                             StealthButton( |                             StealthButton( | ||||||
|                                 text = R.string.edit, |                                 text = R.string.edit, | ||||||
|                                 onClick = { timerOverviewActions.onEditClick(timerInfo) } |                                 onClick = { timerOverviewActions.onEditClick(timerInfo) } | ||||||
|                             ) |                             ) | ||||||
|                         } |                         } | ||||||
|  |                     ) | ||||||
| 
 | 
 | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_overview | package be.ugent.sel.studeez.screens.timer_overview | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.data.EditTimerState | import be.ugent.sel.studeez.data.SelectedTimerInfo | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
| import be.ugent.sel.studeez.domain.ConfigurationService | import be.ugent.sel.studeez.domain.ConfigurationService | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
|  | @ -15,11 +15,11 @@ import javax.inject.Inject | ||||||
| class TimerOverviewViewModel @Inject constructor( | class TimerOverviewViewModel @Inject constructor( | ||||||
|     private val configurationService: ConfigurationService, |     private val configurationService: ConfigurationService, | ||||||
|     private val timerDAO: TimerDAO, |     private val timerDAO: TimerDAO, | ||||||
|     private val editTimerState: EditTimerState, |     private val selectedTimerInfo: SelectedTimerInfo, | ||||||
|     logService: LogService |     logService: LogService | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
| 
 | 
 | ||||||
|     fun getUserTimers() : Flow<List<TimerInfo>> { |     fun getUserTimers(): Flow<List<TimerInfo>> { | ||||||
|         return timerDAO.getUserTimers() |         return timerDAO.getUserTimers() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -28,15 +28,15 @@ class TimerOverviewViewModel @Inject constructor( | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun update(timerInfo: TimerInfo, open: (String) -> Unit) { |     fun update(timerInfo: TimerInfo, open: (String) -> Unit) { | ||||||
|         editTimerState.timerInfo = timerInfo |         selectedTimerInfo.set(timerInfo) | ||||||
|         open(StudeezDestinations.TIMER_EDIT_SCREEN) |         open(StudeezDestinations.TIMER_EDIT_SCREEN) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun create(open: (String) -> Unit) { |     fun onAddClick(open: (String) -> Unit) { | ||||||
|         open(StudeezDestinations.ADD_TIMER_SCREEN) |         open(StudeezDestinations.TIMER_TYPE_CHOOSING_SCREEN) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun delete(timerInfo: TimerInfo) =timerDAO.deleteTimer(timerInfo) |     fun delete(timerInfo: TimerInfo) = timerDAO.deleteTimer(timerInfo) | ||||||
| 
 | 
 | ||||||
|     fun save(timerInfo: TimerInfo) = timerDAO.saveTimer(timerInfo) |     fun save(timerInfo: TimerInfo) = timerDAO.saveTimer(timerInfo) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,12 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_overview.add_timer |  | ||||||
| 
 |  | ||||||
| data class AddTimerUiState( |  | ||||||
|     val studyTimeHours: Int = 1, |  | ||||||
|     val studyTimeMinutes: Int = 0, |  | ||||||
|     val withBreaks: Boolean = false, |  | ||||||
|     val breakTimeMinutes: Int = 5, |  | ||||||
|     val breakTimeHours: Int = 0, |  | ||||||
|     val repeats: Int = 1, |  | ||||||
|     val name: String = "Timer", |  | ||||||
|     val description: String = "Long study session", |  | ||||||
| ) |  | ||||||
|  | @ -1,91 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_overview.add_timer |  | ||||||
| 
 |  | ||||||
| import androidx.compose.runtime.mutableStateOf |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo |  | ||||||
| import be.ugent.sel.studeez.domain.LogService |  | ||||||
| import be.ugent.sel.studeez.domain.TimerDAO |  | ||||||
| import be.ugent.sel.studeez.screens.StudeezViewModel |  | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel |  | ||||||
| import javax.inject.Inject |  | ||||||
| 
 |  | ||||||
| @HiltViewModel |  | ||||||
| class AddTimerViewModel @Inject constructor( |  | ||||||
|     logService: LogService, |  | ||||||
|     private val timerDAO: TimerDAO, |  | ||||||
| ): StudeezViewModel(logService) { |  | ||||||
|     var uiState = mutableStateOf(AddTimerUiState()) |  | ||||||
|         private set |  | ||||||
| 
 |  | ||||||
|     private val studyTimeHours |  | ||||||
|         get() = uiState.value.studyTimeHours |  | ||||||
| 
 |  | ||||||
|     private val studyTimeMinutes |  | ||||||
|         get() = uiState.value.studyTimeMinutes |  | ||||||
| 
 |  | ||||||
|     private val breakTimeHours |  | ||||||
|         get() = uiState.value.breakTimeHours |  | ||||||
| 
 |  | ||||||
|     private val breakTimeMinutes |  | ||||||
|         get() = uiState.value.breakTimeMinutes |  | ||||||
| 
 |  | ||||||
|     private val repeats |  | ||||||
|         get() = uiState.value.repeats |  | ||||||
| 
 |  | ||||||
|     private val name |  | ||||||
|         get() = uiState.value.name |  | ||||||
| 
 |  | ||||||
|     private val description |  | ||||||
|         get() = uiState.value.description |  | ||||||
| 
 |  | ||||||
|     fun onStudyTimeHoursChange(newValue: Int) { |  | ||||||
|         uiState.value = uiState.value.copy(studyTimeHours = newValue) |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun onStudyTimeMinutesChange(newValue: Int) { |  | ||||||
|         uiState.value = uiState.value.copy(studyTimeMinutes = newValue) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun onWithBreaksChange() { |  | ||||||
|         uiState.value = uiState.value.copy(withBreaks = !uiState.value.withBreaks) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun onBreakTimeHourChange(newValue: Int) { |  | ||||||
|         uiState.value = uiState.value.copy(breakTimeHours = newValue) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun onBreakTimeMinutesChange(newValue: Int) { |  | ||||||
|         uiState.value = uiState.value.copy(breakTimeMinutes = newValue) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun onRepeatsChange(newValue: Int) { |  | ||||||
|         uiState.value = uiState.value.copy(repeats = newValue) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun addTimer() { |  | ||||||
|         if (uiState.value.withBreaks) { |  | ||||||
|             timerDAO.saveTimer(PomodoroTimerInfo( |  | ||||||
|                 name = uiState.value.name, |  | ||||||
|                 description = uiState.value.description, |  | ||||||
|                 studyTime = studyTimeHours * 60 * 60 + studyTimeMinutes * 60, |  | ||||||
|                 breakTime = breakTimeHours * 60 * 60 + breakTimeMinutes * 60, |  | ||||||
|                 repeats = repeats |  | ||||||
|             )) |  | ||||||
|         } else { |  | ||||||
|             timerDAO.saveTimer(CustomTimerInfo( |  | ||||||
|                 name = uiState.value.name, |  | ||||||
|                 description = uiState.value.description, |  | ||||||
|                 studyTime = studyTimeHours * 60 * 60 + studyTimeMinutes * 60 |  | ||||||
|             )) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun onNameChange(newValue: String) { |  | ||||||
|         uiState.value = uiState.value.copy(name = newValue) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun onDescriptionChange(newValue: String) { |  | ||||||
|         uiState.value = uiState.value.copy(description = newValue) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,274 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_overview.add_timer |  | ||||||
| 
 |  | ||||||
| import androidx.compose.foundation.layout.Arrangement |  | ||||||
| import androidx.compose.foundation.layout.Row |  | ||||||
| import androidx.compose.foundation.layout.fillMaxHeight |  | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth |  | ||||||
| import androidx.compose.foundation.layout.padding |  | ||||||
| import androidx.compose.foundation.lazy.LazyColumn |  | ||||||
| import androidx.compose.material.Button |  | ||||||
| import androidx.compose.material.Checkbox |  | ||||||
| import androidx.compose.material.Text |  | ||||||
| import androidx.compose.material.TextField |  | ||||||
| import androidx.compose.runtime.Composable |  | ||||||
| import androidx.compose.runtime.getValue |  | ||||||
| import androidx.compose.ui.Alignment |  | ||||||
| import androidx.compose.ui.Modifier |  | ||||||
| import androidx.compose.ui.graphics.Color |  | ||||||
| import androidx.compose.ui.res.stringResource |  | ||||||
| import androidx.compose.ui.text.style.TextAlign |  | ||||||
| import androidx.compose.ui.tooling.preview.Preview |  | ||||||
| import androidx.compose.ui.unit.dp |  | ||||||
| import be.ugent.sel.studeez.R |  | ||||||
| import be.ugent.sel.studeez.common.composable.BasicButton |  | ||||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.BasicTimePicker |  | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations |  | ||||||
| import be.ugent.sel.studeez.resources |  | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme |  | ||||||
| 
 |  | ||||||
| data class AddTimerActions( |  | ||||||
|     val open: (String) -> Unit, |  | ||||||
|     val goBack: () -> Unit, |  | ||||||
|     val onStudyTimeHoursChange: (Int) -> Unit, |  | ||||||
|     val onStudyTimeMinutesChange: (Int) -> Unit, |  | ||||||
|     val onBreakTimeHourChange: (Int) -> Unit, |  | ||||||
|     val onBreakTimeMinutesChange: (Int) -> Unit, |  | ||||||
|     val onRepeatsChange: (Int) -> Unit, |  | ||||||
|     val onWithBreaksChange: () -> Unit, |  | ||||||
|     val addTimer: () -> Unit, |  | ||||||
|     val onNameChange: (String) -> Unit, |  | ||||||
|     val onDescriptionChange: (String) -> Unit, |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| fun getAddTimerActions( |  | ||||||
|     open: (String) -> Unit, |  | ||||||
|     goBack: () -> Unit, |  | ||||||
|     viewModel: AddTimerViewModel, |  | ||||||
| ): AddTimerActions { |  | ||||||
|     return AddTimerActions( |  | ||||||
|         open = open, |  | ||||||
|         goBack = goBack, |  | ||||||
|         onWithBreaksChange = viewModel::onWithBreaksChange, |  | ||||||
|         onStudyTimeHoursChange = viewModel::onStudyTimeHoursChange, |  | ||||||
|         onStudyTimeMinutesChange = viewModel::onStudyTimeMinutesChange, |  | ||||||
|         onBreakTimeHourChange = viewModel::onBreakTimeHourChange, |  | ||||||
|         onBreakTimeMinutesChange = viewModel::onBreakTimeMinutesChange, |  | ||||||
|         onRepeatsChange = viewModel::onRepeatsChange, |  | ||||||
|         addTimer = viewModel::addTimer, |  | ||||||
|         onNameChange = viewModel::onNameChange, |  | ||||||
|         onDescriptionChange = viewModel::onDescriptionChange |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun AddTimerRoute( |  | ||||||
|     open: (String) -> Unit, |  | ||||||
|     goBack: () -> Unit, |  | ||||||
|     viewModel: AddTimerViewModel, |  | ||||||
| ) { |  | ||||||
|     val uiState by viewModel.uiState |  | ||||||
| 
 |  | ||||||
|     AddTimerScreen( |  | ||||||
|         addTimerActions = getAddTimerActions( |  | ||||||
|             open = open, |  | ||||||
|             goBack = goBack, |  | ||||||
|             viewModel = viewModel, |  | ||||||
|         ), |  | ||||||
|         uiState = uiState |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun AddTimerScreen( |  | ||||||
|     addTimerActions: AddTimerActions, |  | ||||||
|     uiState: AddTimerUiState, |  | ||||||
| ) { |  | ||||||
|     val mStudyTimePicker = BasicTimePicker( |  | ||||||
|         onHoursChange = addTimerActions.onStudyTimeHoursChange, |  | ||||||
|         onMinutesChange = addTimerActions.onStudyTimeMinutesChange, |  | ||||||
|         Hours = uiState.studyTimeHours, |  | ||||||
|         Minutes = uiState.studyTimeMinutes |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     val mBreakTimePicker = BasicTimePicker( |  | ||||||
|         onHoursChange = addTimerActions.onBreakTimeHourChange, |  | ||||||
|         onMinutesChange = addTimerActions.onBreakTimeMinutesChange, |  | ||||||
|         Hours = uiState.breakTimeHours, |  | ||||||
|         Minutes = uiState.breakTimeMinutes |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     SecondaryScreenTemplate( |  | ||||||
|         title = resources().getString(R.string.add_timer), |  | ||||||
|         popUp = addTimerActions.goBack |  | ||||||
|     ) { |  | ||||||
|         LazyColumn( |  | ||||||
|             modifier = Modifier |  | ||||||
|                 .fillMaxWidth() |  | ||||||
|                 .padding(16.dp), |  | ||||||
|             horizontalAlignment = Alignment.CenterHorizontally |  | ||||||
|         ) { |  | ||||||
|             item { |  | ||||||
|                 Row( |  | ||||||
|                     modifier = Modifier |  | ||||||
|                         .padding(16.dp) |  | ||||||
|                 ) { |  | ||||||
|                     Text( |  | ||||||
|                         text = stringResource(R.string.addTimer_question), |  | ||||||
|                         textAlign = TextAlign.Center |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             item { |  | ||||||
|                 Text( |  | ||||||
|                     text = uiState.studyTimeHours.toString() + stringResource(R.string.addTimer_studytime_1) + uiState.studyTimeMinutes + stringResource( |  | ||||||
|                                             R.string.addTimer_studytime_2) |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             item { |  | ||||||
|                 Button( |  | ||||||
|                     onClick = { |  | ||||||
|                         mStudyTimePicker.show() |  | ||||||
|                     }, |  | ||||||
|                 ) { |  | ||||||
|                     Text( |  | ||||||
|                         text = stringResource(R.string.addTimer_timepicker), |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             item { |  | ||||||
|                 Row( |  | ||||||
|                     modifier = Modifier.fillMaxWidth(), |  | ||||||
|                     verticalAlignment = Alignment.CenterVertically, |  | ||||||
|                     horizontalArrangement = Arrangement.Center |  | ||||||
|                 ) { |  | ||||||
|                     Text( |  | ||||||
|                         text = stringResource(R.string.addTimer_break_question), |  | ||||||
|                     ) |  | ||||||
|                     Checkbox( |  | ||||||
|                         checked = uiState.withBreaks, |  | ||||||
|                         onCheckedChange = { addTimerActions.onWithBreaksChange() } |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (uiState.withBreaks) { |  | ||||||
|                 item { |  | ||||||
|                     Text( |  | ||||||
|                         text = if (uiState.repeats == 1) uiState.repeats.toString() + stringResource( |  | ||||||
|                                                     R.string.addTimer_break_1) |  | ||||||
|                                else uiState.repeats.toString() + stringResource( |  | ||||||
|                                                     R.string.addTimer_break_s) |  | ||||||
|                     ) |  | ||||||
|                     TextField( |  | ||||||
|                         value = uiState.repeats.toString(), |  | ||||||
|                         onValueChange = { it: String -> |  | ||||||
|                             it.toIntOrNull()?.let { it1 -> |  | ||||||
|                                 addTimerActions.onRepeatsChange( |  | ||||||
|                                     kotlin.math.abs(it1) |  | ||||||
|                                 ) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 item { |  | ||||||
|                     Text( |  | ||||||
|                         text = uiState.breakTimeHours.toString() + stringResource(R.string.breakTime_1) + uiState.breakTimeMinutes + stringResource( |  | ||||||
|                                                     R.string.breakTime_2) |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 item { |  | ||||||
|                     Button( |  | ||||||
|                         onClick = { |  | ||||||
|                             mBreakTimePicker.show() |  | ||||||
|                         }, |  | ||||||
|                     ) { |  | ||||||
|                         Text( |  | ||||||
|                             text = stringResource(R.string.addTimer_timepicker), |  | ||||||
|                         ) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             item { |  | ||||||
|                 Text( |  | ||||||
|                     text = stringResource(R.string.addTimer_name) |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             item { |  | ||||||
|                 TextField( |  | ||||||
|                     value = uiState.name, |  | ||||||
|                     onValueChange = { addTimerActions.onNameChange(it) } |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             item { |  | ||||||
|                 if (uiState.name == "") { |  | ||||||
|                     Text( |  | ||||||
|                         text = stringResource(R.string.addTimer_name_error), |  | ||||||
|                         color = Color.Red |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             item { |  | ||||||
|                 Text( |  | ||||||
|                     text = stringResource(R.string.addTimer_description) |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             item { |  | ||||||
|                 TextField( |  | ||||||
|                     value = uiState.description, |  | ||||||
|                     onValueChange = { addTimerActions.onDescriptionChange(it) } |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             item { |  | ||||||
|                 if (uiState.description == "") { |  | ||||||
|                     Text( |  | ||||||
|                         text = stringResource(R.string.addTimer_description_error), |  | ||||||
|                         color = Color.Red |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|              |  | ||||||
|             item { |  | ||||||
|                 Row( |  | ||||||
|                     modifier = Modifier |  | ||||||
|                         .fillMaxWidth() |  | ||||||
|                         .fillMaxHeight(), |  | ||||||
|                     verticalAlignment = Alignment.Bottom, |  | ||||||
|                     horizontalArrangement = Arrangement.Center |  | ||||||
|                 ) { |  | ||||||
|                     BasicButton( |  | ||||||
|                         text = R.string.add_timer, |  | ||||||
|                         modifier = Modifier, |  | ||||||
|                         onClick = { |  | ||||||
|                             if (uiState.description != "" && uiState.name != "") { |  | ||||||
|                                 addTimerActions.addTimer() |  | ||||||
|                                 addTimerActions.open(StudeezDestinations.TIMER_SCREEN) |  | ||||||
|                             } |  | ||||||
|                          } |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Preview |  | ||||||
| @Composable |  | ||||||
| fun AddTimerScreenPreview() { StudeezTheme { |  | ||||||
|     AddTimerScreen( |  | ||||||
|         addTimerActions = AddTimerActions({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}), |  | ||||||
|         uiState = AddTimerUiState() |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,10 +1,13 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_selection | package be.ugent.sel.studeez.screens.timer_selection | ||||||
| 
 | 
 | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
| import androidx.compose.foundation.lazy.LazyColumn | import androidx.compose.foundation.lazy.LazyColumn | ||||||
| import androidx.compose.foundation.lazy.items | import androidx.compose.foundation.lazy.items | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.collectAsState | import androidx.compose.runtime.collectAsState | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||||
| import be.ugent.sel.studeez.common.composable.StealthButton | import be.ugent.sel.studeez.common.composable.StealthButton | ||||||
|  | @ -99,7 +102,10 @@ fun CustomTimerEntry( | ||||||
|             ) |             ) | ||||||
|         }, |         }, | ||||||
|         rightButton = { |         rightButton = { | ||||||
|             TimePickerButton(initialSeconds = hms.getTotalSeconds()) { chosenTime -> |             TimePickerButton( | ||||||
|  |                 initialSeconds = hms.getTotalSeconds(), | ||||||
|  |                 modifier = Modifier.padding(horizontal = 5.dp) | ||||||
|  |             ) { chosenTime -> | ||||||
|                 timerInfo.studyTime = chosenTime |                 timerInfo.studyTime = chosenTime | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,10 +1,9 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_selection | package be.ugent.sel.studeez.screens.timer_selection | ||||||
| 
 | 
 | ||||||
| import androidx.compose.runtime.MutableState | import androidx.compose.runtime.MutableState | ||||||
| import androidx.compose.runtime.getValue |  | ||||||
| import androidx.compose.runtime.mutableStateOf | import androidx.compose.runtime.mutableStateOf | ||||||
| import androidx.compose.runtime.remember | import be.ugent.sel.studeez.data.SelectedTimer | ||||||
| import be.ugent.sel.studeez.data.SelectedTimerState | import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
| import be.ugent.sel.studeez.domain.TimerDAO | import be.ugent.sel.studeez.domain.TimerDAO | ||||||
|  | @ -17,18 +16,20 @@ import javax.inject.Inject | ||||||
| @HiltViewModel | @HiltViewModel | ||||||
| class TimerSelectionViewModel @Inject constructor( | class TimerSelectionViewModel @Inject constructor( | ||||||
|     private val timerDAO: TimerDAO, |     private val timerDAO: TimerDAO, | ||||||
|     private val selectedTimerState: SelectedTimerState, |     private val selectedTimer: SelectedTimer, | ||||||
|     logService: LogService |     logService: LogService | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
| 
 | 
 | ||||||
|     var customTimerStudyTime: MutableState<Int> = mutableStateOf(0) |     var customTimerStudyTime: MutableState<Int> = mutableStateOf( | ||||||
|  |         HoursMinutesSeconds(1, 0, 0).getTotalSeconds() | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
|     fun getAllTimers() : Flow<List<TimerInfo>> { |     fun getAllTimers(): Flow<List<TimerInfo>> { | ||||||
|         return timerDAO.getAllTimers() |         return timerDAO.getAllTimers() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun startSession(open: (String) -> Unit, timerInfo: TimerInfo) { |     fun startSession(open: (String) -> Unit, timerInfo: TimerInfo) { | ||||||
|         selectedTimerState.selectedTimer = timerInfo.getFunctionalTimer() |         selectedTimer.set(timerInfo.getFunctionalTimer()) | ||||||
|         open(StudeezDestinations.SESSION_SCREEN) |         open(StudeezDestinations.SESSION_SCREEN) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -29,6 +29,12 @@ | ||||||
|     <string name="home">Home</string> |     <string name="home">Home</string> | ||||||
|     <string name="start_session">Start session</string> |     <string name="start_session">Start session</string> | ||||||
| 
 | 
 | ||||||
|  |     <!-- Feed--> | ||||||
|  |     <string name="continue_task">Continue</string> | ||||||
|  |     <string name="deleted">Deleted</string> | ||||||
|  |     <string name="your_feed">This is your feed</string> | ||||||
|  |     <string name="empty_feed_help_text">Click here to create you first subject and tasks to get started</string> | ||||||
|  | 
 | ||||||
|     <!-- Tasks --> |     <!-- Tasks --> | ||||||
|     <string name="tasks">Tasks</string> |     <string name="tasks">Tasks</string> | ||||||
|     <string name="task">Task</string> |     <string name="task">Task</string> | ||||||
|  | @ -131,8 +137,13 @@ | ||||||
|     <string name="addTimer_studytime_2">" minutes of studytime"</string> |     <string name="addTimer_studytime_2">" minutes of studytime"</string> | ||||||
|     <string name="addTimer_question">How long do you want to study?</string> |     <string name="addTimer_question">How long do you want to study?</string> | ||||||
| 
 | 
 | ||||||
|  |     <!-- Timer Type Select --> | ||||||
|  |     <string name="timer_type_select">Select Timer Type</string> | ||||||
|  | 
 | ||||||
|     <!-- Edit Timer--> |     <!-- Edit Timer--> | ||||||
|     <string name="name">Name</string> |     <string name="name">Name</string> | ||||||
|  |     <string name="edit_timer">Edit Timer</string> | ||||||
|  |     <string name="repeats_error">Repeats must be a positive non-zero number</string> | ||||||
|     <string name="description">Description</string> |     <string name="description">Description</string> | ||||||
|     <string name="studyTime">Study Time</string> |     <string name="studyTime">Study Time</string> | ||||||
|     <string name="breakTime">Break Time</string> |     <string name="breakTime">Break Time</string> | ||||||
|  |  | ||||||
|  | @ -1,11 +1,13 @@ | ||||||
| package be.ugent.sel.studeez.timer_functional | package be.ugent.sel.studeez.timer_functional | ||||||
| 
 | 
 | ||||||
| import android.media.MediaPlayer | import android.media.MediaPlayer | ||||||
| import be.ugent.sel.studeez.data.SelectedTimerState | import be.ugent.sel.studeez.data.SelectedSessionReport | ||||||
| import be.ugent.sel.studeez.data.SessionReportState | import be.ugent.sel.studeez.data.SelectedTimer | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer | import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer | import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer | import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer | ||||||
|  | import be.ugent.sel.studeez.domain.LogService | ||||||
|  | import be.ugent.sel.studeez.domain.implementation.LogServiceImpl | ||||||
| import be.ugent.sel.studeez.screens.session.InvisibleSessionManager | import be.ugent.sel.studeez.screens.session.InvisibleSessionManager | ||||||
| import be.ugent.sel.studeez.screens.session.SessionViewModel | import be.ugent.sel.studeez.screens.session.SessionViewModel | ||||||
| import kotlinx.coroutines.ExperimentalCoroutinesApi | import kotlinx.coroutines.ExperimentalCoroutinesApi | ||||||
|  | @ -18,14 +20,14 @@ import org.mockito.kotlin.mock | ||||||
| 
 | 
 | ||||||
| @ExperimentalCoroutinesApi | @ExperimentalCoroutinesApi | ||||||
| class InvisibleSessionManagerTest { | class InvisibleSessionManagerTest { | ||||||
|     private var timerState: SelectedTimerState = SelectedTimerState() |     private var selectedTimer: SelectedTimer = SelectedTimer() | ||||||
|     private lateinit var viewModel: SessionViewModel |     private lateinit var viewModel: SessionViewModel | ||||||
|     private var mediaPlayer: MediaPlayer = mock() |     private var mediaPlayer: MediaPlayer = mock() | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun InvisibleEndlessTimerTest() = runTest { |     fun InvisibleEndlessTimerTest() = runTest { | ||||||
|         timerState.selectedTimer = FunctionalEndlessTimer() |         selectedTimer.set(FunctionalEndlessTimer()) | ||||||
|         viewModel = SessionViewModel(timerState, SessionReportState(), mock()) |         viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl()) | ||||||
|         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) |         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) | ||||||
| 
 | 
 | ||||||
|         val test = launch { |         val test = launch { | ||||||
|  | @ -46,8 +48,8 @@ class InvisibleSessionManagerTest { | ||||||
|         val studyTime = 10 |         val studyTime = 10 | ||||||
|         val breakTime = 5 |         val breakTime = 5 | ||||||
|         val repeats = 1 |         val repeats = 1 | ||||||
|         timerState.selectedTimer = FunctionalPomodoroTimer(studyTime, breakTime, repeats) |         selectedTimer.set(FunctionalPomodoroTimer(studyTime, breakTime, repeats)) | ||||||
|         viewModel = SessionViewModel(timerState, SessionReportState(), mock()) |         viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl()) | ||||||
|         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) |         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) | ||||||
| 
 | 
 | ||||||
|         val test = launch { |         val test = launch { | ||||||
|  | @ -79,8 +81,8 @@ class InvisibleSessionManagerTest { | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun InvisibleCustomTimerTest() = runTest { |     fun InvisibleCustomTimerTest() = runTest { | ||||||
|         timerState.selectedTimer = FunctionalCustomTimer(5) |         selectedTimer.set(FunctionalCustomTimer(5)) | ||||||
|         viewModel = SessionViewModel(timerState, SessionReportState(), mock()) |         viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), mock(), LogServiceImpl()) | ||||||
|         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) |         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) | ||||||
| 
 | 
 | ||||||
|         val test = launch { |         val test = launch { | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ class TimeUnitTest { | ||||||
|     private val hours = 4 |     private val hours = 4 | ||||||
|     private val minutes = 20 |     private val minutes = 20 | ||||||
|     private val seconds = 39 |     private val seconds = 39 | ||||||
|     private val time: Time = Time(seconds + minutes * 60 + hours * 60 * 60) |     private var time: Time = Time(seconds + minutes * 60 + hours * 60 * 60) | ||||||
| 
 | 
 | ||||||
|     @Before |     @Before | ||||||
|     fun setup() { |     fun setup() { | ||||||
|  | @ -21,9 +21,9 @@ class TimeUnitTest { | ||||||
|     fun formatTime() { |     fun formatTime() { | ||||||
|         Assert.assertEquals( |         Assert.assertEquals( | ||||||
|             HoursMinutesSeconds( |             HoursMinutesSeconds( | ||||||
|                 hours.toString().padStart(2, '0'), |                 hours, | ||||||
|                 minutes.toString().padStart(2, '0'), |                 minutes, | ||||||
|                 seconds.toString().padStart(2, '0'), |                 seconds, | ||||||
|             ), |             ), | ||||||
|             time.getAsHMS(), |             time.getAsHMS(), | ||||||
|         ) |         ) | ||||||
|  | @ -39,7 +39,11 @@ class TimeUnitTest { | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun minOne() { |     fun minOne() { | ||||||
|         time.minOne() |         Assert.assertEquals( | ||||||
|  |             (seconds + minutes * 60 + hours * 60 * 60), | ||||||
|  |             time.time, | ||||||
|  |         ) | ||||||
|  |         time-- | ||||||
|         Assert.assertEquals( |         Assert.assertEquals( | ||||||
|             (seconds + minutes * 60 + hours * 60 * 60) - 1, |             (seconds + minutes * 60 + hours * 60 * 60) - 1, | ||||||
|             time.time, |             time.time, | ||||||
|  | @ -48,7 +52,7 @@ class TimeUnitTest { | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun plusOne() { |     fun plusOne() { | ||||||
|         time.plusOne() |         time++ | ||||||
|         Assert.assertEquals( |         Assert.assertEquals( | ||||||
|             (seconds + minutes * 60 + hours * 60 * 60) + 1, |             (seconds + minutes * 60 + hours * 60 * 60) + 1, | ||||||
|             time.time, |             time.time, | ||||||
|  | @ -59,7 +63,7 @@ class TimeUnitTest { | ||||||
|     fun minMultiple() { |     fun minMultiple() { | ||||||
|         val n = 10 |         val n = 10 | ||||||
|         for (i in 1 .. n) { |         for (i in 1 .. n) { | ||||||
|             time.minOne() |             time-- | ||||||
|         } |         } | ||||||
|         Assert.assertEquals( |         Assert.assertEquals( | ||||||
|             (seconds + minutes * 60 + hours * 60 * 60) - n, |             (seconds + minutes * 60 + hours * 60 * 60) - n, | ||||||
|  | @ -71,7 +75,7 @@ class TimeUnitTest { | ||||||
|     fun plusMultiple() { |     fun plusMultiple() { | ||||||
|         val n = 10 |         val n = 10 | ||||||
|         for (i in 1 .. n) { |         for (i in 1 .. n) { | ||||||
|             time.plusOne() |             time++ | ||||||
|         } |         } | ||||||
|         Assert.assertEquals( |         Assert.assertEquals( | ||||||
|             (seconds + minutes * 60 + hours * 60 * 60) + n, |             (seconds + minutes * 60 + hours * 60 * 60) + n, | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Rune Dyselinck
						Rune Dyselinck