normal test fails
This commit is contained in:
		
						commit
						31e78856c0
					
				
					 48 changed files with 878 additions and 698 deletions
				
			
		
							
								
								
									
										13
									
								
								.idea/androidTestResultsUserPreferences.xml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								.idea/androidTestResultsUserPreferences.xml
									
										
									
										generated
									
									
									
								
							|  | @ -291,6 +291,19 @@ | ||||||
|             </AndroidTestResultsTableState> |             </AndroidTestResultsTableState> | ||||||
|           </value> |           </value> | ||||||
|         </entry> |         </entry> | ||||||
|  |         <entry key="971972923"> | ||||||
|  |           <value> | ||||||
|  |             <AndroidTestResultsTableState> | ||||||
|  |               <option name="preferredColumnWidths"> | ||||||
|  |                 <map> | ||||||
|  |                   <entry key="Duration" value="90" /> | ||||||
|  |                   <entry key="Pixel_XL_API_30" value="120" /> | ||||||
|  |                   <entry key="Tests" value="360" /> | ||||||
|  |                 </map> | ||||||
|  |               </option> | ||||||
|  |             </AndroidTestResultsTableState> | ||||||
|  |           </value> | ||||||
|  |         </entry> | ||||||
|         <entry key="1072291594"> |         <entry key="1072291594"> | ||||||
|           <value> |           <value> | ||||||
|             <AndroidTestResultsTableState> |             <AndroidTestResultsTableState> | ||||||
|  |  | ||||||
|  | @ -123,9 +123,6 @@ dependencies { | ||||||
|     implementation 'com.google.firebase:firebase-firestore-ktx' |     implementation 'com.google.firebase:firebase-firestore-ktx' | ||||||
|     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 | ||||||
|  |  | ||||||
|  | @ -1,119 +0,0 @@ | ||||||
| package be.ugent.sel.studeez |  | ||||||
| 
 |  | ||||||
| import androidx.compose.ui.test.junit4.createComposeRule |  | ||||||
| import androidx.compose.ui.test.onNodeWithText |  | ||||||
| import androidx.compose.ui.test.performClick |  | ||||||
| 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.SessionActions |  | ||||||
| import be.ugent.sel.studeez.screens.session.sessionScreens.BreakSessionScreen |  | ||||||
| import be.ugent.sel.studeez.screens.session.sessionScreens.CustomSessionScreen |  | ||||||
| import be.ugent.sel.studeez.screens.session.sessionScreens.EndlessSessionScreen |  | ||||||
| import org.junit.Assert |  | ||||||
| import org.junit.Rule |  | ||||||
| import org.junit.Test |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SessionScreenTest { |  | ||||||
|     @get:Rule |  | ||||||
|     val composeTestRule = createComposeRule() |  | ||||||
| 
 |  | ||||||
|     @Test |  | ||||||
|     fun customSessionScreenTest() { |  | ||||||
|         var endSession = false |  | ||||||
| 
 |  | ||||||
|         composeTestRule.setContent { |  | ||||||
|             CustomSessionScreen( |  | ||||||
|                 functionalTimer = FunctionalCustomTimer(0), |  | ||||||
|                 mediaplayer = null |  | ||||||
|             ).invoke( |  | ||||||
|                 open = {}, |  | ||||||
|                 sessionActions = SessionActions( |  | ||||||
|                     {FunctionalCustomTimer(0)}, |  | ||||||
|                     { "" }, |  | ||||||
|                     {}, {}, {endSession = true} |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composeTestRule.waitForIdle() |  | ||||||
| 
 |  | ||||||
|         composeTestRule |  | ||||||
|             .onNodeWithText( |  | ||||||
|                 "end session", |  | ||||||
|                 substring = true, |  | ||||||
|                 ignoreCase = true |  | ||||||
|             ) |  | ||||||
|             .assertExists() |  | ||||||
|             .performClick() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         Assert.assertTrue(endSession) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Test |  | ||||||
|     fun endlessSessionScreenTest() { |  | ||||||
|         var endSession = false |  | ||||||
| 
 |  | ||||||
|         composeTestRule.setContent { |  | ||||||
|             EndlessSessionScreen() |  | ||||||
|                 .invoke( |  | ||||||
|                     open = {}, |  | ||||||
|                     sessionActions = SessionActions( |  | ||||||
|                         {FunctionalEndlessTimer()}, |  | ||||||
|                         { "" }, |  | ||||||
|                         {}, {}, {endSession = true} |  | ||||||
|                     ) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composeTestRule.waitForIdle() |  | ||||||
| 
 |  | ||||||
|         composeTestRule |  | ||||||
|             .onNodeWithText( |  | ||||||
|                 "end session", |  | ||||||
|                 substring = true, |  | ||||||
|                 ignoreCase = true |  | ||||||
|             ) |  | ||||||
|             .assertExists() |  | ||||||
|             .performClick() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         Assert.assertTrue(endSession) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Test |  | ||||||
|     fun breakSessionScreenTest() { |  | ||||||
|         var endSession = false |  | ||||||
| 
 |  | ||||||
|         composeTestRule.setContent { |  | ||||||
|             BreakSessionScreen( |  | ||||||
|                 funPomoDoroTimer = FunctionalPomodoroTimer(0, 0, 0), |  | ||||||
|                 mediaplayer = null |  | ||||||
|             ) |  | ||||||
|             .invoke( |  | ||||||
|                 open = {}, |  | ||||||
|                 sessionActions = SessionActions( |  | ||||||
|                     {FunctionalPomodoroTimer(0, 0, 0)}, |  | ||||||
|                     { "" }, |  | ||||||
|                     {}, {}, {endSession = true} |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composeTestRule.waitForIdle() |  | ||||||
| 
 |  | ||||||
|         composeTestRule |  | ||||||
|             .onNodeWithText( |  | ||||||
|                 "end session", |  | ||||||
|                 substring = true, |  | ||||||
|                 ignoreCase = true |  | ||||||
|             ) |  | ||||||
|             .assertExists() |  | ||||||
|             .performClick() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         Assert.assertTrue(endSession) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -117,12 +117,15 @@ class SubjectScreenTest { | ||||||
|                 onAddSubject = { add = true }, |                 onAddSubject = { add = true }, | ||||||
|                 onViewSubject = { view = true }, |                 onViewSubject = { view = true }, | ||||||
|                 getStudyTime = { flowOf() }, |                 getStudyTime = { flowOf() }, | ||||||
|  |                 getCompletedTaskCount = { flowOf() }, | ||||||
|  |                 getTaskCount = { flowOf() }, | ||||||
|                 uiState = SubjectUiState.Succes( |                 uiState = SubjectUiState.Succes( | ||||||
|                     listOf( |                     listOf( | ||||||
|                         Subject( |                         Subject( | ||||||
|  |                             id = "", | ||||||
|                             name = "Test Subject", |                             name = "Test Subject", | ||||||
|                             argb_color = 0xFFFFD200, |                             argb_color = 0xFFFFD200, | ||||||
|                             taskCount = 5, taskCompletedCount = 2, |                             archived = false | ||||||
|                         ) |                         ) | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
|  | @ -1,37 +0,0 @@ | ||||||
| package be.ugent.sel.studeez |  | ||||||
| 
 |  | ||||||
| import androidx.compose.ui.test.junit4.createComposeRule |  | ||||||
| import androidx.compose.ui.test.onNodeWithText |  | ||||||
| import androidx.compose.ui.test.performClick |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo |  | ||||||
| import be.ugent.sel.studeez.screens.timer_form.form_screens.EndlessTimerFormScreen |  | ||||||
| import org.junit.Rule |  | ||||||
| import org.junit.Test |  | ||||||
| 
 |  | ||||||
| class TimerScreenTest { |  | ||||||
|     @get:Rule |  | ||||||
|     val composeTestRule = createComposeRule() |  | ||||||
| 
 |  | ||||||
|     @Test |  | ||||||
|     fun timerFormScreenTest() { |  | ||||||
|         var save = false |  | ||||||
| 
 |  | ||||||
|         composeTestRule.setContent { |  | ||||||
|             EndlessTimerFormScreen(EndlessTimerInfo("", "")) |  | ||||||
|                 .invoke(onSaveClick = {save = true}) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         composeTestRule.waitForIdle() |  | ||||||
| 
 |  | ||||||
|         composeTestRule |  | ||||||
|             .onNodeWithText( |  | ||||||
|                 text = "save", |  | ||||||
|                 substring = true, |  | ||||||
|                 ignoreCase = true |  | ||||||
|             ) |  | ||||||
|             .assertExists() |  | ||||||
|             .performClick() |  | ||||||
| 
 |  | ||||||
|         assert(save) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -2,7 +2,6 @@ package be.ugent.sel.studeez.common.composable | ||||||
| 
 | 
 | ||||||
| import androidx.compose.animation.core.animateFloat | import androidx.compose.animation.core.animateFloat | ||||||
| import androidx.compose.animation.core.updateTransition | import androidx.compose.animation.core.updateTransition | ||||||
| import androidx.compose.foundation.border |  | ||||||
| import androidx.compose.foundation.layout.* | import androidx.compose.foundation.layout.* | ||||||
| import androidx.compose.material.FloatingActionButton | import androidx.compose.material.FloatingActionButton | ||||||
| import androidx.compose.material.Icon | import androidx.compose.material.Icon | ||||||
|  |  | ||||||
|  | @ -0,0 +1,22 @@ | ||||||
|  | package be.ugent.sel.studeez.common.composable | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.layout.Box | ||||||
|  | import androidx.compose.foundation.rememberScrollState | ||||||
|  | import androidx.compose.foundation.verticalScroll | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun FormComposable( | ||||||
|  |     title: String, | ||||||
|  |     popUp: () -> Unit, | ||||||
|  |     content: @Composable () -> Unit, | ||||||
|  | ) { | ||||||
|  |     SecondaryScreenTemplate(title = title, popUp = popUp) { | ||||||
|  |         Box( | ||||||
|  |             modifier = Modifier.verticalScroll(rememberScrollState()), | ||||||
|  |         ) { | ||||||
|  |             content() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,39 @@ | ||||||
|  | package be.ugent.sel.studeez.common.composable | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.Image | ||||||
|  | import androidx.compose.foundation.border | ||||||
|  | import androidx.compose.foundation.clickable | ||||||
|  | import androidx.compose.foundation.shape.RoundedCornerShape | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import androidx.compose.ui.graphics.painter.Painter | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun ImageBackgroundButton( | ||||||
|  |     paint: Painter, | ||||||
|  |     str: String, | ||||||
|  |     background2: Color, | ||||||
|  |     setBackground1: (Color) -> Unit, | ||||||
|  |     setBackground2: (Color) -> Unit | ||||||
|  | ) { | ||||||
|  |     Image( | ||||||
|  |         painter = paint, | ||||||
|  |         str, | ||||||
|  |         modifier = Modifier | ||||||
|  |             .clickable { | ||||||
|  |                 if (background2 == Color.Transparent) { | ||||||
|  |                     setBackground1(Color.LightGray) | ||||||
|  |                     setBackground2(Color.Transparent) | ||||||
|  |                 } else { | ||||||
|  |                     setBackground2(Color.Transparent) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             .border( | ||||||
|  |                 width = 2.dp, | ||||||
|  |                 color = background2, | ||||||
|  |                 shape = RoundedCornerShape(16.dp) | ||||||
|  |             ) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -3,7 +3,6 @@ package be.ugent.sel.studeez.common.composable | ||||||
| import androidx.annotation.StringRes | import androidx.annotation.StringRes | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.compose.foundation.layout.Column | ||||||
| import androidx.compose.foundation.layout.padding | import androidx.compose.foundation.layout.padding | ||||||
| import androidx.compose.foundation.text.KeyboardActions |  | ||||||
| import androidx.compose.foundation.text.KeyboardOptions | import androidx.compose.foundation.text.KeyboardOptions | ||||||
| import androidx.compose.material.* | import androidx.compose.material.* | ||||||
| import androidx.compose.material.icons.Icons | import androidx.compose.material.icons.Icons | ||||||
|  | @ -22,7 +21,6 @@ import androidx.compose.ui.tooling.preview.Preview | ||||||
| import androidx.compose.ui.unit.dp | import androidx.compose.ui.unit.dp | ||||||
| import be.ugent.sel.studeez.common.ext.fieldModifier | import be.ugent.sel.studeez.common.ext.fieldModifier | ||||||
| import be.ugent.sel.studeez.resources | import be.ugent.sel.studeez.resources | ||||||
| import kotlin.math.sin |  | ||||||
| import be.ugent.sel.studeez.R.drawable as AppIcon | import be.ugent.sel.studeez.R.drawable as AppIcon | ||||||
| import be.ugent.sel.studeez.R.string as AppText | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
|  | @ -47,7 +45,7 @@ fun LabelledInputField( | ||||||
|     value: String, |     value: String, | ||||||
|     onNewValue: (String) -> Unit, |     onNewValue: (String) -> Unit, | ||||||
|     @StringRes label: Int, |     @StringRes label: Int, | ||||||
|     singleLine: Boolean = false |     singleLine: Boolean = true | ||||||
| ) { | ) { | ||||||
|     OutlinedTextField( |     OutlinedTextField( | ||||||
|         value = value, |         value = value, | ||||||
|  | @ -119,7 +117,9 @@ fun LabeledErrorTextField( | ||||||
|     initialValue: String, |     initialValue: String, | ||||||
|     @StringRes label: Int, |     @StringRes label: Int, | ||||||
|     singleLine: Boolean = false, |     singleLine: Boolean = false, | ||||||
|     errorText: Int, |     isValid: MutableState<Boolean> = remember { mutableStateOf(true) }, | ||||||
|  |     isFirst: MutableState<Boolean> = remember { mutableStateOf(false) }, | ||||||
|  |     @StringRes errorText: Int, | ||||||
|     keyboardType: KeyboardType, |     keyboardType: KeyboardType, | ||||||
|     predicate: (String) -> Boolean, |     predicate: (String) -> Boolean, | ||||||
|     onNewCorrectValue: (String) -> Unit |     onNewCorrectValue: (String) -> Unit | ||||||
|  | @ -128,31 +128,28 @@ fun LabeledErrorTextField( | ||||||
|         mutableStateOf(initialValue) |         mutableStateOf(initialValue) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     var isValid by remember { |  | ||||||
|         mutableStateOf(predicate(value)) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Column { |     Column { | ||||||
|         OutlinedTextField( |         OutlinedTextField( | ||||||
|             modifier = modifier.fieldModifier(), |             modifier = modifier.fieldModifier(), | ||||||
|             value = value, |             value = value, | ||||||
|             onValueChange = { newText -> |             onValueChange = { newText -> | ||||||
|  |                 isFirst.value = false | ||||||
|                 value = newText |                 value = newText | ||||||
|                 isValid = predicate(value) |                 isValid.value = predicate(value) | ||||||
|                 if (isValid) { |                 if (isValid.value) { | ||||||
|                     onNewCorrectValue(newText) |                     onNewCorrectValue(newText) | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             singleLine = singleLine, |             singleLine = singleLine, | ||||||
|             label = { Text(text = stringResource(id = label)) }, |             label = { Text(text = stringResource(id = label)) }, | ||||||
|             isError = !isValid, |             isError = !isValid.value && !isFirst.value, | ||||||
|             keyboardOptions = KeyboardOptions( |             keyboardOptions = KeyboardOptions( | ||||||
|                 keyboardType = keyboardType, |                 keyboardType = keyboardType, | ||||||
|                 imeAction = ImeAction.Done |                 imeAction = ImeAction.Done | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         if (!isValid) { |         if (!isValid.value && !isFirst.value) { | ||||||
|             Text( |             Text( | ||||||
|                 modifier = Modifier.padding(start = 16.dp), |                 modifier = Modifier.padding(start = 16.dp), | ||||||
|                 text = stringResource(id = errorText), |                 text = stringResource(id = errorText), | ||||||
|  |  | ||||||
|  | @ -31,9 +31,13 @@ import be.ugent.sel.studeez.R.string as AppText | ||||||
| fun SubjectEntry( | fun SubjectEntry( | ||||||
|     subject: Subject, |     subject: Subject, | ||||||
|     onViewSubject: () -> Unit, |     onViewSubject: () -> Unit, | ||||||
|  |     getTaskCount: () -> Flow<Int>, | ||||||
|  |     getCompletedTaskCount: () -> Flow<Int>, | ||||||
|     getStudyTime: () -> Flow<Int>, |     getStudyTime: () -> Flow<Int>, | ||||||
| ) { | ) { | ||||||
|     val studytime by getStudyTime().collectAsState(initial = 0) |     val studytime by getStudyTime().collectAsState(initial = 0) | ||||||
|  |     val taskCount by getTaskCount().collectAsState(initial = 0) | ||||||
|  |     val completedTaskCount by getCompletedTaskCount().collectAsState(initial = 0) | ||||||
|     Card( |     Card( | ||||||
|         modifier = Modifier |         modifier = Modifier | ||||||
|             .fillMaxWidth() |             .fillMaxWidth() | ||||||
|  | @ -80,7 +84,7 @@ fun SubjectEntry( | ||||||
|                                 imageVector = Icons.Default.List, |                                 imageVector = Icons.Default.List, | ||||||
|                                 contentDescription = stringResource(id = AppText.tasks) |                                 contentDescription = stringResource(id = AppText.tasks) | ||||||
|                             ) |                             ) | ||||||
|                             Text(text = "${subject.taskCompletedCount}/${subject.taskCount}") |                             Text(text = "${completedTaskCount}/${taskCount}") | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -104,11 +108,11 @@ fun SubjectEntryPreview() { | ||||||
|         subject = Subject( |         subject = Subject( | ||||||
|             name = "Test Subject", |             name = "Test Subject", | ||||||
|             argb_color = 0xFFFFD200, |             argb_color = 0xFFFFD200, | ||||||
|             taskCount = 5, |  | ||||||
|             taskCompletedCount = 2, |  | ||||||
|         ), |         ), | ||||||
|         onViewSubject = {}, |         onViewSubject = {}, | ||||||
|         getStudyTime = { flowOf() } |         getTaskCount = { flowOf() }, | ||||||
|  |         getCompletedTaskCount = { flowOf() }, | ||||||
|  |         getStudyTime = { flowOf() }, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -121,6 +125,8 @@ fun OverflowSubjectEntryPreview() { | ||||||
|             argb_color = 0xFFFFD200, |             argb_color = 0xFFFFD200, | ||||||
|         ), |         ), | ||||||
|         onViewSubject = {}, |         onViewSubject = {}, | ||||||
|         getStudyTime = { flowOf() } |         getTaskCount = { flowOf() }, | ||||||
|  |         getCompletedTaskCount = { flowOf() }, | ||||||
|  |         getStudyTime = { flowOf() }, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | package be.ugent.sel.studeez.common.ext | ||||||
|  | 
 | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import kotlin.random.Random | ||||||
|  | 
 | ||||||
|  | fun Color.Companion.generateRandomArgb(): Long { | ||||||
|  |     val random = Random | ||||||
|  |     val mask: Long = (0x000000FFL shl random.nextInt(0, 3)).inv() | ||||||
|  |     return random.nextLong(0xFF000000L, 0xFFFFFFFFL) and mask | ||||||
|  | } | ||||||
|  | @ -1,17 +1,12 @@ | ||||||
| package be.ugent.sel.studeez.data.local.models.task | package be.ugent.sel.studeez.data.local.models.task | ||||||
| 
 | 
 | ||||||
| import com.google.firebase.firestore.DocumentId | import com.google.firebase.firestore.DocumentId | ||||||
| import com.google.firebase.firestore.Exclude |  | ||||||
| 
 | 
 | ||||||
| data class Subject( | data class Subject( | ||||||
|     @DocumentId val id: String = "", |     @DocumentId val id: String = "", | ||||||
|     val name: String = "", |     val name: String = "", | ||||||
|     val argb_color: Long = 0, |     val argb_color: Long = 0, | ||||||
|     var archived: Boolean = false, |     var archived: Boolean = false, | ||||||
|     @get:Exclude @set:Exclude |  | ||||||
|     var taskCount: Int = 0, |  | ||||||
|     @get:Exclude @set:Exclude |  | ||||||
|     var taskCompletedCount: Int = 0, |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| object SubjectDocument { | object SubjectDocument { | ||||||
|  |  | ||||||
|  | @ -6,14 +6,13 @@ class FunctionalPomodoroTimer( | ||||||
|     val repeats: Int |     val repeats: Int | ||||||
| ) : FunctionalTimer(studyTime) { | ) : FunctionalTimer(studyTime) { | ||||||
| 
 | 
 | ||||||
|     var breaksRemaining = repeats |     var breaksRemaining = repeats - 1 | ||||||
|     var isInBreak = false |     var isInBreak = false | ||||||
| 
 | 
 | ||||||
|     override fun tick() { |     override fun tick() { | ||||||
|         if (hasEnded()) { |         if (hasEnded()) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         if (hasCurrentCountdownEnded()) { |         if (hasCurrentCountdownEnded()) { | ||||||
|             if (isInBreak) { |             if (isInBreak) { | ||||||
|                 breaksRemaining-- |                 breaksRemaining-- | ||||||
|  |  | ||||||
|  | @ -13,8 +13,10 @@ interface SubjectDAO { | ||||||
| 
 | 
 | ||||||
|     fun updateSubject(newSubject: Subject) |     fun updateSubject(newSubject: Subject) | ||||||
| 
 | 
 | ||||||
|     suspend fun getTaskCount(subject: Subject): Int |     suspend fun archiveSubject(subject: Subject) | ||||||
|     suspend fun getCompletedTaskCount(subject: Subject): Int | 
 | ||||||
|  |     fun getTaskCount(subject: Subject): Flow<Int> | ||||||
|  |     fun getCompletedTaskCount(subject: Subject): Flow<Int> | ||||||
|     fun getStudyTime(subject: Subject): Flow<Int> |     fun getStudyTime(subject: Subject): Flow<Int> | ||||||
| 
 | 
 | ||||||
|     suspend fun getSubject(subjectId: String): Subject? |     suspend fun getSubject(subjectId: String): Subject? | ||||||
|  |  | ||||||
|  | @ -2,10 +2,11 @@ package be.ugent.sel.studeez.domain.implementation | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Subject | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
| import be.ugent.sel.studeez.data.local.models.task.SubjectDocument | import be.ugent.sel.studeez.data.local.models.task.SubjectDocument | ||||||
|  | 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.AccountDAO | ||||||
| import be.ugent.sel.studeez.domain.SubjectDAO | import be.ugent.sel.studeez.domain.SubjectDAO | ||||||
| import be.ugent.sel.studeez.domain.TaskDAO | import be.ugent.sel.studeez.domain.TaskDAO | ||||||
| import com.google.firebase.firestore.AggregateSource |  | ||||||
| import com.google.firebase.firestore.CollectionReference | import com.google.firebase.firestore.CollectionReference | ||||||
| import com.google.firebase.firestore.FirebaseFirestore | import com.google.firebase.firestore.FirebaseFirestore | ||||||
| import com.google.firebase.firestore.Query | import com.google.firebase.firestore.Query | ||||||
|  | @ -15,6 +16,7 @@ import kotlinx.coroutines.flow.Flow | ||||||
| import kotlinx.coroutines.flow.map | import kotlinx.coroutines.flow.map | ||||||
| import kotlinx.coroutines.tasks.await | import kotlinx.coroutines.tasks.await | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
|  | import kotlin.collections.count | ||||||
| 
 | 
 | ||||||
| class FireBaseSubjectDAO @Inject constructor( | class FireBaseSubjectDAO @Inject constructor( | ||||||
|     private val firestore: FirebaseFirestore, |     private val firestore: FirebaseFirestore, | ||||||
|  | @ -26,13 +28,6 @@ class FireBaseSubjectDAO @Inject constructor( | ||||||
|             .subjectNotArchived() |             .subjectNotArchived() | ||||||
|             .snapshots() |             .snapshots() | ||||||
|             .map { it.toObjects(Subject::class.java) } |             .map { it.toObjects(Subject::class.java) } | ||||||
|             .map { subjects -> |  | ||||||
|                 subjects.map { subject -> |  | ||||||
|                     subject.taskCount = getTaskCount(subject) |  | ||||||
|                     subject.taskCompletedCount = getCompletedTaskCount(subject) |  | ||||||
|                     subject |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override suspend fun getSubject(subjectId: String): Subject? { |     override suspend fun getSubject(subjectId: String): Subject? { | ||||||
|  | @ -51,23 +46,26 @@ class FireBaseSubjectDAO @Inject constructor( | ||||||
|         currentUserSubjectsCollection().document(newSubject.id).set(newSubject) |         currentUserSubjectsCollection().document(newSubject.id).set(newSubject) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override suspend fun getTaskCount(subject: Subject): Int { |     override suspend fun archiveSubject(subject: Subject) { | ||||||
|         return subjectTasksCollection(subject) |         currentUserSubjectsCollection().document(subject.id).update(SubjectDocument.archived, true) | ||||||
|  |         currentUserSubjectsCollection().document(subject.id) | ||||||
|  |             .collection(FireBaseCollections.TASK_COLLECTION) | ||||||
|             .taskNotArchived() |             .taskNotArchived() | ||||||
|             .count() |             .get().await() | ||||||
|             .get(AggregateSource.SERVER) |             .documents | ||||||
|             .await() |             .forEach { | ||||||
|             .count.toInt() |                 it.reference.update(TaskDocument.archived, true) | ||||||
|  |             } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override suspend fun getCompletedTaskCount(subject: Subject): Int { |     override fun getTaskCount(subject: Subject): Flow<Int> { | ||||||
|         return subjectTasksCollection(subject) |         return taskDAO.getTasks(subject) | ||||||
|             .taskNotArchived() |             .map(List<Task>::count) | ||||||
|             .taskNotCompleted() |     } | ||||||
|             .count() | 
 | ||||||
|             .get(AggregateSource.SERVER) |     override fun getCompletedTaskCount(subject: Subject): Flow<Int> { | ||||||
|             .await() |         return taskDAO.getTasks(subject) | ||||||
|             .count.toInt() |             .map { tasks -> tasks.count { it.completed && !it.archived } } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun getStudyTime(subject: Subject): Flow<Int> { |     override fun getStudyTime(subject: Subject): Flow<Int> { | ||||||
|  |  | ||||||
|  | @ -25,9 +25,9 @@ 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.subjects.SubjectRoute | import be.ugent.sel.studeez.screens.subjects.SubjectRoute | ||||||
| import be.ugent.sel.studeez.screens.tasks.TaskRoute |  | ||||||
| import be.ugent.sel.studeez.screens.subjects.form.SubjectCreateRoute | import be.ugent.sel.studeez.screens.subjects.form.SubjectCreateRoute | ||||||
| import be.ugent.sel.studeez.screens.subjects.form.SubjectEditRoute | import be.ugent.sel.studeez.screens.subjects.form.SubjectEditRoute | ||||||
|  | import be.ugent.sel.studeez.screens.tasks.TaskRoute | ||||||
| import be.ugent.sel.studeez.screens.tasks.form.TaskCreateRoute | import be.ugent.sel.studeez.screens.tasks.form.TaskCreateRoute | ||||||
| import be.ugent.sel.studeez.screens.tasks.form.TaskEditRoute | import be.ugent.sel.studeez.screens.tasks.form.TaskEditRoute | ||||||
| import be.ugent.sel.studeez.screens.timer_form.TimerAddRoute | import be.ugent.sel.studeez.screens.timer_form.TimerAddRoute | ||||||
|  | @ -51,6 +51,7 @@ fun StudeezNavGraph( | ||||||
|     val open: (String) -> Unit = { appState.navigate(it) } |     val open: (String) -> Unit = { appState.navigate(it) } | ||||||
|     val openAndPopUp: (String, String) -> Unit = |     val openAndPopUp: (String, String) -> Unit = | ||||||
|         { route, popUp -> appState.navigateAndPopUp(route, popUp) } |         { route, popUp -> appState.navigateAndPopUp(route, popUp) } | ||||||
|  |     val clearAndNavigate: (route: String) -> Unit = { route -> appState.clearAndNavigate(route) } | ||||||
| 
 | 
 | ||||||
|     val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp) |     val drawerActions: DrawerActions = getDrawerActions(drawerViewModel, open, openAndPopUp) | ||||||
|     val navigationBarActions: NavigationBarActions = |     val navigationBarActions: NavigationBarActions = | ||||||
|  | @ -200,7 +201,7 @@ fun StudeezNavGraph( | ||||||
| 
 | 
 | ||||||
|         composable(StudeezDestinations.SESSION_RECAP) { |         composable(StudeezDestinations.SESSION_RECAP) { | ||||||
|             SessionRecapRoute( |             SessionRecapRoute( | ||||||
|                 openAndPopUp = openAndPopUp, |                 clearAndNavigate = clearAndNavigate, | ||||||
|                 viewModel = hiltViewModel() |                 viewModel = hiltViewModel() | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,9 @@ | ||||||
| package be.ugent.sel.studeez.screens.session | package be.ugent.sel.studeez.screens.session | ||||||
| 
 | 
 | ||||||
|  | import android.content.Context | ||||||
| import android.media.MediaPlayer | import android.media.MediaPlayer | ||||||
|  | import android.media.RingtoneManager | ||||||
|  | import android.net.Uri | ||||||
| import kotlinx.coroutines.delay | import kotlinx.coroutines.delay | ||||||
| import javax.inject.Singleton | import javax.inject.Singleton | ||||||
| import kotlin.time.Duration.Companion.seconds | import kotlin.time.Duration.Companion.seconds | ||||||
|  | @ -10,9 +13,11 @@ object InvisibleSessionManager { | ||||||
|     private var viewModel: SessionViewModel? = null |     private var viewModel: SessionViewModel? = null | ||||||
|     private lateinit var mediaPlayer: MediaPlayer |     private lateinit var mediaPlayer: MediaPlayer | ||||||
| 
 | 
 | ||||||
|     fun setParameters(viewModel: SessionViewModel, mediaplayer: MediaPlayer) { |     fun setParameters(viewModel: SessionViewModel, context: Context) { | ||||||
|  |         val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) | ||||||
|  |         this.mediaPlayer = MediaPlayer.create(context, uri) | ||||||
|  |         this.mediaPlayer.isLooping = false | ||||||
|         this.viewModel = viewModel |         this.viewModel = viewModel | ||||||
|         this.mediaPlayer = mediaplayer |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     suspend fun updateTimer() { |     suspend fun updateTimer() { | ||||||
|  |  | ||||||
|  | @ -1,33 +1,24 @@ | ||||||
| package be.ugent.sel.studeez.screens.session | 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.runtime.Composable | ||||||
| import androidx.compose.ui.platform.LocalContext | import androidx.compose.ui.platform.LocalContext | ||||||
| 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.screens.session.sessionScreens.AbstractSessionScreen | import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreenComposable | ||||||
| import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreen |  | ||||||
| 
 | 
 | ||||||
| data class SessionActions( | data class SessionActions( | ||||||
|     val getTimer: () -> FunctionalTimer, |     val getTimer: () -> FunctionalTimer, | ||||||
|     val getTask: () -> String, |     val getTask: () -> String, | ||||||
|     val startMediaPlayer: () -> Unit, |  | ||||||
|     val releaseMediaPlayer: () -> Unit, |  | ||||||
|     val endSession: () -> Unit |     val endSession: () -> Unit | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| private fun getSessionActions( | private fun getSessionActions( | ||||||
|     viewModel: SessionViewModel, |     viewModel: SessionViewModel, | ||||||
|     openAndPopUp: (String, String) -> Unit, |     openAndPopUp: (String, String) -> Unit, | ||||||
|     mediaplayer: MediaPlayer, |  | ||||||
| ): SessionActions { | ): SessionActions { | ||||||
|     return SessionActions( |     return SessionActions( | ||||||
|         getTimer = viewModel::getTimer, |         getTimer = viewModel::getTimer, | ||||||
|         getTask = viewModel::getTask, |         getTask = viewModel::getTask, | ||||||
|         endSession = { viewModel.endSession(openAndPopUp) }, |         endSession = { viewModel.endSession(openAndPopUp) }, | ||||||
|         startMediaPlayer = mediaplayer::start, |  | ||||||
|         releaseMediaPlayer = mediaplayer::release, |  | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -37,20 +28,12 @@ fun SessionRoute( | ||||||
|     openAndPopUp: (String, String) -> Unit, |     openAndPopUp: (String, String) -> Unit, | ||||||
|     viewModel: SessionViewModel, |     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( |     InvisibleSessionManager.setParameters(viewModel = viewModel, context = LocalContext.current) | ||||||
|         viewModel = viewModel, |  | ||||||
|         mediaplayer = mediaplayer |  | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
|     val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen(mediaplayer)) |     val soundPlayer = SoundPlayer(LocalContext.current) | ||||||
|  |     val sessionActions = getSessionActions(viewModel, openAndPopUp) | ||||||
|  |     val sessionScreen = viewModel.getTimer().accept(GetSessionScreenComposable(soundPlayer, open, sessionActions)) | ||||||
| 
 | 
 | ||||||
|     sessionScreen( |     sessionScreen() | ||||||
|         open = open, |  | ||||||
|         sessionActions = getSessionActions(viewModel, openAndPopUp, mediaplayer) |  | ||||||
|     ) |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session | ||||||
|  | 
 | ||||||
|  | import android.content.Context | ||||||
|  | import android.media.MediaPlayer | ||||||
|  | import android.media.RingtoneManager | ||||||
|  | 
 | ||||||
|  | class SoundPlayer(private val context: Context) { | ||||||
|  | 
 | ||||||
|  |     var oldValue: Boolean = false | ||||||
|  |     var mediaPlayer: MediaPlayer = initPlayer() | ||||||
|  | 
 | ||||||
|  |     fun playOn(newValue: Boolean) { | ||||||
|  |         if (oldValue != newValue) { | ||||||
|  |             mediaPlayer.start() | ||||||
|  |             mediaPlayer.setOnCompletionListener { | ||||||
|  |                 mediaPlayer = initPlayer() | ||||||
|  |             } | ||||||
|  |             oldValue = newValue | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     private fun initPlayer(): MediaPlayer { | ||||||
|  |         return  MediaPlayer.create( | ||||||
|  |             context, | ||||||
|  |             RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,150 +0,0 @@ | ||||||
| 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.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.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.toString(), |  | ||||||
|                 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 |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|             MidSection() |  | ||||||
| 
 |  | ||||||
|             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 |  | ||||||
| 
 |  | ||||||
|     @Composable |  | ||||||
|     open fun MidSection() { |  | ||||||
|         // Default has no midsection, unless overwritten. |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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" }, {}, {}, {})) |  | ||||||
| } |  | ||||||
|  | @ -1,93 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.session.sessionScreens |  | ||||||
| 
 |  | ||||||
| import android.media.MediaPlayer |  | ||||||
| import androidx.compose.foundation.background |  | ||||||
| import androidx.compose.foundation.layout.* |  | ||||||
| import androidx.compose.foundation.shape.CircleShape |  | ||||||
| import androidx.compose.material.Text |  | ||||||
| import androidx.compose.runtime.Composable |  | ||||||
| import androidx.compose.ui.Alignment |  | ||||||
| import androidx.compose.ui.Modifier |  | ||||||
| import androidx.compose.ui.draw.clip |  | ||||||
| import androidx.compose.ui.graphics.Color |  | ||||||
| import androidx.compose.ui.text.font.FontWeight |  | ||||||
| import androidx.compose.ui.text.style.TextAlign |  | ||||||
| import androidx.compose.ui.tooling.preview.Preview |  | ||||||
| import androidx.compose.ui.unit.dp |  | ||||||
| import androidx.compose.ui.unit.sp |  | ||||||
| import be.ugent.sel.studeez.R |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer |  | ||||||
| import be.ugent.sel.studeez.resources |  | ||||||
| import be.ugent.sel.studeez.R.string as AppText |  | ||||||
| 
 |  | ||||||
| class BreakSessionScreen( |  | ||||||
|     private val funPomoDoroTimer: FunctionalPomodoroTimer, |  | ||||||
|     private var mediaplayer: MediaPlayer? |  | ||||||
| ): AbstractSessionScreen() { |  | ||||||
| 
 |  | ||||||
|     @Composable |  | ||||||
|     override fun MidSection() { |  | ||||||
|         Dots() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Composable |  | ||||||
|     fun Dots() { |  | ||||||
|         Row( |  | ||||||
|             modifier = Modifier.fillMaxWidth(), |  | ||||||
|             verticalAlignment = Alignment.CenterVertically, |  | ||||||
|             horizontalArrangement = Arrangement.Center, |  | ||||||
|         ) { |  | ||||||
|             repeat(funPomoDoroTimer.repeats - funPomoDoroTimer.breaksRemaining) { |  | ||||||
|                 Dot(color = Color.DarkGray) |  | ||||||
|             } |  | ||||||
|             if (!funPomoDoroTimer.isInBreak) Dot(Color.Green) else Dot(Color.DarkGray) |  | ||||||
|             repeat(funPomoDoroTimer.breaksRemaining - 1) { |  | ||||||
|                 Dot(color = Color.Gray) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Composable |  | ||||||
|     private fun Dot(color: Color) { |  | ||||||
|         Box(modifier = Modifier |  | ||||||
|             .padding(5.dp) |  | ||||||
|             .size(10.dp) |  | ||||||
|             .clip(CircleShape) |  | ||||||
|             .background(color)) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Composable |  | ||||||
|     override fun motivationString(): String { |  | ||||||
|         if (funPomoDoroTimer.isInBreak) { |  | ||||||
|             return resources().getString(AppText.state_take_a_break) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (funPomoDoroTimer.hasEnded()) { |  | ||||||
|             return resources().getString(AppText.state_done) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return resources().getString(AppText.state_focus) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun callMediaPlayer() { |  | ||||||
|         if (funPomoDoroTimer.hasEnded()) { |  | ||||||
|             mediaplayer?.let { it: MediaPlayer -> |  | ||||||
|                 it.setOnCompletionListener { |  | ||||||
|                     it.release() |  | ||||||
|                     mediaplayer = null |  | ||||||
|                 } |  | ||||||
|                 it.start() |  | ||||||
|             } |  | ||||||
|         } else if (funPomoDoroTimer.hasCurrentCountdownEnded()) { |  | ||||||
|             mediaplayer?.start() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Preview |  | ||||||
| @Composable |  | ||||||
| fun MidsectionPreview() { |  | ||||||
|     val funPomoDoroTimer = FunctionalPomodoroTimer(15, 60, 5) |  | ||||||
|     val breakSessionScreen = BreakSessionScreen(funPomoDoroTimer, MediaPlayer()) |  | ||||||
|     breakSessionScreen.MidSection() |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,79 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session.sessionScreens | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.background | ||||||
|  | import androidx.compose.foundation.layout.* | ||||||
|  | import androidx.compose.foundation.shape.CircleShape | ||||||
|  | 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.unit.dp | ||||||
|  | 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.screens.session.SessionActions | ||||||
|  | import be.ugent.sel.studeez.screens.session.SoundPlayer | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun BreakSessionScreenComposable( | ||||||
|  |     open: (String) -> Unit, | ||||||
|  |     sessionActions: SessionActions, | ||||||
|  |     pomodoroTimer: FunctionalPomodoroTimer, | ||||||
|  |     soundPlayer: SoundPlayer, | ||||||
|  | ) { | ||||||
|  |     SessionScreen( | ||||||
|  |         open = open, | ||||||
|  |         sessionActions = sessionActions, | ||||||
|  |         midSection = { Dots(pomodoroTimer = pomodoroTimer) }, | ||||||
|  |         callMediaPlayer = { soundPlayer.playOn(pomodoroTimer.hasCurrentCountdownEnded()) }, | ||||||
|  |         motivationString = { motivationString (pomodoroTimer = pomodoroTimer) } | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | private fun Dots(pomodoroTimer: FunctionalPomodoroTimer): Int { | ||||||
|  |     Row( | ||||||
|  |         modifier = Modifier.fillMaxWidth(), | ||||||
|  |         verticalAlignment = Alignment.CenterVertically, | ||||||
|  |         horizontalArrangement = Arrangement.Center, | ||||||
|  |     ) { | ||||||
|  |         if (pomodoroTimer.hasEnded()) { | ||||||
|  |             repeat(pomodoroTimer.repeats) { | ||||||
|  |                 Dot(Color.Green) | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             repeat(pomodoroTimer.repeats - pomodoroTimer.breaksRemaining - 1) { | ||||||
|  |                 Dot(color = Color.DarkGray) | ||||||
|  |             } | ||||||
|  |             if (!pomodoroTimer.isInBreak) Dot(Color.Green) else Dot(Color.DarkGray) | ||||||
|  |             repeat(pomodoroTimer.breaksRemaining) { | ||||||
|  |                 Dot(color = Color.Gray) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return pomodoroTimer.breaksRemaining | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | private fun Dot(color: Color) { | ||||||
|  |     Box(modifier = Modifier | ||||||
|  |         .padding(5.dp) | ||||||
|  |         .size(10.dp) | ||||||
|  |         .clip(CircleShape) | ||||||
|  |         .background(color)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | private fun motivationString(pomodoroTimer: FunctionalPomodoroTimer): String { | ||||||
|  |     if (pomodoroTimer.isInBreak) { | ||||||
|  |         return resources().getString(R.string.state_take_a_break) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (pomodoroTimer.hasEnded()) { | ||||||
|  |         return resources().getString(R.string.state_done) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return resources().getString(R.string.state_focus) | ||||||
|  | } | ||||||
|  | @ -1,35 +0,0 @@ | ||||||
| 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,32 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session.sessionScreens | ||||||
|  | 
 | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import be.ugent.sel.studeez.R | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
|  | import be.ugent.sel.studeez.screens.session.SessionActions | ||||||
|  | import be.ugent.sel.studeez.screens.session.SoundPlayer | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun CustomTimerSessionScreenComposable( | ||||||
|  |     open: (String) -> Unit, | ||||||
|  |     sessionActions: SessionActions, | ||||||
|  |     customTimer: FunctionalCustomTimer, | ||||||
|  |     soundPlayer: SoundPlayer | ||||||
|  | ) { | ||||||
|  |     SessionScreen( | ||||||
|  |         open = open, | ||||||
|  |         callMediaPlayer = { soundPlayer.playOn(customTimer.hasEnded()) }, | ||||||
|  |         sessionActions = sessionActions | ||||||
|  |     ) { | ||||||
|  |         motivationString(customTimer = customTimer) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | private fun motivationString(customTimer: FunctionalCustomTimer): String { | ||||||
|  |     if (customTimer.hasEnded()) { | ||||||
|  |         return resources().getString(R.string.state_done) | ||||||
|  |     } | ||||||
|  |     return resources().getString(R.string.state_focus) | ||||||
|  | } | ||||||
|  | @ -1,16 +0,0 @@ | ||||||
| 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,24 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session.sessionScreens | ||||||
|  | 
 | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import be.ugent.sel.studeez.R | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
|  | import be.ugent.sel.studeez.screens.session.SessionActions | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun EndlessTimerSessionScreenComposable( | ||||||
|  |     open: (String) -> Unit, | ||||||
|  |     sessionActions: SessionActions, | ||||||
|  | ) { | ||||||
|  |     SessionScreen( | ||||||
|  |         open = open, | ||||||
|  |         sessionActions = sessionActions | ||||||
|  |     ) { | ||||||
|  |         motivationString() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | private fun motivationString(): String { | ||||||
|  |     return resources().getString(R.string.state_focus) | ||||||
|  | } | ||||||
|  | @ -1,18 +0,0 @@ | ||||||
| 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,47 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session.sessionScreens | ||||||
|  | 
 | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | 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 | ||||||
|  | import be.ugent.sel.studeez.screens.session.SessionActions | ||||||
|  | import be.ugent.sel.studeez.screens.session.SoundPlayer | ||||||
|  | 
 | ||||||
|  | class GetSessionScreenComposable( | ||||||
|  |     private val soundPlayer: SoundPlayer, | ||||||
|  |     private val open: (String) -> Unit, | ||||||
|  |     private val sessionActions: SessionActions | ||||||
|  |     ) : | ||||||
|  |     FunctionalTimerVisitor<@Composable () -> Unit> { | ||||||
|  | 
 | ||||||
|  |     override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): @Composable () -> Unit { | ||||||
|  |         return { CustomTimerSessionScreenComposable( | ||||||
|  |                 open = open, | ||||||
|  |                 sessionActions = sessionActions, | ||||||
|  |                 soundPlayer = soundPlayer, | ||||||
|  |                 customTimer = functionalCustomTimer, | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): @Composable () -> Unit { | ||||||
|  |         return { | ||||||
|  |             EndlessTimerSessionScreenComposable( | ||||||
|  |                 open = open, | ||||||
|  |                 sessionActions = sessionActions, | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): @Composable () -> Unit { | ||||||
|  |         return { | ||||||
|  |             BreakSessionScreenComposable( | ||||||
|  |                 open = open, | ||||||
|  |                 sessionActions = sessionActions, | ||||||
|  |                 soundPlayer = soundPlayer, | ||||||
|  |                 pomodoroTimer = functionalPomodoroTimer | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,73 @@ | ||||||
|  | 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.Composable | ||||||
|  | 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.unit.dp | ||||||
|  | import androidx.compose.ui.unit.sp | ||||||
|  | import be.ugent.sel.studeez.screens.session.SessionActions | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun SessionScreen( | ||||||
|  |     open: (String) -> Unit, | ||||||
|  |     sessionActions: SessionActions, | ||||||
|  |     callMediaPlayer: () -> Unit = {}, | ||||||
|  |     midSection: @Composable () -> Int = {0}, | ||||||
|  |     motivationString: @Composable () -> String, | ||||||
|  | 
 | ||||||
|  | ) { | ||||||
|  |     Column( | ||||||
|  |         modifier = Modifier.padding(10.dp) | ||||||
|  |     ) { | ||||||
|  |         Timer( | ||||||
|  |             sessionActions = sessionActions, | ||||||
|  |             callMediaPlayer = callMediaPlayer, | ||||||
|  |             motivationString = motivationString, | ||||||
|  |             MidSection = midSection | ||||||
|  |         ) | ||||||
|  |         Box( | ||||||
|  |             contentAlignment = Alignment.Center, modifier = Modifier | ||||||
|  |                 .fillMaxWidth() | ||||||
|  |                 .padding(50.dp) | ||||||
|  |         ) { | ||||||
|  |             EndSessionButton(sessionActions = sessionActions) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun EndSessionButton(sessionActions: SessionActions) { | ||||||
|  |     TextButton( | ||||||
|  |         onClick = { | ||||||
|  |             sessionActions.endSession() | ||||||
|  |         }, | ||||||
|  |         modifier = Modifier | ||||||
|  |             .padding(horizontal = 20.dp) | ||||||
|  |             .border(1.dp, Color.Red, RoundedCornerShape(32.dp)) | ||||||
|  |             .background(Color.Transparent) | ||||||
|  |     ) { | ||||||
|  |         EndsessionText() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun EndsessionText() { | ||||||
|  |     Text( | ||||||
|  |         text = "End session", | ||||||
|  |         color = Color.Red, | ||||||
|  |         fontWeight = FontWeight.Bold, | ||||||
|  |         fontSize = 18.sp, | ||||||
|  |         modifier = Modifier.padding(1.dp) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,95 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.session.sessionScreens | ||||||
|  | 
 | ||||||
|  | import androidx.compose.foundation.background | ||||||
|  | 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.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.unit.dp | ||||||
|  | import androidx.compose.ui.unit.sp | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||||
|  | import be.ugent.sel.studeez.screens.session.SessionActions | ||||||
|  | import kotlinx.coroutines.delay | ||||||
|  | import kotlin.time.Duration.Companion.seconds | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun Timer( | ||||||
|  |     sessionActions: SessionActions, | ||||||
|  |     callMediaPlayer: () -> Unit, | ||||||
|  |     motivationString: @Composable () -> String, | ||||||
|  |     MidSection: @Composable () -> Int | ||||||
|  | ) { | ||||||
|  |     var tikker by remember { mutableStateOf(false) } | ||||||
|  |     LaunchedEffect(tikker) { | ||||||
|  |         delay(1.seconds) | ||||||
|  |         sessionActions.getTimer().tick() | ||||||
|  |         callMediaPlayer() | ||||||
|  |         tikker = !tikker | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     val hms = sessionActions.getTimer().getHoursMinutesSeconds() | ||||||
|  |     Column { | ||||||
|  | 
 | ||||||
|  |         TimerClock(hms) | ||||||
|  |         MotivationText(text = motivationString()) | ||||||
|  |         MidSection() | ||||||
|  | 
 | ||||||
|  |         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)) | ||||||
|  |             ) { | ||||||
|  |                 TaskText(taskName = sessionActions.getTask()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TimerClock(hms: HoursMinutesSeconds) { | ||||||
|  |     Text( | ||||||
|  |         text = hms.toString(), | ||||||
|  |         modifier = Modifier | ||||||
|  |             .fillMaxWidth() | ||||||
|  |             .padding(50.dp), | ||||||
|  |         textAlign = TextAlign.Center, | ||||||
|  |         fontWeight = FontWeight.Bold, | ||||||
|  |         fontSize = 40.sp, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun MotivationText(text: String) { | ||||||
|  |     Text( | ||||||
|  |         text = text, | ||||||
|  |         modifier = Modifier.fillMaxWidth(), | ||||||
|  |         textAlign = TextAlign.Center, | ||||||
|  |         fontWeight = FontWeight.Light, | ||||||
|  |         fontSize = 30.sp | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TaskText(taskName: String) { | ||||||
|  |     Text( | ||||||
|  |         text = taskName, | ||||||
|  |         color = Color.White, | ||||||
|  |         fontSize = 18.sp, | ||||||
|  |         fontWeight = FontWeight.Bold, | ||||||
|  |         modifier = Modifier.padding(vertical = 4.dp, horizontal = 20.dp) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -1,13 +1,24 @@ | ||||||
| package be.ugent.sel.studeez.screens.session_recap | package be.ugent.sel.studeez.screens.session_recap | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.compose.foundation.layout.* | ||||||
| import androidx.compose.material.ButtonDefaults | import androidx.compose.material.ButtonDefaults | ||||||
| import androidx.compose.material.Text | import androidx.compose.material.Text | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.mutableStateOf | ||||||
|  | import androidx.compose.runtime.remember | ||||||
|  | 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 | ||||||
|  | import androidx.compose.ui.res.painterResource | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import androidx.compose.ui.text.font.FontWeight | ||||||
|  | import androidx.compose.ui.text.style.TextAlign | ||||||
|  | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import androidx.compose.ui.unit.sp | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.composable.BasicButton | import be.ugent.sel.studeez.common.composable.BasicButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.ImageBackgroundButton | ||||||
| import be.ugent.sel.studeez.common.ext.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.SessionReport | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||||
|  | @ -21,24 +32,24 @@ data class SessionRecapActions( | ||||||
| 
 | 
 | ||||||
| fun getSessionRecapActions( | fun getSessionRecapActions( | ||||||
|     viewModel: SessionRecapViewModel, |     viewModel: SessionRecapViewModel, | ||||||
|     openAndPopUp: (String, String) -> Unit, |     clearAndNavigate: (String) -> Unit, | ||||||
| ): SessionRecapActions { | ): SessionRecapActions { | ||||||
|     return SessionRecapActions( |     return SessionRecapActions( | ||||||
|         viewModel::getSessionReport, |         viewModel::getSessionReport, | ||||||
|         {viewModel.saveSession(openAndPopUp)}, |         { viewModel.saveSession(clearAndNavigate) }, | ||||||
|         {viewModel.discardSession(openAndPopUp)} |         { viewModel.discardSession(clearAndNavigate) } | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun SessionRecapRoute( | fun SessionRecapRoute( | ||||||
|     openAndPopUp: (String, String) -> Unit, |     clearAndNavigate: (String) -> Unit, | ||||||
|     modifier: Modifier = Modifier, |     modifier: Modifier = Modifier, | ||||||
|     viewModel: SessionRecapViewModel, |     viewModel: SessionRecapViewModel, | ||||||
| ) { | ) { | ||||||
|     SessionRecapScreen( |     SessionRecapScreen( | ||||||
|         modifier = modifier, |         modifier = modifier, | ||||||
|         getSessionRecapActions(viewModel, openAndPopUp) |         getSessionRecapActions(viewModel, clearAndNavigate) | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -47,11 +58,62 @@ fun SessionRecapScreen(modifier: Modifier, sessionRecapActions: SessionRecapActi | ||||||
|     val sessionReport: SessionReport = sessionRecapActions.getSessionReport() |     val sessionReport: SessionReport = sessionRecapActions.getSessionReport() | ||||||
|     val studyTime: Int = sessionReport.studyTime |     val studyTime: Int = sessionReport.studyTime | ||||||
|     val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS() |     val hms: HoursMinutesSeconds = Time(studyTime).getAsHMS() | ||||||
|  |     val (background1, setBackground1) = remember { mutableStateOf(Color.Transparent) } | ||||||
|  |     val (background2, setBackground2) = remember { mutableStateOf(Color.Transparent) } | ||||||
|     Column( |     Column( | ||||||
|         modifier = modifier |         modifier = modifier | ||||||
|  |             .fillMaxWidth() | ||||||
|  |             .fillMaxHeight() | ||||||
|  |             .padding(16.dp), | ||||||
|  |         horizontalAlignment = Alignment.CenterHorizontally, | ||||||
|  |         verticalArrangement = Arrangement.SpaceBetween | ||||||
|     ) { |     ) { | ||||||
|         Text(text = "You studied: $hms") |         Text( | ||||||
|  |             text = stringResource(R.string.congrats, hms), | ||||||
|  |             modifier = Modifier | ||||||
|  |                 .fillMaxWidth(), | ||||||
|  |             textAlign = TextAlign.Center, | ||||||
|  |             fontWeight = FontWeight.Light, | ||||||
|  |             fontSize = 30.sp, | ||||||
| 
 | 
 | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         Column( | ||||||
|  |             modifier = Modifier.fillMaxWidth() | ||||||
|  |         ) { | ||||||
|  |             Text( | ||||||
|  |                 text = stringResource(R.string.how_did_it_go), | ||||||
|  |                 modifier = Modifier.fillMaxWidth(), | ||||||
|  |                 textAlign = TextAlign.Center, | ||||||
|  |                 fontWeight = FontWeight.Light, | ||||||
|  |                 fontSize = 30.sp | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |             Row( | ||||||
|  |                 horizontalArrangement = Arrangement.Center, | ||||||
|  |                 modifier = Modifier | ||||||
|  |                     .fillMaxWidth() | ||||||
|  |                     .align(Alignment.CenterHorizontally) | ||||||
|  |             ) { | ||||||
|  |                 ImageBackgroundButton( | ||||||
|  |                     paint = painterResource(id = R.drawable.mood_1), | ||||||
|  |                     str = stringResource(id = R.string.good), | ||||||
|  |                     background2 = background2, | ||||||
|  |                     setBackground1 = setBackground2, | ||||||
|  |                     setBackground2 = setBackground1 | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|  |                 ImageBackgroundButton( | ||||||
|  |                     paint = painterResource(id = R.drawable.mood_2), | ||||||
|  |                     str = stringResource(id = R.string.bad), | ||||||
|  |                     background2 = background1, | ||||||
|  |                     setBackground1 = setBackground1, | ||||||
|  |                     setBackground2 = setBackground2 | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Column { | ||||||
|             BasicButton( |             BasicButton( | ||||||
|                 R.string.save, Modifier.basicButton() |                 R.string.save, Modifier.basicButton() | ||||||
|             ) { |             ) { | ||||||
|  | @ -65,3 +127,19 @@ fun SessionRecapScreen(modifier: Modifier, sessionRecapActions: SessionRecapActi | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun SessionRecapScreenPreview() { | ||||||
|  |     SessionRecapScreen( | ||||||
|  |         modifier = Modifier, | ||||||
|  |         sessionRecapActions = SessionRecapActions( | ||||||
|  |             { SessionReport( | ||||||
|  |                 studyTime = 100, | ||||||
|  |             ) }, | ||||||
|  |             {}, | ||||||
|  |             {}, | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -24,15 +24,15 @@ class SessionRecapViewModel @Inject constructor( | ||||||
|         return selectedSessionReport() |         return selectedSessionReport() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun saveSession(open: (String, String) -> Unit) { |     fun saveSession(open: (String) -> Unit) { | ||||||
|         sessionDAO.saveSession(getSessionReport()) |         sessionDAO.saveSession(getSessionReport()) | ||||||
|         val newTask = |         val newTask = | ||||||
|             selectedTask().copy(time = selectedTask().time + selectedSessionReport().studyTime) |             selectedTask().copy(time = selectedTask().time + selectedSessionReport().studyTime) | ||||||
|         taskDAO.updateTask(newTask) |         taskDAO.updateTask(newTask) | ||||||
|         open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) |         open(StudeezDestinations.HOME_SCREEN) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun discardSession(open: (String, String) -> Unit) { |     fun discardSession(open: (String) -> Unit) { | ||||||
|         open(StudeezDestinations.HOME_SCREEN, StudeezDestinations.SESSION_RECAP) |         open(StudeezDestinations.HOME_SCREEN) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -36,6 +36,8 @@ fun SubjectRoute( | ||||||
|         navigationBarActions = navigationBarActions, |         navigationBarActions = navigationBarActions, | ||||||
|         onAddSubject = { viewModel.onAddSubject(open) }, |         onAddSubject = { viewModel.onAddSubject(open) }, | ||||||
|         onViewSubject = { viewModel.onViewSubject(it, open) }, |         onViewSubject = { viewModel.onViewSubject(it, open) }, | ||||||
|  |         getTaskCount = viewModel::getTaskCount, | ||||||
|  |         getCompletedTaskCount = viewModel::getCompletedTaskCount, | ||||||
|         getStudyTime = viewModel::getStudyTime, |         getStudyTime = viewModel::getStudyTime, | ||||||
|         uiState, |         uiState, | ||||||
|     ) |     ) | ||||||
|  | @ -47,6 +49,8 @@ fun SubjectScreen( | ||||||
|     navigationBarActions: NavigationBarActions, |     navigationBarActions: NavigationBarActions, | ||||||
|     onAddSubject: () -> Unit, |     onAddSubject: () -> Unit, | ||||||
|     onViewSubject: (Subject) -> Unit, |     onViewSubject: (Subject) -> Unit, | ||||||
|  |     getTaskCount: (Subject) -> Flow<Int>, | ||||||
|  |     getCompletedTaskCount: (Subject) -> Flow<Int>, | ||||||
|     getStudyTime: (Subject) -> Flow<Int>, |     getStudyTime: (Subject) -> Flow<Int>, | ||||||
|     uiState: SubjectUiState, |     uiState: SubjectUiState, | ||||||
| ) { | ) { | ||||||
|  | @ -76,6 +80,8 @@ fun SubjectScreen( | ||||||
|                             SubjectEntry( |                             SubjectEntry( | ||||||
|                                 subject = it, |                                 subject = it, | ||||||
|                                 onViewSubject = { onViewSubject(it) }, |                                 onViewSubject = { onViewSubject(it) }, | ||||||
|  |                                 getTaskCount = { getTaskCount(it) }, | ||||||
|  |                                 getCompletedTaskCount = { getCompletedTaskCount(it) }, | ||||||
|                                 getStudyTime = { getStudyTime(it) }, |                                 getStudyTime = { getStudyTime(it) }, | ||||||
|                             ) |                             ) | ||||||
|                         } |                         } | ||||||
|  | @ -94,13 +100,14 @@ fun SubjectScreenPreview() { | ||||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), |         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), | ||||||
|         onAddSubject = {}, |         onAddSubject = {}, | ||||||
|         onViewSubject = {}, |         onViewSubject = {}, | ||||||
|  |         getTaskCount = { flowOf() }, | ||||||
|  |         getCompletedTaskCount = { flowOf() }, | ||||||
|         getStudyTime = { flowOf() }, |         getStudyTime = { flowOf() }, | ||||||
|         uiState = SubjectUiState.Succes( |         uiState = SubjectUiState.Succes( | ||||||
|             listOf( |             listOf( | ||||||
|                 Subject( |                 Subject( | ||||||
|                     name = "Test Subject", |                     name = "Test Subject", | ||||||
|                     argb_color = 0xFFFFD200, |                     argb_color = 0xFFFFD200, | ||||||
|                     taskCount = 5, taskCompletedCount = 2, |  | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|  | @ -115,7 +122,9 @@ fun SubjectScreenLoadingPreview() { | ||||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), |         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}), | ||||||
|         onAddSubject = {}, |         onAddSubject = {}, | ||||||
|         onViewSubject = {}, |         onViewSubject = {}, | ||||||
|  |         getTaskCount = { flowOf() }, | ||||||
|  |         getCompletedTaskCount = { flowOf() }, | ||||||
|         getStudyTime = { flowOf() }, |         getStudyTime = { flowOf() }, | ||||||
|         uiState = SubjectUiState.Loading |         uiState = SubjectUiState.Loading, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  | @ -30,6 +30,14 @@ class SubjectViewModel @Inject constructor( | ||||||
|         open(StudeezDestinations.ADD_SUBJECT_FORM) |         open(StudeezDestinations.ADD_SUBJECT_FORM) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     fun getTaskCount(subject: Subject): Flow<Int> { | ||||||
|  |         return subjectDAO.getTaskCount(subject) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getCompletedTaskCount(subject: Subject): Flow<Int> { | ||||||
|  |         return subjectDAO.getCompletedTaskCount(subject) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fun getStudyTime(subject: Subject): Flow<Int> { |     fun getStudyTime(subject: Subject): Flow<Int> { | ||||||
|         return subjectDAO.getStudyTime(subject) |         return subjectDAO.getStudyTime(subject) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -2,20 +2,27 @@ package be.ugent.sel.studeez.screens.subjects.form | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.StringRes | import androidx.annotation.StringRes | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.compose.foundation.layout.Column | ||||||
| import androidx.compose.material.OutlinedTextField | import androidx.compose.foundation.shape.RoundedCornerShape | ||||||
|  | import androidx.compose.material.Button | ||||||
|  | import androidx.compose.material.ButtonDefaults | ||||||
| import androidx.compose.material.Text | import androidx.compose.material.Text | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
| import androidx.compose.runtime.getValue | import androidx.compose.runtime.getValue | ||||||
|  | import androidx.compose.runtime.rememberCoroutineScope | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.graphics.Color | import androidx.compose.ui.graphics.Color | ||||||
| import androidx.compose.ui.res.stringResource | import androidx.compose.ui.res.stringResource | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
| import be.ugent.sel.studeez.common.composable.BasicButton | import be.ugent.sel.studeez.common.composable.BasicButton | ||||||
| import be.ugent.sel.studeez.common.composable.DeleteButton | import be.ugent.sel.studeez.common.composable.DeleteButton | ||||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | import be.ugent.sel.studeez.common.composable.FormComposable | ||||||
|  | import be.ugent.sel.studeez.common.composable.LabelledInputField | ||||||
| import be.ugent.sel.studeez.common.ext.basicButton | import be.ugent.sel.studeez.common.ext.basicButton | ||||||
| import be.ugent.sel.studeez.common.ext.fieldModifier | import be.ugent.sel.studeez.common.ext.fieldModifier | ||||||
|  | import be.ugent.sel.studeez.common.ext.generateRandomArgb | ||||||
| import be.ugent.sel.studeez.resources | import be.ugent.sel.studeez.resources | ||||||
|  | import kotlinx.coroutines.launch | ||||||
| import be.ugent.sel.studeez.R.string as AppText | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
|  | @ -31,7 +38,7 @@ fun SubjectCreateRoute( | ||||||
|         uiState = uiState, |         uiState = uiState, | ||||||
|         onConfirm = { viewModel.onCreate(openAndPopUp) }, |         onConfirm = { viewModel.onCreate(openAndPopUp) }, | ||||||
|         onNameChange = viewModel::onNameChange, |         onNameChange = viewModel::onNameChange, | ||||||
|         onColorChange = {}, |         onColorChange = viewModel::onColorChange, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -42,19 +49,22 @@ fun SubjectEditRoute( | ||||||
|     viewModel: SubjectEditFormViewModel, |     viewModel: SubjectEditFormViewModel, | ||||||
| ) { | ) { | ||||||
|     val uiState by viewModel.uiState |     val uiState by viewModel.uiState | ||||||
|  |     val coroutineScope = rememberCoroutineScope() | ||||||
|     SubjectForm( |     SubjectForm( | ||||||
|         title = AppText.edit_subject, |         title = AppText.edit_subject, | ||||||
|         goBack = goBack, |         goBack = goBack, | ||||||
|         uiState = uiState, |         uiState = uiState, | ||||||
|         onConfirm = { viewModel.onEdit(openAndPopUp) }, |         onConfirm = { viewModel.onEdit(openAndPopUp) }, | ||||||
|         onNameChange = viewModel::onNameChange, |         onNameChange = viewModel::onNameChange, | ||||||
|         onColorChange = {}, |         onColorChange = viewModel::onColorChange, | ||||||
|     ) { |     ) { | ||||||
|         DeleteButton(text = AppText.delete_subject) { |         DeleteButton(text = AppText.delete_subject) { | ||||||
|  |             coroutineScope.launch { | ||||||
|                 viewModel.onDelete(openAndPopUp) |                 viewModel.onDelete(openAndPopUp) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| @Composable | @Composable | ||||||
| fun SubjectForm( | fun SubjectForm( | ||||||
|  | @ -63,21 +73,21 @@ fun SubjectForm( | ||||||
|     uiState: SubjectFormUiState, |     uiState: SubjectFormUiState, | ||||||
|     onConfirm: () -> Unit, |     onConfirm: () -> Unit, | ||||||
|     onNameChange: (String) -> Unit, |     onNameChange: (String) -> Unit, | ||||||
|     onColorChange: (Color) -> Unit, |     onColorChange: (Long) -> Unit, | ||||||
|     extraButton: @Composable () -> Unit = {}, |     extraButton: @Composable () -> Unit = {}, | ||||||
| ) { | ) { | ||||||
|     SecondaryScreenTemplate( |     FormComposable( | ||||||
|         title = resources().getString(title), |         title = resources().getString(title), | ||||||
|         popUp = goBack, |         popUp = goBack, | ||||||
|     ) { |     ) { | ||||||
|         Column { |         Column { | ||||||
|             OutlinedTextField( |             LabelledInputField( | ||||||
|                 singleLine = true, |                 singleLine = true, | ||||||
|                 value = uiState.name, |                 value = uiState.name, | ||||||
|                 onValueChange = onNameChange, |                 onNewValue = onNameChange, | ||||||
|                 placeholder = { Text(stringResource(id = AppText.name)) }, |                 label = AppText.name, | ||||||
|                 modifier = Modifier.fieldModifier(), |  | ||||||
|             ) |             ) | ||||||
|  |             ColorPicker(onColorChange, uiState) | ||||||
|             BasicButton( |             BasicButton( | ||||||
|                 text = AppText.confirm, |                 text = AppText.confirm, | ||||||
|                 modifier = Modifier.basicButton(), |                 modifier = Modifier.basicButton(), | ||||||
|  | @ -88,6 +98,24 @@ fun SubjectForm( | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @Composable | ||||||
|  | fun ColorPicker( | ||||||
|  |     onColorChange: (Long) -> Unit, | ||||||
|  |     uiState: SubjectFormUiState, | ||||||
|  | ) { | ||||||
|  |     Button( | ||||||
|  |         onClick = { onColorChange(Color.generateRandomArgb()) }, | ||||||
|  |         modifier = Modifier.fieldModifier(), | ||||||
|  |         colors = ButtonDefaults.buttonColors( | ||||||
|  |             backgroundColor = Color(uiState.color), | ||||||
|  |             contentColor = Color.White, | ||||||
|  |         ), | ||||||
|  |         shape = RoundedCornerShape(4.dp), | ||||||
|  |     ) { | ||||||
|  |         Text(text = stringResource(id = AppText.regenerate_color)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| @Preview | @Preview | ||||||
| @Composable | @Composable | ||||||
| fun AddSubjectFormPreview() { | fun AddSubjectFormPreview() { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,9 @@ | ||||||
| package be.ugent.sel.studeez.screens.subjects.form | package be.ugent.sel.studeez.screens.subjects.form | ||||||
| 
 | 
 | ||||||
|  | import androidx.compose.ui.graphics.Color | ||||||
|  | import be.ugent.sel.studeez.common.ext.generateRandomArgb | ||||||
|  | 
 | ||||||
| data class SubjectFormUiState( | data class SubjectFormUiState( | ||||||
|     val name: String = "", |     val name: String = "", | ||||||
|     val color: Long = 0xFFFFD200, |     val color: Long = Color.generateRandomArgb(), | ||||||
| ) | ) | ||||||
|  | @ -6,6 +6,7 @@ import be.ugent.sel.studeez.data.SelectedSubject | ||||||
| import be.ugent.sel.studeez.data.local.models.task.Subject | import be.ugent.sel.studeez.data.local.models.task.Subject | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
| import be.ugent.sel.studeez.domain.SubjectDAO | 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.navigation.StudeezDestinations | ||||||
| import be.ugent.sel.studeez.screens.StudeezViewModel | import be.ugent.sel.studeez.screens.StudeezViewModel | ||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | import dagger.hilt.android.lifecycle.HiltViewModel | ||||||
|  | @ -59,6 +60,7 @@ class SubjectCreateFormViewModel @Inject constructor( | ||||||
| @HiltViewModel | @HiltViewModel | ||||||
| class SubjectEditFormViewModel @Inject constructor( | class SubjectEditFormViewModel @Inject constructor( | ||||||
|     subjectDAO: SubjectDAO, |     subjectDAO: SubjectDAO, | ||||||
|  |     private val taskDAO: TaskDAO, | ||||||
|     selectedSubject: SelectedSubject, |     selectedSubject: SelectedSubject, | ||||||
|     logService: LogService, |     logService: LogService, | ||||||
| ) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) { | ) : SubjectFormViewModel(subjectDAO, selectedSubject, logService) { | ||||||
|  | @ -69,17 +71,19 @@ class SubjectEditFormViewModel @Inject constructor( | ||||||
|         ) |         ) | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     fun onDelete(openAndPopUp: (String, String) -> Unit) { |     suspend fun onDelete(openAndPopUp: (String, String) -> Unit) { | ||||||
|         subjectDAO.updateSubject(selectedSubject().copy(archived = true)) |         subjectDAO.archiveSubject(selectedSubject()) | ||||||
|         openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) |         openAndPopUp(StudeezDestinations.SUBJECT_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun onEdit(openAndPopUp: (String, String) -> Unit) { |     fun onEdit(openAndPopUp: (String, String) -> Unit) { | ||||||
|         val newSubject = selectedSubject().copy( |         selectedSubject.set( | ||||||
|  |             selectedSubject().copy( | ||||||
|                 name = name, |                 name = name, | ||||||
|                 argb_color = color, |                 argb_color = color, | ||||||
|             ) |             ) | ||||||
|         subjectDAO.updateSubject(newSubject) |         ) | ||||||
|  |         subjectDAO.updateSubject(selectedSubject()) | ||||||
|         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) |         openAndPopUp(StudeezDestinations.TASKS_SCREEN, StudeezDestinations.EDIT_SUBJECT_FORM) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -11,7 +11,7 @@ import androidx.compose.ui.res.stringResource | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import be.ugent.sel.studeez.common.composable.BasicButton | import be.ugent.sel.studeez.common.composable.BasicButton | ||||||
| import be.ugent.sel.studeez.common.composable.DeleteButton | import be.ugent.sel.studeez.common.composable.DeleteButton | ||||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | import be.ugent.sel.studeez.common.composable.FormComposable | ||||||
| import be.ugent.sel.studeez.common.ext.basicButton | import be.ugent.sel.studeez.common.ext.basicButton | ||||||
| import be.ugent.sel.studeez.common.ext.fieldModifier | import be.ugent.sel.studeez.common.ext.fieldModifier | ||||||
| import be.ugent.sel.studeez.resources | import be.ugent.sel.studeez.resources | ||||||
|  | @ -62,7 +62,7 @@ fun TaskForm( | ||||||
|     onNameChange: (String) -> Unit, |     onNameChange: (String) -> Unit, | ||||||
|     extraButton: @Composable () -> Unit = {} |     extraButton: @Composable () -> Unit = {} | ||||||
| ) { | ) { | ||||||
|     SecondaryScreenTemplate( |     FormComposable( | ||||||
|         title = resources().getString(title), |         title = resources().getString(title), | ||||||
|         popUp = goBack, |         popUp = goBack, | ||||||
|     ) { |     ) { | ||||||
|  |  | ||||||
|  | @ -1,55 +0,0 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_form |  | ||||||
| 
 |  | ||||||
| import androidx.annotation.StringRes |  | ||||||
| import androidx.compose.runtime.Composable |  | ||||||
| import androidx.compose.ui.res.stringResource |  | ||||||
| import androidx.compose.ui.tooling.preview.Preview |  | ||||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo |  | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo |  | ||||||
| import be.ugent.sel.studeez.R.string as AppText |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun TimerAddRoute( |  | ||||||
|     popUp: () -> Unit, |  | ||||||
|     viewModel: TimerFormViewModel |  | ||||||
| ) { |  | ||||||
|     TimerFormScreen(popUp = popUp, getTimerInfo = viewModel::getTimerInfo, AppText.add_timer) { |  | ||||||
|         viewModel.saveTimer(it, goBack = popUp) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun TimerEditRoute( |  | ||||||
|     popUp: () -> Unit, |  | ||||||
|     viewModel: TimerFormViewModel |  | ||||||
| ) { |  | ||||||
|     TimerFormScreen(popUp = popUp, getTimerInfo = viewModel::getTimerInfo, AppText.edit_timer) { |  | ||||||
|         viewModel.editTimer(it, goBack = popUp) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Composable |  | ||||||
| fun TimerFormScreen( |  | ||||||
|     popUp: () -> Unit, |  | ||||||
|     getTimerInfo: () -> TimerInfo, |  | ||||||
|     @StringRes label: Int, |  | ||||||
|     onConfirmClick: (TimerInfo) -> Unit |  | ||||||
| ) { |  | ||||||
|     val timerFormScreen = getTimerInfo().accept(GetTimerFormScreen()) |  | ||||||
| 
 |  | ||||||
|     SecondaryScreenTemplate(title = stringResource(id = label), popUp = popUp) { |  | ||||||
|         timerFormScreen(onConfirmClick) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @Preview |  | ||||||
| @Composable |  | ||||||
| fun AddTimerPreview() { |  | ||||||
|     TimerFormScreen( |  | ||||||
|         popUp = {  }, |  | ||||||
|         getTimerInfo = { PomodoroTimerInfo("", "", 0, 0, 0) }, |  | ||||||
|         label = AppText.add_timer, |  | ||||||
|         onConfirmClick = {} |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,68 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.timer_form | ||||||
|  | 
 | ||||||
|  | import androidx.annotation.StringRes | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import be.ugent.sel.studeez.common.composable.DeleteButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.FormComposable | ||||||
|  | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TimerAddRoute( | ||||||
|  |     popUp: () -> Unit, | ||||||
|  |     viewModel: TimerFormViewModel | ||||||
|  | ) { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     TimerFormScreen( | ||||||
|  |         popUp = popUp, | ||||||
|  |         getTimerInfo = viewModel::getTimerInfo, | ||||||
|  |         extraButton= { }, | ||||||
|  |         AppText.add_timer | ||||||
|  |     ) { | ||||||
|  |         viewModel.saveTimer(it, goBack = {popUp(); popUp()}) | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TimerEditRoute( | ||||||
|  |     popUp: () -> Unit, | ||||||
|  |     viewModel: TimerFormViewModel | ||||||
|  | ) { | ||||||
|  | 
 | ||||||
|  |     @Composable | ||||||
|  |     fun deleteButton() { | ||||||
|  |         DeleteButton(text = AppText.delete_timer) { | ||||||
|  |             viewModel.deleteTimer(viewModel.getTimerInfo(), popUp) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     TimerFormScreen( | ||||||
|  |         popUp = popUp, | ||||||
|  |         getTimerInfo = viewModel::getTimerInfo, | ||||||
|  |         extraButton= { deleteButton() }, | ||||||
|  |         AppText.edit_timer | ||||||
|  |     ) { | ||||||
|  |         viewModel.editTimer(it, goBack = popUp) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun TimerFormScreen( | ||||||
|  |     popUp: () -> Unit, | ||||||
|  |     getTimerInfo: () -> TimerInfo, | ||||||
|  |     extraButton: @Composable () -> Unit, | ||||||
|  |     @StringRes label: Int, | ||||||
|  |     onConfirmClick: (TimerInfo) -> Unit | ||||||
|  | ) { | ||||||
|  |     val timerFormScreen = getTimerInfo().accept(GetTimerFormScreen()) | ||||||
|  | 
 | ||||||
|  |     FormComposable( | ||||||
|  |         title = stringResource(id = label), | ||||||
|  |         popUp = popUp | ||||||
|  |     ) { | ||||||
|  |         timerFormScreen(onConfirmClick, extraButton) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -23,6 +23,11 @@ class TimerFormViewModel @Inject constructor( | ||||||
|         goBack() |         goBack() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     fun deleteTimer(timerInfo: TimerInfo, goBack: () -> Unit) { | ||||||
|  |         timerDAO.deleteTimer(timerInfo) | ||||||
|  |         goBack() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) { |     fun saveTimer(timerInfo: TimerInfo, goBack: () -> Unit) { | ||||||
|         timerDAO.saveTimer(timerInfo) |         timerDAO.saveTimer(timerInfo) | ||||||
|         goBack() |         goBack() | ||||||
|  |  | ||||||
|  | @ -1,67 +1,82 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_form.form_screens | package be.ugent.sel.studeez.screens.timer_form.form_screens | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.Arrangement |  | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.compose.foundation.layout.Column | ||||||
| import androidx.compose.foundation.layout.fillMaxHeight | import androidx.compose.runtime.* | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth |  | ||||||
| import androidx.compose.foundation.rememberScrollState |  | ||||||
| import androidx.compose.foundation.verticalScroll |  | ||||||
| import androidx.compose.runtime.Composable |  | ||||||
| import androidx.compose.runtime.getValue |  | ||||||
| import androidx.compose.runtime.mutableStateOf |  | ||||||
| import androidx.compose.runtime.remember |  | ||||||
| import androidx.compose.runtime.setValue |  | ||||||
| import androidx.compose.ui.Alignment |  | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.text.input.KeyboardType | ||||||
| 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.LabelledInputField | import be.ugent.sel.studeez.common.composable.LabeledErrorTextField | ||||||
| import be.ugent.sel.studeez.common.ext.basicButton | import be.ugent.sel.studeez.common.ext.basicButton | ||||||
|  | import be.ugent.sel.studeez.common.snackbar.SnackbarManager | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||||
| import be.ugent.sel.studeez.R.string as AppText | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) { | abstract class AbstractTimerFormScreen(private val timerInfo: TimerInfo) { | ||||||
| 
 | 
 | ||||||
|  |     protected val valids = mutableMapOf( | ||||||
|  |         "name" to mutableStateOf(textPredicate(timerInfo.name)), | ||||||
|  |         "description" to mutableStateOf(textPredicate(timerInfo.description)) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     protected val firsts = mutableMapOf( | ||||||
|  |         "name" to mutableStateOf(true), | ||||||
|  |         "description" to mutableStateOf(true) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     @Composable |     @Composable | ||||||
|     operator fun invoke(onSaveClick: (TimerInfo) -> Unit) { |     operator fun invoke( | ||||||
| 
 |         onSaveClick: (TimerInfo) -> Unit, | ||||||
|         var name by remember { mutableStateOf(timerInfo.name) } |         extraButton: @Composable () -> Unit = {}, | ||||||
|         var description by remember { mutableStateOf(timerInfo.description) } |  | ||||||
| 
 |  | ||||||
|         // This shall rerun whenever name and description change |  | ||||||
|         timerInfo.name = name |  | ||||||
|         timerInfo.description = description |  | ||||||
| 
 |  | ||||||
|         Column( |  | ||||||
|             verticalArrangement = Arrangement.SpaceBetween, |  | ||||||
|             modifier = Modifier.fillMaxHeight().verticalScroll(rememberScrollState()), |  | ||||||
|         ) { |  | ||||||
|             Column( |  | ||||||
|                 modifier = Modifier.fillMaxWidth(), |  | ||||||
|                 horizontalAlignment = Alignment.CenterHorizontally |  | ||||||
|     ) { |     ) { | ||||||
| 
 | 
 | ||||||
|  |         Column { | ||||||
|  | 
 | ||||||
|             // Fields that every timer shares (ommited id) |             // Fields that every timer shares (ommited id) | ||||||
|                 LabelledInputField( |             LabeledErrorTextField( | ||||||
|                     value = name, |                 initialValue = timerInfo.name, | ||||||
|                     onNewValue = { name = it }, |                 label = R.string.name, | ||||||
|                     label = R.string.name |                 errorText = AppText.name_error, | ||||||
|                 ) |                 isValid = valids.getValue("name"), | ||||||
|  |                 isFirst = firsts.getValue("name"), | ||||||
|  |                 keyboardType = KeyboardType.Text, | ||||||
|  |                 predicate = { it.isNotBlank() } | ||||||
|  |             ) { correctName -> | ||||||
|  |                 timerInfo.name = correctName | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|                 LabelledInputField( |             LabeledErrorTextField( | ||||||
|                     value = description, |                 initialValue = timerInfo.description, | ||||||
|                     onNewValue = { description = it }, |                 label = R.string.description, | ||||||
|                     label = AppText.description, |                 errorText = AppText.description_error, | ||||||
|                     singleLine = false |                 isValid = valids.getValue("description"), | ||||||
|                 ) |                 isFirst = firsts.getValue("description"), | ||||||
|  |                 singleLine = false, | ||||||
|  |                 keyboardType = KeyboardType.Text, | ||||||
|  |                 predicate = { textPredicate(it) } | ||||||
|  |             ) { correctName -> | ||||||
|  |                 timerInfo.description = correctName | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             ExtraFields() |             ExtraFields() | ||||||
| 
 | 
 | ||||||
|             } |  | ||||||
|             BasicButton(R.string.save, Modifier.basicButton()) { |             BasicButton(R.string.save, Modifier.basicButton()) { | ||||||
|  |                 if (valids.all { it.component2().value }) { // All fields are valid | ||||||
|                     onSaveClick(timerInfo) |                     onSaveClick(timerInfo) | ||||||
|  |                 } else { | ||||||
|  |                     firsts.map { | ||||||
|  |                         it.component2().value = false | ||||||
|  |                     } // dont mask error because its not been filled out yet | ||||||
|  |                     SnackbarManager.showMessage(AppText.fill_out_error) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             extraButton() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun textPredicate(text: String): Boolean { | ||||||
|  |         return text.isNotBlank() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Composable |     @Composable | ||||||
|  |  | ||||||
|  | @ -15,6 +15,8 @@ class BreakTimerFormScreen( | ||||||
|     private val breakTimerInfo: PomodoroTimerInfo |     private val breakTimerInfo: PomodoroTimerInfo | ||||||
| ): AbstractTimerFormScreen(breakTimerInfo) { | ): AbstractTimerFormScreen(breakTimerInfo) { | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     @Composable |     @Composable | ||||||
|     override fun ExtraFields() { |     override fun ExtraFields() { | ||||||
|         // If the user presses the OK button on the timepicker, the time in the button should change |         // If the user presses the OK button on the timepicker, the time in the button should change | ||||||
|  | @ -26,12 +28,17 @@ class BreakTimerFormScreen( | ||||||
|             breakTimerInfo.breakTime = newTime |             breakTimerInfo.breakTime = newTime | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         valids["repeats"] = remember {mutableStateOf(true)} | ||||||
|  |         firsts["repeats"] = remember { mutableStateOf(true) } | ||||||
|  | 
 | ||||||
|         LabeledErrorTextField( |         LabeledErrorTextField( | ||||||
|             initialValue = breakTimerInfo.repeats.toString(), |             initialValue = breakTimerInfo.repeats.toString(), | ||||||
|             label = R.string.repeats, |             label = R.string.repeats, | ||||||
|             errorText = AppText.repeats_error, |             errorText = AppText.repeats_error, | ||||||
|  |             isValid = valids.getValue("repeats"), | ||||||
|  |             isFirst = firsts.getValue("repeats"), | ||||||
|             keyboardType = KeyboardType.Decimal, |             keyboardType = KeyboardType.Decimal, | ||||||
|             predicate = { it.matches(Regex("[1-9]+\\d*")) } |             predicate = { isNumber(it) } | ||||||
|         ) { correctlyTypedInt -> |         ) { correctlyTypedInt -> | ||||||
|             breakTimerInfo.repeats = correctlyTypedInt.toInt() |             breakTimerInfo.repeats = correctlyTypedInt.toInt() | ||||||
|         } |         } | ||||||
|  | @ -39,6 +46,10 @@ class BreakTimerFormScreen( | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | fun isNumber(text: String): Boolean { | ||||||
|  |     return text.matches(Regex("[1-9]+\\d*")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| @Preview | @Preview | ||||||
| @Composable | @Composable | ||||||
| fun BreakEditScreenPreview() { | fun BreakEditScreenPreview() { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.screens.timer_form.timer_type_select | package be.ugent.sel.studeez.screens.timer_form.timer_type_select | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.compose.foundation.layout.* | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth |  | ||||||
| import androidx.compose.material.Button | import androidx.compose.material.Button | ||||||
| import androidx.compose.material.Text | import androidx.compose.material.Text | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
|  | @ -9,6 +8,7 @@ import androidx.compose.ui.Alignment | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| 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.hilt.navigation.compose.hiltViewModel | import androidx.hilt.navigation.compose.hiltViewModel | ||||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||||
| import be.ugent.sel.studeez.data.local.models.timer_info.* | import be.ugent.sel.studeez.data.local.models.timer_info.* | ||||||
|  | @ -38,7 +38,10 @@ fun TimerTypeSelectScreen( | ||||||
|         ) { |         ) { | ||||||
|             TimerType.values().forEach { timerType -> |             TimerType.values().forEach { timerType -> | ||||||
|                 val default: TimerInfo = defaultTimerInfo.getValue(timerType) |                 val default: TimerInfo = defaultTimerInfo.getValue(timerType) | ||||||
|                 Button(onClick = { viewModel.onTimerTypeChosen(default, open) }) { |                 Button( | ||||||
|  |                     onClick = { viewModel.onTimerTypeChosen(default, open) }, | ||||||
|  |                     modifier = Modifier.fillMaxWidth().padding(5.dp) | ||||||
|  |                 ) { | ||||||
|                     Text(text = timerType.name) |                     Text(text = timerType.name) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/mood_1.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/drawable/mood_1.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | <vector android:height="75dp" android:tint="#999999" | ||||||
|  |     android:viewportHeight="24" android:viewportWidth="24" | ||||||
|  |     android:width="75dp" xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <path android:fillColor="@android:color/white" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,17.5c2.33,0 4.31,-1.46 5.11,-3.5L6.89,14c0.8,2.04 2.78,3.5 5.11,3.5z"/> | ||||||
|  | </vector> | ||||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/mood_2.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/drawable/mood_2.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | <vector android:height="75dp" android:tint="#999999" | ||||||
|  |     android:viewportHeight="24" android:viewportWidth="24" | ||||||
|  |     android:width="75dp" xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <path android:fillColor="@android:color/white" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,14c-2.33,0 -4.31,1.46 -5.11,3.5h10.22c-0.8,-2.04 -2.78,-3.5 -5.11,-3.5z"/> | ||||||
|  | </vector> | ||||||
|  | @ -46,6 +46,7 @@ | ||||||
|     <string name="delete_subject">Delete Subject</string> |     <string name="delete_subject">Delete Subject</string> | ||||||
|     <string name="delete_task">Delete Task</string> |     <string name="delete_task">Delete Task</string> | ||||||
|     <string name="view_tasks">View</string> |     <string name="view_tasks">View</string> | ||||||
|  |     <string name="regenerate_color">Regenerate Color</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. --> | ||||||
|  | @ -69,8 +70,15 @@ | ||||||
| 
 | 
 | ||||||
|     <!-- Timers --> |     <!-- Timers --> | ||||||
|     <string name="timers">Timers</string> |     <string name="timers">Timers</string> | ||||||
|  |     <string name="delete_timer">Delete Timer</string> | ||||||
|     <string name="edit">Edit</string> |     <string name="edit">Edit</string> | ||||||
|     <string name="add_timer">Add timer</string> |     <string name="add_timer">Add timer</string> | ||||||
|  | 
 | ||||||
|  |     <string name="name_error">Name should not be blank</string> | ||||||
|  |     <string name="description_error">Description should not be blank</string> | ||||||
|  |     <string name="fill_out_error">Fill out all the fields correctly!</string> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     <string name="pick_time">Select time</string> |     <string name="pick_time">Select time</string> | ||||||
|     <string name="state_focus">Focus!</string> |     <string name="state_focus">Focus!</string> | ||||||
|     <plurals name="state_focus_remaining"> |     <plurals name="state_focus_remaining"> | ||||||
|  | @ -149,4 +157,11 @@ | ||||||
|     <string name="breakTime">Break Time</string> |     <string name="breakTime">Break Time</string> | ||||||
|     <string name="repeats">Number of Repeats</string> |     <string name="repeats">Number of Repeats</string> | ||||||
| 
 | 
 | ||||||
|  |     <!-- Session Recap --> | ||||||
|  |     <string name="congrats">"Congratulations! You studied: %s"</string> | ||||||
|  |     <string name="how_did_it_go">How did it go?</string> | ||||||
|  |     <string name="good">Good</string> | ||||||
|  |     <string name="bad">Bad</string> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| </resources> | </resources> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| package be.ugent.sel.studeez.timer_functional | package be.ugent.sel.studeez.timer_functional | ||||||
| 
 | 
 | ||||||
| import android.media.MediaPlayer |  | ||||||
| import be.ugent.sel.studeez.data.SelectedSessionReport | import be.ugent.sel.studeez.data.SelectedSessionReport | ||||||
| import be.ugent.sel.studeez.data.SelectedTask | import be.ugent.sel.studeez.data.SelectedTask | ||||||
| import be.ugent.sel.studeez.data.SelectedTimer | import be.ugent.sel.studeez.data.SelectedTimer | ||||||
|  | @ -22,13 +21,12 @@ import org.mockito.kotlin.mock | ||||||
| class InvisibleSessionManagerTest { | class InvisibleSessionManagerTest { | ||||||
|     private var selectedTimer: SelectedTimer = SelectedTimer() |     private var selectedTimer: SelectedTimer = SelectedTimer() | ||||||
|     private lateinit var viewModel: SessionViewModel |     private lateinit var viewModel: SessionViewModel | ||||||
|     private var mediaPlayer: MediaPlayer = mock() |  | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun InvisibleEndlessTimerTest() = runTest { |     fun InvisibleEndlessTimerTest() = runTest { | ||||||
|         selectedTimer.set(FunctionalEndlessTimer()) |         selectedTimer.set(FunctionalEndlessTimer()) | ||||||
|         viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), SelectedTask(), LogServiceImpl()) |         viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), SelectedTask(), LogServiceImpl()) | ||||||
|         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) |         InvisibleSessionManager.setParameters(viewModel, mock()) | ||||||
| 
 | 
 | ||||||
|         val test = launch { |         val test = launch { | ||||||
|             InvisibleSessionManager.updateTimer() |             InvisibleSessionManager.updateTimer() | ||||||
|  | @ -50,7 +48,7 @@ class InvisibleSessionManagerTest { | ||||||
|         val repeats = 1 |         val repeats = 1 | ||||||
|         selectedTimer.set(FunctionalPomodoroTimer(studyTime, breakTime, repeats)) |         selectedTimer.set(FunctionalPomodoroTimer(studyTime, breakTime, repeats)) | ||||||
|         viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), SelectedTask(), LogServiceImpl()) |         viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), SelectedTask(), LogServiceImpl()) | ||||||
|         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) |         InvisibleSessionManager.setParameters(viewModel, mock()) | ||||||
| 
 | 
 | ||||||
|         val test = launch { |         val test = launch { | ||||||
|             InvisibleSessionManager.updateTimer() |             InvisibleSessionManager.updateTimer() | ||||||
|  | @ -83,7 +81,7 @@ class InvisibleSessionManagerTest { | ||||||
|     fun InvisibleCustomTimerTest() = runTest { |     fun InvisibleCustomTimerTest() = runTest { | ||||||
|         selectedTimer.set(FunctionalCustomTimer(5)) |         selectedTimer.set(FunctionalCustomTimer(5)) | ||||||
|         viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), SelectedTask(), LogServiceImpl()) |         viewModel = SessionViewModel(selectedTimer, SelectedSessionReport(), SelectedTask(), LogServiceImpl()) | ||||||
|         InvisibleSessionManager.setParameters(viewModel, mediaPlayer) |         InvisibleSessionManager.setParameters(viewModel, mock()) | ||||||
| 
 | 
 | ||||||
|         val test = launch { |         val test = launch { | ||||||
|             InvisibleSessionManager.updateTimer() |             InvisibleSessionManager.updateTimer() | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Rune Dyselinck
						Rune Dyselinck