commit
						d5fbb2adab
					
				
					 38 changed files with 1309 additions and 75 deletions
				
			
		
							
								
								
									
										1
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
										
									
										generated
									
									
									
								
							|  | @ -42,6 +42,5 @@ | ||||||
|       <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> | ||||||
							
								
								
									
										2
									
								
								.idea/misc.xml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/misc.xml
									
										
									
										generated
									
									
									
								
							|  | @ -1,7 +1,7 @@ | ||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="ExternalStorageConfigurationManager" enabled="true" /> |   <component name="ExternalStorageConfigurationManager" enabled="true" /> | ||||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK"> |   <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> | ||||||
|     <output url="file://$PROJECT_DIR$/build/classes" /> |     <output url="file://$PROJECT_DIR$/build/classes" /> | ||||||
|   </component> |   </component> | ||||||
|   <component name="ProjectType"> |   <component name="ProjectType"> | ||||||
|  |  | ||||||
|  | @ -124,6 +124,8 @@ dependencies { | ||||||
|     implementation 'com.google.firebase:firebase-perf-ktx' |     implementation 'com.google.firebase:firebase-perf-ktx' | ||||||
|     implementation 'com.google.firebase:firebase-config-ktx' |     implementation 'com.google.firebase:firebase-config-ktx' | ||||||
| 
 | 
 | ||||||
|  |     // Colorpicker | ||||||
|  |     implementation 'com.github.skydoves:colorpicker-compose:1.0.2' | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Allow references to generate code | // Allow references to generate code | ||||||
|  |  | ||||||
|  | @ -2,8 +2,18 @@ package be.ugent.sel.studeez | ||||||
| 
 | 
 | ||||||
| import android.content.res.Resources | import android.content.res.Resources | ||||||
| import androidx.compose.foundation.layout.padding | import androidx.compose.foundation.layout.padding | ||||||
| import androidx.compose.material.* | import androidx.compose.material.MaterialTheme | ||||||
| import androidx.compose.runtime.* | import androidx.compose.material.Scaffold | ||||||
|  | import androidx.compose.material.ScaffoldState | ||||||
|  | import androidx.compose.material.Snackbar | ||||||
|  | import androidx.compose.material.SnackbarHost | ||||||
|  | import androidx.compose.material.Surface | ||||||
|  | import androidx.compose.material.rememberScaffoldState | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.ReadOnlyComposable | ||||||
|  | import androidx.compose.runtime.getValue | ||||||
|  | import androidx.compose.runtime.remember | ||||||
|  | import androidx.compose.runtime.rememberCoroutineScope | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.platform.LocalConfiguration | import androidx.compose.ui.platform.LocalConfiguration | ||||||
| import androidx.compose.ui.platform.LocalContext | import androidx.compose.ui.platform.LocalContext | ||||||
|  | @ -32,6 +42,12 @@ import be.ugent.sel.studeez.screens.sessions.SessionsRoute | ||||||
| import be.ugent.sel.studeez.screens.settings.SettingsRoute | import be.ugent.sel.studeez.screens.settings.SettingsRoute | ||||||
| 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.tasks.SubjectRoute | ||||||
|  | import be.ugent.sel.studeez.screens.tasks.TaskRoute | ||||||
|  | import be.ugent.sel.studeez.screens.tasks.forms.SubjectAddRoute | ||||||
|  | import be.ugent.sel.studeez.screens.tasks.forms.SubjectEditRoute | ||||||
|  | import be.ugent.sel.studeez.screens.tasks.forms.TaskAddRoute | ||||||
|  | import be.ugent.sel.studeez.screens.tasks.forms.TaskEditRoute | ||||||
| import be.ugent.sel.studeez.screens.timer_edit.TimerEditRoute | import be.ugent.sel.studeez.screens.timer_edit.TimerEditRoute | ||||||
| import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute | import be.ugent.sel.studeez.screens.timer_overview.TimerOverviewRoute | ||||||
| import be.ugent.sel.studeez.screens.timer_overview.add_timer.AddTimerRoute | import be.ugent.sel.studeez.screens.timer_overview.add_timer.AddTimerRoute | ||||||
|  | @ -117,10 +133,56 @@ fun StudeezNavGraph( | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         composable(StudeezDestinations.TASKS_SCREEN) { |         composable(StudeezDestinations.SUBJECT_SCREEN) { | ||||||
|             // TODO |             SubjectRoute( | ||||||
|  |                 open = open, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |                 drawerActions = drawerActions, | ||||||
|  |                 navigationBarActions = navigationBarActions, | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         composable(StudeezDestinations.ADD_SUBJECT_FORM) { | ||||||
|  |             SubjectAddRoute( | ||||||
|  |                 goBack = goBack, | ||||||
|  |                 openAndPopUp = openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.EDIT_SUBJECT_FORM) { | ||||||
|  |             SubjectEditRoute( | ||||||
|  |                 goBack = goBack, | ||||||
|  |                 openAndPopUp = openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.TASKS_SCREEN) { | ||||||
|  |             TaskRoute( | ||||||
|  |                 goBack = goBack, | ||||||
|  |                 open = open, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.ADD_TASK_FORM) { | ||||||
|  |             TaskAddRoute( | ||||||
|  |                 goBack = goBack, | ||||||
|  |                 openAndPopUp = openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         composable(StudeezDestinations.EDIT_TASK_FORM) { | ||||||
|  |             TaskEditRoute( | ||||||
|  |                 goBack = goBack, | ||||||
|  |                 openAndPopUp = openAndPopUp, | ||||||
|  |                 viewModel = hiltViewModel(), | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|         composable(StudeezDestinations.SESSIONS_SCREEN) { |         composable(StudeezDestinations.SESSIONS_SCREEN) { | ||||||
|             SessionsRoute( |             SessionsRoute( | ||||||
|                 drawerActions = drawerActions, |                 drawerActions = drawerActions, | ||||||
|  | @ -133,7 +195,7 @@ fun StudeezNavGraph( | ||||||
|                 open, |                 open, | ||||||
|                 viewModel = hiltViewModel(), |                 viewModel = hiltViewModel(), | ||||||
|                 drawerActions = drawerActions, |                 drawerActions = drawerActions, | ||||||
|                 navigationBarActions = navigationBarActions |                 navigationBarActions = navigationBarActions, | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,17 +2,33 @@ 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.material.* | import androidx.compose.foundation.layout.Arrangement | ||||||
|  | import androidx.compose.foundation.layout.Row | ||||||
|  | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | import androidx.compose.foundation.layout.height | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
|  | import androidx.compose.foundation.shape.RoundedCornerShape | ||||||
|  | import androidx.compose.material.Button | ||||||
|  | import androidx.compose.material.ButtonColors | ||||||
|  | import androidx.compose.material.ButtonDefaults | ||||||
|  | import androidx.compose.material.Icon | ||||||
|  | import androidx.compose.material.MaterialTheme | ||||||
|  | import androidx.compose.material.Text | ||||||
|  | import androidx.compose.material.TextButton | ||||||
|  | import androidx.compose.material.icons.Icons | ||||||
|  | import androidx.compose.material.icons.filled.Add | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.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 | ||||||
| import androidx.compose.ui.unit.sp | import androidx.compose.ui.unit.sp | ||||||
| import be.ugent.sel.studeez.R |  | ||||||
| import be.ugent.sel.studeez.common.ext.basicButton | import be.ugent.sel.studeez.common.ext.basicButton | ||||||
| import be.ugent.sel.studeez.common.ext.card | import be.ugent.sel.studeez.common.ext.card | ||||||
| import be.ugent.sel.studeez.common.ext.defaultButtonShape | import be.ugent.sel.studeez.common.ext.defaultButtonShape | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { | fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { | ||||||
|  | @ -29,7 +45,7 @@ fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit | ||||||
| @Composable | @Composable | ||||||
| fun BasicButton( | fun BasicButton( | ||||||
|     @StringRes text: Int, |     @StringRes text: Int, | ||||||
|     modifier: Modifier, |     modifier: Modifier = Modifier, | ||||||
|     colors: ButtonColors = ButtonDefaults.buttonColors(), |     colors: ButtonColors = ButtonDefaults.buttonColors(), | ||||||
|     border: BorderStroke? = null, |     border: BorderStroke? = null, | ||||||
|     onClick: () -> Unit, |     onClick: () -> Unit, | ||||||
|  | @ -51,53 +67,58 @@ fun BasicButton( | ||||||
| @Preview | @Preview | ||||||
| @Composable | @Composable | ||||||
| fun BasicButtonPreview() { | fun BasicButtonPreview() { | ||||||
|     BasicButton(text = R.string.add_timer, modifier = Modifier.basicButton()) {} |     BasicButton(text = AppText.add_timer, modifier = Modifier.basicButton()) {} | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun NotInternationalisedButton( |  | ||||||
|     text: String, |  | ||||||
|     modifier: Modifier = Modifier, |  | ||||||
|     colors: ButtonColors = ButtonDefaults.buttonColors(), |  | ||||||
|     border: BorderStroke? = null, |  | ||||||
|     onClick: () -> Unit |  | ||||||
| ) { |  | ||||||
|     Button( |  | ||||||
|         onClick = onClick, |  | ||||||
|         modifier = modifier, |  | ||||||
|         shape = defaultButtonShape(), |  | ||||||
|         colors = colors, |  | ||||||
|         border = border |  | ||||||
|     ) { |  | ||||||
|         Text(text = text) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun StealthButton( | fun StealthButton( | ||||||
|     @StringRes text: Int, |     @StringRes text: Int, | ||||||
|  |     modifier: Modifier = Modifier.card(), | ||||||
|     onClick: () -> Unit, |     onClick: () -> Unit, | ||||||
| ) { | ) { | ||||||
|     BasicButton( |     BasicButton( | ||||||
|         text = text, |         text = text, | ||||||
|         onClick = onClick, |         onClick = onClick, | ||||||
|         modifier = Modifier.card(), |         modifier = modifier, | ||||||
|         colors = ButtonDefaults.buttonColors( |         colors = ButtonDefaults.buttonColors( | ||||||
|             backgroundColor = MaterialTheme.colors.surface, |             backgroundColor = MaterialTheme.colors.surface, | ||||||
|             contentColor = MaterialTheme.colors.onSurface |             contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.4f) | ||||||
|         ), |         ), | ||||||
|         border = BorderStroke(1.dp, MaterialTheme.colors.onSurface) |         border = BorderStroke(3.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.4f)) | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Preview | @Preview | ||||||
| @Composable | @Composable | ||||||
| fun StealthButtonCardPreview() { | fun StealthButtonCardPreview() { | ||||||
|     StealthButton(text = R.string.edit) { |     StealthButton(text = AppText.edit) { | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun DeleteButton( | ||||||
|  |     @StringRes text: Int, | ||||||
|  |     onClick: () -> Unit, | ||||||
|  | ) { | ||||||
|  |     BasicButton( | ||||||
|  |         text = text, | ||||||
|  |         modifier = Modifier.basicButton(), | ||||||
|  |         onClick = onClick, | ||||||
|  |         colors = ButtonDefaults.buttonColors( | ||||||
|  |             backgroundColor = MaterialTheme.colors.error, | ||||||
|  |             contentColor = MaterialTheme.colors.onSurface, | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun DeleteButtonPreview() { | ||||||
|  |     DeleteButton(text = AppText.delete_subject) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| @Composable | @Composable | ||||||
| fun DialogConfirmButton(@StringRes text: Int, action: () -> Unit) { | fun DialogConfirmButton(@StringRes text: Int, action: () -> Unit) { | ||||||
|     Button( |     Button( | ||||||
|  | @ -120,3 +141,39 @@ fun DialogCancelButton(@StringRes text: Int, action: () -> Unit) { | ||||||
|         Text(text = stringResource(text)) |         Text(text = stringResource(text)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun NewTaskSubjectButton( | ||||||
|  |     onClick: () -> Unit, | ||||||
|  |     @StringRes text: Int, | ||||||
|  | ) { | ||||||
|  |     Button( | ||||||
|  |         onClick = onClick, | ||||||
|  |         modifier = Modifier | ||||||
|  |             .fillMaxWidth() | ||||||
|  |             .height(60.dp) | ||||||
|  |             .padding(10.dp, 5.dp), | ||||||
|  |         colors = ButtonDefaults.buttonColors( | ||||||
|  |             backgroundColor = Color.Transparent, | ||||||
|  |             contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.4f), | ||||||
|  |         ), | ||||||
|  |         shape = RoundedCornerShape(2.dp), | ||||||
|  |         border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.4f)), | ||||||
|  |         elevation = null, | ||||||
|  |     ) { | ||||||
|  |         Row( | ||||||
|  |             modifier = Modifier.fillMaxWidth(), | ||||||
|  |             horizontalArrangement = Arrangement.SpaceBetween, | ||||||
|  |             verticalAlignment = Alignment.CenterVertically, | ||||||
|  |         ) { | ||||||
|  |             Text(text = stringResource(id = text)) | ||||||
|  |             Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(id = text)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun NewTaskButtonPreview() { | ||||||
|  |     NewTaskSubjectButton(onClick = {}, text = AppText.new_task) | ||||||
|  | } | ||||||
|  | @ -15,7 +15,7 @@ import androidx.compose.ui.unit.dp | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN | import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN | import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN | import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.TASKS_SCREEN | import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_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 | ||||||
|  | @ -43,7 +43,6 @@ fun getNavigationBarActions( | ||||||
|         isSelectedTab = { screen -> |         isSelectedTab = { screen -> | ||||||
|             screen == getCurrentScreen() |             screen == getCurrentScreen() | ||||||
|         }, |         }, | ||||||
| 
 |  | ||||||
|         onHomeClick = { |         onHomeClick = { | ||||||
|             navigationBarViewModel.onHomeClick(open) |             navigationBarViewModel.onHomeClick(open) | ||||||
|         }, |         }, | ||||||
|  | @ -90,7 +89,7 @@ fun NavigationBar( | ||||||
|                 ) |                 ) | ||||||
|             }, |             }, | ||||||
|             label = { Text(text = resources().getString(AppText.tasks)) }, |             label = { Text(text = resources().getString(AppText.tasks)) }, | ||||||
|             selected = navigationBarActions.isSelectedTab(TASKS_SCREEN), |             selected = navigationBarActions.isSelectedTab(SUBJECT_SCREEN), | ||||||
|             onClick = navigationBarActions.onTasksClick |             onClick = navigationBarActions.onTasksClick | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,12 +1,11 @@ | ||||||
| package be.ugent.sel.studeez.common.composable.navbar | package be.ugent.sel.studeez.common.composable.navbar | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.common.snackbar.SnackbarManager | import be.ugent.sel.studeez.common.snackbar.SnackbarManager | ||||||
| import be.ugent.sel.studeez.domain.AccountDAO |  | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN | import be.ugent.sel.studeez.navigation.StudeezDestinations.HOME_SCREEN | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN | import be.ugent.sel.studeez.navigation.StudeezDestinations.PROFILE_SCREEN | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN | import be.ugent.sel.studeez.navigation.StudeezDestinations.SESSIONS_SCREEN | ||||||
| import be.ugent.sel.studeez.navigation.StudeezDestinations.TASKS_SCREEN | import be.ugent.sel.studeez.navigation.StudeezDestinations.SUBJECT_SCREEN | ||||||
| import be.ugent.sel.studeez.screens.StudeezViewModel | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
|  | @ -14,7 +13,6 @@ import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| @HiltViewModel | @HiltViewModel | ||||||
| class NavigationBarViewModel @Inject constructor( | class NavigationBarViewModel @Inject constructor( | ||||||
|     private val accountDAO: AccountDAO, |  | ||||||
|     logService: LogService |     logService: LogService | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
| 
 | 
 | ||||||
|  | @ -23,7 +21,7 @@ class NavigationBarViewModel @Inject constructor( | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun onTasksClick(open: (String) -> Unit) { |     fun onTasksClick(open: (String) -> Unit) { | ||||||
|         open(TASKS_SCREEN) |         open(SUBJECT_SCREEN) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun onSessionsClick(open: (String) -> Unit) { |     fun onSessionsClick(open: (String) -> Unit) { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,122 @@ | ||||||
|  | package be.ugent.sel.studeez.common.composable.tasks | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.background | ||||||
|  | import androidx.compose.foundation.layout.Arrangement | ||||||
|  | import androidx.compose.foundation.layout.Box | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
|  | import androidx.compose.foundation.layout.Row | ||||||
|  | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
|  | import androidx.compose.foundation.layout.size | ||||||
|  | import androidx.compose.foundation.shape.CircleShape | ||||||
|  | import androidx.compose.material.Card | ||||||
|  | import androidx.compose.material.Icon | ||||||
|  | import androidx.compose.material.Text | ||||||
|  | import androidx.compose.material.icons.Icons | ||||||
|  | import androidx.compose.material.icons.filled.List | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.Alignment | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.draw.clip | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import androidx.compose.ui.text.font.FontWeight | ||||||
|  | import androidx.compose.ui.text.style.TextOverflow | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | import be.ugent.sel.studeez.common.composable.StealthButton | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SubjectEntry( | ||||||
|  |     subject: Subject, | ||||||
|  |     onViewSubject: () -> Unit, | ||||||
|  | ) { | ||||||
|  |     Card( | ||||||
|  |         modifier = Modifier | ||||||
|  |             .fillMaxWidth() | ||||||
|  |             .padding(horizontal = 10.dp, vertical = 5.dp), | ||||||
|  |     ) { | ||||||
|  |         Row( | ||||||
|  |             horizontalArrangement = Arrangement.SpaceBetween, | ||||||
|  |             verticalAlignment = Alignment.CenterVertically, | ||||||
|  |         ) { | ||||||
|  |             Row( | ||||||
|  |                 horizontalArrangement = Arrangement.spacedBy(8.dp), | ||||||
|  |                 verticalAlignment = Alignment.CenterVertically, | ||||||
|  |                 modifier = Modifier | ||||||
|  |                     .padding(start = 10.dp) | ||||||
|  |                     .weight(3f) | ||||||
|  |             ) { | ||||||
|  |                 Box( | ||||||
|  |                     modifier = Modifier | ||||||
|  |                         .size(20.dp) | ||||||
|  |                         .clip(CircleShape) | ||||||
|  |                         .background(Color(subject.argb_color)), | ||||||
|  |                 ) | ||||||
|  |                 Column( | ||||||
|  |                     verticalArrangement = Arrangement.spacedBy(0.dp) | ||||||
|  |                 ) { | ||||||
|  |                     Text( | ||||||
|  |                         text = subject.name, | ||||||
|  |                         fontWeight = FontWeight.Bold, | ||||||
|  |                         overflow = TextOverflow.Ellipsis, | ||||||
|  |                         maxLines = 1, | ||||||
|  |                     ) | ||||||
|  |                     Row( | ||||||
|  |                         horizontalArrangement = Arrangement.spacedBy(10.dp), | ||||||
|  |                         verticalAlignment = Alignment.CenterVertically, | ||||||
|  |                     ) { | ||||||
|  |                         Text( | ||||||
|  |                             text = HoursMinutesSeconds(subject.time).toString(), | ||||||
|  |                         ) | ||||||
|  |                         Row( | ||||||
|  |                             verticalAlignment = Alignment.CenterVertically, | ||||||
|  |                             horizontalArrangement = Arrangement.spacedBy(3.dp) | ||||||
|  |                         ) { | ||||||
|  |                             Icon( | ||||||
|  |                                 imageVector = Icons.Default.List, | ||||||
|  |                                 contentDescription = stringResource(id = AppText.tasks) | ||||||
|  |                             ) | ||||||
|  |                             Text(text = "0/0") // TODO | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             StealthButton( | ||||||
|  |                 text = AppText.view_tasks, | ||||||
|  |                 modifier = Modifier | ||||||
|  |                     .padding(start = 10.dp, end = 5.dp) | ||||||
|  |                     .weight(1f) | ||||||
|  |             ) { | ||||||
|  |                 onViewSubject() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun SubjectEntryPreview() { | ||||||
|  |     SubjectEntry( | ||||||
|  |         subject = Subject( | ||||||
|  |             name = "Test Subject", | ||||||
|  |             argb_color = 0xFFFFD200, | ||||||
|  |             time = 60 | ||||||
|  |         ), | ||||||
|  |     ) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun OverflowSubjectEntryPreview() { | ||||||
|  |     SubjectEntry( | ||||||
|  |         subject = Subject( | ||||||
|  |             name = "Testttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt", | ||||||
|  |             argb_color = 0xFFFFD200, | ||||||
|  |             time = 60 | ||||||
|  |         ), | ||||||
|  |     ) {} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,139 @@ | ||||||
|  | package be.ugent.sel.studeez.common.composable.tasks | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.layout.Arrangement | ||||||
|  | import androidx.compose.foundation.layout.Box | ||||||
|  | import androidx.compose.foundation.layout.Row | ||||||
|  | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
|  | import androidx.compose.material.Card | ||||||
|  | import androidx.compose.material.Checkbox | ||||||
|  | import androidx.compose.material.CheckboxDefaults | ||||||
|  | import androidx.compose.material.Icon | ||||||
|  | import androidx.compose.material.IconButton | ||||||
|  | import androidx.compose.material.MaterialTheme | ||||||
|  | import androidx.compose.material.Text | ||||||
|  | import androidx.compose.material.icons.Icons | ||||||
|  | import androidx.compose.material.icons.filled.Delete | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.Alignment | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import androidx.compose.ui.text.style.TextOverflow | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import be.ugent.sel.studeez.R | ||||||
|  | import be.ugent.sel.studeez.common.composable.StealthButton | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Task | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TaskEntry( | ||||||
|  |     task: Task, | ||||||
|  |     onCheckTask: (Boolean) -> Unit, | ||||||
|  |     onDeleteTask: () -> Unit, | ||||||
|  | ) { | ||||||
|  |     Card( | ||||||
|  |         modifier = Modifier | ||||||
|  |             .fillMaxWidth() | ||||||
|  |             .padding(horizontal = 10.dp, vertical = 5.dp), | ||||||
|  |     ) { | ||||||
|  |         val color = if (task.completed) Color.Gray else MaterialTheme.colors.onSurface | ||||||
|  |         Row( | ||||||
|  |             horizontalArrangement = Arrangement.SpaceBetween, | ||||||
|  |             verticalAlignment = Alignment.CenterVertically, | ||||||
|  |         ) { | ||||||
|  |             Row( | ||||||
|  |                 verticalAlignment = Alignment.CenterVertically, | ||||||
|  |                 modifier = Modifier | ||||||
|  |                     .padding(start = 10.dp) | ||||||
|  |                     .weight(22f), | ||||||
|  |             ) { | ||||||
|  |                 Checkbox( | ||||||
|  |                     checked = task.completed, | ||||||
|  |                     onCheckedChange = onCheckTask, | ||||||
|  |                     colors = CheckboxDefaults.colors( | ||||||
|  |                         checkedColor = Color.Gray, | ||||||
|  |                         uncheckedColor = MaterialTheme.colors.onSurface, | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |                 Row( | ||||||
|  |                     horizontalArrangement = Arrangement.SpaceBetween, | ||||||
|  |                     verticalAlignment = Alignment.CenterVertically, | ||||||
|  |                     modifier = Modifier.fillMaxWidth() | ||||||
|  |                 ) { | ||||||
|  |                     Text( | ||||||
|  |                         text = task.name, | ||||||
|  |                         overflow = TextOverflow.Ellipsis, | ||||||
|  |                         maxLines = 1, | ||||||
|  |                         color = color, | ||||||
|  |                         modifier = Modifier.weight(13f), | ||||||
|  |                     ) | ||||||
|  |                     Text( | ||||||
|  |                         text = "${HoursMinutesSeconds(task.time)}", | ||||||
|  |                         color = color, | ||||||
|  |                         modifier = Modifier.weight(7f) | ||||||
|  |                     ) | ||||||
|  | 
 | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Box(modifier = Modifier.weight(7f)) { | ||||||
|  |                 if (task.completed) { | ||||||
|  |                     IconButton( | ||||||
|  |                         onClick = onDeleteTask, | ||||||
|  |                         modifier = Modifier | ||||||
|  |                             .padding(start = 20.dp) | ||||||
|  |                     ) { | ||||||
|  |                         Icon( | ||||||
|  |                             imageVector = Icons.Default.Delete, | ||||||
|  |                             contentDescription = resources().getString(R.string.delete_task), | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     StealthButton( | ||||||
|  |                         text = R.string.start, | ||||||
|  |                         modifier = Modifier | ||||||
|  |                             .padding(end = 5.dp), | ||||||
|  |                     ) { | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun TaskEntryPreview() { | ||||||
|  |     TaskEntry( | ||||||
|  |         task = Task( | ||||||
|  |             name = "Test Task", | ||||||
|  |             completed = false, | ||||||
|  |         ), | ||||||
|  |         {}, {}, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun CompletedTaskEntryPreview() { | ||||||
|  |     TaskEntry( | ||||||
|  |         task = Task( | ||||||
|  |             name = "Test Task", | ||||||
|  |             completed = true, | ||||||
|  |         ), | ||||||
|  |         {}, {}, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun OverflowTaskEntryPreview() { | ||||||
|  |     TaskEntry( | ||||||
|  |         task = Task( | ||||||
|  |             name = "Test Taskkkkkkkkkkkkkkkkkkkkkkkkkkk", | ||||||
|  |             completed = false, | ||||||
|  |         ), | ||||||
|  |         {}, {}, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | package be.ugent.sel.studeez.data | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import javax.inject.Inject | ||||||
|  | import javax.inject.Singleton | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Used to communicate the selected subject from the subject overview other screens. | ||||||
|  |  * Because this is a singleton-class the view-models of both screens observe the same data. | ||||||
|  |  */ | ||||||
|  | @Singleton | ||||||
|  | class SelectedSubject @Inject constructor() { | ||||||
|  |     private lateinit var subject: Subject | ||||||
|  |     operator fun invoke() = subject | ||||||
|  |     fun set(subject: Subject) { | ||||||
|  |         this.subject = subject | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun isSet() = this::subject.isInitialized | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								app/src/main/java/be/ugent/sel/studeez/data/SelectedTask.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/src/main/java/be/ugent/sel/studeez/data/SelectedTask.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | package be.ugent.sel.studeez.data | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Task | ||||||
|  | import javax.inject.Inject | ||||||
|  | import javax.inject.Singleton | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Used to communicate the selected task from the task overview other screens. | ||||||
|  |  * Because this is a singleton-class the view-models of both screens observe the same data. | ||||||
|  |  */ | ||||||
|  | @Singleton | ||||||
|  | class SelectedTask @Inject constructor() { | ||||||
|  |     private lateinit var task: Task | ||||||
|  | 
 | ||||||
|  |     operator fun invoke() = task | ||||||
|  |     fun set(task: Task) { | ||||||
|  |         this.task = task | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun isSet() = this::task.isInitialized | ||||||
|  | } | ||||||
|  | @ -7,4 +7,4 @@ data class SessionReport( | ||||||
|     @DocumentId val id: String = "", |     @DocumentId val id: String = "", | ||||||
|     val studyTime: Int = 0, |     val studyTime: Int = 0, | ||||||
|     val endTime: Timestamp = Timestamp(0, 0) |     val endTime: Timestamp = Timestamp(0, 0) | ||||||
|     ) | ) | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | package be.ugent.sel.studeez.data.local.models.task | ||||||
|  | 
 | ||||||
|  | import com.google.firebase.firestore.DocumentId | ||||||
|  | 
 | ||||||
|  | data class Subject( | ||||||
|  |     @DocumentId val id: String = "", | ||||||
|  |     val name: String = "", | ||||||
|  |     val time: Int = 0, | ||||||
|  |     val argb_color: Long = 0, | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | package be.ugent.sel.studeez.data.local.models.task | ||||||
|  | 
 | ||||||
|  | import com.google.firebase.firestore.DocumentId | ||||||
|  | 
 | ||||||
|  | data class Task( | ||||||
|  |     @DocumentId val id: String = "", | ||||||
|  |     val name: String = "", | ||||||
|  |     val completed: Boolean = false, | ||||||
|  |     val time: Int = 0, | ||||||
|  |     val subjectId: String = "", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | object TaskDocument { | ||||||
|  |     const val id = "id" | ||||||
|  |     const val name = "name" | ||||||
|  |     const val completed = "completed" | ||||||
|  |     const val time = "time" | ||||||
|  |     const val subjectId = "subjectId" | ||||||
|  | } | ||||||
|  | @ -3,19 +3,19 @@ package be.ugent.sel.studeez.data.local.models.timer_functional | ||||||
| data class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) { | data class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) { | ||||||
| 
 | 
 | ||||||
|     constructor(sec: Int): this( |     constructor(sec: Int): this( | ||||||
|         sec / (60 * 60), |         hours = sec / (60 * 60), | ||||||
|         (sec / (60)) % 60, |         minutes = (sec / (60)) % 60, | ||||||
|         sec % 60 |         seconds = sec % 60, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     fun getTotalSeconds(): Int { |     fun getTotalSeconds(): Int { | ||||||
|         return hours * 60 * 60 + minutes * 60 + seconds |         return (hours * 60 * 60) + (minutes * 60) + seconds | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun toString(): String { |     override fun toString(): String { | ||||||
|         val hoursString = hours.toString().padStart(2, '0') |         val hoursString = hours.toString().padStart(2, '0') | ||||||
|         val minutesString = minutes.toString().padStart(2, '0') |         val minutesString = minutes.toString().padStart(2, '0') | ||||||
|         val secondsString = seconds.toString().padStart(2, '0') |         val secondsString = seconds.toString().padStart(2, '0') | ||||||
|         return "$hoursString : $minutesString : $secondsString" |         return "$hoursString:$minutesString:$secondsString" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -10,15 +10,27 @@ import dagger.hilt.components.SingletonComponent | ||||||
| @Module | @Module | ||||||
| @InstallIn(SingletonComponent::class) | @InstallIn(SingletonComponent::class) | ||||||
| abstract class DatabaseModule { | abstract class DatabaseModule { | ||||||
|     @Binds abstract fun provideAccountDAO(impl: FirebaseAccountDAO): AccountDAO |     @Binds | ||||||
|  |     abstract fun provideAccountDAO(impl: FirebaseAccountDAO): AccountDAO | ||||||
| 
 | 
 | ||||||
|     @Binds abstract fun provideUserDAO(impl: FirebaseUserDAO): UserDAO |     @Binds | ||||||
|  |     abstract fun provideUserDAO(impl: FirebaseUserDAO): UserDAO | ||||||
| 
 | 
 | ||||||
|     @Binds abstract fun provideTimerDAO(impl: FirebaseTimerDAO): TimerDAO |     @Binds | ||||||
|  |     abstract fun provideTimerDAO(impl: FirebaseTimerDAO): TimerDAO | ||||||
| 
 | 
 | ||||||
|     @Binds abstract fun provideLogService(impl: LogServiceImpl): LogService |     @Binds | ||||||
|  |     abstract fun provideLogService(impl: LogServiceImpl): LogService | ||||||
| 
 | 
 | ||||||
|     @Binds abstract fun provideConfigurationService(impl: FirebaseConfigurationService): ConfigurationService |     @Binds | ||||||
|  |     abstract fun provideConfigurationService(impl: FirebaseConfigurationService): ConfigurationService | ||||||
| 
 | 
 | ||||||
|     @Binds abstract fun provideSessionDAO(impl: FireBaseSessionDAO): SessionDAO |     @Binds | ||||||
|  |     abstract fun provideSessionDAO(impl: FireBaseSessionDAO): SessionDAO | ||||||
|  | 
 | ||||||
|  |     @Binds | ||||||
|  |     abstract fun provideSubjectDAO(impl: FireBaseSubjectDAO): SubjectDAO | ||||||
|  | 
 | ||||||
|  |     @Binds | ||||||
|  |     abstract fun provideTaskDAO(impl: FireBaseTaskDAO): TaskDAO | ||||||
| } | } | ||||||
							
								
								
									
										15
									
								
								app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/src/main/java/be/ugent/sel/studeez/domain/SubjectDAO.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | package be.ugent.sel.studeez.domain | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | 
 | ||||||
|  | interface SubjectDAO { | ||||||
|  | 
 | ||||||
|  |     fun getSubjects(): Flow<List<Subject>> | ||||||
|  | 
 | ||||||
|  |     fun saveSubject(newSubject: Subject) | ||||||
|  | 
 | ||||||
|  |     fun deleteSubject(oldSubject: Subject) | ||||||
|  | 
 | ||||||
|  |     fun updateSubject(newSubject: Subject) | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								app/src/main/java/be/ugent/sel/studeez/domain/TaskDAO.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | package be.ugent.sel.studeez.domain | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Task | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | 
 | ||||||
|  | interface TaskDAO { | ||||||
|  | 
 | ||||||
|  |     fun getTasks(subject: Subject): Flow<List<Task>> | ||||||
|  | 
 | ||||||
|  |     fun saveTask(newTask: Task) | ||||||
|  | 
 | ||||||
|  |     fun updateTask(newTask: Task) | ||||||
|  | 
 | ||||||
|  |     fun deleteTask(oldTask: Task) | ||||||
|  | 
 | ||||||
|  |     fun toggleTaskCompleted(task: Task, completed: Boolean) | ||||||
|  | } | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| package be.ugent.sel.studeez.domain.implementation | package be.ugent.sel.studeez.domain.implementation | ||||||
| 
 | 
 | ||||||
| object FirebaseCollectionRoutes { | object FireBaseCollections { | ||||||
|     const val SESSION_COLLECTION = "sessions" |     const val SESSION_COLLECTION = "sessions" | ||||||
|     const val USER_COLLECTION = "users" |     const val USER_COLLECTION = "users" | ||||||
|     const val TIMER_COLLECTION = "timers" |     const val TIMER_COLLECTION = "timers" | ||||||
| 
 |     const val SUBJECT_COLLECTION = "subjects" | ||||||
|  |     const val TASK_COLLECTION = "tasks" | ||||||
| } | } | ||||||
|  | @ -31,8 +31,7 @@ class FireBaseSessionDAO @Inject constructor( | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun currentUserSessionsCollection(): CollectionReference = |     private fun currentUserSessionsCollection(): CollectionReference = | ||||||
|         firestore.collection(FirebaseCollectionRoutes.USER_COLLECTION) |         firestore.collection(FireBaseCollections.USER_COLLECTION) | ||||||
|             .document(auth.currentUserId) |             .document(auth.currentUserId) | ||||||
|             .collection(FirebaseCollectionRoutes.SESSION_COLLECTION) |             .collection(FireBaseCollections.SESSION_COLLECTION) | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | @ -0,0 +1,39 @@ | ||||||
|  | package be.ugent.sel.studeez.domain.implementation | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.domain.AccountDAO | ||||||
|  | import be.ugent.sel.studeez.domain.SubjectDAO | ||||||
|  | import com.google.firebase.firestore.CollectionReference | ||||||
|  | import com.google.firebase.firestore.FirebaseFirestore | ||||||
|  | import com.google.firebase.firestore.ktx.snapshots | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.map | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | class FireBaseSubjectDAO @Inject constructor( | ||||||
|  |     private val firestore: FirebaseFirestore, | ||||||
|  |     private val auth: AccountDAO, | ||||||
|  | ) : SubjectDAO { | ||||||
|  |     override fun getSubjects(): Flow<List<Subject>> { | ||||||
|  |         return currentUserSubjectsCollection() | ||||||
|  |             .snapshots() | ||||||
|  |             .map { it.toObjects(Subject::class.java) } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun saveSubject(newSubject: Subject) { | ||||||
|  |         currentUserSubjectsCollection().add(newSubject) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun deleteSubject(oldSubject: Subject) { | ||||||
|  |         currentUserSubjectsCollection().document(oldSubject.id).delete() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun updateSubject(newSubject: Subject) { | ||||||
|  |         currentUserSubjectsCollection().document(newSubject.id).set(newSubject) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun currentUserSubjectsCollection(): CollectionReference = | ||||||
|  |         firestore.collection(FireBaseCollections.USER_COLLECTION) | ||||||
|  |             .document(auth.currentUserId) | ||||||
|  |             .collection(FireBaseCollections.SUBJECT_COLLECTION) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,49 @@ | ||||||
|  | package be.ugent.sel.studeez.domain.implementation | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Task | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.TaskDocument | ||||||
|  | import be.ugent.sel.studeez.domain.AccountDAO | ||||||
|  | import be.ugent.sel.studeez.domain.TaskDAO | ||||||
|  | import com.google.firebase.firestore.CollectionReference | ||||||
|  | import com.google.firebase.firestore.FirebaseFirestore | ||||||
|  | import com.google.firebase.firestore.ktx.snapshots | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.map | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | class FireBaseTaskDAO @Inject constructor( | ||||||
|  |     private val firestore: FirebaseFirestore, | ||||||
|  |     private val auth: AccountDAO, | ||||||
|  | ) : TaskDAO { | ||||||
|  |     override fun getTasks(subject: Subject): Flow<List<Task>> { | ||||||
|  |         return selectedSubjectTasksCollection(subject.id) | ||||||
|  |             .snapshots() | ||||||
|  |             .map { it.toObjects(Task::class.java) } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun saveTask(newTask: Task) { | ||||||
|  |         selectedSubjectTasksCollection(newTask.subjectId).add(newTask) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun updateTask(newTask: Task) { | ||||||
|  |         selectedSubjectTasksCollection(newTask.id).document(newTask.id).set(newTask) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun deleteTask(oldTask: Task) { | ||||||
|  |         selectedSubjectTasksCollection(oldTask.subjectId).document(oldTask.id).delete() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun toggleTaskCompleted(task: Task, completed: Boolean) { | ||||||
|  |         selectedSubjectTasksCollection(task.subjectId) | ||||||
|  |             .document(task.id) | ||||||
|  |             .update(TaskDocument.completed, completed) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun selectedSubjectTasksCollection(subjectId: String): CollectionReference = | ||||||
|  |         firestore.collection(FireBaseCollections.USER_COLLECTION) | ||||||
|  |             .document(auth.currentUserId) | ||||||
|  |             .collection(FireBaseCollections.SUBJECT_COLLECTION) | ||||||
|  |             .document(subjectId) | ||||||
|  |             .collection(FireBaseCollections.TASK_COLLECTION) | ||||||
|  | } | ||||||
|  | @ -1,11 +1,9 @@ | ||||||
| package be.ugent.sel.studeez.domain.implementation | package be.ugent.sel.studeez.domain.implementation | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.* | import be.ugent.sel.studeez.data.local.models.timer_info.* | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerType.* |  | ||||||
| import be.ugent.sel.studeez.domain.AccountDAO | import be.ugent.sel.studeez.domain.AccountDAO | ||||||
| import be.ugent.sel.studeez.domain.TimerDAO | import be.ugent.sel.studeez.domain.TimerDAO | ||||||
| import com.google.firebase.firestore.CollectionReference | import com.google.firebase.firestore.CollectionReference | ||||||
| import com.google.firebase.firestore.DocumentSnapshot |  | ||||||
| import com.google.firebase.firestore.FirebaseFirestore | import com.google.firebase.firestore.FirebaseFirestore | ||||||
| import com.google.firebase.firestore.ktx.snapshots | import com.google.firebase.firestore.ktx.snapshots | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
|  | @ -50,8 +48,8 @@ class FirebaseTimerDAO @Inject constructor( | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun currentUserTimersCollection(): CollectionReference = |     private fun currentUserTimersCollection(): CollectionReference = | ||||||
|         firestore.collection(FirebaseCollectionRoutes.USER_COLLECTION) |         firestore.collection(FireBaseCollections.USER_COLLECTION) | ||||||
|             .document(auth.currentUserId) |             .document(auth.currentUserId) | ||||||
|             .collection(FirebaseCollectionRoutes.TIMER_COLLECTION) |             .collection(FireBaseCollections.TIMER_COLLECTION) | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -3,7 +3,7 @@ package be.ugent.sel.studeez.navigation | ||||||
| object StudeezDestinations { | object StudeezDestinations { | ||||||
|     // NavBar |     // NavBar | ||||||
|     const val HOME_SCREEN = "home" |     const val HOME_SCREEN = "home" | ||||||
|     const val TASKS_SCREEN = "tasks" |     const val SUBJECT_SCREEN = "subjects" | ||||||
|     const val SESSIONS_SCREEN = "sessions" |     const val SESSIONS_SCREEN = "sessions" | ||||||
|     const val PROFILE_SCREEN = "profile" |     const val PROFILE_SCREEN = "profile" | ||||||
| 
 | 
 | ||||||
|  | @ -22,6 +22,12 @@ object StudeezDestinations { | ||||||
|     const val SESSION_SCREEN = "session" |     const val SESSION_SCREEN = "session" | ||||||
|     const val SESSION_RECAP = "session_recap" |     const val SESSION_RECAP = "session_recap" | ||||||
| 
 | 
 | ||||||
|  |     const val ADD_SUBJECT_FORM = "add_subject" | ||||||
|  |     const val EDIT_SUBJECT_FORM = "edit_subject" | ||||||
|  |     const val TASKS_SCREEN = "tasks" | ||||||
|  |     const val ADD_TASK_FORM = "add_task" | ||||||
|  |     const val EDIT_TASK_FORM = "edit_task" | ||||||
|  | 
 | ||||||
|     // Friends flow |     // Friends flow | ||||||
|     const val SEARCH_FRIENDS_SCREEN = "search_friends" |     const val SEARCH_FRIENDS_SCREEN = "search_friends" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,12 @@ import androidx.compose.foundation.layout.padding | ||||||
| import androidx.compose.foundation.shape.RoundedCornerShape | import androidx.compose.foundation.shape.RoundedCornerShape | ||||||
| import androidx.compose.material.Text | import androidx.compose.material.Text | ||||||
| import androidx.compose.material.TextButton | import androidx.compose.material.TextButton | ||||||
| import androidx.compose.runtime.* | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.LaunchedEffect | ||||||
|  | import androidx.compose.runtime.getValue | ||||||
|  | import androidx.compose.runtime.mutableStateOf | ||||||
|  | import androidx.compose.runtime.remember | ||||||
|  | import androidx.compose.runtime.setValue | ||||||
| import androidx.compose.ui.Alignment | import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.graphics.Color | import androidx.compose.ui.graphics.Color | ||||||
|  |  | ||||||
|  | @ -0,0 +1,80 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.tasks | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
|  | import androidx.compose.foundation.lazy.LazyColumn | ||||||
|  | import androidx.compose.foundation.lazy.items | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.collectAsState | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | ||||||
|  | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
|  | import be.ugent.sel.studeez.common.composable.navbar.NavigationBarActions | ||||||
|  | import be.ugent.sel.studeez.common.composable.tasks.SubjectEntry | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.flowOf | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SubjectRoute( | ||||||
|  |     open: (String) -> Unit, | ||||||
|  |     viewModel: SubjectViewModel, | ||||||
|  |     drawerActions: DrawerActions, | ||||||
|  |     navigationBarActions: NavigationBarActions, | ||||||
|  | ) { | ||||||
|  |     SubjectScreen( | ||||||
|  |         drawerActions = drawerActions, | ||||||
|  |         navigationBarActions = navigationBarActions, | ||||||
|  |         addSubject = { viewModel.addSubject(open) }, | ||||||
|  |         getSubjects = viewModel::getSubjects, | ||||||
|  |         onViewSubject = { viewModel.onViewSubject(it, open) }, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SubjectScreen( | ||||||
|  |     drawerActions: DrawerActions, | ||||||
|  |     navigationBarActions: NavigationBarActions, | ||||||
|  |     addSubject: () -> Unit, | ||||||
|  |     getSubjects: () -> Flow<List<Subject>>, | ||||||
|  |     onViewSubject: (Subject) -> Unit, | ||||||
|  | ) { | ||||||
|  |     PrimaryScreenTemplate( | ||||||
|  |         title = stringResource(AppText.my_subjects), | ||||||
|  |         drawerActions = drawerActions, | ||||||
|  |         navigationBarActions = navigationBarActions, | ||||||
|  |         barAction = {}, | ||||||
|  |     ) { | ||||||
|  |         val subjects = getSubjects().collectAsState(initial = emptyList()) | ||||||
|  |         Column( | ||||||
|  |             modifier = Modifier.padding(top = 5.dp) | ||||||
|  |         ) { | ||||||
|  |             LazyColumn { | ||||||
|  |                 items(subjects.value) { | ||||||
|  |                     SubjectEntry( | ||||||
|  |                         subject = it, | ||||||
|  |                         onViewSubject = { onViewSubject(it) }, | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             NewTaskSubjectButton(onClick = addSubject, AppText.new_subject) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun SubjectScreenPreview() { | ||||||
|  |     SubjectScreen( | ||||||
|  |         drawerActions = DrawerActions({}, {}, {}, {}, {}), | ||||||
|  |         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), | ||||||
|  |         addSubject = {}, | ||||||
|  |         getSubjects = { flowOf() }, | ||||||
|  |         onViewSubject = {}, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,31 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.tasks | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.SelectedSubject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.domain.LogService | ||||||
|  | import be.ugent.sel.studeez.domain.SubjectDAO | ||||||
|  | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
|  | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
|  | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | @HiltViewModel | ||||||
|  | class SubjectViewModel @Inject constructor( | ||||||
|  |     private val subjectDAO: SubjectDAO, | ||||||
|  |     private val selectedSubject: SelectedSubject, | ||||||
|  |     logService: LogService, | ||||||
|  | ) : StudeezViewModel(logService) { | ||||||
|  |     fun addSubject(open: (String) -> Unit) { | ||||||
|  |         open(StudeezDestinations.ADD_SUBJECT_FORM) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getSubjects(): Flow<List<Subject>> { | ||||||
|  |         return subjectDAO.getSubjects() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun onViewSubject(subject: Subject, open: (String) -> Unit) { | ||||||
|  |         selectedSubject.set(subject) | ||||||
|  |         open(StudeezDestinations.TASKS_SCREEN) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,113 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.tasks | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
|  | import androidx.compose.foundation.lazy.LazyColumn | ||||||
|  | import androidx.compose.foundation.lazy.items | ||||||
|  | import androidx.compose.material.Icon | ||||||
|  | import androidx.compose.material.IconButton | ||||||
|  | import androidx.compose.material.icons.Icons | ||||||
|  | import androidx.compose.material.icons.filled.Edit | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.collectAsState | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import be.ugent.sel.studeez.common.composable.NewTaskSubjectButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||||
|  | import be.ugent.sel.studeez.common.composable.tasks.TaskEntry | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Task | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.flowOf | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | data class TaskActions( | ||||||
|  |     val addTask: () -> Unit, | ||||||
|  |     val getSubject: () -> Subject, | ||||||
|  |     val getTasks: () -> Flow<List<Task>>, | ||||||
|  |     val deleteTask: (Task) -> Unit, | ||||||
|  |     val onCheckTask: (Task, Boolean) -> Unit, | ||||||
|  |     val editSubject: () -> Unit, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | fun getTaskActions(viewModel: TaskViewModel, open: (String) -> Unit): TaskActions { | ||||||
|  |     return TaskActions( | ||||||
|  |         addTask = { viewModel.addTask(open) }, | ||||||
|  |         getTasks = viewModel::getTasks, | ||||||
|  |         getSubject = viewModel::getSelectedSubject, | ||||||
|  |         deleteTask = viewModel::deleteTask, | ||||||
|  |         onCheckTask = { task, isChecked -> viewModel.toggleTaskCompleted(task, isChecked) }, | ||||||
|  |         editSubject = { viewModel.editSubject(open) } | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TaskRoute( | ||||||
|  |     goBack: () -> Unit, | ||||||
|  |     open: (String) -> Unit, | ||||||
|  |     viewModel: TaskViewModel, | ||||||
|  | ) { | ||||||
|  |     TaskScreen( | ||||||
|  |         goBack = goBack, | ||||||
|  |         taskActions = getTaskActions(viewModel = viewModel, open = open), | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TaskScreen( | ||||||
|  |     goBack: () -> Unit, | ||||||
|  |     taskActions: TaskActions, | ||||||
|  | ) { | ||||||
|  |     SecondaryScreenTemplate( | ||||||
|  |         title = taskActions.getSubject().name, | ||||||
|  |         popUp = goBack, | ||||||
|  |         barAction = { EditAction(onClick = taskActions.editSubject) } | ||||||
|  |     ) { | ||||||
|  |         val tasks = taskActions.getTasks().collectAsState(initial = emptyList()) | ||||||
|  |         Column( | ||||||
|  |             modifier = Modifier.padding(top = 5.dp) | ||||||
|  |         ) { | ||||||
|  |             LazyColumn { | ||||||
|  |                 items(tasks.value) { | ||||||
|  |                     TaskEntry( | ||||||
|  |                         task = it, | ||||||
|  |                         onCheckTask = { isChecked -> taskActions.onCheckTask(it, isChecked) }, | ||||||
|  |                         onDeleteTask = { taskActions.deleteTask(it) }, | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             NewTaskSubjectButton(onClick = taskActions.addTask, AppText.new_task) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun EditAction( | ||||||
|  |     onClick: () -> Unit | ||||||
|  | ) { | ||||||
|  |     IconButton(onClick = onClick) { | ||||||
|  |         Icon( | ||||||
|  |             imageVector = Icons.Default.Edit, | ||||||
|  |             contentDescription = stringResource(AppText.edit_task) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun TaskScreenPreview() { | ||||||
|  |     TaskScreen( | ||||||
|  |         goBack = {}, | ||||||
|  |         taskActions = TaskActions( | ||||||
|  |             {}, | ||||||
|  |             { Subject(name = "Test Subject") }, | ||||||
|  |             { flowOf() }, | ||||||
|  |             {}, | ||||||
|  |             { _, _ -> run {} }, | ||||||
|  |             {}, | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,50 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.tasks | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.SelectedSubject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Task | ||||||
|  | import be.ugent.sel.studeez.domain.LogService | ||||||
|  | import be.ugent.sel.studeez.domain.SubjectDAO | ||||||
|  | import be.ugent.sel.studeez.domain.TaskDAO | ||||||
|  | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
|  | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
|  | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | @HiltViewModel | ||||||
|  | class TaskViewModel @Inject constructor( | ||||||
|  |     private val taskDAO: TaskDAO, | ||||||
|  |     private val subjectDAO: SubjectDAO, | ||||||
|  |     private val selectedSubject: SelectedSubject, | ||||||
|  |     logService: LogService, | ||||||
|  | ) : StudeezViewModel(logService) { | ||||||
|  |     fun addTask(open: (String) -> Unit) { | ||||||
|  |         open(StudeezDestinations.ADD_TASK_FORM) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getTasks(): Flow<List<Task>> { | ||||||
|  |         return taskDAO.getTasks(selectedSubject()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun deleteSubject(open: (String) -> Unit) { | ||||||
|  |         subjectDAO.deleteSubject(selectedSubject()) | ||||||
|  |         open(StudeezDestinations.SUBJECT_SCREEN) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getSelectedSubject(): Subject { | ||||||
|  |         return selectedSubject() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun deleteTask(task: Task) { | ||||||
|  |         taskDAO.deleteTask(task) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun toggleTaskCompleted(task: Task, completed: Boolean) { | ||||||
|  |         taskDAO.toggleTaskCompleted(task, completed) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun editSubject(open: (String) -> Unit) { | ||||||
|  |         open(StudeezDestinations.EDIT_SUBJECT_FORM) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,119 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.tasks.forms | ||||||
|  | 
 | ||||||
|  | import androidx.annotation.StringRes | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
|  | import androidx.compose.material.OutlinedTextField | ||||||
|  | import androidx.compose.material.Text | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.getValue | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import be.ugent.sel.studeez.common.composable.BasicButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.DeleteButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||||
|  | import be.ugent.sel.studeez.common.ext.basicButton | ||||||
|  | import be.ugent.sel.studeez.common.ext.fieldModifier | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SubjectAddRoute( | ||||||
|  |     goBack: () -> Unit, | ||||||
|  |     openAndPopUp: (String, String) -> Unit, | ||||||
|  |     viewModel: SubjectFormViewModel, | ||||||
|  | ) { | ||||||
|  |     val uiState by viewModel.uiState | ||||||
|  |     SubjectForm( | ||||||
|  |         title = AppText.new_subject, | ||||||
|  |         goBack = goBack, | ||||||
|  |         uiState = uiState, | ||||||
|  |         onConfirm = { viewModel.onCreate(openAndPopUp) }, | ||||||
|  |         onNameChange = viewModel::onNameChange, | ||||||
|  |         onColorChange = {}, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SubjectEditRoute( | ||||||
|  |     goBack: () -> Unit, | ||||||
|  |     openAndPopUp: (String, String) -> Unit, | ||||||
|  |     viewModel: SubjectFormViewModel, | ||||||
|  | ) { | ||||||
|  |     val uiState by viewModel.uiState | ||||||
|  |     SubjectForm( | ||||||
|  |         title = AppText.edit_subject, | ||||||
|  |         goBack = goBack, | ||||||
|  |         uiState = uiState, | ||||||
|  |         onConfirm = { viewModel.onEdit(openAndPopUp) }, | ||||||
|  |         onNameChange = viewModel::onNameChange, | ||||||
|  |         onColorChange = {}, | ||||||
|  |     ) { | ||||||
|  |         DeleteButton(text = AppText.delete_subject) { | ||||||
|  |             viewModel.onDelete(openAndPopUp) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SubjectForm( | ||||||
|  |     @StringRes title: Int, | ||||||
|  |     goBack: () -> Unit, | ||||||
|  |     uiState: SubjectFormUiState, | ||||||
|  |     onConfirm: () -> Unit, | ||||||
|  |     onNameChange: (String) -> Unit, | ||||||
|  |     onColorChange: (Color) -> Unit, | ||||||
|  |     extraButton: @Composable () -> Unit = {}, | ||||||
|  | ) { | ||||||
|  |     SecondaryScreenTemplate( | ||||||
|  |         title = resources().getString(title), | ||||||
|  |         popUp = goBack, | ||||||
|  |     ) { | ||||||
|  |         Column { | ||||||
|  |             OutlinedTextField( | ||||||
|  |                 singleLine = true, | ||||||
|  |                 value = uiState.name, | ||||||
|  |                 onValueChange = onNameChange, | ||||||
|  |                 placeholder = { Text(stringResource(id = AppText.name)) }, | ||||||
|  |                 modifier = Modifier.fieldModifier(), | ||||||
|  |             ) | ||||||
|  |             BasicButton( | ||||||
|  |                 text = AppText.confirm, | ||||||
|  |                 modifier = Modifier.basicButton(), | ||||||
|  |                 onClick = onConfirm, | ||||||
|  |             ) | ||||||
|  |             extraButton() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun AddSubjectFormPreview() { | ||||||
|  |     SubjectForm( | ||||||
|  |         title = AppText.new_subject, | ||||||
|  |         goBack = {}, | ||||||
|  |         uiState = SubjectFormUiState(), | ||||||
|  |         onConfirm = {}, | ||||||
|  |         onNameChange = {}, | ||||||
|  |         onColorChange = {}, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun EditSubjectFormPreview() { | ||||||
|  |     SubjectForm( | ||||||
|  |         title = AppText.edit_subject, | ||||||
|  |         goBack = {}, | ||||||
|  |         uiState = SubjectFormUiState( | ||||||
|  |             name = "Test Subject", | ||||||
|  |         ), | ||||||
|  |         onConfirm = {}, | ||||||
|  |         onNameChange = {}, | ||||||
|  |         onColorChange = {}, | ||||||
|  |     ) { | ||||||
|  |         DeleteButton(text = AppText.delete_subject) {} | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.tasks.forms | ||||||
|  | 
 | ||||||
|  | data class SubjectFormUiState( | ||||||
|  |     val name: String = "", | ||||||
|  |     val color: Long = 0xFFFFD200, | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,69 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.tasks.forms | ||||||
|  | 
 | ||||||
|  | import androidx.compose.runtime.mutableStateOf | ||||||
|  | import be.ugent.sel.studeez.data.SelectedSubject | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
|  | import be.ugent.sel.studeez.domain.LogService | ||||||
|  | import be.ugent.sel.studeez.domain.SubjectDAO | ||||||
|  | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
|  | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
|  | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | @HiltViewModel | ||||||
|  | class SubjectFormViewModel @Inject constructor( | ||||||
|  |     private val subjectDAO: SubjectDAO, | ||||||
|  |     private val selectedSubject: SelectedSubject, | ||||||
|  |     logService: LogService, | ||||||
|  | ) : StudeezViewModel(logService) { | ||||||
|  |     var uiState = mutableStateOf( | ||||||
|  |         if (selectedSubject.isSet()) SubjectFormUiState( | ||||||
|  |             name = selectedSubject().name, | ||||||
|  |             color = selectedSubject().argb_color | ||||||
|  |         ) | ||||||
|  |         else SubjectFormUiState() | ||||||
|  |     ) | ||||||
|  |         private set | ||||||
|  | 
 | ||||||
|  |     private val name: String | ||||||
|  |         get() = uiState.value.name | ||||||
|  | 
 | ||||||
|  |     private val color: Long | ||||||
|  |         get() = uiState.value.color | ||||||
|  | 
 | ||||||
|  |     fun onNameChange(newValue: String) { | ||||||
|  |         uiState.value = uiState.value.copy(name = newValue) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun onColorChange(newValue: Long) { | ||||||
|  |         uiState.value = uiState.value.copy(color = newValue) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun onDelete(openAndPopUp: (String, String) -> Unit) { | ||||||
|  |         subjectDAO.deleteSubject(selectedSubject()) | ||||||
|  |         openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun onCreate(openAndPopUp: (String, String) -> Unit) { | ||||||
|  |         val newSubject = Subject( | ||||||
|  |             name = name, | ||||||
|  |             argb_color = color, | ||||||
|  |         ) | ||||||
|  |         subjectDAO.saveSubject( | ||||||
|  |             newSubject | ||||||
|  |         ) | ||||||
|  |         // TODO open newly created subject | ||||||
|  | //        selectedSubject.set(newSubject) | ||||||
|  | //        open(StudeezDestinations.TASKS_SCREEN) | ||||||
|  |         openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.ADD_SUBJECT_FORM) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun onEdit(openAndPopUp: (String, String) -> Unit) { | ||||||
|  |         val newSubject = selectedSubject().copy( | ||||||
|  |             name = name, | ||||||
|  |             argb_color = color, | ||||||
|  |         ) | ||||||
|  |         subjectDAO.updateSubject(newSubject) | ||||||
|  |         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,113 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.tasks.forms | ||||||
|  | 
 | ||||||
|  | import androidx.annotation.StringRes | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
|  | import androidx.compose.material.OutlinedTextField | ||||||
|  | import androidx.compose.material.Text | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.getValue | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import be.ugent.sel.studeez.common.composable.BasicButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.DeleteButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||||
|  | import be.ugent.sel.studeez.common.ext.basicButton | ||||||
|  | import be.ugent.sel.studeez.common.ext.fieldModifier | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TaskAddRoute( | ||||||
|  |     goBack: () -> Unit, | ||||||
|  |     openAndPopUp: (String, String) -> Unit, | ||||||
|  |     viewModel: TaskFormViewModel, | ||||||
|  | ) { | ||||||
|  |     val uiState by viewModel.uiState | ||||||
|  |     TaskForm( | ||||||
|  |         title = AppText.new_task, | ||||||
|  |         goBack = goBack, | ||||||
|  |         uiState = uiState, | ||||||
|  |         onConfirm = { viewModel.onCreate(openAndPopUp) }, | ||||||
|  |         onNameChange = viewModel::onNameChange | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TaskEditRoute( | ||||||
|  |     goBack: () -> Unit, | ||||||
|  |     openAndPopUp: (String, String) -> Unit, | ||||||
|  |     viewModel: TaskFormViewModel, | ||||||
|  | ) { | ||||||
|  |     val uiState by viewModel.uiState | ||||||
|  |     TaskForm( | ||||||
|  |         title = AppText.edit_task, | ||||||
|  |         goBack = goBack, | ||||||
|  |         uiState = uiState, | ||||||
|  |         onConfirm = { viewModel.onEdit(openAndPopUp) }, | ||||||
|  |         onNameChange = viewModel::onNameChange | ||||||
|  |     ) { | ||||||
|  |         DeleteButton(text = AppText.delete_task) { | ||||||
|  |             viewModel.onDelete(openAndPopUp) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TaskForm( | ||||||
|  |     @StringRes title: Int, | ||||||
|  |     goBack: () -> Unit, | ||||||
|  |     uiState: TaskFormUiState, | ||||||
|  |     onConfirm: () -> Unit, | ||||||
|  |     onNameChange: (String) -> Unit, | ||||||
|  |     extraButton: @Composable () -> Unit = {} | ||||||
|  | ) { | ||||||
|  |     SecondaryScreenTemplate( | ||||||
|  |         title = resources().getString(title), | ||||||
|  |         popUp = goBack, | ||||||
|  |     ) { | ||||||
|  |         Column { | ||||||
|  |             OutlinedTextField( | ||||||
|  |                 singleLine = true, | ||||||
|  |                 value = uiState.name, | ||||||
|  |                 onValueChange = onNameChange, | ||||||
|  |                 placeholder = { Text(stringResource(id = AppText.name)) }, | ||||||
|  |                 modifier = Modifier.fieldModifier(), | ||||||
|  |             ) | ||||||
|  |             BasicButton( | ||||||
|  |                 text = AppText.confirm, | ||||||
|  |                 modifier = Modifier.basicButton(), | ||||||
|  |                 onClick = onConfirm, | ||||||
|  |             ) | ||||||
|  |             extraButton() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun AddTaskFormPreview() { | ||||||
|  |     TaskForm( | ||||||
|  |         title = AppText.new_task, | ||||||
|  |         goBack = {}, | ||||||
|  |         uiState = TaskFormUiState(), | ||||||
|  |         onConfirm = {}, | ||||||
|  |         onNameChange = {}, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun EditTaskFormPreview() { | ||||||
|  |     TaskForm( | ||||||
|  |         title = AppText.edit_task, | ||||||
|  |         goBack = {}, | ||||||
|  |         uiState = TaskFormUiState( | ||||||
|  |             name = "Test Task", | ||||||
|  |         ), | ||||||
|  |         onConfirm = {}, | ||||||
|  |         onNameChange = {}, | ||||||
|  |     ) { | ||||||
|  |         DeleteButton(text = AppText.delete_task) {} | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.tasks.forms | ||||||
|  | 
 | ||||||
|  | data class TaskFormUiState( | ||||||
|  |     val name: String = "", | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,49 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.tasks.forms | ||||||
|  | 
 | ||||||
|  | import androidx.compose.runtime.mutableStateOf | ||||||
|  | import be.ugent.sel.studeez.data.SelectedSubject | ||||||
|  | import be.ugent.sel.studeez.data.SelectedTask | ||||||
|  | import be.ugent.sel.studeez.data.local.models.task.Task | ||||||
|  | import be.ugent.sel.studeez.domain.LogService | ||||||
|  | import be.ugent.sel.studeez.domain.TaskDAO | ||||||
|  | import be.ugent.sel.studeez.navigation.StudeezDestinations | ||||||
|  | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
|  | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | @HiltViewModel | ||||||
|  | class TaskFormViewModel @Inject constructor( | ||||||
|  |     private val taskDAO: TaskDAO, | ||||||
|  |     private val selectedSubject: SelectedSubject, | ||||||
|  |     private val selectedTask: SelectedTask, | ||||||
|  |     logService: LogService, | ||||||
|  | ) : StudeezViewModel(logService) { | ||||||
|  |     var uiState = mutableStateOf( | ||||||
|  |         if (selectedTask.isSet()) TaskFormUiState(selectedTask().name) else TaskFormUiState() | ||||||
|  |     ) | ||||||
|  |         private set | ||||||
|  | 
 | ||||||
|  |     private val name: String | ||||||
|  |         get() = uiState.value.name | ||||||
|  | 
 | ||||||
|  |     fun onNameChange(newValue: String) { | ||||||
|  |         uiState.value = uiState.value.copy(name = newValue) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun onDelete(openAndPopUp: (String, String) -> Unit) { | ||||||
|  |         taskDAO.deleteTask(selectedTask()) | ||||||
|  |         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun onCreate(openAndPopUp: (String, String) -> Unit) { | ||||||
|  |         val newTask = Task(name = name, subjectId = selectedSubject().id) | ||||||
|  |         taskDAO.saveTask(newTask) | ||||||
|  |         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.ADD_TASK_FORM) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun onEdit(openAndPopUp: (String, String) -> Unit) { | ||||||
|  |         val newTask = Task(name = name) | ||||||
|  |         taskDAO.updateTask(newTask) | ||||||
|  |         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_TASK_FORM) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -6,7 +6,11 @@ import androidx.compose.foundation.layout.fillMaxHeight | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
| import androidx.compose.foundation.rememberScrollState | import androidx.compose.foundation.rememberScrollState | ||||||
| import androidx.compose.foundation.verticalScroll | import androidx.compose.foundation.verticalScroll | ||||||
| import androidx.compose.runtime.* | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.getValue | ||||||
|  | import androidx.compose.runtime.mutableStateOf | ||||||
|  | import androidx.compose.runtime.remember | ||||||
|  | import androidx.compose.runtime.setValue | ||||||
| import androidx.compose.ui.Alignment | import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
|  | @ -43,15 +47,12 @@ abstract class AbstractTimerEditScreen(private val timerInfo: TimerInfo) { | ||||||
|                     label = R.string.name |                     label = R.string.name | ||||||
|                 ) |                 ) | ||||||
| 
 | 
 | ||||||
|                 repeat(20) { |                 LabelledInputField( | ||||||
|                     LabelledInputField( |                     value = description, | ||||||
|                         value = description, |                     onNewValue = { description = it }, | ||||||
|                         onNewValue = { description = it }, |                     label = R.string.description, | ||||||
|                         label = R.string.description, |                     singleLine = false | ||||||
|                         singleLine = false |                 ) | ||||||
|                     ) |  | ||||||
| 
 |  | ||||||
|                 } |  | ||||||
| 
 | 
 | ||||||
|                 ExtraFields() |                 ExtraFields() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -32,6 +32,14 @@ | ||||||
|     <!-- Tasks --> |     <!-- Tasks --> | ||||||
|     <string name="tasks">Tasks</string> |     <string name="tasks">Tasks</string> | ||||||
|     <string name="task">Task</string> |     <string name="task">Task</string> | ||||||
|  |     <string name="my_subjects">My Subjects</string> | ||||||
|  |     <string name="new_subject">New Subject</string> | ||||||
|  |     <string name="new_task">New Task</string> | ||||||
|  |     <string name="edit_subject">Edit Subject</string> | ||||||
|  |     <string name="edit_task">Edit Task</string> | ||||||
|  |     <string name="delete_subject">Delete Subject</string> | ||||||
|  |     <string name="delete_task">Delete Task</string> | ||||||
|  |     <string name="view_tasks">View</string> | ||||||
| 
 | 
 | ||||||
|     <!-- Sessions --> |     <!-- Sessions --> | ||||||
|     <string name="sessions_temp_description">Looks like you found the sessions screen! In here, your upcoming studying sessions with friends will be listed. You can accept invites or edit your own.</string> <!-- TODO Remove this description line once implemented. --> |     <string name="sessions_temp_description">Looks like you found the sessions screen! In here, your upcoming studying sessions with friends will be listed. You can accept invites or edit your own.</string> <!-- TODO Remove this description line once implemented. --> | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 brreynie
						brreynie