fix mergeconflict when merging tasksoverview
This commit is contained in:
		
						commit
						e2ada0b9d4
					
				
					 67 changed files with 1833 additions and 281 deletions
				
			
		|  | @ -66,6 +66,7 @@ dependencies { | |||
|     implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" | ||||
|     implementation 'androidx.compose.material:material:1.2.0' | ||||
| 
 | ||||
| 
 | ||||
|     debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" | ||||
| 
 | ||||
|     // ViewModel | ||||
|  | @ -97,6 +98,9 @@ dependencies { | |||
|     testImplementation 'junit:junit:4.13.2' | ||||
|     androidTestImplementation 'androidx.test.ext:junit:1.1.5' | ||||
| 
 | ||||
|     // Coroutine testing | ||||
|     testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' | ||||
| 
 | ||||
|     // Mocking | ||||
|     testImplementation 'org.mockito.kotlin:mockito-kotlin:3.2.0' | ||||
| 
 | ||||
|  |  | |||
|  | @ -38,6 +38,8 @@ 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 | ||||
|  | @ -46,7 +48,9 @@ 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 kotlinx.coroutines.CoroutineScope | ||||
|  | @ -97,7 +101,7 @@ fun resources(): Resources { | |||
| @Composable | ||||
| fun StudeezNavGraph( | ||||
|     appState: StudeezAppstate, | ||||
|     modifier: Modifier, | ||||
|     modifier: Modifier = Modifier, | ||||
| ) { | ||||
|     val drawerViewModel: DrawerViewModel = hiltViewModel() | ||||
|     val navBarViewModel: NavigationBarViewModel = hiltViewModel() | ||||
|  | @ -119,35 +123,13 @@ fun StudeezNavGraph( | |||
|         startDestination = StudeezDestinations.SPLASH_SCREEN, | ||||
|         modifier = modifier, | ||||
|     ) { | ||||
| 
 | ||||
| 
 | ||||
|         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(), | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         // NavBar | ||||
|         composable(StudeezDestinations.HOME_SCREEN) { | ||||
|             HomeRoute( | ||||
|                 open, | ||||
|                 viewModel = hiltViewModel(), | ||||
|                 drawerActions = drawerActions, | ||||
|                 navigationBarActions = navigationBarActions, | ||||
|                 navigationBarActions = navigationBarActions | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|  | @ -176,6 +158,14 @@ fun StudeezNavGraph( | |||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         composable(StudeezDestinations.TASKS_SCREEN) { | ||||
|             TaskRoute( | ||||
|                 goBack = goBack, | ||||
|                 open = open, | ||||
|                 viewModel = hiltViewModel(), | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         composable(StudeezDestinations.ADD_TASK_FORM) { | ||||
|             TaskAddRoute( | ||||
|                 goBack = goBack, | ||||
|  | @ -192,16 +182,14 @@ fun StudeezNavGraph( | |||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         composable(StudeezDestinations.TASKS_SCREEN) { | ||||
|             TaskRoute( | ||||
|                 goBack = goBack, | ||||
|                 open = open, | ||||
|                 viewModel = hiltViewModel(), | ||||
| 
 | ||||
|         composable(StudeezDestinations.SESSIONS_SCREEN) { | ||||
|             SessionsRoute( | ||||
|                 drawerActions = drawerActions, | ||||
|                 navigationBarActions = navigationBarActions | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         // TODO Sessions screen | ||||
| 
 | ||||
|         composable(StudeezDestinations.PROFILE_SCREEN) { | ||||
|             ProfileRoute( | ||||
|                 open, | ||||
|  | @ -211,10 +199,49 @@ fun StudeezNavGraph( | |||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         composable(StudeezDestinations.TIMER_OVERVIEW_SCREEN) { | ||||
|         // 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(), | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|  | @ -226,10 +253,43 @@ fun StudeezNavGraph( | |||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         // TODO Timers screen | ||||
|         // TODO Settings screen | ||||
|         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 | ||||
|         } | ||||
| 
 | ||||
|         // Edit screens | ||||
|         composable(StudeezDestinations.EDIT_PROFILE_SCREEN) { | ||||
|             EditProfileRoute( | ||||
|                 goBack, | ||||
|  | @ -237,20 +297,5 @@ fun StudeezNavGraph( | |||
|                 viewModel = hiltViewModel(), | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         composable(StudeezDestinations.TIMER_SELECTION_SCREEN) { | ||||
|             TimerSelectionRoute( | ||||
|                 open, | ||||
|                 goBack, | ||||
|                 viewModel = hiltViewModel(), | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         composable(StudeezDestinations.SESSION_RECAP) { | ||||
|             SessionRecapRoute( | ||||
|                 openAndPopUp = openAndPopUp, | ||||
|                 viewModel = hiltViewModel() | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -10,9 +10,15 @@ import androidx.compose.material.Text | |||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.lifecycle.lifecycleScope | ||||
| import be.ugent.sel.studeez.StudeezApp | ||||
| import be.ugent.sel.studeez.screens.session.InvisibleSessionManager | ||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||
| import dagger.hilt.android.AndroidEntryPoint | ||||
| import kotlinx.coroutines.Job | ||||
| import kotlinx.coroutines.launch | ||||
| 
 | ||||
| var onTimerInvisible: Job? = null | ||||
| 
 | ||||
| @AndroidEntryPoint | ||||
| class MainActivity : ComponentActivity() { | ||||
|  | @ -30,6 +36,18 @@ class MainActivity : ComponentActivity() { | |||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun onStop() { | ||||
|         onTimerInvisible = lifecycleScope.launch { | ||||
|             InvisibleSessionManager.updateTimer() | ||||
|         } | ||||
|         super.onStop() | ||||
|     } | ||||
| 
 | ||||
|     override fun onStart() { | ||||
|         onTimerInvisible?.cancel() | ||||
|         super.onStart() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
|  |  | |||
|  | @ -27,11 +27,19 @@ import androidx.compose.ui.unit.dp | |||
| import androidx.compose.ui.unit.sp | ||||
| import be.ugent.sel.studeez.common.ext.basicButton | ||||
| import be.ugent.sel.studeez.common.ext.card | ||||
| import be.ugent.sel.studeez.common.ext.defaultButtonShape | ||||
| import be.ugent.sel.studeez.R.string as AppText | ||||
| 
 | ||||
| @Composable | ||||
| fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { | ||||
|     TextButton(onClick = action, modifier = modifier) { Text(text = stringResource(text)) } | ||||
|     TextButton( | ||||
|         onClick = action, | ||||
|         modifier = modifier | ||||
|     ) { | ||||
|         Text( | ||||
|             text = stringResource(text) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
|  | @ -45,7 +53,7 @@ fun BasicButton( | |||
|     Button( | ||||
|         onClick = onClick, | ||||
|         modifier = modifier, | ||||
|         shape = RoundedCornerShape(20.dp), | ||||
|         shape = defaultButtonShape(), | ||||
|         colors = colors, | ||||
|         border = border, | ||||
|     ) { | ||||
|  |  | |||
|  | @ -1,7 +1,9 @@ | |||
| package be.ugent.sel.studeez.common.composable | ||||
| 
 | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.animation.core.animateFloat | ||||
| import androidx.compose.animation.core.updateTransition | ||||
| import androidx.compose.foundation.border | ||||
| import androidx.compose.foundation.layout.* | ||||
| import androidx.compose.material.FloatingActionButton | ||||
| import androidx.compose.material.Icon | ||||
| import androidx.compose.material.IconButton | ||||
|  | @ -11,52 +13,134 @@ import androidx.compose.material.icons.filled.Add | |||
| import androidx.compose.material.icons.filled.Check | ||||
| import androidx.compose.material.icons.filled.DateRange | ||||
| import androidx.compose.material.icons.filled.Person | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.* | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.draw.rotate | ||||
| import androidx.compose.ui.graphics.vector.ImageVector | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
| import be.ugent.sel.studeez.resources | ||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||
| import be.ugent.sel.studeez.R.string as AppText | ||||
| 
 | ||||
| const val TRANSITION = "transition" | ||||
| val HEIGHT_DIFFERENCE = 30.dp | ||||
| 
 | ||||
| data class AddButtonActions( | ||||
|     val onTaskClick: () -> Unit, | ||||
|     val onFriendClick: () -> Unit, | ||||
|     val onSessionClick: () -> Unit | ||||
| ) | ||||
| 
 | ||||
| @Composable | ||||
| fun CollapsedAddButton() { | ||||
|     FloatingActionButton( | ||||
|         onClick = { /* TODO popup add options */ } | ||||
| fun AddButton( | ||||
|     addButtonActions: AddButtonActions | ||||
| ) { | ||||
|         Icon(imageVector = Icons.Default.Add, contentDescription = "fab") | ||||
|     var isExpanded by remember { mutableStateOf(false) } | ||||
| 
 | ||||
|     // Rotate the button when expanded, normal when collapsed. | ||||
|     val transition = updateTransition(targetState = isExpanded, label = TRANSITION) | ||||
|     val rotate by transition.animateFloat(label = TRANSITION) { expanded -> if (expanded) 315f else 0f } | ||||
| 
 | ||||
|     Column( | ||||
|         horizontalAlignment = Alignment.CenterHorizontally, | ||||
|         verticalArrangement = Arrangement.Top | ||||
|     ) { | ||||
|         Box { | ||||
|             // Show minis when expanded. | ||||
|             if (isExpanded) { | ||||
|                 ExpandedAddButton( | ||||
|                     addButtonActions = addButtonActions | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // The base add button | ||||
|         FloatingActionButton( | ||||
|             onClick = { | ||||
|                 // Toggle expanded/collapsed. | ||||
|                 isExpanded = !isExpanded | ||||
|             }, | ||||
|             modifier = Modifier.padding(bottom = if (isExpanded) 78.dp else 0.dp) | ||||
|         ) { | ||||
|             Icon( | ||||
|                 imageVector = Icons.Default.Add, | ||||
|                 contentDescription = "fab", | ||||
|                 modifier = Modifier.rotate(rotate) // The rotation | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
| fun ExpandedAddButton() { | ||||
|     Row() { | ||||
|         IconButton(onClick = { /* TODO Go to next step */ }) { | ||||
| fun ExpandedAddButton( | ||||
|     addButtonActions: AddButtonActions | ||||
| ) { | ||||
|     Row { | ||||
|         ExpandedEntry( | ||||
|             name = AppText.task, | ||||
|             imageVector = Icons.Default.Check, | ||||
|             onClick = addButtonActions.onTaskClick, | ||||
|             modifier = Modifier.padding(36.dp, HEIGHT_DIFFERENCE, 36.dp, 0.dp) | ||||
|         ) | ||||
| 
 | ||||
|         ExpandedEntry( | ||||
|             name = AppText.friend, | ||||
|             imageVector = Icons.Default.Person, | ||||
|             onClick = addButtonActions.onFriendClick | ||||
|         ) | ||||
| 
 | ||||
|         ExpandedEntry( | ||||
|             name = AppText.session, | ||||
|             imageVector = Icons.Default.DateRange, | ||||
|             onClick = addButtonActions.onSessionClick, | ||||
|             modifier = Modifier.padding(36.dp, HEIGHT_DIFFERENCE, 36.dp, 0.dp) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
| fun ExpandedEntry( | ||||
|     name: Int, | ||||
|     imageVector: ImageVector, | ||||
|     onClick: () -> Unit, | ||||
|     modifier: Modifier = Modifier | ||||
| ) { | ||||
|     IconButton( | ||||
|         onClick = onClick, | ||||
|         modifier = modifier | ||||
|     ) { | ||||
|         Column(horizontalAlignment = Alignment.CenterHorizontally) { | ||||
|                 Icon(imageVector = Icons.Default.Check, contentDescription = "Task") | ||||
|                 Text(text = "Task") | ||||
|             } | ||||
|         } | ||||
|         IconButton(onClick = { /* TODO Go to next step */ }) { | ||||
|             Column (horizontalAlignment = Alignment.CenterHorizontally) { | ||||
|                 Icon(imageVector = Icons.Default.Person, contentDescription = "Friend") | ||||
|                 Text(text = "Friend") | ||||
|             } | ||||
|         } | ||||
|         IconButton(onClick = { /* TODO Go to next step */ }) { | ||||
|             Column (horizontalAlignment = Alignment.CenterHorizontally) { | ||||
|                 Icon(imageVector = Icons.Default.DateRange, contentDescription = "Session") | ||||
|                 Text(text = "Session") | ||||
|             } | ||||
|             Icon( | ||||
|                 imageVector = imageVector, | ||||
|                 contentDescription = resources().getString(name), | ||||
|                 // TODO Dark overlay | ||||
|                 //  tint = colors.surface | ||||
|             ) | ||||
|             Text( | ||||
|                 text = resources().getString(name), | ||||
|                 // TODO Dark overlay | ||||
|                 //  color = colors.surface | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| @Preview | ||||
| @Composable | ||||
| fun CollapsedAddButtonPreview() { | ||||
|     StudeezTheme { CollapsedAddButton() } | ||||
| fun AddButtonPreview() { | ||||
|     StudeezTheme { AddButton( | ||||
|         addButtonActions = AddButtonActions({}, {}, {}) | ||||
|     )} | ||||
| } | ||||
| 
 | ||||
| @Preview | ||||
| @Composable | ||||
| fun ExpandedAddButtonPreview() { | ||||
|     StudeezTheme { ExpandedAddButton() } | ||||
|     StudeezTheme { ExpandedAddButton ( | ||||
|         addButtonActions = AddButtonActions({}, {}, {}) | ||||
|     ) } | ||||
| } | ||||
|  | @ -57,7 +57,11 @@ fun PrimaryScreenTemplate( | |||
|         bottomBar = { NavigationBar(navigationBarActions) }, | ||||
|         floatingActionButtonPosition = FabPosition.Center, | ||||
|         isFloatingActionButtonDocked = true, | ||||
|         floatingActionButton = { CollapsedAddButton() } | ||||
|         floatingActionButton = { AddButton(AddButtonActions( | ||||
|             onTaskClick = navigationBarActions.onAddTaskClick, | ||||
|             onFriendClick = navigationBarActions.onAddFriendClick, | ||||
|             onSessionClick = navigationBarActions.onAddSessionClick | ||||
|         )) } | ||||
|     ) { | ||||
|         content(it) | ||||
|     } | ||||
|  | @ -70,7 +74,7 @@ fun PrimaryScreenPreview() { | |||
|         PrimaryScreenTemplate( | ||||
|             "Preview screen", | ||||
|             DrawerActions({}, {}, {}, {}, {}), | ||||
|             NavigationBarActions({ false }, {}, {}, {}, {}), | ||||
|             NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), | ||||
|             { | ||||
|                 IconButton(onClick = { /*TODO*/ }) { | ||||
|                     Icon( | ||||
|  |  | |||
|  | @ -41,10 +41,12 @@ fun BasicField( | |||
| fun LabelledInputField( | ||||
|     value: String, | ||||
|     onNewValue: (String) -> Unit, | ||||
|     @StringRes label: Int | ||||
|     @StringRes label: Int, | ||||
|     singleLine: Boolean = false | ||||
| ) { | ||||
|     OutlinedTextField( | ||||
|         value = value, | ||||
|         singleLine = singleLine, | ||||
|         onValueChange = onNewValue, | ||||
|         label = { Text(text = stringResource(id = label)) }, | ||||
|         modifier = Modifier.fieldModifier() | ||||
|  |  | |||
|  | @ -0,0 +1,115 @@ | |||
| package be.ugent.sel.studeez.common.composable | ||||
| 
 | ||||
| import android.app.TimePickerDialog | ||||
| import android.app.TimePickerDialog.OnTimeSetListener | ||||
| import android.content.Context | ||||
| import androidx.annotation.StringRes | ||||
| import androidx.compose.foundation.BorderStroke | ||||
| import androidx.compose.foundation.layout.Arrangement | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||
| import androidx.compose.material.* | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.MutableState | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| 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 be.ugent.sel.studeez.R | ||||
| import be.ugent.sel.studeez.common.ext.fieldModifier | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||
| 
 | ||||
| @Composable | ||||
| fun TimePickerCard( | ||||
|     @StringRes text: Int, | ||||
|     initialSeconds: Int, | ||||
|     onTimeChosen: (Int) -> Unit | ||||
| ) { | ||||
|     Card( | ||||
|         modifier = Modifier | ||||
|             .fillMaxWidth() | ||||
|             .fieldModifier(), | ||||
|         elevation = 10.dp | ||||
|     ) { | ||||
|         Row( | ||||
|             modifier = Modifier | ||||
|                 .fillMaxWidth() | ||||
|                 .fieldModifier(), | ||||
|             horizontalArrangement = Arrangement.SpaceBetween, | ||||
|             verticalAlignment = Alignment.CenterVertically | ||||
|         ) { | ||||
|             Text( | ||||
|                 text = stringResource(id = text), | ||||
|                 fontWeight = FontWeight.Medium | ||||
|             ) | ||||
| 
 | ||||
|             TimePickerButton( | ||||
|                 initialSeconds = initialSeconds, | ||||
|                 onTimeChosen = onTimeChosen | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
| fun TimePickerButton( | ||||
|     initialSeconds: Int, | ||||
|     modifier: Modifier = Modifier, | ||||
|     colors: ButtonColors = ButtonDefaults.buttonColors(), | ||||
|     border: BorderStroke? = null, | ||||
|     onTimeChosen: (Int) -> Unit | ||||
| ) { | ||||
|     val context = LocalContext.current | ||||
|     val timeState: MutableState<Int> = remember { | ||||
|         mutableStateOf(initialSeconds) | ||||
|     } | ||||
| 
 | ||||
|     Button( | ||||
|         onClick = { pickDuration(context, onTimeChosen, timeState) }, | ||||
|         modifier = modifier, | ||||
|         shape = RoundedCornerShape(20.dp), | ||||
|         colors = colors, | ||||
|         border = border | ||||
|     ) { | ||||
|         Text(text = HoursMinutesSeconds(timeState.value).toString()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| private fun pickDuration(context: Context, onTimeChosen: (Int) -> Unit, timeState: MutableState<Int>) { | ||||
|     val listener = OnTimeSetListener { _, hour, minute -> | ||||
|         timeState.value = HoursMinutesSeconds(hour, minute, 0).getTotalSeconds() | ||||
|         onTimeChosen(timeState.value) | ||||
|     } | ||||
|     val hms = HoursMinutesSeconds(timeState.value) | ||||
|     val mTimePickerDialog = TimePickerDialog( | ||||
|         context, | ||||
|         listener, | ||||
|         hms.hours, | ||||
|         hms.minutes, | ||||
|         true | ||||
|     ) | ||||
|     mTimePickerDialog.show() | ||||
| } | ||||
| 
 | ||||
| @Preview | ||||
| @Composable | ||||
| fun TimePickerButtonPreview() { | ||||
|     StudeezTheme { | ||||
|         TimePickerButton(initialSeconds = 5 * 60 + 12, onTimeChosen = {}) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @Preview | ||||
| @Composable | ||||
| fun TimePickerCardPreview() { | ||||
|     StudeezTheme { | ||||
|         TimePickerCard(text = R.string.studyTime, initialSeconds = 5 * 60 + 12, onTimeChosen = {}) | ||||
|     } | ||||
| } | ||||
|  | @ -1,5 +1,6 @@ | |||
| package be.ugent.sel.studeez.common.composable.drawer | ||||
| 
 | ||||
| import android.content.Context | ||||
| import androidx.compose.foundation.clickable | ||||
| import androidx.compose.foundation.layout.Arrangement | ||||
| import androidx.compose.foundation.layout.Box | ||||
|  | @ -16,6 +17,7 @@ import androidx.compose.material.icons.outlined.Info | |||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.vector.ImageVector | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| import androidx.compose.ui.res.vectorResource | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
|  | @ -28,7 +30,7 @@ data class DrawerActions( | |||
|     val onTimersClick: () -> Unit, | ||||
|     val onSettingsClick: () -> Unit, | ||||
|     val onLogoutClick: () -> Unit, | ||||
|     val onAboutClick: () -> Unit, | ||||
|     val onAboutClick: (Context) -> Unit, | ||||
| ) | ||||
| 
 | ||||
| fun getDrawerActions( | ||||
|  | @ -41,7 +43,7 @@ fun getDrawerActions( | |||
|         onTimersClick = { drawerViewModel.onTimersClick(open) }, | ||||
|         onSettingsClick = { drawerViewModel.onSettingsClick(open) }, | ||||
|         onLogoutClick = { drawerViewModel.onLogoutClick(openAndPopUp) }, | ||||
|         onAboutClick = { drawerViewModel.onAboutClick(open) }, | ||||
|         onAboutClick = { context -> drawerViewModel.onAboutClick(open, context = context) }, | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
|  | @ -79,10 +81,11 @@ fun Drawer( | |||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         val context = LocalContext.current | ||||
|         DrawerEntry( | ||||
|             icon = Icons.Outlined.Info, | ||||
|             text = resources().getString(R.string.about), | ||||
|             onClick = drawerActions.onAboutClick, | ||||
|             onClick = { drawerActions.onAboutClick(context) }, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -96,7 +99,7 @@ fun DrawerEntry( | |||
|     Row( | ||||
|         horizontalArrangement = Arrangement.Center, | ||||
|         modifier = Modifier | ||||
|             .clickable(onClick = { onClick() }) | ||||
|             .clickable(onClick = onClick) | ||||
|             .fillMaxWidth() | ||||
|     ) { | ||||
|         Box( | ||||
|  |  | |||
|  | @ -1,5 +1,10 @@ | |||
| package be.ugent.sel.studeez.common.composable.drawer | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.net.Uri | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| import be.ugent.sel.studeez.domain.AccountDAO | ||||
| import be.ugent.sel.studeez.domain.LogService | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||
|  | @ -9,6 +14,8 @@ import be.ugent.sel.studeez.screens.StudeezViewModel | |||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||||
| import javax.inject.Inject | ||||
| 
 | ||||
| const val REPO_URL: String = "https://github.ugent.be/SELab1/project2023-groep14/" | ||||
| 
 | ||||
| @HiltViewModel | ||||
| class DrawerViewModel @Inject constructor( | ||||
|     private val accountDAO: AccountDAO, | ||||
|  | @ -20,11 +27,11 @@ class DrawerViewModel @Inject constructor( | |||
|     } | ||||
| 
 | ||||
|     fun onTimersClick(openAndPopup: (String) -> Unit) { | ||||
|         openAndPopup(StudeezDestinations.TIMER_OVERVIEW_SCREEN) | ||||
|         openAndPopup(StudeezDestinations.TIMER_SCREEN) | ||||
|     } | ||||
| 
 | ||||
|     fun onSettingsClick(open: (String) -> Unit) { | ||||
|         // TODO | ||||
|         open(StudeezDestinations.SETTINGS_SCREEN) | ||||
|     } | ||||
| 
 | ||||
|     fun onLogoutClick(openAndPopUp: (String, String) -> Unit) { | ||||
|  | @ -34,7 +41,8 @@ class DrawerViewModel @Inject constructor( | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun onAboutClick(open: (String) -> Unit) { | ||||
|         // TODO | ||||
|     fun onAboutClick(open: (String) -> Unit, context: Context) { | ||||
|         val intent = Intent(Intent.ACTION_VIEW, Uri.parse(REPO_URL)) | ||||
|         context.startActivity(intent) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| package be.ugent.sel.studeez.common.composable.navbar | ||||
| 
 | ||||
| import android.util.Log | ||||
| import androidx.compose.material.BottomNavigation | ||||
| import androidx.compose.material.BottomNavigationItem | ||||
| import androidx.compose.material.Icon | ||||
|  | @ -15,6 +14,7 @@ import androidx.compose.ui.tooling.preview.Preview | |||
| import androidx.compose.ui.unit.dp | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN | ||||
| import be.ugent.sel.studeez.resources | ||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||
|  | @ -22,10 +22,16 @@ import be.ugent.sel.studeez.R.string as AppText | |||
| 
 | ||||
| data class NavigationBarActions( | ||||
|     val isSelectedTab: (String) -> Boolean, | ||||
| 
 | ||||
|     val onHomeClick: () -> Unit, | ||||
|     val onTasksClick: () -> Unit, | ||||
|     val onSessionsClick: () -> Unit, | ||||
|     val onProfileClick: () -> Unit, | ||||
| 
 | ||||
|     // AddButton | ||||
|     val onAddTaskClick: () -> Unit, | ||||
|     val onAddFriendClick: () -> Unit, | ||||
|     val onAddSessionClick: () -> Unit | ||||
| ) | ||||
| 
 | ||||
| fun getNavigationBarActions( | ||||
|  | @ -49,6 +55,16 @@ fun getNavigationBarActions( | |||
|         onProfileClick = { | ||||
|             navigationBarViewModel.onProfileClick(open) | ||||
|         }, | ||||
| 
 | ||||
|         onAddTaskClick = { | ||||
|             navigationBarViewModel.onAddTaskClick(open) | ||||
|         }, | ||||
|         onAddFriendClick = { | ||||
|             navigationBarViewModel.onAddFriendClick(open) | ||||
|         }, | ||||
|         onAddSessionClick = { | ||||
|             navigationBarViewModel.onAddSessionClick(open) | ||||
|         } | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
|  | @ -78,7 +94,7 @@ fun NavigationBar( | |||
|         ) | ||||
| 
 | ||||
|         // Hack to space the entries in the navigation bar, make space for fab | ||||
|         BottomNavigationItem(icon = {}, onClick = {}, selected = false) | ||||
|         BottomNavigationItem(icon = {}, onClick = {}, selected = false, enabled = false) | ||||
| 
 | ||||
|         BottomNavigationItem( | ||||
|             icon = { | ||||
|  | @ -87,8 +103,7 @@ fun NavigationBar( | |||
|                 ) | ||||
|             }, | ||||
|             label = { Text(text = resources().getString(AppText.sessions)) }, | ||||
|             // TODO selected = navigationBarActions.isSelectedTab(SESSIONS_SCREEN), | ||||
|             selected = false, | ||||
|             selected = navigationBarActions.isSelectedTab(SESSIONS_SCREEN), | ||||
|             onClick = navigationBarActions.onSessionsClick | ||||
|         ) | ||||
| 
 | ||||
|  | @ -111,7 +126,7 @@ fun NavigationBar( | |||
| fun NavigationBarPreview() { | ||||
|     StudeezTheme { | ||||
|         NavigationBar( | ||||
|             navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}), | ||||
|             navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -1,13 +1,15 @@ | |||
| package be.ugent.sel.studeez.common.composable.navbar | ||||
| 
 | ||||
| import be.ugent.sel.studeez.common.snackbar.SnackbarManager | ||||
| import be.ugent.sel.studeez.domain.LogService | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.TASKS_SCREEN | ||||
| import be.ugent.sel.studeez.screens.StudeezViewModel | ||||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||||
| import javax.inject.Inject | ||||
| import be.ugent.sel.studeez.R.string as AppText | ||||
| 
 | ||||
| @HiltViewModel | ||||
| class NavigationBarViewModel @Inject constructor( | ||||
|  | @ -23,10 +25,25 @@ class NavigationBarViewModel @Inject constructor( | |||
|     } | ||||
| 
 | ||||
|     fun onSessionsClick(open: (String) -> Unit) { | ||||
|         // TODO | ||||
|         open(SESSIONS_SCREEN) | ||||
|     } | ||||
| 
 | ||||
|     fun onProfileClick(open: (String) -> Unit) { | ||||
|         open(PROFILE_SCREEN) | ||||
|     } | ||||
| 
 | ||||
|     fun onAddTaskClick(open: (String) -> Unit) { | ||||
|         // TODO open(CREATE_TASK_SCREEN) | ||||
|         SnackbarManager.showMessage(AppText.create_task_not_possible_yet) // TODO Remove | ||||
|     } | ||||
| 
 | ||||
|     fun onAddFriendClick(open: (String) -> Unit) { | ||||
|         // TODO open(SEARCH_FRIENDS_SCREEN) | ||||
|         SnackbarManager.showMessage(AppText.add_friend_not_possible_yet) // TODO Remove | ||||
|     } | ||||
| 
 | ||||
|     fun onAddSessionClick(open: (String) -> Unit) { | ||||
|         // TODO open(CREATE_SESSION_SCREEN) | ||||
|         SnackbarManager.showMessage(AppText.create_session_not_possible_yet) // TODO Remove | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,24 @@ | |||
| package be.ugent.sel.studeez.common.composable.navbar | ||||
| 
 | ||||
| import android.app.TimePickerDialog | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| 
 | ||||
| @Composable | ||||
| fun BasicTimePicker( | ||||
|     onHoursChange: (Int) -> Unit, | ||||
|     onMinutesChange: (Int) -> Unit, | ||||
|     Hours: Int, | ||||
|     Minutes: Int, | ||||
| ): TimePickerDialog { | ||||
|     return TimePickerDialog( | ||||
|         LocalContext.current, | ||||
|         { _, mHour: Int, mMinute: Int -> | ||||
|             onHoursChange(mHour) | ||||
|             onMinutesChange(mMinute) | ||||
|         }, | ||||
|         Hours, | ||||
|         Minutes, | ||||
|         true | ||||
|     ) | ||||
| } | ||||
|  | @ -0,0 +1,8 @@ | |||
| package be.ugent.sel.studeez.common.ext | ||||
| 
 | ||||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||
| import androidx.compose.ui.unit.dp | ||||
| 
 | ||||
| fun defaultButtonShape(): RoundedCornerShape { | ||||
|     return RoundedCornerShape(20.dp) | ||||
| } | ||||
|  | @ -0,0 +1,11 @@ | |||
| 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 | ||||
| } | ||||
|  | @ -1,14 +1,10 @@ | |||
| package be.ugent.sel.studeez.data.local.models.timer_functional | ||||
| 
 | ||||
| import be.ugent.sel.studeez.data.local.models.SessionReport | ||||
| import be.ugent.sel.studeez.screens.session.sessionScreens.CustomSessionScreen | ||||
| import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen | ||||
| 
 | ||||
| class FunctionalCustomTimer(studyTime: Int) : FunctionalTimer(studyTime) { | ||||
| 
 | ||||
|     override fun tick() { | ||||
|         if (!hasEnded()) { | ||||
|             time.minOne() | ||||
|             time-- | ||||
|             totalStudyTime++ | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ class FunctionalEndlessTimer : FunctionalTimer(0) { | |||
|     } | ||||
| 
 | ||||
|     override fun tick() { | ||||
|         time.plusOne() | ||||
|         time++ | ||||
|         totalStudyTime++ | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,8 +1,5 @@ | |||
| package be.ugent.sel.studeez.data.local.models.timer_functional | ||||
| 
 | ||||
| import be.ugent.sel.studeez.screens.session.sessionScreens.BreakSessionScreen | ||||
| import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen | ||||
| 
 | ||||
| class FunctionalPomodoroTimer( | ||||
|     private var studyTime: Int, | ||||
|     private var breakTime: Int, repeats: Int | ||||
|  | @ -25,7 +22,7 @@ class FunctionalPomodoroTimer( | |||
|             } | ||||
|             isInBreak = !isInBreak | ||||
|         } | ||||
|         time.minOne() | ||||
|         time-- | ||||
| 
 | ||||
|         if (!isInBreak) { | ||||
|             totalStudyTime++ | ||||
|  |  | |||
|  | @ -1,11 +1,10 @@ | |||
| package be.ugent.sel.studeez.data.local.models.timer_functional | ||||
| 
 | ||||
| import be.ugent.sel.studeez.data.local.models.SessionReport | ||||
| import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen | ||||
| import com.google.firebase.Timestamp | ||||
| 
 | ||||
| abstract class FunctionalTimer(initialValue: Int) { | ||||
|     val time: Time = Time(initialValue) | ||||
|     var time: Time = Time(initialValue) | ||||
|     var totalStudyTime: Int = 0 | ||||
| 
 | ||||
|     fun getHoursMinutesSeconds(): HoursMinutesSeconds { | ||||
|  |  | |||
|  | @ -1,17 +1,21 @@ | |||
| package be.ugent.sel.studeez.data.local.models.timer_functional | ||||
| 
 | ||||
| class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) { | ||||
|     constructor(seconds: Int) : this( | ||||
|         hours = seconds / (60 * 60), | ||||
|         minutes = (seconds / 60) % 60, | ||||
|         seconds = seconds % 60, | ||||
| data class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) { | ||||
| 
 | ||||
|     constructor(sec: Int): this( | ||||
|         hours = sec / (60 * 60), | ||||
|         minutes = (sec / (60)) % 60, | ||||
|         seconds = sec % 60, | ||||
|     ) | ||||
| 
 | ||||
|     fun getTotalSeconds(): Int { | ||||
|         return (hours * 60 * 60) + (minutes * 60) + seconds | ||||
|     } | ||||
| 
 | ||||
|     override fun toString(): String { | ||||
|         return hours.toString().padStart(2, '0') + | ||||
|                 ":" + | ||||
|                 minutes.toString().padStart(2, '0') + | ||||
|                 ":" + | ||||
|                 seconds.toString().padStart(2, '0') | ||||
|         val hoursString = hours.toString().padStart(2, '0') | ||||
|         val minutesString = minutes.toString().padStart(2, '0') | ||||
|         val secondsString = seconds.toString().padStart(2, '0') | ||||
|         return "$hoursString:$minutesString:$secondsString" | ||||
|     } | ||||
| } | ||||
|  | @ -1,16 +1,11 @@ | |||
| package be.ugent.sel.studeez.data.local.models.timer_functional | ||||
| 
 | ||||
| class Time(initialTime: Int) { | ||||
| class Time(var time: Int) { | ||||
|     operator fun invoke() = time | ||||
| 
 | ||||
|     var time = initialTime | ||||
|     operator fun inc(): Time = Time(time + 1) | ||||
| 
 | ||||
|     fun minOne() { | ||||
|         time-- | ||||
|     } | ||||
| 
 | ||||
|     fun plusOne() { | ||||
|         time++ | ||||
|     } | ||||
|     operator fun dec(): Time = Time(time - 1) | ||||
| 
 | ||||
|     fun getAsHMS(): HoursMinutesSeconds { | ||||
|         return HoursMinutesSeconds(time) | ||||
|  |  | |||
|  | @ -6,11 +6,10 @@ import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | |||
| class CustomTimerInfo( | ||||
|     name: String, | ||||
|     description: String, | ||||
|     private val studyTime: Int, | ||||
|     var studyTime: Int, | ||||
|     id: String = "" | ||||
| ):  TimerInfo(id, name, description) { | ||||
| 
 | ||||
| 
 | ||||
|     override fun getFunctionalTimer(): FunctionalTimer { | ||||
|         return FunctionalCustomTimer(studyTime) | ||||
|     } | ||||
|  | @ -24,4 +23,8 @@ class CustomTimerInfo( | |||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     override fun <T> accept(visitor: TimerInfoVisitor<T>): T { | ||||
|         return visitor.visitCustomTimerInfo(this) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -22,4 +22,8 @@ class EndlessTimerInfo( | |||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     override fun <T> accept(visitor: TimerInfoVisitor<T>): T { | ||||
|         return visitor.visitEndlessTimerInfo(this) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -2,13 +2,14 @@ package be.ugent.sel.studeez.data.local.models.timer_info | |||
| 
 | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimerVisitor | ||||
| 
 | ||||
| class PomodoroTimerInfo( | ||||
|     name: String, | ||||
|     description: String, | ||||
|     private val studyTime: Int, | ||||
|     private val breakTime: Int, | ||||
|     private val repeats: Int, | ||||
|     var studyTime: Int, | ||||
|     var breakTime: Int, | ||||
|     val repeats: Int, | ||||
|     id: String = "" | ||||
| ):  TimerInfo(id, name, description) { | ||||
| 
 | ||||
|  | @ -28,4 +29,8 @@ class PomodoroTimerInfo( | |||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     override fun <T> accept(visitor: TimerInfoVisitor<T>): T { | ||||
|         return visitor.visitBreakTimerInfo(this) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -7,8 +7,8 @@ import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | |||
|  */ | ||||
| abstract class TimerInfo( | ||||
|     val id: String, | ||||
|     val name: String, | ||||
|     val description: String | ||||
|     var name: String, | ||||
|     var description: String | ||||
| ) { | ||||
| 
 | ||||
|     /** | ||||
|  | @ -21,6 +21,7 @@ abstract class TimerInfo( | |||
|      * TODO implementaties hebben nog hardgecodeerde strings. | ||||
|      */ | ||||
|     abstract fun asJson(): Map<String, Any> | ||||
|     abstract fun <T> accept(visitor: TimerInfoVisitor<T>): T | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,11 @@ | |||
| package be.ugent.sel.studeez.data.local.models.timer_info | ||||
| 
 | ||||
| interface TimerInfoVisitor<T> { | ||||
| 
 | ||||
|     fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): T | ||||
| 
 | ||||
|     fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo): T | ||||
| 
 | ||||
|     fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo): T | ||||
| 
 | ||||
| } | ||||
|  | @ -1,29 +1,40 @@ | |||
| package be.ugent.sel.studeez.navigation | ||||
| 
 | ||||
| object StudeezDestinations { | ||||
|     const val SPLASH_SCREEN = "splash" | ||||
|     const val SIGN_UP_SCREEN = "signup" | ||||
|     const val LOGIN_SCREEN = "login" | ||||
| 
 | ||||
|     // NavBar | ||||
|     const val HOME_SCREEN = "home" | ||||
|     const val TIMER_OVERVIEW_SCREEN = "timer_overview" | ||||
|     const val SUBJECT_SCREEN = "subjects" | ||||
|     const val SESSIONS_SCREEN = "sessions" | ||||
|     const val PROFILE_SCREEN = "profile" | ||||
| 
 | ||||
|     // Drawer | ||||
|     const val TIMER_SCREEN = "timer_overview" | ||||
|     const val SETTINGS_SCREEN = "settings" | ||||
| 
 | ||||
|     // Login flow | ||||
|     const val SPLASH_SCREEN = "splash" | ||||
|     const val LOGIN_SCREEN = "login" | ||||
|     const val SIGN_UP_SCREEN = "signup" | ||||
| 
 | ||||
|     // Studying flow | ||||
|     const val TIMER_SELECTION_SCREEN = "timer_selection" | ||||
|     const val TIMER_EDIT_SCREEN = "timer_edit" | ||||
|     const val SESSION_SCREEN = "session" | ||||
|     const val SESSION_RECAP = "session_recap" | ||||
| 
 | ||||
|     const val SUBJECT_SCREEN = "subjects" | ||||
|     const val ADD_SUBJECT_FORM = "add_subject" | ||||
|     const val EDIT_SUBJECT_FORM = "edit_subject" | ||||
|     const val TASKS_SCREEN = "tasks" | ||||
|     const val ADD_TASK_FORM = "add_task" | ||||
|     const val EDIT_TASK_FORM = "edit_task" | ||||
| 
 | ||||
|     //    const val SESSIONS_SCREEN = "sessions" | ||||
|     const val PROFILE_SCREEN = "profile" | ||||
|     // Friends flow | ||||
|     const val SEARCH_FRIENDS_SCREEN = "search_friends" | ||||
| 
 | ||||
| //    const val TIMERS_SCREEN = "timers" | ||||
| //    const val SETTINGS_SCREEN = "settings" | ||||
| 
 | ||||
|     // Edit screens | ||||
|     // Create & edit screens | ||||
|     const val CREATE_TASK_SCREEN = "create_task" | ||||
|     const val CREATE_SESSION_SCREEN = "create_session" | ||||
|     const val EDIT_PROFILE_SCREEN = "edit_profile" | ||||
| 
 | ||||
|     const val ADD_TIMER_SCREEN = "add_timer" | ||||
| } | ||||
|  | @ -33,13 +33,13 @@ fun HomeRoute( | |||
| fun HomeScreen( | ||||
|     onStartSessionClick: () -> Unit, | ||||
|     drawerActions: DrawerActions, | ||||
|     navigationBarActions: NavigationBarActions, | ||||
|     navigationBarActions: NavigationBarActions | ||||
| ) { | ||||
|     PrimaryScreenTemplate( | ||||
|         title = resources().getString(R.string.home), | ||||
|         drawerActions = drawerActions, | ||||
|         navigationBarActions = navigationBarActions, | ||||
|         barAction = { FriendsAction() } | ||||
|         // TODO barAction = { FriendsAction() } | ||||
|     ) { | ||||
|         BasicButton(R.string.start_session, Modifier.basicButton()) { | ||||
|             onStartSessionClick() | ||||
|  | @ -63,6 +63,6 @@ fun HomeScreenPreview() { | |||
|     HomeScreen( | ||||
|         onStartSessionClick = {}, | ||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}), | ||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}) | ||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}) | ||||
|     ) | ||||
| } | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ import androidx.compose.runtime.Composable | |||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.hilt.navigation.compose.hiltViewModel | ||||
| import be.ugent.sel.studeez.R | ||||
| import be.ugent.sel.studeez.common.composable.BasicTextButton | ||||
| import be.ugent.sel.studeez.common.composable.LabelledInputField | ||||
|  | @ -64,7 +63,10 @@ fun EditProfileScreen( | |||
|             BasicTextButton( | ||||
|                 text = R.string.save, | ||||
|                 Modifier.textButton(), | ||||
|                 action = editProfileActions.onSaveClick | ||||
|                 action = { | ||||
|                     editProfileActions.onSaveClick() | ||||
|                     goBack() | ||||
|                 } | ||||
|             ) | ||||
|             BasicTextButton( | ||||
|                 text = R.string.delete_profile, | ||||
|  |  | |||
|  | @ -88,6 +88,6 @@ fun ProfileScreenPreview() { | |||
|     ProfileScreen( | ||||
|         profileActions = ProfileActions({ null }, {}), | ||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}), | ||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}) | ||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}) | ||||
|     ) | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| package be.ugent.sel.studeez.screens.session | ||||
| 
 | ||||
| import android.media.MediaPlayer | ||||
| import kotlinx.coroutines.delay | ||||
| import javax.inject.Singleton | ||||
| import kotlin.time.Duration.Companion.seconds | ||||
| 
 | ||||
| @Singleton | ||||
| object InvisibleSessionManager { | ||||
|     private var viewModel: SessionViewModel? = null | ||||
|     private lateinit var mediaPlayer: MediaPlayer | ||||
| 
 | ||||
|     fun setParameters(viewModel: SessionViewModel, mediaplayer: MediaPlayer) { | ||||
|         this.viewModel = viewModel | ||||
|         this.mediaPlayer = mediaplayer | ||||
|     } | ||||
| 
 | ||||
|     suspend fun updateTimer() { | ||||
|         viewModel?.let { | ||||
|             while (!it.getTimer().hasEnded()) { | ||||
|                 delay(1.seconds) | ||||
|                 it.getTimer().tick() | ||||
|                 if (it.getTimer().hasCurrentCountdownEnded()) { | ||||
|                     mediaPlayer.start() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -12,7 +12,7 @@ import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreen | |||
| data class SessionActions( | ||||
|     val getTimer: () -> FunctionalTimer, | ||||
|     val getTask: () -> String, | ||||
|     val prepareMediaPlayer: () -> Unit, | ||||
|     val startMediaPlayer: () -> Unit, | ||||
|     val releaseMediaPlayer: () -> Unit, | ||||
|     val endSession: () -> Unit | ||||
| ) | ||||
|  | @ -26,8 +26,8 @@ private fun getSessionActions( | |||
|         getTimer = viewModel::getTimer, | ||||
|         getTask = viewModel::getTask, | ||||
|         endSession = { viewModel.endSession(openAndPopUp) }, | ||||
|         prepareMediaPlayer = mediaplayer::prepareAsync, | ||||
|         releaseMediaPlayer = mediaplayer::release | ||||
|         startMediaPlayer = mediaplayer::start, | ||||
|         releaseMediaPlayer = mediaplayer::release, | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
|  | @ -39,26 +39,15 @@ fun SessionRoute( | |||
| ) { | ||||
|     val context = LocalContext.current | ||||
|     val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) | ||||
|     val mediaplayer = MediaPlayer() | ||||
|     mediaplayer.setDataSource(context, uri) | ||||
|     mediaplayer.setOnCompletionListener { | ||||
|         mediaplayer.stop() | ||||
|         //if (timerEnd) { | ||||
| //            mediaplayer.release() | ||||
|         //} | ||||
|     } | ||||
|     mediaplayer.setOnPreparedListener { | ||||
| //        mediaplayer.start() | ||||
|     } | ||||
|     val mediaplayer = MediaPlayer.create(context, uri) | ||||
|     mediaplayer.isLooping = false | ||||
| 
 | ||||
|     val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen()) | ||||
|     InvisibleSessionManager.setParameters( | ||||
|         viewModel = viewModel, | ||||
|         mediaplayer = mediaplayer | ||||
|     ) | ||||
| 
 | ||||
|     //val sessionScreen = when (val timer = viewModel.getTimer()) { | ||||
|     //    is FunctionalCustomTimer -> CustomSessionScreen(timer) | ||||
|     //    is FunctionalPomodoroTimer -> BreakSessionScreen(timer) | ||||
|     //    is FunctionalEndlessTimer -> EndlessSessionScreen() | ||||
|     //    else -> throw java.lang.IllegalArgumentException("Unknown Timer") | ||||
|     //} | ||||
|     val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen(mediaplayer)) | ||||
| 
 | ||||
|     sessionScreen( | ||||
|         open = open, | ||||
|  |  | |||
|  | @ -9,7 +9,12 @@ import androidx.compose.foundation.layout.padding | |||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.material.TextButton | ||||
| import androidx.compose.runtime.* | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.LaunchedEffect | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.Color | ||||
|  | @ -19,16 +24,12 @@ import androidx.compose.ui.tooling.preview.Preview | |||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.compose.ui.unit.sp | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||
| import be.ugent.sel.studeez.screens.session.SessionActions | ||||
| import kotlinx.coroutines.delay | ||||
| import kotlin.time.Duration.Companion.seconds | ||||
| 
 | ||||
| abstract class AbstractSessionScreen { | ||||
| 
 | ||||
|     var timerEnd = false | ||||
| 
 | ||||
|     @Composable | ||||
|     operator fun invoke( | ||||
|         open: (String) -> Unit, | ||||
|  | @ -75,22 +76,10 @@ abstract class AbstractSessionScreen { | |||
|         LaunchedEffect(tikker) { | ||||
|             delay(1.seconds) | ||||
|             sessionActions.getTimer().tick() | ||||
|             callMediaPlayer() | ||||
|             tikker = !tikker | ||||
|         } | ||||
| 
 | ||||
|         if ( | ||||
|             sessionActions.getTimer().hasCurrentCountdownEnded() && !sessionActions.getTimer() | ||||
|                 .hasEnded() | ||||
|         ) { | ||||
| //        sessionActions.prepareMediaPlayer() | ||||
|         } | ||||
| 
 | ||||
|         if (!timerEnd && sessionActions.getTimer().hasEnded()) { | ||||
| //        sessionActions.prepareMediaPlayer() | ||||
|              timerEnd = | ||||
|                  true // Placeholder, vanaf hier moet het report opgestart worden en de sessie afgesloten | ||||
|         } | ||||
| 
 | ||||
|         val hms = sessionActions.getTimer().getHoursMinutesSeconds() | ||||
|         Column { | ||||
|             Text( | ||||
|  | @ -137,6 +126,8 @@ abstract class AbstractSessionScreen { | |||
|     @Composable | ||||
|     abstract fun motivationString(): String | ||||
| 
 | ||||
|     abstract fun callMediaPlayer() | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| @Preview | ||||
|  | @ -145,6 +136,7 @@ fun TimerPreview() { | |||
|     val sessionScreen = object : AbstractSessionScreen() { | ||||
|         @Composable | ||||
|         override fun motivationString(): String = "Test" | ||||
|         override fun callMediaPlayer() {} | ||||
| 
 | ||||
|     } | ||||
|     sessionScreen.Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}, {})) | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| package be.ugent.sel.studeez.screens.session.sessionScreens | ||||
| 
 | ||||
| import android.media.MediaPlayer | ||||
| import androidx.compose.runtime.Composable | ||||
| import be.ugent.sel.studeez.R | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer | ||||
|  | @ -7,7 +8,8 @@ import be.ugent.sel.studeez.resources | |||
| import be.ugent.sel.studeez.R.string as AppText | ||||
| 
 | ||||
| class BreakSessionScreen( | ||||
|     private val funPomoDoroTimer: FunctionalPomodoroTimer | ||||
|     private val funPomoDoroTimer: FunctionalPomodoroTimer, | ||||
|     private var mediaplayer: MediaPlayer? | ||||
| ): AbstractSessionScreen() { | ||||
| 
 | ||||
|     @Composable | ||||
|  | @ -27,4 +29,17 @@ class BreakSessionScreen( | |||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     override fun callMediaPlayer() { | ||||
|         if (funPomoDoroTimer.hasEnded()) { | ||||
|             mediaplayer?.let { it: MediaPlayer -> | ||||
|                 it.setOnCompletionListener { | ||||
|                     it.release() | ||||
|                     mediaplayer = null | ||||
|                 } | ||||
|                 it.start() | ||||
|             } | ||||
|         } else if (funPomoDoroTimer.hasCurrentCountdownEnded()) { | ||||
|             mediaplayer?.start() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,5 +1,6 @@ | |||
| package be.ugent.sel.studeez.screens.session.sessionScreens | ||||
| 
 | ||||
| import android.media.MediaPlayer | ||||
| import androidx.compose.runtime.Composable | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer | ||||
| import be.ugent.sel.studeez.resources | ||||
|  | @ -7,7 +8,8 @@ import be.ugent.sel.studeez.R.string as AppText | |||
| 
 | ||||
| 
 | ||||
| class CustomSessionScreen( | ||||
|     private val functionalTimer: FunctionalCustomTimer | ||||
|     private val functionalTimer: FunctionalCustomTimer, | ||||
|     private var mediaplayer: MediaPlayer? | ||||
| ): AbstractSessionScreen() { | ||||
| 
 | ||||
|     @Composable | ||||
|  | @ -18,4 +20,16 @@ class CustomSessionScreen( | |||
|         return resources().getString(AppText.state_focus) | ||||
|     } | ||||
| 
 | ||||
|     override fun callMediaPlayer() { | ||||
|         if (functionalTimer.hasEnded()) { | ||||
|             mediaplayer?.let { it: MediaPlayer -> | ||||
|                 it.setOnCompletionListener { | ||||
|                     it.release() | ||||
|                     mediaplayer = null | ||||
|                 } | ||||
|                 it.start() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -11,4 +11,6 @@ class EndlessSessionScreen : AbstractSessionScreen() { | |||
|     override fun motivationString(): String { | ||||
|         return resources().getString(AppText.state_focus) | ||||
|     } | ||||
| 
 | ||||
|     override fun callMediaPlayer() {} | ||||
| } | ||||
|  | @ -1,17 +1,18 @@ | |||
| package be.ugent.sel.studeez.screens.session.sessionScreens | ||||
| 
 | ||||
| import android.media.MediaPlayer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimerVisitor | ||||
| 
 | ||||
| class GetSessionScreen : FunctionalTimerVisitor<AbstractSessionScreen> { | ||||
| class GetSessionScreen(private val mediaplayer: MediaPlayer?) : FunctionalTimerVisitor<AbstractSessionScreen> { | ||||
|     override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): AbstractSessionScreen = | ||||
|         CustomSessionScreen(functionalCustomTimer) | ||||
|         CustomSessionScreen(functionalCustomTimer, mediaplayer) | ||||
| 
 | ||||
|     override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): AbstractSessionScreen = | ||||
|         EndlessSessionScreen() | ||||
| 
 | ||||
|     override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): AbstractSessionScreen = | ||||
|         BreakSessionScreen(functionalPomodoroTimer) | ||||
|         BreakSessionScreen(functionalPomodoroTimer, mediaplayer) | ||||
| } | ||||
|  | @ -26,7 +26,7 @@ fun getSessionRecapActions( | |||
|     return SessionRecapActions( | ||||
|         viewModel::getSessionReport, | ||||
|         {viewModel.saveSession(openAndPopUp)}, | ||||
|         {viewModel.saveSession(openAndPopUp)} | ||||
|         {viewModel.discardSession(openAndPopUp)} | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
|  | @ -47,8 +47,10 @@ fun SessionRecapScreen(modifier: Modifier, sessionRecapActions: SessionRecapActi | |||
|     val sessionReport: SessionReport = sessionRecapActions.getSessionReport() | ||||
|     val studyTime: Int = sessionReport.studyTime | ||||
|     val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS() | ||||
|     Column { | ||||
|         Text(text = "You studied: ${hms.hours} : ${hms.minutes} : ${hms.seconds}") | ||||
|     Column( | ||||
|         modifier = modifier | ||||
|     ) { | ||||
|         Text(text = "You studied: $hms") | ||||
| 
 | ||||
|         BasicButton( | ||||
|             R.string.save, Modifier.basicButton() | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ import javax.inject.Inject | |||
| 
 | ||||
| @HiltViewModel | ||||
| class SessionRecapViewModel @Inject constructor( | ||||
|     private val sessionReportState: SessionReportState, | ||||
|     sessionReportState: SessionReportState, | ||||
|     private val sessionDAO: SessionDAO, | ||||
|     logService: LogService | ||||
| ) : StudeezViewModel(logService) { | ||||
|  |  | |||
|  | @ -0,0 +1,42 @@ | |||
| package be.ugent.sel.studeez.screens.sessions | ||||
| 
 | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.text.style.TextAlign | ||||
| 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.resources | ||||
| import be.ugent.sel.studeez.R.string as AppText | ||||
| 
 | ||||
| @Composable | ||||
| fun SessionsRoute( | ||||
|     // viewModel: SessionsViewModel, | ||||
|     drawerActions: DrawerActions, | ||||
|     navigationBarActions: NavigationBarActions | ||||
| ) { | ||||
|     SessionsScreen( | ||||
|         drawerActions = drawerActions, | ||||
|         navigationBarActions = navigationBarActions | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
| fun SessionsScreen( | ||||
|     drawerActions: DrawerActions, | ||||
|     navigationBarActions: NavigationBarActions | ||||
| ) { | ||||
|     PrimaryScreenTemplate( | ||||
|         title = resources().getString(AppText.upcoming_sessions), | ||||
|         drawerActions = drawerActions, | ||||
|         navigationBarActions = navigationBarActions | ||||
|     ) { | ||||
|         Text( | ||||
|             text = resources().getString(AppText.sessions_temp_description), | ||||
|             modifier = Modifier.fillMaxSize(), | ||||
|             textAlign = TextAlign.Center | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,37 @@ | |||
| package be.ugent.sel.studeez.screens.settings | ||||
| 
 | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.text.style.TextAlign | ||||
| import be.ugent.sel.studeez.common.composable.DrawerScreenTemplate | ||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||
| import be.ugent.sel.studeez.resources | ||||
| import be.ugent.sel.studeez.R.string as AppText | ||||
| 
 | ||||
| @Composable | ||||
| fun SettingsRoute( | ||||
|     // viewModel: SettingsViewModel, | ||||
|     drawerActions: DrawerActions | ||||
| ) { | ||||
|     SettingsScreen( | ||||
|         drawerActions = drawerActions | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
| fun SettingsScreen( | ||||
|     drawerActions: DrawerActions | ||||
| ) { | ||||
|     DrawerScreenTemplate( | ||||
|         title = resources().getString(AppText.settings), | ||||
|         drawerActions = drawerActions | ||||
|     ) { | ||||
|         Text( | ||||
|             text = resources().getString(AppText.settings_temp_description), | ||||
|             modifier = Modifier.fillMaxSize(), | ||||
|             textAlign = TextAlign.Center | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -72,7 +72,7 @@ fun SubjectScreen( | |||
| fun SubjectScreenPreview() { | ||||
|     SubjectScreen( | ||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}), | ||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}), | ||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), | ||||
|         addSubject = {}, | ||||
|         getSubjects = { flowOf() }, | ||||
|         onViewSubject = {}, | ||||
|  |  | |||
|  | @ -0,0 +1,44 @@ | |||
| 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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| package be.ugent.sel.studeez.screens.timer_add | ||||
| 
 | ||||
| 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 TimerAddViewModel @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,43 @@ | |||
| package be.ugent.sel.studeez.screens.timer_add | ||||
| 
 | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.material.Button | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.hilt.navigation.compose.hiltViewModel | ||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.* | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.CUSTOM | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.BREAK | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.ENDLESS | ||||
| 
 | ||||
| val defaultTimerInfo: Map<TimerType, TimerInfo> = mapOf( | ||||
|     CUSTOM to CustomTimerInfo("", "", 0), | ||||
|     BREAK to PomodoroTimerInfo("", "", 0, 0, 0), | ||||
|     ENDLESS to EndlessTimerInfo("", ""), | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| @Composable | ||||
| fun TimerTypeSelectScreen( | ||||
|     open: (String) -> Unit, | ||||
|     popUp: () -> Unit, | ||||
|     viewModel: TimerTypeSelectViewModel = hiltViewModel() | ||||
| ) { | ||||
| 
 | ||||
|     SecondaryScreenTemplate(title = "Edit Timer", popUp = popUp) { | ||||
|         Column( | ||||
|             horizontalAlignment = Alignment.CenterHorizontally, | ||||
|             modifier = Modifier.fillMaxWidth() | ||||
|         ) { | ||||
|             TimerType.values().forEach { timerType -> | ||||
|                 Button(onClick = { viewModel.onTimerTypeChosen(defaultTimerInfo[timerType]!!, open) }) { | ||||
|                     Text(text = timerType.name) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,25 @@ | |||
| package be.ugent.sel.studeez.screens.timer_add | ||||
| 
 | ||||
| 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.navigation.StudeezDestinations | ||||
| import be.ugent.sel.studeez.screens.StudeezViewModel | ||||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import javax.inject.Inject | ||||
| 
 | ||||
| @HiltViewModel | ||||
| class TimerTypeSelectViewModel @Inject constructor( | ||||
|     private val editTimerState: EditTimerState, | ||||
|     private val timerDAO: TimerDAO, | ||||
|     logService: LogService | ||||
| ) : StudeezViewModel(logService) { | ||||
| 
 | ||||
| 
 | ||||
|     fun onTimerTypeChosen(timerInfo: TimerInfo, open: (String) -> Unit) { | ||||
|         editTimerState.timerInfo = timerInfo | ||||
|         open(StudeezDestinations.TIMER_EDIT_SCREEN) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,27 @@ | |||
| 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) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,65 @@ | |||
| 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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | @ -0,0 +1,29 @@ | |||
| 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,71 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit.editScreens | ||||
| 
 | ||||
| import androidx.compose.foundation.layout.Arrangement | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.fillMaxHeight | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.rememberScrollState | ||||
| import androidx.compose.foundation.verticalScroll | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import be.ugent.sel.studeez.R | ||||
| import be.ugent.sel.studeez.common.composable.BasicButton | ||||
| import be.ugent.sel.studeez.common.composable.LabelledInputField | ||||
| import be.ugent.sel.studeez.common.ext.basicButton | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||
| 
 | ||||
| abstract class AbstractTimerEditScreen(private val timerInfo: TimerInfo) { | ||||
| 
 | ||||
|     @Composable | ||||
|     operator fun invoke(onSaveClick: (TimerInfo) -> Unit) { | ||||
| 
 | ||||
|         var name by remember { mutableStateOf(timerInfo.name) } | ||||
|         var description by remember { mutableStateOf(timerInfo.description) } | ||||
| 
 | ||||
|         // This shall rerun whenever name and description change | ||||
|         timerInfo.name = name | ||||
|         timerInfo.description = description | ||||
| 
 | ||||
|         Column( | ||||
|             verticalArrangement = Arrangement.SpaceBetween, | ||||
|             modifier = Modifier.fillMaxHeight().verticalScroll(rememberScrollState()), | ||||
|         ) { | ||||
|             Column( | ||||
|                 modifier = Modifier.fillMaxWidth(), | ||||
|                 horizontalAlignment = Alignment.CenterHorizontally | ||||
|             ) { | ||||
| 
 | ||||
|                 // Fields that every timer shares (ommited id) | ||||
|                 LabelledInputField( | ||||
|                     value = name, | ||||
|                     onNewValue = { name = it }, | ||||
|                     label = R.string.name | ||||
|                 ) | ||||
| 
 | ||||
|                 LabelledInputField( | ||||
|                     value = description, | ||||
|                     onNewValue = { description = it }, | ||||
|                     label = R.string.description, | ||||
|                     singleLine = false | ||||
|                 ) | ||||
| 
 | ||||
|                 ExtraFields() | ||||
| 
 | ||||
|             } | ||||
|             BasicButton(R.string.save, Modifier.basicButton()) { | ||||
|                 onSaveClick(timerInfo) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Composable | ||||
|     open fun ExtraFields() { | ||||
|         // By default no extra fields, unless overwritten by subclass. | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,46 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit.editScreens | ||||
| 
 | ||||
| import androidx.compose.runtime.* | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import be.ugent.sel.studeez.R | ||||
| import be.ugent.sel.studeez.common.composable.TimePickerButton | ||||
| import be.ugent.sel.studeez.common.composable.TimePickerCard | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.Time | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo | ||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||
| 
 | ||||
| class BreakTimerEditScreen( | ||||
|     private val breakTimerInfo: PomodoroTimerInfo | ||||
| ): AbstractTimerEditScreen(breakTimerInfo) { | ||||
| 
 | ||||
|     @Composable | ||||
|     override fun ExtraFields() { | ||||
|         // If the user presses the OK button on the timepicker, the time in the button should change | ||||
| 
 | ||||
|         TimePickerCard(R.string.studyTime, breakTimerInfo.studyTime) { newTime -> | ||||
|             breakTimerInfo.studyTime = newTime | ||||
|         } | ||||
|         TimePickerCard(R.string.breakTime, breakTimerInfo.breakTime) { newTime -> | ||||
|             breakTimerInfo.breakTime = newTime | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| @Preview | ||||
| @Composable | ||||
| fun BreakEditScreenPreview() { | ||||
|     val pomodoroTimerInfo = PomodoroTimerInfo( | ||||
|         "Breaky the Breaktimer", | ||||
|         "Breaky is a breakdancer", | ||||
|         10 * 60, | ||||
|         60, | ||||
|         5 | ||||
|     ) | ||||
|     StudeezTheme { | ||||
|         BreakTimerEditScreen(pomodoroTimerInfo).invoke(onSaveClick = {}) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,34 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit.editScreens | ||||
| 
 | ||||
| import androidx.compose.runtime.* | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import be.ugent.sel.studeez.common.composable.TimePickerCard | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo | ||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||
| import be.ugent.sel.studeez.R.string as AppText | ||||
| 
 | ||||
| class CustomTimerEditScreen( | ||||
|     private val customTimerInfo: CustomTimerInfo | ||||
|     ): AbstractTimerEditScreen(customTimerInfo) { | ||||
| 
 | ||||
|     @Composable | ||||
|     override fun ExtraFields() { | ||||
|         TimePickerCard( | ||||
|             text = AppText.studyTime, | ||||
|             initialSeconds = customTimerInfo.studyTime | ||||
|         ) { newTime -> | ||||
|             customTimerInfo.studyTime = newTime | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| @Preview | ||||
| @Composable | ||||
| fun CustomEditScreenPreview() { | ||||
|     val customTimerInfo = CustomTimerInfo("custom", "my description", 25) | ||||
|     StudeezTheme { | ||||
|         CustomTimerEditScreen(customTimerInfo).invoke(onSaveClick = {}) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,23 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit.editScreens | ||||
| 
 | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo | ||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||
| 
 | ||||
| class EndlessTimerEditScreen( | ||||
|     endlessTimerInfo: EndlessTimerInfo | ||||
| ): AbstractTimerEditScreen(endlessTimerInfo) { | ||||
| } | ||||
| 
 | ||||
| @Preview | ||||
| @Composable | ||||
| fun EndlessEditScreenPreview() { | ||||
|     val endlessTimerInfo = EndlessTimerInfo( | ||||
|         "Forever and beyond", | ||||
|         "My endless timer description", | ||||
|     ) | ||||
|     StudeezTheme { | ||||
|         EndlessTimerEditScreen(endlessTimerInfo).invoke(onSaveClick = {}) | ||||
|     } | ||||
| } | ||||
|  | @ -16,6 +16,7 @@ import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | |||
| import be.ugent.sel.studeez.common.ext.basicButton | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||
| import be.ugent.sel.studeez.resources | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.flowOf | ||||
|  | @ -24,15 +25,18 @@ data class TimerOverviewActions( | |||
|     val getUserTimers: () -> Flow<List<TimerInfo>>, | ||||
|     val getDefaultTimers: () -> List<TimerInfo>, | ||||
|     val onEditClick: (TimerInfo) -> Unit, | ||||
|     val onAddClick: () -> Unit, | ||||
| ) | ||||
| 
 | ||||
| fun getTimerOverviewActions( | ||||
|     viewModel: TimerOverviewViewModel, | ||||
|     open: (String) -> Unit, | ||||
| ): TimerOverviewActions { | ||||
|     return TimerOverviewActions( | ||||
|         getUserTimers = viewModel::getUserTimers, | ||||
|         getDefaultTimers = viewModel::getDefaultTimers, | ||||
|         onEditClick = { viewModel.update(it) }, | ||||
|         onEditClick = { viewModel.update(it, open) }, | ||||
|         onAddClick = { viewModel.create(open) } | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
|  | @ -40,10 +44,11 @@ fun getTimerOverviewActions( | |||
| fun TimerOverviewRoute( | ||||
|     viewModel: TimerOverviewViewModel, | ||||
|     drawerActions: DrawerActions, | ||||
|     open: (String) -> Unit | ||||
| ) { | ||||
|     TimerOverviewScreen( | ||||
|         timerOverviewActions = getTimerOverviewActions(viewModel), | ||||
|         drawerActions = drawerActions, | ||||
|         timerOverviewActions = getTimerOverviewActions(viewModel, open), | ||||
|         drawerActions = drawerActions | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
|  | @ -59,8 +64,16 @@ fun TimerOverviewScreen( | |||
|         title = resources().getString(R.string.timers), | ||||
|         drawerActions = drawerActions | ||||
|     ) { | ||||
|         Column { | ||||
|         Column { // TODO knop beneden | ||||
|             LazyColumn { | ||||
|                 // Custom timer, select new duration each time | ||||
|                 item { | ||||
|                     TimerEntry(timerInfo = CustomTimerInfo( | ||||
|                         name = resources().getString(R.string.custom_name), | ||||
|                         description = resources().getString(R.string.custom_name), | ||||
|                         studyTime = 0 | ||||
|                     )) | ||||
|                 } | ||||
|                 // Default Timers, cannot be edited | ||||
|                 items(timerOverviewActions.getDefaultTimers()) { | ||||
|                     TimerEntry(timerInfo = it) {} | ||||
|  | @ -77,9 +90,13 @@ fun TimerOverviewScreen( | |||
|                     } | ||||
| 
 | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|                 // TODO uit lazy column | ||||
|                 item { | ||||
|                     BasicButton(R.string.add_timer, Modifier.basicButton()) { | ||||
|                 // TODO | ||||
|                         timerOverviewActions.onAddClick() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -95,7 +112,9 @@ fun TimerOverviewPreview() { | |||
|         timerOverviewActions = TimerOverviewActions( | ||||
|             { flowOf() }, | ||||
|             { listOf(customTimer, customTimer) }, | ||||
|             {}), | ||||
|             {}, | ||||
|             {} | ||||
|         ), | ||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}) | ||||
|     ) | ||||
| } | ||||
|  |  | |||
|  | @ -1,9 +1,11 @@ | |||
| package be.ugent.sel.studeez.screens.timer_overview | ||||
| 
 | ||||
| import be.ugent.sel.studeez.data.EditTimerState | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||
| import be.ugent.sel.studeez.domain.ConfigurationService | ||||
| import be.ugent.sel.studeez.domain.LogService | ||||
| import be.ugent.sel.studeez.domain.TimerDAO | ||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||
| import be.ugent.sel.studeez.screens.StudeezViewModel | ||||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||||
| import kotlinx.coroutines.flow.Flow | ||||
|  | @ -13,6 +15,7 @@ import javax.inject.Inject | |||
| class TimerOverviewViewModel @Inject constructor( | ||||
|     private val configurationService: ConfigurationService, | ||||
|     private val timerDAO: TimerDAO, | ||||
|     private val editTimerState: EditTimerState, | ||||
|     logService: LogService | ||||
| ) : StudeezViewModel(logService) { | ||||
| 
 | ||||
|  | @ -24,7 +27,14 @@ class TimerOverviewViewModel @Inject constructor( | |||
|         return configurationService.getDefaultTimers() | ||||
|     } | ||||
| 
 | ||||
|     fun update(timerInfo: TimerInfo) = timerDAO.updateTimer(timerInfo) | ||||
|     fun update(timerInfo: TimerInfo, open: (String) -> Unit)  { | ||||
|         editTimerState.timerInfo = timerInfo | ||||
|         open(StudeezDestinations.TIMER_EDIT_SCREEN) | ||||
|     } | ||||
| 
 | ||||
|     fun create(open: (String) -> Unit) { | ||||
|         open(StudeezDestinations.ADD_TIMER_SCREEN) | ||||
|     } | ||||
| 
 | ||||
|     fun delete(timerInfo: TimerInfo) =timerDAO.deleteTimer(timerInfo) | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,12 @@ | |||
| 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", | ||||
| ) | ||||
|  | @ -0,0 +1,91 @@ | |||
| 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) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,274 @@ | |||
| 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() | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -8,7 +8,11 @@ import androidx.compose.ui.tooling.preview.Preview | |||
| import be.ugent.sel.studeez.R | ||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||
| import be.ugent.sel.studeez.common.composable.StealthButton | ||||
| import be.ugent.sel.studeez.common.composable.TimePickerButton | ||||
| import be.ugent.sel.studeez.common.composable.TimerEntry | ||||
| 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.TimerInfo | ||||
| import be.ugent.sel.studeez.resources | ||||
| import kotlinx.coroutines.flow.Flow | ||||
|  | @ -17,6 +21,7 @@ import kotlinx.coroutines.flow.flowOf | |||
| data class TimerSelectionActions( | ||||
|     val getAllTimers: () -> Flow<List<TimerInfo>>, | ||||
|     val startSession: (TimerInfo) -> Unit, | ||||
|     val customTimeStudyTime: Int | ||||
| ) | ||||
| 
 | ||||
| fun getTimerSelectionActions( | ||||
|  | @ -26,6 +31,7 @@ fun getTimerSelectionActions( | |||
|     return TimerSelectionActions( | ||||
|         getAllTimers = viewModel::getAllTimers, | ||||
|         startSession = { viewModel.startSession(open, it) }, | ||||
|         customTimeStudyTime = viewModel.customTimerStudyTime.value | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
|  | @ -52,6 +58,11 @@ fun TimerSelectionScreen( | |||
|         popUp = popUp | ||||
|     ) { | ||||
|         LazyColumn { | ||||
|             // Custom timer with duration selection button | ||||
|             item { | ||||
|                 CustomTimerEntry(timerSelectionActions) | ||||
|             } | ||||
| 
 | ||||
|             // All timers | ||||
|             items(timers.value) { timerInfo -> | ||||
|                 TimerEntry( | ||||
|  | @ -68,11 +79,38 @@ fun TimerSelectionScreen( | |||
|     } | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
| fun CustomTimerEntry( | ||||
|     timerSelectionActions: TimerSelectionActions | ||||
| ) { | ||||
|     val timerInfo = CustomTimerInfo( | ||||
|         name = resources().getString(R.string.custom_name), | ||||
|         description = resources().getString(R.string.custom_description), | ||||
|         studyTime = timerSelectionActions.customTimeStudyTime | ||||
|     ) | ||||
|     val hms: HoursMinutesSeconds = Time(timerInfo.studyTime).getAsHMS() | ||||
| 
 | ||||
|     TimerEntry( | ||||
|         timerInfo = timerInfo, | ||||
|         leftButton = { | ||||
|             StealthButton( | ||||
|                 text = R.string.start, | ||||
|                 onClick = { timerSelectionActions.startSession(timerInfo) } | ||||
|             ) | ||||
|         }, | ||||
|         rightButton = { | ||||
|             TimePickerButton(initialSeconds = hms.getTotalSeconds()) { chosenTime -> | ||||
|                 timerInfo.studyTime = chosenTime | ||||
|             } | ||||
|         } | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| @Preview | ||||
| @Composable | ||||
| fun TimerSelectionPreview() { | ||||
|     TimerSelectionScreen( | ||||
|         timerSelectionActions = TimerSelectionActions({ flowOf() }, {}), | ||||
|         timerSelectionActions = TimerSelectionActions({ flowOf() }, {}, 0), | ||||
|         popUp = {} | ||||
|     ) | ||||
| } | ||||
|  | @ -1,5 +1,9 @@ | |||
| package be.ugent.sel.studeez.screens.timer_selection | ||||
| 
 | ||||
| import androidx.compose.runtime.MutableState | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.remember | ||||
| import be.ugent.sel.studeez.data.SelectedTimerState | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||
| import be.ugent.sel.studeez.domain.LogService | ||||
|  | @ -17,6 +21,8 @@ class TimerSelectionViewModel @Inject constructor( | |||
|     logService: LogService | ||||
| ) : StudeezViewModel(logService) { | ||||
| 
 | ||||
|     var customTimerStudyTime: MutableState<Int> = mutableStateOf(0) | ||||
| 
 | ||||
|     fun getAllTimers() : Flow<List<TimerInfo>> { | ||||
|         return timerDAO.getAllTimers() | ||||
|     } | ||||
|  |  | |||
|  | @ -12,7 +12,9 @@ import androidx.compose.ui.graphics.Color | |||
| private val DarkColorPalette = darkColors( | ||||
|     primary = Blue100, | ||||
|     primaryVariant = Blue120, | ||||
|     secondary = Yellow100 | ||||
|     secondary = Yellow100, | ||||
| 
 | ||||
|     onPrimary = Color.White | ||||
| ) | ||||
| 
 | ||||
| private val LightColorPalette = lightColors( | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| <resources> | ||||
|     <!-- Common --> | ||||
|     <!-- ========== Common ========== --> | ||||
| 
 | ||||
|     <string name="app_name">Studeez</string> | ||||
|     <string name="username">Username</string> | ||||
|     <string name="email">Email</string> | ||||
|  | @ -22,6 +23,69 @@ | |||
|     <string name="generic_error">Something wrong happened. Please try again.</string> | ||||
|     <string name="email_error">Please insert a valid email.</string> | ||||
| 
 | ||||
|     <!-- ========== NavBar ========== --> | ||||
| 
 | ||||
|     <!-- HomeScreen --> | ||||
|     <string name="home">Home</string> | ||||
|     <string name="start_session">Start session</string> | ||||
| 
 | ||||
|     <!-- Tasks --> | ||||
|     <string name="tasks">Tasks</string> | ||||
|     <string name="task">Task</string> | ||||
|     <string name="my_subjects">My Subjects</string> | ||||
|     <string name="new_subject">New Subject</string> | ||||
|     <string name="new_task">New Task</string> | ||||
|     <string name="edit_subject">Edit Subject</string> | ||||
|     <string name="edit_task">Edit Task</string> | ||||
|     <string name="delete_subject">Delete Subject</string> | ||||
|     <string name="delete_task">Delete Task</string> | ||||
|     <string name="view_tasks">View</string> | ||||
| 
 | ||||
|     <!-- Sessions --> | ||||
|     <string name="sessions_temp_description">Looks like you found the sessions screen! In here, your upcoming studying sessions with friends will be listed. You can accept invites or edit your own.</string> <!-- TODO Remove this description line once implemented. --> | ||||
|     <string name="sessions">Sessions</string> | ||||
|     <string name="session">Session</string> | ||||
|     <string name="end_session">End session</string> | ||||
|     <string name="upcoming_sessions">Upcoming sessions</string> | ||||
| 
 | ||||
|     <!-- Profile --> | ||||
|     <string name="profile">Profile</string> | ||||
|     <string name="no_username">Unknown username</string> | ||||
|     <string name="edit_profile">Edit profile</string> | ||||
|     <string name="editing_profile">Editing profile</string> | ||||
|     <string name="delete_profile">Delete profile</string> | ||||
| 
 | ||||
|     <!-- ========== Drawer ========== --> | ||||
| 
 | ||||
|     <string name="log_out">Log out</string> | ||||
|     <string name="profile_picture_description">Profile Picture</string> | ||||
|     <string name="user_description">Normal user</string> | ||||
| 
 | ||||
|     <!-- Timers --> | ||||
|     <string name="timers">Timers</string> | ||||
|     <string name="edit">Edit</string> | ||||
|     <string name="add_timer">Add timer</string> | ||||
|     <string name="pick_time">Select time</string> | ||||
|     <string name="state_focus">Focus!</string> | ||||
|     <plurals name="state_focus_remaining"> | ||||
|         <item quantity="zero">Focus one more time!</item> | ||||
|         <item quantity="one">Focus! (%d break remaining)</item> | ||||
|         <item quantity="other">Focus! (%d breaks remaining)</item> | ||||
|     </plurals> | ||||
|     <string name="state_done">Done!</string> | ||||
|     <string name="state_take_a_break">Take a break!</string> | ||||
|     <string name="custom_name">Custom</string> | ||||
|     <string name="custom_description">Select how long you want to study</string> | ||||
| 
 | ||||
|     <!-- Settings --> | ||||
|     <string name="settings_temp_description">Looks like you found the settings screen! In the future, this will enable you to edit your preferenes such as light/dark mode, end sessions automatically when we detect you are gone etc.</string> <!-- TODO Remove this description line once implemented. --> | ||||
|     <string name="settings">Settings</string> | ||||
| 
 | ||||
|     <!-- About --> | ||||
|     <string name="about">About Studeez</string> | ||||
| 
 | ||||
|     <!-- ========== Login flow ========== --> | ||||
| 
 | ||||
|     <!-- SignUpScreen --> | ||||
|     <string name="create_account">Create account</string> | ||||
|     <string name="password_error">Your password should have at least six characters and include one digit, one lower case letter and one upper case letter.</string> | ||||
|  | @ -36,58 +100,42 @@ | |||
|     <string name="recovery_email_sent">Check your inbox for the recovery email.</string> | ||||
|     <string name="empty_password_error">Password cannot be empty.</string> | ||||
| 
 | ||||
|     <!-- HomeScreen --> | ||||
|     <string name="home">Home</string> | ||||
|     <string name="start_session">Start session</string> | ||||
|     <!-- ========== Studying flow ========== --> | ||||
| 
 | ||||
|     <!-- Tasks --> | ||||
|     <string name="tasks">Tasks</string> | ||||
|     <string name="my_subjects">My Subjects</string> | ||||
|     <string name="new_subject">New Subject</string> | ||||
|     <string name="new_task">New Task</string> | ||||
|     <string name="edit_subject">Edit Subject</string> | ||||
|     <string name="edit_task">Edit Task</string> | ||||
|     <string name="delete_subject">Delete Subject</string> | ||||
|     <string name="delete_task">Delete Task</string> | ||||
|     <string name="view_tasks">View</string> | ||||
|     <!-- ========== Friends flow ========== --> | ||||
| 
 | ||||
|     <!-- Form --> | ||||
|     <string name="name">Name</string> | ||||
| 
 | ||||
|     <!-- Sessions --> | ||||
|     <string name="sessions">Sessions</string> | ||||
| 
 | ||||
|     <!-- Profile --> | ||||
|     <string name="profile">Profile</string> | ||||
|     <string name="no_username">Unknown username</string> | ||||
|     <string name="edit_profile">Edit profile</string> | ||||
|     <string name="editing_profile">Editing profile</string> | ||||
|     <string name="delete_profile">Delete profile</string> | ||||
| 
 | ||||
|     <!-- Friends --> | ||||
|     <string name="friends">Friends</string> | ||||
|     <string name="friend">Friend</string> | ||||
|     <string name="add_friend_not_possible_yet">Adding friends still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. --> | ||||
| 
 | ||||
|     <!-- Drawer / SideMenu --> | ||||
|     <string name="log_out">Log out</string> | ||||
|     <string name="profile_picture_description">Profile Picture</string> | ||||
|     <string name="user_description">Normal user</string> | ||||
|     <!-- ========== Create & edit screens ========== --> | ||||
| 
 | ||||
|     <!-- Timers --> | ||||
|     <string name="timers">Timers</string> | ||||
|     <string name="edit">Edit</string> | ||||
|     <string name="add_timer">Add timer</string> | ||||
|     <string name="state_focus">Focus!</string> | ||||
|     <plurals name="state_focus_remaining"> | ||||
|         <item quantity="zero">Focus one more time!</item> | ||||
|         <item quantity="one">Focus! (%d break remaining)</item> | ||||
|         <item quantity="other">Focus! (%d breaks remaining)</item> | ||||
|     </plurals> | ||||
|     <string name="state_done">Done!</string> | ||||
|     <string name="state_take_a_break">Take a break!</string> | ||||
|     <!-- Task --> | ||||
|     <string name="create_task_not_possible_yet">Creating tasks still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. --> | ||||
| 
 | ||||
|     <!-- Settings --> | ||||
|     <string name="settings">Settings</string> | ||||
|     <!-- Session --> | ||||
|     <string name="create_session_not_possible_yet">Creating sessions still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. --> | ||||
| 
 | ||||
|     <!-- Add Timer --> | ||||
|     <string name="addTimer_description_error">Timer description cannot be empty!</string> | ||||
|     <string name="addTimer_description">Timer description</string> | ||||
|     <string name="addTimer_name_error">Timer name cannot be empty!</string> | ||||
|     <string name="addTimer_name">Timer name</string> | ||||
|     <string name="addTimer_timepicker">Open Time Picker</string> | ||||
|     <string name="breakTime_1">" hours and "</string> | ||||
|     <string name="breakTime_2">" minutes of breaktime"</string> | ||||
|     <string name="addTimer_break_1">" break"</string> | ||||
|     <string name="addTimer_break_s">" breaks"</string> | ||||
|     <string name="addTimer_break_question">With breaks?</string> | ||||
|     <string name="addTimer_studytime_1">" hours and "</string> | ||||
|     <string name="addTimer_studytime_2">" minutes of studytime"</string> | ||||
|     <string name="addTimer_question">How long do you want to study?</string> | ||||
| 
 | ||||
|     <!-- Edit Timer--> | ||||
|     <string name="name">Name</string> | ||||
|     <string name="description">Description</string> | ||||
|     <string name="studyTime">Study Time</string> | ||||
|     <string name="breakTime">Break Time</string> | ||||
|     <string name="repeats">Number of Repeats</string> | ||||
| 
 | ||||
|     <!-- About --> | ||||
|     <string name="about">About Studeez</string> | ||||
| </resources> | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| package be.ugent.sel.studeez.timer_functional | ||||
| 
 | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | ||||
| import org.junit.Assert | ||||
| import org.junit.Test | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| package be.ugent.sel.studeez.timer_functional | ||||
| 
 | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | ||||
| import org.junit.Assert | ||||
| import org.junit.Test | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| package be.ugent.sel.studeez.timer_functional | ||||
| 
 | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | ||||
| import org.junit.Assert | ||||
| import org.junit.Test | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,100 @@ | |||
| package be.ugent.sel.studeez.timer_functional | ||||
| 
 | ||||
| import android.media.MediaPlayer | ||||
| import be.ugent.sel.studeez.data.SelectedTimerState | ||||
| import be.ugent.sel.studeez.data.SessionReportState | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer | ||||
| import be.ugent.sel.studeez.screens.session.InvisibleSessionManager | ||||
| import be.ugent.sel.studeez.screens.session.SessionViewModel | ||||
| import kotlinx.coroutines.ExperimentalCoroutinesApi | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.coroutines.test.advanceTimeBy | ||||
| import kotlinx.coroutines.test.runTest | ||||
| import org.junit.Assert | ||||
| import org.junit.Test | ||||
| import org.mockito.kotlin.mock | ||||
| 
 | ||||
| @ExperimentalCoroutinesApi | ||||
| class InvisibleSessionManagerTest { | ||||
|     private var timerState: SelectedTimerState = SelectedTimerState() | ||||
|     private lateinit var viewModel: SessionViewModel | ||||
|     private var mediaPlayer: MediaPlayer = mock() | ||||
| 
 | ||||
|     @Test | ||||
|     fun InvisibleEndlessTimerTest() = runTest { | ||||
|         timerState.selectedTimer = FunctionalEndlessTimer() | ||||
|         viewModel = SessionViewModel(timerState, SessionReportState(), mock()) | ||||
|         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) | ||||
| 
 | ||||
|         val test = launch { | ||||
|             InvisibleSessionManager.updateTimer() | ||||
|         } | ||||
| 
 | ||||
|         Assert.assertEquals(viewModel.getTimer().time.time, 0) | ||||
|         advanceTimeBy(1_000) // Start tikker | ||||
|         advanceTimeBy(10_000_000) | ||||
|         Assert.assertEquals(viewModel.getTimer().time.time, 10000) | ||||
| 
 | ||||
|         test.cancel() | ||||
|         return@runTest | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun InvisiblePomodoroTimerTest() = runTest { | ||||
|         val studyTime = 10 | ||||
|         val breakTime = 5 | ||||
|         val repeats = 1 | ||||
|         timerState.selectedTimer = FunctionalPomodoroTimer(studyTime, breakTime, repeats) | ||||
|         viewModel = SessionViewModel(timerState, SessionReportState(), mock()) | ||||
|         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) | ||||
| 
 | ||||
|         val test = launch { | ||||
|             InvisibleSessionManager.updateTimer() | ||||
|         } | ||||
| 
 | ||||
|         Assert.assertEquals(viewModel.getTimer().time.time, 10) | ||||
|         advanceTimeBy(1_000) // start tikker | ||||
| 
 | ||||
|         advanceTimeBy(9_000) | ||||
|         Assert.assertEquals(viewModel.getTimer().time.time, 1) | ||||
|         // focus, 9 sec, 1 sec nog | ||||
| 
 | ||||
|         advanceTimeBy(2_000) | ||||
|         Assert.assertEquals(viewModel.getTimer().time.time, 4) | ||||
|         // pauze, 11 sec bezig, 4 seconden nog pauze | ||||
| 
 | ||||
|         advanceTimeBy(5_000) | ||||
|         Assert.assertEquals(viewModel.getTimer().time.time, 9) | ||||
|         // 2e focus, 16 sec, 9 sec in 2e focus nog | ||||
| 
 | ||||
|         advanceTimeBy(13_000) | ||||
|         Assert.assertTrue(viewModel.getTimer().hasEnded()) | ||||
|         // Done | ||||
| 
 | ||||
|         test.cancel() | ||||
|         return@runTest | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun InvisibleCustomTimerTest() = runTest { | ||||
|         timerState.selectedTimer = FunctionalCustomTimer(5) | ||||
|         viewModel = SessionViewModel(timerState, SessionReportState(), mock()) | ||||
|         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) | ||||
| 
 | ||||
|         val test = launch { | ||||
|             InvisibleSessionManager.updateTimer() | ||||
|         } | ||||
| 
 | ||||
|         Assert.assertEquals(viewModel.getTimer().time.time, 5) | ||||
|         advanceTimeBy(1_000) // Start tikker | ||||
|         advanceTimeBy(4_000) | ||||
|         Assert.assertEquals(viewModel.getTimer().time.time, 1) | ||||
|         advanceTimeBy(1_000) | ||||
|         Assert.assertEquals(viewModel.getTimer().time.time, 0) | ||||
| 
 | ||||
|         test.cancel() | ||||
|         return@runTest | ||||
|     } | ||||
| } | ||||
|  | @ -7,6 +7,8 @@ | |||
| # Specifies the JVM arguments used for the daemon process. | ||||
| # The setting is particularly useful for tweaking memory settings. | ||||
| org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 | ||||
| org.gradle.daemon=true | ||||
| org.gradle.parallel=true | ||||
| # When configured, Gradle will run in incubating parallel mode. | ||||
| # This option should only be used with decoupled projects. More details, visit | ||||
| # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | ||||
|  |  | |||
		Reference in a new issue
	
	 brreynie
						brreynie