merge with dev
This commit is contained in:
		
						commit
						51c8fb122d
					
				
					 40 changed files with 890 additions and 444 deletions
				
			
		
							
								
								
									
										1
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
										
									
										generated
									
									
									
								
							|  | @ -42,5 +42,6 @@ | ||||||
|       <option name="processLiterals" value="true" /> |       <option name="processLiterals" value="true" /> | ||||||
|       <option name="processComments" value="true" /> |       <option name="processComments" value="true" /> | ||||||
|     </inspection_tool> |     </inspection_tool> | ||||||
|  |     <inspection_tool class="TestFunctionName" enabled="false" level="WEAK WARNING" enabled_by_default="false" /> | ||||||
|   </profile> |   </profile> | ||||||
| </component> | </component> | ||||||
|  | @ -66,6 +66,7 @@ dependencies { | ||||||
|     implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" |     implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" | ||||||
|     implementation 'androidx.compose.material:material:1.2.0' |     implementation 'androidx.compose.material:material:1.2.0' | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" |     debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" | ||||||
| 
 | 
 | ||||||
|     // ViewModel |     // ViewModel | ||||||
|  | @ -97,6 +98,9 @@ dependencies { | ||||||
|     testImplementation 'junit:junit:4.13.2' |     testImplementation 'junit:junit:4.13.2' | ||||||
|     androidTestImplementation 'androidx.test.ext:junit:1.1.5' |     androidTestImplementation 'androidx.test.ext:junit:1.1.5' | ||||||
| 
 | 
 | ||||||
|  |     // Coroutine testing | ||||||
|  |     testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' | ||||||
|  | 
 | ||||||
|     // Mocking |     // Mocking | ||||||
|     testImplementation 'org.mockito.kotlin:mockito-kotlin:3.2.0' |     testImplementation 'org.mockito.kotlin:mockito-kotlin:3.2.0' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import androidx.compose.material.Surface | ||||||
| import androidx.compose.material.rememberScaffoldState | import androidx.compose.material.rememberScaffoldState | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.ReadOnlyComposable | import androidx.compose.runtime.ReadOnlyComposable | ||||||
|  | import androidx.compose.runtime.getValue | ||||||
| import androidx.compose.runtime.remember | import androidx.compose.runtime.remember | ||||||
| import androidx.compose.runtime.rememberCoroutineScope | import androidx.compose.runtime.rememberCoroutineScope | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
|  | @ -21,9 +22,14 @@ import androidx.hilt.navigation.compose.hiltViewModel | ||||||
| import androidx.navigation.NavHostController | import androidx.navigation.NavHostController | ||||||
| import androidx.navigation.compose.NavHost | import androidx.navigation.compose.NavHost | ||||||
| import androidx.navigation.compose.composable | import androidx.navigation.compose.composable | ||||||
|  | import androidx.navigation.compose.currentBackStackEntryAsState | ||||||
| import androidx.navigation.compose.rememberNavController | import androidx.navigation.compose.rememberNavController | ||||||
|  | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel | import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel | ||||||
|  | import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions | ||||||
|  | import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel | import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel | ||||||
|  | import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions | ||||||
| import be.ugent.sel.studeez.common.snackbar.SnackbarManager | import be.ugent.sel.studeez.common.snackbar.SnackbarManager | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
| import be.ugent.sel.studeez.screens.home.HomeRoute | import be.ugent.sel.studeez.screens.home.HomeRoute | ||||||
|  | @ -31,6 +37,7 @@ import be.ugent.sel.studeez.screens.log_in.LoginRoute | ||||||
| import be.ugent.sel.studeez.screens.profile.EditProfileRoute | import be.ugent.sel.studeez.screens.profile.EditProfileRoute | ||||||
| import be.ugent.sel.studeez.screens.profile.ProfileRoute | import be.ugent.sel.studeez.screens.profile.ProfileRoute | ||||||
| import be.ugent.sel.studeez.screens.session.SessionRoute | import be.ugent.sel.studeez.screens.session.SessionRoute | ||||||
|  | import be.ugent.sel.studeez.screens.session_recap.SessionRecapRoute | ||||||
| import be.ugent.sel.studeez.screens.sign_up.SignUpRoute | import be.ugent.sel.studeez.screens.sign_up.SignUpRoute | ||||||
| import be.ugent.sel.studeez.screens.splash.SplashRoute | import be.ugent.sel.studeez.screens.splash.SplashRoute | ||||||
| import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute | import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute | ||||||
|  | @ -90,43 +97,52 @@ fun StudeezNavGraph( | ||||||
|     val drawerViewModel: DrawerViewModel = hiltViewModel() |     val drawerViewModel: DrawerViewModel = hiltViewModel() | ||||||
|     val navBarViewModel: NavigationBarViewModel = hiltViewModel() |     val navBarViewModel: NavigationBarViewModel = hiltViewModel() | ||||||
| 
 | 
 | ||||||
|  |     val backStackEntry by appState.navController.currentBackStackEntryAsState() | ||||||
|  |     val getCurrentScreen: () -> String? = { backStackEntry?.destination?.route } | ||||||
|  | 
 | ||||||
|  |     val goBack: () -> Unit = { appState.popUp() } | ||||||
|  |     val open: (String) -> Unit = { appState.navigate(it) } | ||||||
|  |     val openAndPopUp: (String, String) -> Unit = | ||||||
|  |         { route, popUp -> appState.navigateAndPopUp(route, popUp) } | ||||||
|  | 
 | ||||||
|  |     val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp) | ||||||
|  |     val navigationBarActions: NavigationBarActions = | ||||||
|  |         getNavigationBarActions(navBarViewModel, open, getCurrentScreen) | ||||||
|  | 
 | ||||||
|     NavHost( |     NavHost( | ||||||
|         navController = appState.navController, |         navController = appState.navController, | ||||||
|         startDestination = StudeezDestinations.SPLASH_SCREEN, |         startDestination = StudeezDestinations.SPLASH_SCREEN, | ||||||
|         modifier = modifier, |         modifier = modifier, | ||||||
|     ) { |     ) { | ||||||
|         val goBack: () -> Unit = { |  | ||||||
|             appState.popUp() |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         val open: (String) -> Unit = { route -> |  | ||||||
|             appState.navigate(route) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         val openAndPopUp: (String, String) -> Unit = { route, popUp -> |  | ||||||
|             appState.navigateAndPopUp(route, popUp) |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         composable(StudeezDestinations.SPLASH_SCREEN) { |         composable(StudeezDestinations.SPLASH_SCREEN) { | ||||||
|             SplashRoute(openAndPopUp, viewModel = hiltViewModel()) |             SplashRoute( | ||||||
|  |                 openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         composable(StudeezDestinations.LOGIN_SCREEN) { |         composable(StudeezDestinations.LOGIN_SCREEN) { | ||||||
|             LoginRoute(openAndPopUp, viewModel = hiltViewModel()) |             LoginRoute( | ||||||
|  |                 openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         composable(StudeezDestinations.SIGN_UP_SCREEN) { |         composable(StudeezDestinations.SIGN_UP_SCREEN) { | ||||||
|             SignUpRoute(openAndPopUp, viewModel = hiltViewModel()) |             SignUpRoute( | ||||||
|  |                 openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         composable(StudeezDestinations.HOME_SCREEN) { |         composable(StudeezDestinations.HOME_SCREEN) { | ||||||
|             HomeRoute( |             HomeRoute( | ||||||
|                 open, |                 open, | ||||||
|                 openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel(), |                 viewModel = hiltViewModel(), | ||||||
|                 drawerViewModel = drawerViewModel, |                 drawerActions = drawerActions, | ||||||
|                 navBarViewModel = navBarViewModel, |                 navigationBarActions = navigationBarActions, | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -134,21 +150,28 @@ fun StudeezNavGraph( | ||||||
|         // TODO Sessions screen |         // TODO Sessions screen | ||||||
| 
 | 
 | ||||||
|         composable(StudeezDestinations.PROFILE_SCREEN) { |         composable(StudeezDestinations.PROFILE_SCREEN) { | ||||||
|             ProfileRoute(open, openAndPopUp, viewModel = hiltViewModel()) |             ProfileRoute( | ||||||
|  |                 open, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |                 drawerActions = drawerActions, | ||||||
|  |                 navigationBarActions = navigationBarActions, | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         composable(StudeezDestinations.TIMER_OVERVIEW_SCREEN) { |         composable(StudeezDestinations.TIMER_OVERVIEW_SCREEN) { | ||||||
|             TimerOverviewRoute( |             TimerOverviewRoute( | ||||||
|                 open, |  | ||||||
|                 openAndPopUp, |  | ||||||
|                 viewModel = hiltViewModel(), |                 viewModel = hiltViewModel(), | ||||||
|                 drawerViewModel = drawerViewModel, |                 drawerActions = drawerActions, | ||||||
|                 navBarViewModel = navBarViewModel, |                 open = open | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         composable(StudeezDestinations.SESSION_SCREEN) { |         composable(StudeezDestinations.SESSION_SCREEN) { | ||||||
|             SessionRoute(open, viewModel = hiltViewModel()) |             SessionRoute( | ||||||
|  |                 open, | ||||||
|  |                 openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel() | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // TODO Timers screen |         // TODO Timers screen | ||||||
|  | @ -156,16 +179,25 @@ fun StudeezNavGraph( | ||||||
| 
 | 
 | ||||||
|         // Edit screens |         // Edit screens | ||||||
|         composable(StudeezDestinations.EDIT_PROFILE_SCREEN) { |         composable(StudeezDestinations.EDIT_PROFILE_SCREEN) { | ||||||
|             EditProfileRoute(goBack, openAndPopUp, viewModel = hiltViewModel()) |             EditProfileRoute( | ||||||
|  |                 goBack, | ||||||
|  |                 openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         composable(StudeezDestinations.TIMER_SELECTION_SCREEN) { |         composable(StudeezDestinations.TIMER_SELECTION_SCREEN) { | ||||||
|             TimerSelectionRoute( |             TimerSelectionRoute( | ||||||
|                 open, |                 open, | ||||||
|                 openAndPopUp, |                 goBack, | ||||||
|                 viewModel = hiltViewModel(), |                 viewModel = hiltViewModel(), | ||||||
|                 drawerViewModel = drawerViewModel, |             ) | ||||||
|                 navBarViewModel = navBarViewModel, |         } | ||||||
|  | 
 | ||||||
|  |         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.runtime.Composable | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.lifecycle.lifecycleScope | ||||||
| import be.ugent.sel.studeez.StudeezApp | import be.ugent.sel.studeez.StudeezApp | ||||||
|  | import be.ugent.sel.studeez.screens.session.InvisibleSessionManager | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
| import dagger.hilt.android.AndroidEntryPoint | import dagger.hilt.android.AndroidEntryPoint | ||||||
|  | import kotlinx.coroutines.Job | ||||||
|  | import kotlinx.coroutines.launch | ||||||
|  | 
 | ||||||
|  | var onTimerInvisible: Job? = null | ||||||
| 
 | 
 | ||||||
| @AndroidEntryPoint | @AndroidEntryPoint | ||||||
| class MainActivity : ComponentActivity() { | 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 | @Composable | ||||||
|  |  | ||||||
|  | @ -3,15 +3,9 @@ package be.ugent.sel.studeez.common.composable | ||||||
| import androidx.annotation.StringRes | import androidx.annotation.StringRes | ||||||
| import androidx.compose.foundation.BorderStroke | import androidx.compose.foundation.BorderStroke | ||||||
| import androidx.compose.foundation.shape.RoundedCornerShape | import androidx.compose.foundation.shape.RoundedCornerShape | ||||||
| import androidx.compose.material.Button | import androidx.compose.material.* | ||||||
| import androidx.compose.material.ButtonColors |  | ||||||
| import androidx.compose.material.ButtonDefaults |  | ||||||
| import androidx.compose.material.MaterialTheme |  | ||||||
| import androidx.compose.material.Text |  | ||||||
| import androidx.compose.material.TextButton |  | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.graphics.Color |  | ||||||
| import androidx.compose.ui.res.stringResource | import androidx.compose.ui.res.stringResource | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import androidx.compose.ui.unit.dp | import androidx.compose.ui.unit.dp | ||||||
|  | @ -21,7 +15,6 @@ import be.ugent.sel.studeez.common.ext.basicButton | ||||||
| import be.ugent.sel.studeez.common.ext.card | import be.ugent.sel.studeez.common.ext.card | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| 
 |  | ||||||
| fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { | fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { | ||||||
|     TextButton( |     TextButton( | ||||||
|         onClick = action, |         onClick = action, | ||||||
|  | @ -71,10 +64,10 @@ fun StealthButton( | ||||||
|         onClick = onClick, |         onClick = onClick, | ||||||
|         modifier = Modifier.card(), |         modifier = Modifier.card(), | ||||||
|         colors = ButtonDefaults.buttonColors( |         colors = ButtonDefaults.buttonColors( | ||||||
|             backgroundColor = Color.Transparent, |             backgroundColor = MaterialTheme.colors.surface, | ||||||
|             contentColor = Color.DarkGray, |             contentColor = MaterialTheme.colors.onSurface | ||||||
|         ), |         ), | ||||||
|         border = BorderStroke(3.dp, Color.DarkGray), |         border = BorderStroke(1.dp, MaterialTheme.colors.onSurface) | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,64 @@ | ||||||
|  | package be.ugent.sel.studeez.common.composable | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.layout.PaddingValues | ||||||
|  | import androidx.compose.foundation.layout.RowScope | ||||||
|  | import androidx.compose.material.* | ||||||
|  | import androidx.compose.material.icons.Icons | ||||||
|  | import androidx.compose.material.icons.filled.Menu | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.rememberCoroutineScope | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import be.ugent.sel.studeez.common.composable.drawer.Drawer | ||||||
|  | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
|  | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
|  | import kotlinx.coroutines.CoroutineScope | ||||||
|  | import kotlinx.coroutines.launch | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun DrawerScreenTemplate( | ||||||
|  |     title: String, | ||||||
|  |     drawerActions: DrawerActions, | ||||||
|  |     barAction: @Composable RowScope.() -> Unit = {}, | ||||||
|  |     content: @Composable (PaddingValues) -> Unit | ||||||
|  | ) { | ||||||
|  |     val scaffoldState: ScaffoldState = rememberScaffoldState() | ||||||
|  |     val coroutineScope: CoroutineScope = rememberCoroutineScope() | ||||||
|  | 
 | ||||||
|  |     Scaffold( | ||||||
|  |         scaffoldState = scaffoldState, | ||||||
|  | 
 | ||||||
|  |         topBar = { TopAppBar( | ||||||
|  |             title = { Text(text = title) }, | ||||||
|  |             navigationIcon = { | ||||||
|  |                 IconButton(onClick = { | ||||||
|  |                     coroutineScope.launch { scaffoldState.drawerState.open() } | ||||||
|  |                 }) { | ||||||
|  |                     Icon( | ||||||
|  |                         imageVector = Icons.Default.Menu, | ||||||
|  |                         contentDescription = resources().getString(AppText.menu) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             actions = barAction | ||||||
|  |         )}, | ||||||
|  | 
 | ||||||
|  |         drawerContent = { | ||||||
|  |             Drawer(drawerActions) | ||||||
|  |         } | ||||||
|  |     ) { | ||||||
|  |         content(it) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun DrawerScreenPreview() { | ||||||
|  |     StudeezTheme { DrawerScreenTemplate( | ||||||
|  |         title = "Drawer screen preview", | ||||||
|  |         drawerActions =DrawerActions({}, {}, {}, {}, {}) | ||||||
|  |     ) { | ||||||
|  |         Text(text = "Preview content") | ||||||
|  |     } } | ||||||
|  | } | ||||||
|  | @ -2,26 +2,19 @@ package be.ugent.sel.studeez.common.composable | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.PaddingValues | import androidx.compose.foundation.layout.PaddingValues | ||||||
| import androidx.compose.foundation.layout.RowScope | import androidx.compose.foundation.layout.RowScope | ||||||
| import androidx.compose.material.FabPosition | import androidx.compose.material.* | ||||||
| import androidx.compose.material.Icon |  | ||||||
| import androidx.compose.material.IconButton |  | ||||||
| import androidx.compose.material.Scaffold |  | ||||||
| import androidx.compose.material.ScaffoldState |  | ||||||
| import androidx.compose.material.Text |  | ||||||
| import androidx.compose.material.TopAppBar |  | ||||||
| import androidx.compose.material.icons.Icons | import androidx.compose.material.icons.Icons | ||||||
| import androidx.compose.material.icons.filled.Edit | import androidx.compose.material.icons.filled.Edit | ||||||
| import androidx.compose.material.icons.filled.Menu | import androidx.compose.material.icons.filled.Menu | ||||||
| import androidx.compose.material.rememberScaffoldState |  | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.rememberCoroutineScope | import androidx.compose.runtime.rememberCoroutineScope | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.resources |  | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.Drawer | import be.ugent.sel.studeez.common.composable.drawer.Drawer | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBar | import be.ugent.sel.studeez.common.composable.navbar.NavigationBar | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
|  | @ -31,7 +24,7 @@ fun PrimaryScreenTemplate( | ||||||
|     title: String, |     title: String, | ||||||
|     drawerActions: DrawerActions, |     drawerActions: DrawerActions, | ||||||
|     navigationBarActions: NavigationBarActions, |     navigationBarActions: NavigationBarActions, | ||||||
|     action: @Composable RowScope.() -> Unit = {}, |     barAction: @Composable RowScope.() -> Unit = {}, | ||||||
|     content: @Composable (PaddingValues) -> Unit |     content: @Composable (PaddingValues) -> Unit | ||||||
| ) { | ) { | ||||||
|     val scaffoldState: ScaffoldState = rememberScaffoldState() |     val scaffoldState: ScaffoldState = rememberScaffoldState() | ||||||
|  | @ -53,7 +46,7 @@ fun PrimaryScreenTemplate( | ||||||
|                         ) |                         ) | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 actions = action |                 actions = barAction | ||||||
|             ) |             ) | ||||||
|         }, |         }, | ||||||
| 
 | 
 | ||||||
|  | @ -77,7 +70,7 @@ fun PrimaryScreenPreview() { | ||||||
|         PrimaryScreenTemplate( |         PrimaryScreenTemplate( | ||||||
|             "Preview screen", |             "Preview screen", | ||||||
|             DrawerActions({}, {}, {}, {}, {}), |             DrawerActions({}, {}, {}, {}, {}), | ||||||
|             NavigationBarActions({}, {}, {}, {}), |             NavigationBarActions({ false }, {}, {}, {}, {}), | ||||||
|             { |             { | ||||||
|                 IconButton(onClick = { /*TODO*/ }) { |                 IconButton(onClick = { /*TODO*/ }) { | ||||||
|                     Icon( |                     Icon( | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package be.ugent.sel.studeez.common.composable | package be.ugent.sel.studeez.common.composable | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.PaddingValues | import androidx.compose.foundation.layout.PaddingValues | ||||||
|  | import androidx.compose.foundation.layout.RowScope | ||||||
| import androidx.compose.material.* | import androidx.compose.material.* | ||||||
| import androidx.compose.material.icons.Icons | import androidx.compose.material.icons.Icons | ||||||
| import androidx.compose.material.icons.filled.ArrowBack | import androidx.compose.material.icons.filled.ArrowBack | ||||||
|  | @ -10,13 +11,12 @@ import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.resources | import be.ugent.sel.studeez.resources | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
| 
 | 
 | ||||||
| // TODO Add option for button in top right corner as extra button |  | ||||||
| 
 |  | ||||||
| @Composable | @Composable | ||||||
| // Does not contain floatingActionButton and bottom bar, used in all the other screens | // Does not contain floatingActionButton and bottom bar, used in all the other screens | ||||||
| fun SecondaryScreenTemplate( | fun SecondaryScreenTemplate( | ||||||
|     title: String, |     title: String, | ||||||
|     popUp: () -> Unit, |     popUp: () -> Unit, | ||||||
|  |     barAction: @Composable RowScope.() -> Unit = {}, | ||||||
|     content: @Composable (PaddingValues) -> Unit |     content: @Composable (PaddingValues) -> Unit | ||||||
| ) { | ) { | ||||||
|     Scaffold( |     Scaffold( | ||||||
|  | @ -30,7 +30,8 @@ fun SecondaryScreenTemplate( | ||||||
|                         contentDescription = resources().getString(R.string.go_back) |                         contentDescription = resources().getString(R.string.go_back) | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|             } |             }, | ||||||
|  |             actions = barAction | ||||||
|         ) }, |         ) }, | ||||||
|     ) { paddingValues -> |     ) { paddingValues -> | ||||||
|         content(paddingValues) |         content(paddingValues) | ||||||
|  |  | ||||||
|  | @ -1,10 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.common.composable | package be.ugent.sel.studeez.common.composable | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.Arrangement | import androidx.compose.foundation.layout.* | ||||||
| import androidx.compose.foundation.layout.Column |  | ||||||
| import androidx.compose.foundation.layout.Row |  | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth |  | ||||||
| import androidx.compose.foundation.layout.padding |  | ||||||
| import androidx.compose.material.Text | import androidx.compose.material.Text | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.Alignment | import androidx.compose.ui.Alignment | ||||||
|  | @ -20,24 +16,39 @@ import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
| @Composable | @Composable | ||||||
| fun TimerEntry( | fun TimerEntry( | ||||||
|     timerInfo: TimerInfo, |     timerInfo: TimerInfo, | ||||||
|     button: @Composable () -> Unit, |     rightButton: @Composable () -> Unit = {}, | ||||||
|  |     leftButton: @Composable () -> Unit = {} | ||||||
| ) { | ) { | ||||||
|     Row( |     Row( | ||||||
|         verticalAlignment = Alignment.CenterVertically, |         modifier = Modifier.fillMaxWidth() | ||||||
|         modifier = Modifier.fillMaxWidth(), |  | ||||||
|         horizontalArrangement = Arrangement.SpaceBetween |  | ||||||
|     ) { |     ) { | ||||||
|  |         Row( | ||||||
|  |             modifier = Modifier.weight(1f) | ||||||
|  |         ) { | ||||||
|  |             Box(modifier = Modifier.align(alignment = Alignment.CenterVertically)) { | ||||||
|  |                 leftButton() | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             Column( |             Column( | ||||||
|             Modifier.padding(horizontal = 10.dp) |                 Modifier.padding( | ||||||
|  |                     horizontal = 20.dp, | ||||||
|  |                     vertical = 11.dp | ||||||
|  |                 ) | ||||||
|             ) { |             ) { | ||||||
|                 Text( |                 Text( | ||||||
|                 text = timerInfo.name, fontWeight = FontWeight.Bold, fontSize = 20.sp |                     text = timerInfo.name, | ||||||
|  |                     fontWeight = FontWeight.Medium, | ||||||
|  |                     fontSize = 20.sp | ||||||
|                 ) |                 ) | ||||||
|                 Text( |                 Text( | ||||||
|                 text = timerInfo.description, fontWeight = FontWeight.Light, fontSize = 15.sp |                     text = timerInfo.description, fontWeight = FontWeight.Light, fontSize = 14.sp | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|         button() |         } | ||||||
|  | 
 | ||||||
|  |         Box(modifier = Modifier.align(alignment = Alignment.CenterVertically)) { | ||||||
|  |             rightButton() | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -12,11 +12,14 @@ import androidx.compose.material.icons.outlined.DateRange | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import androidx.compose.ui.unit.dp | 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.resources | import be.ugent.sel.studeez.resources | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
| import be.ugent.sel.studeez.R.string as AppText | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| data class NavigationBarActions( | data class NavigationBarActions( | ||||||
|  |     val isSelectedTab: (String) -> Boolean, | ||||||
|     val onHomeClick: () -> Unit, |     val onHomeClick: () -> Unit, | ||||||
|     val onTasksClick: () -> Unit, |     val onTasksClick: () -> Unit, | ||||||
|     val onSessionsClick: () -> Unit, |     val onSessionsClick: () -> Unit, | ||||||
|  | @ -26,29 +29,38 @@ data class NavigationBarActions( | ||||||
| fun getNavigationBarActions( | fun getNavigationBarActions( | ||||||
|     navigationBarViewModel: NavigationBarViewModel, |     navigationBarViewModel: NavigationBarViewModel, | ||||||
|     open: (String) -> Unit, |     open: (String) -> Unit, | ||||||
|  |     getCurrentScreen: () -> String? | ||||||
| ): NavigationBarActions { | ): NavigationBarActions { | ||||||
|     return NavigationBarActions( |     return NavigationBarActions( | ||||||
|         onHomeClick = { navigationBarViewModel.onHomeClick(open) }, |         isSelectedTab = { screen -> | ||||||
|         onTasksClick = { navigationBarViewModel.onTasksClick(open) }, |             screen == getCurrentScreen() | ||||||
|         onSessionsClick = { navigationBarViewModel.onSessionsClick(open) }, |         }, | ||||||
|         onProfileClick = { navigationBarViewModel.onProfileClick(open) }, |         onHomeClick = { | ||||||
|  |             navigationBarViewModel.onHomeClick(open) | ||||||
|  |         }, | ||||||
|  |         onTasksClick = { | ||||||
|  |             navigationBarViewModel.onTasksClick(open) | ||||||
|  |         }, | ||||||
|  |         onSessionsClick = { | ||||||
|  |             navigationBarViewModel.onSessionsClick(open) | ||||||
|  |         }, | ||||||
|  |         onProfileClick = { | ||||||
|  |             navigationBarViewModel.onProfileClick(open) | ||||||
|  |         }, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun NavigationBar( | fun NavigationBar( | ||||||
|     navigationBarActions: NavigationBarActions, |     navigationBarActions: NavigationBarActions | ||||||
| ) { | ) { | ||||||
|     // TODO Pass functions and new screens. |  | ||||||
|     // TODO Pass which screen is selected. |  | ||||||
|     // TODO Disabled -> HIGH/MEDIUM_EMPHASIS if the page is implemented |  | ||||||
|     BottomNavigation( |     BottomNavigation( | ||||||
|         elevation = 10.dp |         elevation = 10.dp | ||||||
|     ) { |     ) { | ||||||
|         BottomNavigationItem( |         BottomNavigationItem( | ||||||
|             icon = { Icon(imageVector = Icons.Default.List, resources().getString(AppText.home)) }, |             icon = { Icon(imageVector = Icons.Default.List, resources().getString(AppText.home)) }, | ||||||
|             label = { Text(text = resources().getString(AppText.home)) }, |             label = { Text(text = resources().getString(AppText.home)) }, | ||||||
|             selected = false, // TODO |             selected = navigationBarActions.isSelectedTab(HOME_SCREEN), | ||||||
|             onClick = navigationBarActions.onHomeClick |             onClick = navigationBarActions.onHomeClick | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  | @ -59,7 +71,8 @@ fun NavigationBar( | ||||||
|                 ) |                 ) | ||||||
|             }, |             }, | ||||||
|             label = { Text(text = resources().getString(AppText.tasks)) }, |             label = { Text(text = resources().getString(AppText.tasks)) }, | ||||||
|             selected = false, // TODO |             // TODO selected = navigationBarActions.isSelectedTab(TASKS_SCREEN), | ||||||
|  |             selected = false, | ||||||
|             onClick = navigationBarActions.onTasksClick |             onClick = navigationBarActions.onTasksClick | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  | @ -73,7 +86,8 @@ fun NavigationBar( | ||||||
|                 ) |                 ) | ||||||
|             }, |             }, | ||||||
|             label = { Text(text = resources().getString(AppText.sessions)) }, |             label = { Text(text = resources().getString(AppText.sessions)) }, | ||||||
|             selected = false, // TODO |             // TODO selected = navigationBarActions.isSelectedTab(SESSIONS_SCREEN), | ||||||
|  |             selected = false, | ||||||
|             onClick = navigationBarActions.onSessionsClick |             onClick = navigationBarActions.onSessionsClick | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  | @ -84,7 +98,7 @@ fun NavigationBar( | ||||||
|                 ) |                 ) | ||||||
|             }, |             }, | ||||||
|             label = { Text(text = resources().getString(AppText.profile)) }, |             label = { Text(text = resources().getString(AppText.profile)) }, | ||||||
|             selected = false, // TODO |             selected = navigationBarActions.isSelectedTab(PROFILE_SCREEN), | ||||||
|             onClick = navigationBarActions.onProfileClick |             onClick = navigationBarActions.onProfileClick | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  | @ -95,6 +109,8 @@ fun NavigationBar( | ||||||
| @Composable | @Composable | ||||||
| fun NavigationBarPreview() { | fun NavigationBarPreview() { | ||||||
|     StudeezTheme { |     StudeezTheme { | ||||||
|         NavigationBar(NavigationBarActions({}, {}, {}, {})) |         NavigationBar( | ||||||
|  |             navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}), | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | package be.ugent.sel.studeez.data | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.SessionReport | ||||||
|  | import javax.inject.Inject | ||||||
|  | import javax.inject.Singleton | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Used to communicate the SelectedTimer from the selection screen to the session screen. | ||||||
|  |  * Because this is a singleton-class the view-models of both screens observe the same data. | ||||||
|  |  */ | ||||||
|  | @Singleton | ||||||
|  | class SessionReportState @Inject constructor(){ | ||||||
|  |     var sessionReport: SessionReport? = null | ||||||
|  | } | ||||||
|  | @ -3,19 +3,22 @@ package be.ugent.sel.studeez.data.local.models.timer_functional | ||||||
| class FunctionalCustomTimer(studyTime: Int) : FunctionalTimer(studyTime) { | class FunctionalCustomTimer(studyTime: Int) : FunctionalTimer(studyTime) { | ||||||
| 
 | 
 | ||||||
|     override fun tick() { |     override fun tick() { | ||||||
|         if (time.time == 0) { |         if (!hasEnded()) { | ||||||
|             view = StudyState.DONE |  | ||||||
|         } else { |  | ||||||
|             time.minOne() |             time.minOne() | ||||||
|  |             totalStudyTime++ | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun hasEnded(): Boolean { |     override fun hasEnded(): Boolean { | ||||||
|         return view == StudyState.DONE |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun hasCurrentCountdownEnded(): Boolean { |  | ||||||
|         return time.time == 0 |         return time.time == 0 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     override fun hasCurrentCountdownEnded(): Boolean { | ||||||
|  |         return hasEnded() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun <T> accept(visitor: FunctionalTimerVisitor<T>): T { | ||||||
|  |         return visitor.visitFunctionalCustomTimer(this) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.data.local.models.timer_functional | package be.ugent.sel.studeez.data.local.models.timer_functional | ||||||
| 
 | 
 | ||||||
| class FunctionalEndlessTimer() : FunctionalTimer(0) { | class FunctionalEndlessTimer : FunctionalTimer(0) { | ||||||
| 
 | 
 | ||||||
|     override fun hasEnded(): Boolean { |     override fun hasEnded(): Boolean { | ||||||
|         return false |         return false | ||||||
|  | @ -12,5 +12,10 @@ class FunctionalEndlessTimer() : FunctionalTimer(0) { | ||||||
| 
 | 
 | ||||||
|     override fun tick() { |     override fun tick() { | ||||||
|         time.plusOne() |         time.plusOne() | ||||||
|  |         totalStudyTime++ | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun <T> accept(visitor: FunctionalTimerVisitor<T>): T { | ||||||
|  |         return visitor.visitFunctionalEndlessTimer(this) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -9,30 +9,39 @@ class FunctionalPomodoroTimer( | ||||||
|     var isInBreak = false |     var isInBreak = false | ||||||
| 
 | 
 | ||||||
|     override fun tick() { |     override fun tick() { | ||||||
|         if (time.time == 0 && breaksRemaining == 0) { |         if (hasEnded()) { | ||||||
|             view = StudyState.DONE |  | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (time.time == 0) { |         if (hasCurrentCountdownEnded()) { | ||||||
|             if (isInBreak) { |             if (isInBreak) { | ||||||
|                 breaksRemaining-- |                 breaksRemaining-- | ||||||
|                 view = StudyState.FOCUS_REMAINING |  | ||||||
|                 time.time = studyTime |                 time.time = studyTime | ||||||
|             } else { |             } else { | ||||||
|                 view = StudyState.BREAK |  | ||||||
|                 time.time = breakTime |                 time.time = breakTime | ||||||
|             } |             } | ||||||
|             isInBreak = !isInBreak |             isInBreak = !isInBreak | ||||||
|         } |         } | ||||||
|         time.minOne() |         time.minOne() | ||||||
|  | 
 | ||||||
|  |         if (!isInBreak) { | ||||||
|  |             totalStudyTime++ | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun hasEnded(): Boolean { |     override fun hasEnded(): Boolean { | ||||||
|         return breaksRemaining == 0 && time.time == 0 |         return !hasBreaksRemaining() && hasCurrentCountdownEnded() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun hasBreaksRemaining(): Boolean { | ||||||
|  |         return breaksRemaining > 0 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun hasCurrentCountdownEnded(): Boolean { |     override fun hasCurrentCountdownEnded(): Boolean { | ||||||
|         return time.time == 0 |         return time.time == 0 | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     override fun <T> accept(visitor: FunctionalTimerVisitor<T>): T { | ||||||
|  |         return visitor.visitFunctionalBreakTimer(this) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | @ -1,8 +1,11 @@ | ||||||
| package be.ugent.sel.studeez.data.local.models.timer_functional | package be.ugent.sel.studeez.data.local.models.timer_functional | ||||||
| 
 | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.SessionReport | ||||||
|  | import com.google.firebase.Timestamp | ||||||
|  | 
 | ||||||
| abstract class FunctionalTimer(initialValue: Int) { | abstract class FunctionalTimer(initialValue: Int) { | ||||||
|     val time: Time = Time(initialValue) |     val time: Time = Time(initialValue) | ||||||
|     var view: StudyState = StudyState.FOCUS |     var totalStudyTime: Int = 0 | ||||||
| 
 | 
 | ||||||
|     fun getHoursMinutesSeconds(): HoursMinutesSeconds { |     fun getHoursMinutesSeconds(): HoursMinutesSeconds { | ||||||
|         return time.getAsHMS() |         return time.getAsHMS() | ||||||
|  | @ -14,8 +17,12 @@ abstract class FunctionalTimer(initialValue: Int) { | ||||||
| 
 | 
 | ||||||
|     abstract fun hasCurrentCountdownEnded(): Boolean |     abstract fun hasCurrentCountdownEnded(): Boolean | ||||||
| 
 | 
 | ||||||
|     enum class StudyState { |     fun getSessionReport(): SessionReport { | ||||||
|         FOCUS, DONE, BREAK, FOCUS_REMAINING |         return SessionReport( | ||||||
|  |             studyTime = totalStudyTime, | ||||||
|  |             endTime = Timestamp.now() | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     abstract fun <T> accept(visitor: FunctionalTimerVisitor<T>): T | ||||||
| } | } | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | package be.ugent.sel.studeez.data.local.models.timer_functional | ||||||
|  | 
 | ||||||
|  | interface FunctionalTimerVisitor<T> { | ||||||
|  | 
 | ||||||
|  |     fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): T | ||||||
|  | 
 | ||||||
|  |     fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): T | ||||||
|  | 
 | ||||||
|  |     fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): T | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -9,6 +9,7 @@ object StudeezDestinations { | ||||||
|     const val TIMER_OVERVIEW_SCREEN = "timer_overview" |     const val TIMER_OVERVIEW_SCREEN = "timer_overview" | ||||||
|     const val TIMER_SELECTION_SCREEN = "timer_selection" |     const val TIMER_SELECTION_SCREEN = "timer_selection" | ||||||
|     const val SESSION_SCREEN = "session" |     const val SESSION_SCREEN = "session" | ||||||
|  |     const val SESSION_RECAP = "session_recap" | ||||||
|  //    const val TASKS_SCREEN = "tasks" |  //    const val TASKS_SCREEN = "tasks" | ||||||
|  //    const val SESSIONS_SCREEN = "sessions" |  //    const val SESSIONS_SCREEN = "sessions" | ||||||
|     const val PROFILE_SCREEN = "profile" |     const val PROFILE_SCREEN = "profile" | ||||||
|  |  | ||||||
|  | @ -11,26 +11,21 @@ import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.composable.BasicButton | import be.ugent.sel.studeez.common.composable.BasicButton | ||||||
| import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel |  | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions |  | ||||||
| import be.ugent.sel.studeez.common.ext.basicButton | import be.ugent.sel.studeez.common.ext.basicButton | ||||||
| import be.ugent.sel.studeez.resources | import be.ugent.sel.studeez.resources | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun HomeRoute( | fun HomeRoute( | ||||||
|     open: (String) -> Unit, |     open: (String) -> Unit, | ||||||
|     openAndPopUp: (String, String) -> Unit, |  | ||||||
|     viewModel: HomeViewModel, |     viewModel: HomeViewModel, | ||||||
|     drawerViewModel: DrawerViewModel, |     drawerActions: DrawerActions, | ||||||
|     navBarViewModel: NavigationBarViewModel, |     navigationBarActions: NavigationBarActions, | ||||||
| ) { | ) { | ||||||
|     HomeScreen( |     HomeScreen( | ||||||
|         onStartSessionClick = { viewModel.onStartSessionClick(open) }, |         onStartSessionClick = { viewModel.onStartSessionClick(open) }, | ||||||
|         drawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp), |         drawerActions = drawerActions, | ||||||
|         navigationBarActions = getNavigationBarActions(navBarViewModel, open), |         navigationBarActions = navigationBarActions, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -40,12 +35,11 @@ fun HomeScreen( | ||||||
|     drawerActions: DrawerActions, |     drawerActions: DrawerActions, | ||||||
|     navigationBarActions: NavigationBarActions, |     navigationBarActions: NavigationBarActions, | ||||||
| ) { | ) { | ||||||
| 
 |  | ||||||
|     PrimaryScreenTemplate( |     PrimaryScreenTemplate( | ||||||
|         title = resources().getString(R.string.home), |         title = resources().getString(R.string.home), | ||||||
|         drawerActions = drawerActions, |         drawerActions = drawerActions, | ||||||
|         navigationBarActions = navigationBarActions, |         navigationBarActions = navigationBarActions, | ||||||
|         action = { FriendsAction() } |         barAction = { FriendsAction() } | ||||||
|     ) { |     ) { | ||||||
|         BasicButton(R.string.start_session, Modifier.basicButton()) { |         BasicButton(R.string.start_session, Modifier.basicButton()) { | ||||||
|             onStartSessionClick() |             onStartSessionClick() | ||||||
|  | @ -69,6 +63,6 @@ fun HomeScreenPreview() { | ||||||
|     HomeScreen( |     HomeScreen( | ||||||
|         onStartSessionClick = {}, |         onStartSessionClick = {}, | ||||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}), |         drawerActions = DrawerActions({}, {}, {}, {}, {}), | ||||||
|         navigationBarActions = NavigationBarActions({}, {}, {}, {}) |         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}) | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,7 +5,6 @@ import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.getValue | import androidx.compose.runtime.getValue | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import androidx.hilt.navigation.compose.hiltViewModel |  | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.composable.BasicTextButton | import be.ugent.sel.studeez.common.composable.BasicTextButton | ||||||
| import be.ugent.sel.studeez.common.composable.LabelledInputField | import be.ugent.sel.studeez.common.composable.LabelledInputField | ||||||
|  |  | ||||||
|  | @ -11,15 +11,12 @@ import androidx.compose.runtime.mutableStateOf | ||||||
| import androidx.compose.runtime.remember | import androidx.compose.runtime.remember | ||||||
| import androidx.compose.runtime.setValue | import androidx.compose.runtime.setValue | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import androidx.hilt.navigation.compose.hiltViewModel |  | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.composable.Headline | import be.ugent.sel.studeez.common.composable.Headline | ||||||
| import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | ||||||
| import be.ugent.sel.studeez.resources |  | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions | import be.ugent.sel.studeez.resources | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| import be.ugent.sel.studeez.R.string as AppText | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
|  | @ -41,13 +38,14 @@ fun getProfileActions( | ||||||
| @Composable | @Composable | ||||||
| fun ProfileRoute( | fun ProfileRoute( | ||||||
|     open: (String) -> Unit, |     open: (String) -> Unit, | ||||||
|     openAndPopUp: (String, String) -> Unit, |  | ||||||
|     viewModel: ProfileViewModel, |     viewModel: ProfileViewModel, | ||||||
|  |     drawerActions: DrawerActions, | ||||||
|  |     navigationBarActions: NavigationBarActions, | ||||||
| ) { | ) { | ||||||
|     ProfileScreen( |     ProfileScreen( | ||||||
|         profileActions = getProfileActions(viewModel, open), |         profileActions = getProfileActions(viewModel, open), | ||||||
|         drawerActions = getDrawerActions(hiltViewModel(), open, openAndPopUp), |         drawerActions = drawerActions, | ||||||
|         navigationBarActions = getNavigationBarActions(hiltViewModel(), open), |         navigationBarActions = navigationBarActions, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -65,7 +63,7 @@ fun ProfileScreen( | ||||||
|         title = resources().getString(AppText.profile), |         title = resources().getString(AppText.profile), | ||||||
|         drawerActions = drawerActions, |         drawerActions = drawerActions, | ||||||
|         navigationBarActions = navigationBarActions, |         navigationBarActions = navigationBarActions, | ||||||
|         action = { EditAction(onClick = profileActions.onEditProfileClick) } |         barAction = { EditAction(onClick = profileActions.onEditProfileClick) } | ||||||
|     ) { |     ) { | ||||||
|         Headline(text = (username ?: resources().getString(R.string.no_username))) |         Headline(text = (username ?: resources().getString(R.string.no_username))) | ||||||
|     } |     } | ||||||
|  | @ -90,6 +88,6 @@ fun ProfileScreenPreview() { | ||||||
|     ProfileScreen( |     ProfileScreen( | ||||||
|         profileActions = ProfileActions({ null }, {}), |         profileActions = ProfileActions({ null }, {}), | ||||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}), |         drawerActions = DrawerActions({}, {}, {}, {}, {}), | ||||||
|         navigationBarActions = NavigationBarActions({}, {}, {}, {}) |         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() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,56 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session | ||||||
|  | 
 | ||||||
|  | import android.media.MediaPlayer | ||||||
|  | import android.media.RingtoneManager | ||||||
|  | import android.net.Uri | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.platform.LocalContext | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | ||||||
|  | import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen | ||||||
|  | import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreen | ||||||
|  | 
 | ||||||
|  | data class SessionActions( | ||||||
|  |     val getTimer: () -> FunctionalTimer, | ||||||
|  |     val getTask: () -> String, | ||||||
|  |     val startMediaPlayer: () -> Unit, | ||||||
|  |     val releaseMediaPlayer: () -> Unit, | ||||||
|  |     val endSession: () -> Unit | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | private fun getSessionActions( | ||||||
|  |     viewModel: SessionViewModel, | ||||||
|  |     openAndPopUp: (String, String) -> Unit, | ||||||
|  |     mediaplayer: MediaPlayer, | ||||||
|  | ): SessionActions { | ||||||
|  |     return SessionActions( | ||||||
|  |         getTimer = viewModel::getTimer, | ||||||
|  |         getTask = viewModel::getTask, | ||||||
|  |         endSession = { viewModel.endSession(openAndPopUp) }, | ||||||
|  |         startMediaPlayer = mediaplayer::start, | ||||||
|  |         releaseMediaPlayer = mediaplayer::release, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SessionRoute( | ||||||
|  |     open: (String) -> Unit, | ||||||
|  |     openAndPopUp: (String, String) -> Unit, | ||||||
|  |     viewModel: SessionViewModel, | ||||||
|  | ) { | ||||||
|  |     val context = LocalContext.current | ||||||
|  |     val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) | ||||||
|  |     val mediaplayer = MediaPlayer.create(context, uri) | ||||||
|  |     mediaplayer.isLooping = false | ||||||
|  | 
 | ||||||
|  |     InvisibleSessionManager.setParameters( | ||||||
|  |         viewModel = viewModel, | ||||||
|  |         mediaplayer = mediaplayer | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen(mediaplayer)) | ||||||
|  | 
 | ||||||
|  |     sessionScreen( | ||||||
|  |         open = open, | ||||||
|  |         sessionActions = getSessionActions(viewModel, openAndPopUp, mediaplayer) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -1,212 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.session |  | ||||||
| 
 |  | ||||||
| import android.media.MediaPlayer |  | ||||||
| import android.media.RingtoneManager |  | ||||||
| import android.net.Uri |  | ||||||
| import androidx.compose.foundation.background |  | ||||||
| import androidx.compose.foundation.border |  | ||||||
| import androidx.compose.foundation.layout.Box |  | ||||||
| import androidx.compose.foundation.layout.Column |  | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth |  | ||||||
| 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.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 |  | ||||||
| import androidx.compose.ui.platform.LocalContext |  | ||||||
| import androidx.compose.ui.text.font.FontWeight |  | ||||||
| import androidx.compose.ui.text.style.TextAlign |  | ||||||
| import androidx.compose.ui.tooling.preview.Preview |  | ||||||
| import androidx.compose.ui.unit.dp |  | ||||||
| import androidx.compose.ui.unit.sp |  | ||||||
| import be.ugent.sel.studeez.R |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer |  | ||||||
| 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.FunctionalTimer.StudyState |  | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations |  | ||||||
| import be.ugent.sel.studeez.resources |  | ||||||
| import kotlinx.coroutines.delay |  | ||||||
| import kotlin.time.Duration.Companion.seconds |  | ||||||
| 
 |  | ||||||
| var timerEnd = false |  | ||||||
| 
 |  | ||||||
| data class SessionActions( |  | ||||||
|     val getTimer: () -> FunctionalTimer, |  | ||||||
|     val getTask: () -> String, |  | ||||||
|     val prepareMediaPlayer: () -> Unit, |  | ||||||
|     val releaseMediaPlayer: () -> Unit, |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| fun getSessionActions( |  | ||||||
|     viewModel: SessionViewModel, |  | ||||||
|     mediaplayer: MediaPlayer, |  | ||||||
| ): SessionActions { |  | ||||||
|     return SessionActions( |  | ||||||
|         getTimer = viewModel::getTimer, |  | ||||||
|         getTask = viewModel::getTask, |  | ||||||
|         prepareMediaPlayer = mediaplayer::prepareAsync, |  | ||||||
|         releaseMediaPlayer = mediaplayer::release, |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun SessionRoute( |  | ||||||
|     open: (String) -> Unit, |  | ||||||
|     viewModel: SessionViewModel, |  | ||||||
| ) { |  | ||||||
|     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() |  | ||||||
|     } |  | ||||||
|     SessionScreen( |  | ||||||
|         open = open, |  | ||||||
|         sessionActions = getSessionActions(viewModel, mediaplayer), |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun SessionScreen( |  | ||||||
|     open: (String) -> Unit, |  | ||||||
|     sessionActions: SessionActions, |  | ||||||
| ) { |  | ||||||
|     Column( |  | ||||||
|         modifier = Modifier.padding(10.dp) |  | ||||||
|     ) { |  | ||||||
|         Timer( |  | ||||||
|             sessionActions = sessionActions, |  | ||||||
|         ) |  | ||||||
|         Box( |  | ||||||
|             contentAlignment = Alignment.Center, modifier = Modifier |  | ||||||
|                 .fillMaxWidth() |  | ||||||
|                 .padding(50.dp) |  | ||||||
|         ) { |  | ||||||
|             TextButton( |  | ||||||
|                 onClick = { |  | ||||||
|                     sessionActions.releaseMediaPlayer |  | ||||||
|                     open(StudeezDestinations.HOME_SCREEN) |  | ||||||
|                     // Vanaf hier ook naar report gaan als "end session" knop word ingedrukt |  | ||||||
|                 }, |  | ||||||
|                 modifier = Modifier |  | ||||||
|                     .padding(horizontal = 20.dp) |  | ||||||
|                     .border(1.dp, Color.Red, RoundedCornerShape(32.dp)) |  | ||||||
|                     .background(Color.Transparent) |  | ||||||
|             ) { |  | ||||||
|                 Text( |  | ||||||
|                     text = "End session", |  | ||||||
|                     color = Color.Red, |  | ||||||
|                     fontWeight = FontWeight.Bold, |  | ||||||
|                     fontSize = 18.sp, |  | ||||||
|                     modifier = Modifier.padding(1.dp) |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| private fun Timer( |  | ||||||
|     sessionActions: SessionActions, |  | ||||||
| ) { |  | ||||||
|     var tikker by remember { mutableStateOf(false) } |  | ||||||
|     LaunchedEffect(tikker) { |  | ||||||
|         delay(1.seconds) |  | ||||||
|         sessionActions.getTimer().tick() |  | ||||||
|         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( |  | ||||||
|             text = "${hms.hours} : ${hms.minutes} : ${hms.seconds}", |  | ||||||
|             modifier = Modifier |  | ||||||
|                 .fillMaxWidth() |  | ||||||
|                 .padding(50.dp), |  | ||||||
|             textAlign = TextAlign.Center, |  | ||||||
|             fontWeight = FontWeight.Bold, |  | ||||||
|             fontSize = 40.sp, |  | ||||||
|         ) |  | ||||||
|         val stateString: String = when (sessionActions.getTimer().view) { |  | ||||||
|             StudyState.DONE -> resources().getString(R.string.state_done) |  | ||||||
|             StudyState.FOCUS -> resources().getString(R.string.state_focus) |  | ||||||
|             StudyState.BREAK -> resources().getString(R.string.state_take_a_break) |  | ||||||
|             StudyState.FOCUS_REMAINING -> (sessionActions.getTimer() as FunctionalPomodoroTimer?)?.breaksRemaining?.let { |  | ||||||
|                 resources().getQuantityString(R.plurals.state_focus_remaining, it, it) |  | ||||||
|             }.toString() |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Text( |  | ||||||
|             text = stateString, |  | ||||||
|             modifier = Modifier.fillMaxWidth(), |  | ||||||
|             textAlign = TextAlign.Center, |  | ||||||
|             fontWeight = FontWeight.Light, |  | ||||||
|             fontSize = 30.sp |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         Box( |  | ||||||
|             contentAlignment = Alignment.Center, modifier = Modifier |  | ||||||
|                 .fillMaxWidth() |  | ||||||
|                 .padding(50.dp) |  | ||||||
|         ) { |  | ||||||
|             Box( |  | ||||||
|                 contentAlignment = Alignment.Center, |  | ||||||
|                 modifier = Modifier |  | ||||||
|                     .padding(16.dp) |  | ||||||
|                     .background(Color.Blue, RoundedCornerShape(32.dp)) |  | ||||||
|             ) { |  | ||||||
|                 Text( |  | ||||||
|                     text = sessionActions.getTask(), |  | ||||||
|                     color = Color.White, |  | ||||||
|                     fontSize = 18.sp, |  | ||||||
|                     fontWeight = FontWeight.Bold, |  | ||||||
|                     modifier = Modifier.padding(vertical = 4.dp, horizontal = 20.dp) |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Preview |  | ||||||
| @Composable |  | ||||||
| fun TimerPreview() { |  | ||||||
|     Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {})) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Preview |  | ||||||
| @Composable |  | ||||||
| fun SessionPreview() { |  | ||||||
|     SessionScreen( |  | ||||||
|         open = {}, |  | ||||||
|         sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}) |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  | @ -1,20 +1,21 @@ | ||||||
| package be.ugent.sel.studeez.screens.session | package be.ugent.sel.studeez.screens.session | ||||||
| 
 | 
 | ||||||
|  | import be.ugent.sel.studeez.data.SelectedTimerState | ||||||
|  | import be.ugent.sel.studeez.data.SessionReportState | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
|  | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
| import be.ugent.sel.studeez.screens.StudeezViewModel | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
| import be.ugent.sel.studeez.data.SelectedTimerState |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer |  | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| @HiltViewModel | @HiltViewModel | ||||||
| class SessionViewModel @Inject constructor( | class SessionViewModel @Inject constructor( | ||||||
|     private val selectedTimerState: SelectedTimerState, |     private val selectedTimerState: SelectedTimerState, | ||||||
|  |     private val sessionReportState: SessionReportState, | ||||||
|     logService: LogService |     logService: LogService | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
| 
 | 
 | ||||||
|     private val timer: FunctionalTimer = FunctionalPomodoroTimer(15, 5, 3) |  | ||||||
|     private val task : String = "No task selected" // placeholder for tasks implementation |     private val task : String = "No task selected" // placeholder for tasks implementation | ||||||
| 
 | 
 | ||||||
|     fun getTimer() : FunctionalTimer { |     fun getTimer() : FunctionalTimer { | ||||||
|  | @ -24,4 +25,9 @@ class SessionViewModel @Inject constructor( | ||||||
|     fun getTask(): String { |     fun getTask(): String { | ||||||
|         return task |         return task | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     fun endSession(openAndPopUp: (String, String) -> Unit) { | ||||||
|  |         sessionReportState.sessionReport = getTimer().getSessionReport() | ||||||
|  |         openAndPopUp(StudeezDestinations.SESSION_RECAP, StudeezDestinations.SESSION_SCREEN) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | @ -0,0 +1,138 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session.sessionScreens | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.background | ||||||
|  | import androidx.compose.foundation.border | ||||||
|  | import androidx.compose.foundation.layout.Box | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
|  | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | 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.ui.Alignment | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import androidx.compose.ui.text.font.FontWeight | ||||||
|  | import androidx.compose.ui.text.style.TextAlign | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import androidx.compose.ui.unit.sp | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer | ||||||
|  | import be.ugent.sel.studeez.screens.session.SessionActions | ||||||
|  | import kotlinx.coroutines.delay | ||||||
|  | import kotlin.time.Duration.Companion.seconds | ||||||
|  | 
 | ||||||
|  | abstract class AbstractSessionScreen { | ||||||
|  | 
 | ||||||
|  |     @Composable | ||||||
|  |     operator fun invoke( | ||||||
|  |         open: (String) -> Unit, | ||||||
|  |         sessionActions: SessionActions, | ||||||
|  |     ) { | ||||||
|  |         Column( | ||||||
|  |             modifier = Modifier.padding(10.dp) | ||||||
|  |         ) { | ||||||
|  |             Timer( | ||||||
|  |                 sessionActions = sessionActions, | ||||||
|  |             ) | ||||||
|  |             Box( | ||||||
|  |                 contentAlignment = Alignment.Center, modifier = Modifier | ||||||
|  |                     .fillMaxWidth() | ||||||
|  |                     .padding(50.dp) | ||||||
|  |             ) { | ||||||
|  |                 TextButton( | ||||||
|  |                     onClick = { | ||||||
|  |                         sessionActions.releaseMediaPlayer | ||||||
|  |                         sessionActions.endSession() | ||||||
|  |                     }, | ||||||
|  |                     modifier = Modifier | ||||||
|  |                         .padding(horizontal = 20.dp) | ||||||
|  |                         .border(1.dp, Color.Red, RoundedCornerShape(32.dp)) | ||||||
|  |                         .background(Color.Transparent) | ||||||
|  |                 ) { | ||||||
|  |                     Text( | ||||||
|  |                         text = "End session", | ||||||
|  |                         color = Color.Red, | ||||||
|  |                         fontWeight = FontWeight.Bold, | ||||||
|  |                         fontSize = 18.sp, | ||||||
|  |                         modifier = Modifier.padding(1.dp) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Composable | ||||||
|  |     fun Timer( | ||||||
|  |         sessionActions: SessionActions, | ||||||
|  |     ) { | ||||||
|  |         var tikker by remember { mutableStateOf(false) } | ||||||
|  |         LaunchedEffect(tikker) { | ||||||
|  |             delay(1.seconds) | ||||||
|  |             sessionActions.getTimer().tick() | ||||||
|  |             callMediaPlayer() | ||||||
|  |             tikker = !tikker | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val hms = sessionActions.getTimer().getHoursMinutesSeconds() | ||||||
|  |         Column { | ||||||
|  |             Text( | ||||||
|  |                 text = "${hms.hours} : ${hms.minutes} : ${hms.seconds}", | ||||||
|  |                 modifier = Modifier | ||||||
|  |                     .fillMaxWidth() | ||||||
|  |                     .padding(50.dp), | ||||||
|  |                 textAlign = TextAlign.Center, | ||||||
|  |                 fontWeight = FontWeight.Bold, | ||||||
|  |                 fontSize = 40.sp, | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |             Text( | ||||||
|  |                 text = motivationString(), | ||||||
|  |                 modifier = Modifier.fillMaxWidth(), | ||||||
|  |                 textAlign = TextAlign.Center, | ||||||
|  |                 fontWeight = FontWeight.Light, | ||||||
|  |                 fontSize = 30.sp | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |             Box( | ||||||
|  |                 contentAlignment = Alignment.Center, modifier = Modifier | ||||||
|  |                     .fillMaxWidth() | ||||||
|  |                     .padding(50.dp) | ||||||
|  |             ) { | ||||||
|  |                 Box( | ||||||
|  |                     contentAlignment = Alignment.Center, | ||||||
|  |                     modifier = Modifier | ||||||
|  |                         .padding(16.dp) | ||||||
|  |                         .background(Color.Blue, RoundedCornerShape(32.dp)) | ||||||
|  |                 ) { | ||||||
|  |                     Text( | ||||||
|  |                         text = sessionActions.getTask(), | ||||||
|  |                         color = Color.White, | ||||||
|  |                         fontSize = 18.sp, | ||||||
|  |                         fontWeight = FontWeight.Bold, | ||||||
|  |                         modifier = Modifier.padding(vertical = 4.dp, horizontal = 20.dp) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Composable | ||||||
|  |     abstract fun motivationString(): String | ||||||
|  | 
 | ||||||
|  |     abstract fun callMediaPlayer() | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun TimerPreview() { | ||||||
|  |     val sessionScreen = object : AbstractSessionScreen() { | ||||||
|  |         @Composable | ||||||
|  |         override fun motivationString(): String = "Test" | ||||||
|  |         override fun callMediaPlayer() {} | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  |     sessionScreen.Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}, {})) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,45 @@ | ||||||
|  | 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 | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | class BreakSessionScreen( | ||||||
|  |     private val funPomoDoroTimer: FunctionalPomodoroTimer, | ||||||
|  |     private var mediaplayer: MediaPlayer? | ||||||
|  | ): AbstractSessionScreen() { | ||||||
|  | 
 | ||||||
|  |     @Composable | ||||||
|  |     override fun motivationString(): String { | ||||||
|  |         if (funPomoDoroTimer.isInBreak) { | ||||||
|  |             return resources().getString(AppText.state_take_a_break) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (funPomoDoroTimer.hasEnded()) { | ||||||
|  |             return resources().getString(AppText.state_done) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return resources().getQuantityString( | ||||||
|  |             R.plurals.state_focus_remaining, | ||||||
|  |             funPomoDoroTimer.breaksRemaining, | ||||||
|  |             funPomoDoroTimer.breaksRemaining | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun callMediaPlayer() { | ||||||
|  |         if (funPomoDoroTimer.hasEnded()) { | ||||||
|  |             mediaplayer?.let { it: MediaPlayer -> | ||||||
|  |                 it.setOnCompletionListener { | ||||||
|  |                     it.release() | ||||||
|  |                     mediaplayer = null | ||||||
|  |                 } | ||||||
|  |                 it.start() | ||||||
|  |             } | ||||||
|  |         } else if (funPomoDoroTimer.hasCurrentCountdownEnded()) { | ||||||
|  |             mediaplayer?.start() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | 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 | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CustomSessionScreen( | ||||||
|  |     private val functionalTimer: FunctionalCustomTimer, | ||||||
|  |     private var mediaplayer: MediaPlayer? | ||||||
|  | ): AbstractSessionScreen() { | ||||||
|  | 
 | ||||||
|  |     @Composable | ||||||
|  |     override fun motivationString(): String { | ||||||
|  |         if (functionalTimer.hasEnded()) { | ||||||
|  |             return resources().getString(AppText.state_done) | ||||||
|  |         } | ||||||
|  |         return resources().getString(AppText.state_focus) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun callMediaPlayer() { | ||||||
|  |         if (functionalTimer.hasEnded()) { | ||||||
|  |             mediaplayer?.let { it: MediaPlayer -> | ||||||
|  |                 it.setOnCompletionListener { | ||||||
|  |                     it.release() | ||||||
|  |                     mediaplayer = null | ||||||
|  |                 } | ||||||
|  |                 it.start() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session.sessionScreens | ||||||
|  | 
 | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class EndlessSessionScreen : AbstractSessionScreen() { | ||||||
|  | 
 | ||||||
|  |     @Composable | ||||||
|  |     override fun motivationString(): String { | ||||||
|  |         return resources().getString(AppText.state_focus) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun callMediaPlayer() {} | ||||||
|  | } | ||||||
|  | @ -0,0 +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(private val mediaplayer: MediaPlayer?) : FunctionalTimerVisitor<AbstractSessionScreen> { | ||||||
|  |     override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): AbstractSessionScreen = | ||||||
|  |         CustomSessionScreen(functionalCustomTimer, mediaplayer) | ||||||
|  | 
 | ||||||
|  |     override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): AbstractSessionScreen = | ||||||
|  |         EndlessSessionScreen() | ||||||
|  | 
 | ||||||
|  |     override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): AbstractSessionScreen = | ||||||
|  |         BreakSessionScreen(functionalPomodoroTimer, mediaplayer) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,67 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session_recap | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
|  | import androidx.compose.material.ButtonDefaults | ||||||
|  | import androidx.compose.material.Text | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import be.ugent.sel.studeez.R | ||||||
|  | import be.ugent.sel.studeez.common.composable.BasicButton | ||||||
|  | import be.ugent.sel.studeez.common.ext.basicButton | ||||||
|  | import be.ugent.sel.studeez.data.local.models.SessionReport | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.Time | ||||||
|  | 
 | ||||||
|  | data class SessionRecapActions( | ||||||
|  |     val getSessionReport: () -> SessionReport, | ||||||
|  |     val saveSession: () -> Unit, | ||||||
|  |     val discardSession: () -> Unit | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | fun getSessionRecapActions( | ||||||
|  |     viewModel: SessionRecapViewModel, | ||||||
|  |     openAndPopUp: (String, String) -> Unit, | ||||||
|  | ): SessionRecapActions { | ||||||
|  |     return SessionRecapActions( | ||||||
|  |         viewModel::getSessionReport, | ||||||
|  |         {viewModel.saveSession(openAndPopUp)}, | ||||||
|  |         {viewModel.discardSession(openAndPopUp)} | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SessionRecapRoute( | ||||||
|  |     openAndPopUp: (String, String) -> Unit, | ||||||
|  |     modifier: Modifier = Modifier, | ||||||
|  |     viewModel: SessionRecapViewModel, | ||||||
|  | ) { | ||||||
|  |     SessionRecapScreen( | ||||||
|  |         modifier = modifier, | ||||||
|  |         getSessionRecapActions(viewModel, openAndPopUp) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SessionRecapScreen(modifier: Modifier, sessionRecapActions: SessionRecapActions) { | ||||||
|  |     val sessionReport: SessionReport = sessionRecapActions.getSessionReport() | ||||||
|  |     val studyTime: Int = sessionReport.studyTime | ||||||
|  |     val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS() | ||||||
|  |     Column( | ||||||
|  |         modifier = modifier | ||||||
|  |     ) { | ||||||
|  |         Text(text = "You studied: ${hms.hours} : ${hms.minutes} : ${hms.seconds}") | ||||||
|  | 
 | ||||||
|  |         BasicButton( | ||||||
|  |             R.string.save, Modifier.basicButton() | ||||||
|  |         ) { | ||||||
|  |             sessionRecapActions.saveSession() | ||||||
|  |         } | ||||||
|  |         BasicButton( | ||||||
|  |             R.string.discard, Modifier.basicButton(), | ||||||
|  |             colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red) | ||||||
|  |         ) { | ||||||
|  |             sessionRecapActions.discardSession() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,33 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session_recap | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.SessionReportState | ||||||
|  | import be.ugent.sel.studeez.data.local.models.SessionReport | ||||||
|  | import be.ugent.sel.studeez.domain.LogService | ||||||
|  | import be.ugent.sel.studeez.domain.SessionDAO | ||||||
|  | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
|  | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
|  | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | @HiltViewModel | ||||||
|  | class SessionRecapViewModel @Inject constructor( | ||||||
|  |     sessionReportState: SessionReportState, | ||||||
|  |     private val sessionDAO: SessionDAO, | ||||||
|  |     logService: LogService | ||||||
|  | ) : StudeezViewModel(logService) { | ||||||
|  | 
 | ||||||
|  |     private val report: SessionReport = sessionReportState.sessionReport!! | ||||||
|  | 
 | ||||||
|  |     fun getSessionReport(): SessionReport { | ||||||
|  |         return report | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun saveSession(open: (String, String) -> Unit) { | ||||||
|  |         sessionDAO.saveSession(getSessionReport()) | ||||||
|  |         open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun discardSession(open: (String, String) -> Unit) { | ||||||
|  |         open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| package be.ugent.sel.studeez.screens.splash | package be.ugent.sel.studeez.screens.splash | ||||||
| 
 | 
 | ||||||
| import android.window.SplashScreen |  | ||||||
| import androidx.compose.foundation.background | import androidx.compose.foundation.background | ||||||
| import androidx.compose.foundation.layout.Arrangement | import androidx.compose.foundation.layout.Arrangement | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.compose.foundation.layout.Column | ||||||
|  |  | ||||||
|  | @ -1,25 +1,17 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_overview | package be.ugent.sel.studeez.screens.timer_overview | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.Arrangement |  | ||||||
| import androidx.compose.foundation.layout.Column |  | ||||||
| import androidx.compose.foundation.lazy.LazyColumn | import androidx.compose.foundation.lazy.LazyColumn | ||||||
| import androidx.compose.foundation.lazy.items | import androidx.compose.foundation.lazy.items | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.collectAsState | import androidx.compose.runtime.collectAsState | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import androidx.compose.ui.unit.dp |  | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.composable.BasicButton | import be.ugent.sel.studeez.common.composable.BasicButton | ||||||
| import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | import be.ugent.sel.studeez.common.composable.DrawerScreenTemplate | ||||||
| import be.ugent.sel.studeez.common.composable.StealthButton | import be.ugent.sel.studeez.common.composable.StealthButton | ||||||
| import be.ugent.sel.studeez.common.composable.TimerEntry | import be.ugent.sel.studeez.common.composable.TimerEntry | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel |  | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions |  | ||||||
| import be.ugent.sel.studeez.common.ext.basicButton | 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.CustomTimerInfo | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
|  | @ -50,37 +42,28 @@ fun getTimerOverviewActions( | ||||||
| @Composable | @Composable | ||||||
| fun TimerOverviewRoute( | fun TimerOverviewRoute( | ||||||
|     open: (String) -> Unit, |     open: (String) -> Unit, | ||||||
|     openAndPopUp: (String, String) -> Unit, |  | ||||||
|     viewModel: TimerOverviewViewModel, |     viewModel: TimerOverviewViewModel, | ||||||
|     drawerViewModel: DrawerViewModel, |     drawerActions: DrawerActions, | ||||||
|     navBarViewModel: NavigationBarViewModel, |  | ||||||
| ) { | ) { | ||||||
|     TimerOverviewScreen( |     TimerOverviewScreen( | ||||||
|         timerOverviewActions = getTimerOverviewActions(viewModel, open), |         timerOverviewActions = getTimerOverviewActions(viewModel, open), | ||||||
|         drawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp), |         drawerActions = drawerActions, | ||||||
|         navigationBarActions = getNavigationBarActions(navBarViewModel, open), |  | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun TimerOverviewScreen( | fun TimerOverviewScreen( | ||||||
|     timerOverviewActions: TimerOverviewActions, |     timerOverviewActions: TimerOverviewActions, | ||||||
|     drawerActions: DrawerActions, |     drawerActions: DrawerActions | ||||||
|     navigationBarActions: NavigationBarActions, |  | ||||||
| ) { | ) { | ||||||
| 
 | 
 | ||||||
|     val timers = timerOverviewActions.getUserTimers().collectAsState(initial = emptyList()) |     val timers = timerOverviewActions.getUserTimers().collectAsState(initial = emptyList()) | ||||||
| 
 | 
 | ||||||
|     // TODO moet geen primary screen zijn: geen navbar nodig |     DrawerScreenTemplate( | ||||||
|     PrimaryScreenTemplate( |  | ||||||
|         title = resources().getString(R.string.timers), |         title = resources().getString(R.string.timers), | ||||||
|         drawerActions = drawerActions, |         drawerActions = drawerActions | ||||||
|         navigationBarActions = navigationBarActions, |  | ||||||
|     ) { |  | ||||||
|         Column { |  | ||||||
|             LazyColumn( |  | ||||||
|                 verticalArrangement = Arrangement.spacedBy(7.dp) |  | ||||||
|     ) { |     ) { | ||||||
|  |         LazyColumn { | ||||||
|             // Default Timers, cannot be edited |             // Default Timers, cannot be edited | ||||||
|             items(timerOverviewActions.getDefaultTimers()) { |             items(timerOverviewActions.getDefaultTimers()) { | ||||||
|                 TimerEntry(timerInfo = it) {} |                 TimerEntry(timerInfo = it) {} | ||||||
|  | @ -97,12 +80,13 @@ fun TimerOverviewScreen( | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|             } |             } | ||||||
|             } |             item { | ||||||
|                 BasicButton(R.string.add_timer, Modifier.basicButton()) { |                 BasicButton(R.string.add_timer, Modifier.basicButton()) { | ||||||
|                     timerOverviewActions.open(StudeezDestinations.ADD_TIMER_SCREEN) |                     timerOverviewActions.open(StudeezDestinations.ADD_TIMER_SCREEN) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Preview | @Preview | ||||||
|  | @ -118,7 +102,6 @@ fun TimerOverviewPreview() { | ||||||
|             {}, |             {}, | ||||||
|             {} |             {} | ||||||
|         ), |         ), | ||||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}), |         drawerActions = DrawerActions({}, {}, {}, {}, {}) | ||||||
|         navigationBarActions = NavigationBarActions({}, {}, {}, {}) |  | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,22 +1,14 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_selection | package be.ugent.sel.studeez.screens.timer_selection | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.Arrangement |  | ||||||
| import androidx.compose.foundation.lazy.LazyColumn | import androidx.compose.foundation.lazy.LazyColumn | ||||||
| import androidx.compose.foundation.lazy.items | import androidx.compose.foundation.lazy.items | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.collectAsState | import androidx.compose.runtime.collectAsState | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import androidx.compose.ui.unit.dp |  | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||||
| import be.ugent.sel.studeez.common.composable.StealthButton | import be.ugent.sel.studeez.common.composable.StealthButton | ||||||
| import be.ugent.sel.studeez.common.composable.TimerEntry | import be.ugent.sel.studeez.common.composable.TimerEntry | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerViewModel |  | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.getDrawerActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.NavigationBarViewModel |  | ||||||
| import be.ugent.sel.studeez.common.composable.navbar.getNavigationBarActions |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
| import be.ugent.sel.studeez.resources | import be.ugent.sel.studeez.resources | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
|  | @ -40,43 +32,37 @@ fun getTimerSelectionActions( | ||||||
| @Composable | @Composable | ||||||
| fun TimerSelectionRoute( | fun TimerSelectionRoute( | ||||||
|     open: (String) -> Unit, |     open: (String) -> Unit, | ||||||
|     openAndPopUp: (String, String) -> Unit, |     popUp: () -> Unit, | ||||||
|     viewModel: TimerSelectionViewModel, |     viewModel: TimerSelectionViewModel, | ||||||
|     drawerViewModel: DrawerViewModel, |  | ||||||
|     navBarViewModel: NavigationBarViewModel, |  | ||||||
| ) { | ) { | ||||||
|     TimerSelectionScreen( |     TimerSelectionScreen( | ||||||
|         timerSelectionActions = getTimerSelectionActions(viewModel, open), |         timerSelectionActions = getTimerSelectionActions(viewModel, open), | ||||||
|         drawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp), |         popUp = popUp | ||||||
|         navigationBarActions = getNavigationBarActions(navBarViewModel, open), |  | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun TimerSelectionScreen( | fun TimerSelectionScreen( | ||||||
|     timerSelectionActions: TimerSelectionActions, |     timerSelectionActions: TimerSelectionActions, | ||||||
|     drawerActions: DrawerActions, |     popUp: () -> Unit | ||||||
|     navigationBarActions: NavigationBarActions, |  | ||||||
| ) { | ) { | ||||||
|     val timers = timerSelectionActions.getAllTimers().collectAsState(initial = emptyList()) |     val timers = timerSelectionActions.getAllTimers().collectAsState(initial = emptyList()) | ||||||
|     PrimaryScreenTemplate( |     SecondaryScreenTemplate( | ||||||
|         title = resources().getString(R.string.timers), |         title = resources().getString(R.string.timers), | ||||||
|         drawerActions = drawerActions, |         popUp = popUp | ||||||
|         navigationBarActions = navigationBarActions, |  | ||||||
|     ) { |  | ||||||
|         LazyColumn( |  | ||||||
|             verticalArrangement = Arrangement.spacedBy(7.dp), |  | ||||||
|     ) { |     ) { | ||||||
|  |         LazyColumn { | ||||||
|             // All timers |             // All timers | ||||||
|             items(timers.value) { timerInfo -> |             items(timers.value) { timerInfo -> | ||||||
|                 TimerEntry( |                 TimerEntry( | ||||||
|                     timerInfo = timerInfo, |                     timerInfo = timerInfo, | ||||||
|                 ) { |                     leftButton = { | ||||||
|                         StealthButton( |                         StealthButton( | ||||||
|                             text = R.string.start, |                             text = R.string.start, | ||||||
|                             onClick = { timerSelectionActions.startSession(timerInfo) } |                             onClick = { timerSelectionActions.startSession(timerInfo) } | ||||||
|                         ) |                         ) | ||||||
|                     } |                     } | ||||||
|  |                 ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -87,7 +73,6 @@ fun TimerSelectionScreen( | ||||||
| fun TimerSelectionPreview() { | fun TimerSelectionPreview() { | ||||||
|     TimerSelectionScreen( |     TimerSelectionScreen( | ||||||
|         timerSelectionActions = TimerSelectionActions({ flowOf() }, {}), |         timerSelectionActions = TimerSelectionActions({ flowOf() }, {}), | ||||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}), |         popUp = {} | ||||||
|         navigationBarActions = NavigationBarActions({}, {}, {}, {}), |  | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  | @ -10,6 +10,7 @@ | ||||||
|         <!-- Actions --> |         <!-- Actions --> | ||||||
|         <string name="confirm">Confirm</string> |         <string name="confirm">Confirm</string> | ||||||
|         <string name="save">Save</string> |         <string name="save">Save</string> | ||||||
|  |         <string name="discard">Discard</string> | ||||||
|         <string name="cancel">Cancel</string> |         <string name="cancel">Cancel</string> | ||||||
|         <string name="go_back">Go back</string> |         <string name="go_back">Go back</string> | ||||||
|         <string name="next">Next</string> |         <string name="next">Next</string> | ||||||
|  | @ -44,6 +45,7 @@ | ||||||
| 
 | 
 | ||||||
|     <!-- Sessions --> |     <!-- Sessions --> | ||||||
|     <string name="sessions">Sessions</string> |     <string name="sessions">Sessions</string> | ||||||
|  |     <string name="end_session">End session</string> | ||||||
| 
 | 
 | ||||||
|     <!-- Profile --> |     <!-- Profile --> | ||||||
|     <string name="profile">Profile</string> |     <string name="profile">Profile</string> | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.timer_functional | 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.FunctionalCustomTimer | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer |  | ||||||
| import org.junit.Assert | import org.junit.Assert | ||||||
| import org.junit.Test | import org.junit.Test | ||||||
| 
 | 
 | ||||||
|  | @ -36,9 +35,6 @@ class FunctionalCustomTimerUnitTest : FunctionalTimerUnitTest() { | ||||||
|         timer = FunctionalCustomTimer(0) |         timer = FunctionalCustomTimer(0) | ||||||
|         timer.tick() |         timer.tick() | ||||||
|         Assert.assertTrue(timer.hasEnded()) |         Assert.assertTrue(timer.hasEnded()) | ||||||
|         Assert.assertEquals( |         Assert.assertTrue(timer.hasEnded()) | ||||||
|             FunctionalTimer.StudyState.DONE, |  | ||||||
|             timer.view |  | ||||||
|         ) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.timer_functional | 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.FunctionalEndlessTimer | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer |  | ||||||
| import org.junit.Assert | import org.junit.Assert | ||||||
| import org.junit.Test | import org.junit.Test | ||||||
| 
 | 
 | ||||||
|  | @ -37,10 +36,6 @@ class FunctionalEndlessTimerUnitTest : FunctionalTimerUnitTest() { | ||||||
|         for (i in 1..n) { |         for (i in 1..n) { | ||||||
|             timer.tick() |             timer.tick() | ||||||
|             Assert.assertFalse(timer.hasEnded()) |             Assert.assertFalse(timer.hasEnded()) | ||||||
|             Assert.assertEquals( |  | ||||||
|                 FunctionalTimer.StudyState.FOCUS, |  | ||||||
|                 timer.view |  | ||||||
|             ) |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.timer_functional | 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.FunctionalPomodoroTimer | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer |  | ||||||
| import org.junit.Assert | import org.junit.Assert | ||||||
| import org.junit.Test | import org.junit.Test | ||||||
| 
 | 
 | ||||||
|  | @ -29,10 +28,6 @@ class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() { | ||||||
|             breaks, |             breaks, | ||||||
|             pomodoroTimer.breaksRemaining, |             pomodoroTimer.breaksRemaining, | ||||||
|         ) |         ) | ||||||
|         Assert.assertEquals( |  | ||||||
|             FunctionalTimer.StudyState.FOCUS, |  | ||||||
|             pomodoroTimer.view, |  | ||||||
|         ) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|  | @ -52,10 +47,6 @@ class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() { | ||||||
|         pomodoroTimer = FunctionalPomodoroTimer(0, 0, 0) |         pomodoroTimer = FunctionalPomodoroTimer(0, 0, 0) | ||||||
|         pomodoroTimer.tick() |         pomodoroTimer.tick() | ||||||
|         Assert.assertTrue(pomodoroTimer.hasEnded()) |         Assert.assertTrue(pomodoroTimer.hasEnded()) | ||||||
|         Assert.assertEquals( |  | ||||||
|             FunctionalTimer.StudyState.DONE, |  | ||||||
|             pomodoroTimer.view, |  | ||||||
|         ) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|  | @ -65,10 +56,6 @@ class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() { | ||||||
|         } |         } | ||||||
|         Assert.assertFalse(pomodoroTimer.hasEnded()) |         Assert.assertFalse(pomodoroTimer.hasEnded()) | ||||||
|         Assert.assertTrue(pomodoroTimer.isInBreak) |         Assert.assertTrue(pomodoroTimer.isInBreak) | ||||||
|         Assert.assertEquals( |  | ||||||
|             FunctionalTimer.StudyState.BREAK, |  | ||||||
|             pomodoroTimer.view |  | ||||||
|         ) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|  | @ -77,10 +64,6 @@ class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() { | ||||||
|             pomodoroTimer.tick() |             pomodoroTimer.tick() | ||||||
|         } |         } | ||||||
|         Assert.assertTrue(pomodoroTimer.isInBreak) |         Assert.assertTrue(pomodoroTimer.isInBreak) | ||||||
|         Assert.assertEquals( |  | ||||||
|             FunctionalTimer.StudyState.BREAK, |  | ||||||
|             pomodoroTimer.view |  | ||||||
|         ) |  | ||||||
|         for (i in 0..breakTime) { |         for (i in 0..breakTime) { | ||||||
|             pomodoroTimer.tick() |             pomodoroTimer.tick() | ||||||
|         } |         } | ||||||
|  | @ -90,9 +73,5 @@ class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() { | ||||||
|             breaksRemaining, |             breaksRemaining, | ||||||
|             pomodoroTimer.breaksRemaining |             pomodoroTimer.breaksRemaining | ||||||
|         ) |         ) | ||||||
|         Assert.assertEquals( |  | ||||||
|             FunctionalTimer.StudyState.FOCUS_REMAINING, |  | ||||||
|             pomodoroTimer.view |  | ||||||
|         ) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -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. | # Specifies the JVM arguments used for the daemon process. | ||||||
| # The setting is particularly useful for tweaking memory settings. | # The setting is particularly useful for tweaking memory settings. | ||||||
| org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 | 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. | # When configured, Gradle will run in incubating parallel mode. | ||||||
| # This option should only be used with decoupled projects. More details, visit | # 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 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Rune Dyselinck
						Rune Dyselinck