commit
						ee7914d0c9
					
				
					 23 changed files with 250 additions and 18 deletions
				
			
		|  | @ -2,7 +2,6 @@ package be.ugent.sel.studeez.common.composable | |||
| 
 | ||||
| import androidx.annotation.StringRes | ||||
| import androidx.compose.foundation.BorderStroke | ||||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||
| import androidx.compose.material.* | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
|  | @ -13,6 +12,7 @@ import androidx.compose.ui.unit.sp | |||
| import be.ugent.sel.studeez.R | ||||
| import be.ugent.sel.studeez.common.ext.basicButton | ||||
| import be.ugent.sel.studeez.common.ext.card | ||||
| import be.ugent.sel.studeez.common.ext.defaultButtonShape | ||||
| 
 | ||||
| @Composable | ||||
| fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { | ||||
|  | @ -30,7 +30,7 @@ fun BasicButton( | |||
|     Button( | ||||
|         onClick = onClick, | ||||
|         modifier = modifier, | ||||
|         shape = RoundedCornerShape(20.dp), | ||||
|         shape = defaultButtonShape(), | ||||
|         colors = colors, | ||||
|         border = border, | ||||
|     ) { | ||||
|  | @ -47,6 +47,25 @@ fun BasicButtonPreview() { | |||
|     BasicButton(text = R.string.add_timer, modifier = Modifier.basicButton()) {} | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
| fun NotInternationalisedButton( | ||||
|     text: String, | ||||
|     modifier: Modifier = Modifier, | ||||
|     colors: ButtonColors = ButtonDefaults.buttonColors(), | ||||
|     border: BorderStroke? = null, | ||||
|     onClick: () -> Unit | ||||
| ) { | ||||
|     Button( | ||||
|         onClick = onClick, | ||||
|         modifier = modifier, | ||||
|         shape = defaultButtonShape(), | ||||
|         colors = colors, | ||||
|         border = border | ||||
|     ) { | ||||
|         Text(text = text) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
| fun StealthButton( | ||||
|     @StringRes text: Int, | ||||
|  |  | |||
|  | @ -41,10 +41,12 @@ fun BasicField( | |||
| fun LabelledInputField( | ||||
|     value: String, | ||||
|     onNewValue: (String) -> Unit, | ||||
|     @StringRes label: Int | ||||
|     @StringRes label: Int, | ||||
|     singleLine: Boolean = false | ||||
| ) { | ||||
|     OutlinedTextField( | ||||
|         value = value, | ||||
|         singleLine = singleLine, | ||||
|         onValueChange = onNewValue, | ||||
|         label = { Text(text = stringResource(id = label)) }, | ||||
|         modifier = Modifier.fieldModifier() | ||||
|  |  | |||
|  | @ -0,0 +1,48 @@ | |||
| package be.ugent.sel.studeez.common.composable | ||||
| 
 | ||||
| import android.app.TimePickerDialog | ||||
| import android.app.TimePickerDialog.OnTimeSetListener | ||||
| import android.content.Context | ||||
| import androidx.compose.foundation.BorderStroke | ||||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||
| import androidx.compose.material.Button | ||||
| import androidx.compose.material.ButtonColors | ||||
| import androidx.compose.material.ButtonDefaults | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| import androidx.compose.ui.unit.dp | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||
| import java.util.* | ||||
| 
 | ||||
| @Composable | ||||
| fun TimePickerButton( | ||||
|     hoursMinutesSeconds: HoursMinutesSeconds, | ||||
|     modifier: Modifier = Modifier, | ||||
|     colors: ButtonColors = ButtonDefaults.buttonColors(), | ||||
|     border: BorderStroke? = null, | ||||
|     onTimeSetListener: OnTimeSetListener | ||||
| ) { | ||||
|     val context = LocalContext.current | ||||
|     Button( | ||||
|         onClick = { pickDuration(context, onTimeSetListener) }, | ||||
|         modifier = modifier, | ||||
|         shape = RoundedCornerShape(20.dp), | ||||
|         colors = colors, | ||||
|         border = border | ||||
|     ) { | ||||
|         Text(text = hoursMinutesSeconds.toString()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| private fun pickDuration(context: Context, listener: OnTimeSetListener) { | ||||
|     val timePickerDialog = TimePickerDialog( | ||||
|         context, | ||||
|         listener, | ||||
|         0, | ||||
|         0, | ||||
|         true | ||||
|     ) | ||||
|     timePickerDialog.show() | ||||
| } | ||||
|  | @ -0,0 +1,8 @@ | |||
| package be.ugent.sel.studeez.common.ext | ||||
| 
 | ||||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||
| import androidx.compose.ui.unit.dp | ||||
| 
 | ||||
| fun defaultButtonShape(): RoundedCornerShape { | ||||
|     return RoundedCornerShape(20.dp) | ||||
| } | ||||
|  | @ -0,0 +1,4 @@ | |||
| package be.ugent.sel.studeez.data | ||||
| 
 | ||||
| class EditTimerState { | ||||
| } | ||||
|  | @ -1,4 +1,15 @@ | |||
| package be.ugent.sel.studeez.data.local.models.timer_functional | ||||
| 
 | ||||
| data class HoursMinutesSeconds(val hours: String, val minutes: String, val seconds: String | ||||
| ) | ||||
| data class HoursMinutesSeconds(val hours: Int, val minutes: Int, val seconds: Int) { | ||||
| 
 | ||||
|     fun getTotalSeconds(): Int { | ||||
|         return hours * 60 * 60 + minutes * 60 + seconds | ||||
|     } | ||||
| 
 | ||||
|     override fun toString(): String { | ||||
|         val hoursString = hours.toString().padStart(2, '0') | ||||
|         val minutesString = minutes.toString().padStart(2, '0') | ||||
|         val secondsString = seconds.toString().padStart(2, '0') | ||||
|         return "$hoursString : $minutesString : $secondsString" | ||||
|     } | ||||
| } | ||||
|  | @ -17,11 +17,7 @@ class Time(initialTime: Int) { | |||
|         val minutes: Int = (time / (60)) % 60 | ||||
|         val seconds: Int = time % 60 | ||||
| 
 | ||||
|         return HoursMinutesSeconds( | ||||
|             hours.toString().padStart(2, '0'), | ||||
|             minutes.toString().padStart(2, '0'), | ||||
|             seconds.toString().padStart(2, '0') | ||||
|         ) | ||||
|         return HoursMinutesSeconds(hours, minutes, seconds) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -6,11 +6,10 @@ import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | |||
| class CustomTimerInfo( | ||||
|     name: String, | ||||
|     description: String, | ||||
|     private val studyTime: Int, | ||||
|     var studyTime: Int, | ||||
|     id: String = "" | ||||
| ):  TimerInfo(id, name, description) { | ||||
| 
 | ||||
| 
 | ||||
|     override fun getFunctionalTimer(): FunctionalTimer { | ||||
|         return FunctionalCustomTimer(studyTime) | ||||
|     } | ||||
|  | @ -24,4 +23,8 @@ class CustomTimerInfo( | |||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     override fun <T> accept(visitor: TimerInfoVisitor<T>): T { | ||||
|         return visitor.visitCustomTimerInfo(this) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -22,4 +22,8 @@ class EndlessTimerInfo( | |||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     override fun <T> accept(visitor: TimerInfoVisitor<T>): T { | ||||
|         return visitor.visitEndlessTimerInfo(this) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -2,13 +2,14 @@ package be.ugent.sel.studeez.data.local.models.timer_info | |||
| 
 | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimerVisitor | ||||
| 
 | ||||
| class PomodoroTimerInfo( | ||||
|     name: String, | ||||
|     description: String, | ||||
|     private val studyTime: Int, | ||||
|     private val breakTime: Int, | ||||
|     private val repeats: Int, | ||||
|     val studyTime: Int, | ||||
|     val breakTime: Int, | ||||
|     val repeats: Int, | ||||
|     id: String = "" | ||||
| ):  TimerInfo(id, name, description) { | ||||
| 
 | ||||
|  | @ -28,4 +29,8 @@ class PomodoroTimerInfo( | |||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     override fun <T> accept(visitor: TimerInfoVisitor<T>): T { | ||||
|         return visitor.visitBreakTimerInfo(this) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -7,8 +7,8 @@ import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer | |||
|  */ | ||||
| abstract class TimerInfo( | ||||
|     val id: String, | ||||
|     val name: String, | ||||
|     val description: String | ||||
|     var name: String, | ||||
|     var description: String | ||||
| ) { | ||||
| 
 | ||||
|     /** | ||||
|  | @ -21,6 +21,7 @@ abstract class TimerInfo( | |||
|      * TODO implementaties hebben nog hardgecodeerde strings. | ||||
|      */ | ||||
|     abstract fun asJson(): Map<String, Any> | ||||
|     abstract fun <T> accept(visitor: TimerInfoVisitor<T>): T | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,11 @@ | |||
| package be.ugent.sel.studeez.data.local.models.timer_info | ||||
| 
 | ||||
| interface TimerInfoVisitor<T> { | ||||
| 
 | ||||
|     fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo): T | ||||
| 
 | ||||
|     fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo): T | ||||
| 
 | ||||
|     fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo): T | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,35 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit | ||||
| 
 | ||||
| import android.annotation.SuppressLint | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
| import be.ugent.sel.studeez.R | ||||
| import be.ugent.sel.studeez.common.composable.BasicButton | ||||
| import be.ugent.sel.studeez.common.ext.basicButton | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.EndlessTimerInfo | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.PomodoroTimerInfo | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfoVisitor | ||||
| 
 | ||||
| class GetTimerEditView: TimerInfoVisitor<Unit> { | ||||
| 
 | ||||
|     @SuppressLint("ComposableNaming") | ||||
|     override fun visitCustomTimerInfo(customTimerInfo: CustomTimerInfo) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @SuppressLint("ComposableNaming") | ||||
|     override fun visitEndlessTimerInfo(endlessTimerInfo: EndlessTimerInfo) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @SuppressLint("ComposableNaming") | ||||
|     override fun visitBreakTimerInfo(pomodoroTimerInfo: PomodoroTimerInfo) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,2 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit | ||||
| 
 | ||||
|  | @ -0,0 +1,4 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit | ||||
| 
 | ||||
| class TimerEditViewModel { | ||||
| } | ||||
|  | @ -0,0 +1,4 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit | ||||
| 
 | ||||
| abstract class AbstractTimerEditScreen { | ||||
| } | ||||
|  | @ -0,0 +1,4 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit.editScreens | ||||
| 
 | ||||
| class BreakTimerEditScreen { | ||||
| } | ||||
|  | @ -0,0 +1,4 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit | ||||
| 
 | ||||
| class CustomTimerEditScreen { | ||||
| } | ||||
|  | @ -0,0 +1,4 @@ | |||
| package be.ugent.sel.studeez.screens.timer_edit.editScreens | ||||
| 
 | ||||
| class EndlessTimerEditScreen { | ||||
| } | ||||
|  | @ -61,6 +61,15 @@ fun TimerOverviewScreen( | |||
|     ) { | ||||
|         Column { | ||||
|             LazyColumn { | ||||
|                 // Custom timer, select new duration each time | ||||
|                 item { | ||||
|                     TimerEntry(timerInfo = CustomTimerInfo( | ||||
|                         name = resources().getString(R.string.custom_name), | ||||
|                         description = resources().getString(R.string.custom_name), | ||||
|                         studyTime = 0 | ||||
|                     )) | ||||
|                 } | ||||
| 
 | ||||
|                 // Default Timers, cannot be edited | ||||
|                 items(timerOverviewActions.getDefaultTimers()) { | ||||
|                     TimerEntry(timerInfo = it) {} | ||||
|  |  | |||
|  | @ -1,14 +1,20 @@ | |||
| package be.ugent.sel.studeez.screens.timer_selection | ||||
| 
 | ||||
| import android.widget.TimePicker | ||||
| import androidx.compose.foundation.lazy.LazyColumn | ||||
| import androidx.compose.foundation.lazy.items | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.collectAsState | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import be.ugent.sel.studeez.R | ||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||
| import be.ugent.sel.studeez.common.composable.StealthButton | ||||
| import be.ugent.sel.studeez.common.composable.TimePickerButton | ||||
| import be.ugent.sel.studeez.common.composable.TimerEntry | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.HoursMinutesSeconds | ||||
| import be.ugent.sel.studeez.data.local.models.timer_functional.Time | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.CustomTimerInfo | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||
| import be.ugent.sel.studeez.resources | ||||
| import kotlinx.coroutines.flow.Flow | ||||
|  | @ -17,6 +23,8 @@ import kotlinx.coroutines.flow.flowOf | |||
| data class TimerSelectionActions( | ||||
|     val getAllTimers: () -> Flow<List<TimerInfo>>, | ||||
|     val startSession: (TimerInfo) -> Unit, | ||||
|     val pickDuration: (TimePicker?, Int, Int) -> Unit, | ||||
|     val customTimeStudyTime: Int | ||||
| ) | ||||
| 
 | ||||
| fun getTimerSelectionActions( | ||||
|  | @ -26,6 +34,10 @@ fun getTimerSelectionActions( | |||
|     return TimerSelectionActions( | ||||
|         getAllTimers = viewModel::getAllTimers, | ||||
|         startSession = { viewModel.startSession(open, it) }, | ||||
|         pickDuration = { _, hour: Int, minute: Int -> | ||||
|             viewModel.customTimerStudyTime.value = hour * 60 * 60 + minute * 60 | ||||
|         }, | ||||
|         customTimeStudyTime = viewModel.customTimerStudyTime.value | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
|  | @ -52,6 +64,11 @@ fun TimerSelectionScreen( | |||
|         popUp = popUp | ||||
|     ) { | ||||
|         LazyColumn { | ||||
|             // Custom timer with duration selection button | ||||
|             item { | ||||
|                 CustomTimerEntry(timerSelectionActions) | ||||
|             } | ||||
| 
 | ||||
|             // All timers | ||||
|             items(timers.value) { timerInfo -> | ||||
|                 TimerEntry( | ||||
|  | @ -68,11 +85,39 @@ fun TimerSelectionScreen( | |||
|     } | ||||
| } | ||||
| 
 | ||||
| @Composable | ||||
| fun CustomTimerEntry( | ||||
|     timerSelectionActions: TimerSelectionActions | ||||
| ) { | ||||
|     val timerInfo = CustomTimerInfo( | ||||
|         name = resources().getString(R.string.custom_name), | ||||
|         description = resources().getString(R.string.custom_description), | ||||
|         studyTime = timerSelectionActions.customTimeStudyTime | ||||
|     ) | ||||
|     val hms: HoursMinutesSeconds = Time(timerInfo.studyTime).getAsHMS() | ||||
| 
 | ||||
|     TimerEntry( | ||||
|         timerInfo = timerInfo, | ||||
|         leftButton = { | ||||
|             StealthButton( | ||||
|                 text = R.string.start, | ||||
|                 onClick = { timerSelectionActions.startSession(timerInfo) } | ||||
|             ) | ||||
|         }, | ||||
|         rightButton = { | ||||
|             TimePickerButton( | ||||
|                 hoursMinutesSeconds = hms, | ||||
|                 onTimeSetListener = timerSelectionActions.pickDuration | ||||
|             ) | ||||
|         } | ||||
|     ) | ||||
| } | ||||
| 
 | ||||
| @Preview | ||||
| @Composable | ||||
| fun TimerSelectionPreview() { | ||||
|     TimerSelectionScreen( | ||||
|         timerSelectionActions = TimerSelectionActions({ flowOf() }, {}), | ||||
|         timerSelectionActions = TimerSelectionActions({ flowOf() }, {}, { _, _, _ -> {} }, 0), | ||||
|         popUp = {} | ||||
|     ) | ||||
| } | ||||
|  | @ -1,5 +1,9 @@ | |||
| package be.ugent.sel.studeez.screens.timer_selection | ||||
| 
 | ||||
| import androidx.compose.runtime.MutableState | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.remember | ||||
| import be.ugent.sel.studeez.data.SelectedTimerState | ||||
| import be.ugent.sel.studeez.data.local.models.timer_info.TimerInfo | ||||
| import be.ugent.sel.studeez.domain.LogService | ||||
|  | @ -17,6 +21,8 @@ class TimerSelectionViewModel @Inject constructor( | |||
|     logService: LogService | ||||
| ) : StudeezViewModel(logService) { | ||||
| 
 | ||||
|     var customTimerStudyTime: MutableState<Int> = mutableStateOf(0) | ||||
| 
 | ||||
|     fun getAllTimers() : Flow<List<TimerInfo>> { | ||||
|         return timerDAO.getAllTimers() | ||||
|     } | ||||
|  |  | |||
|  | @ -57,6 +57,7 @@ | |||
|     <string name="timers">Timers</string> | ||||
|     <string name="edit">Edit</string> | ||||
|     <string name="add_timer">Add timer</string> | ||||
|     <string name="pick_time">Select time</string> | ||||
|     <string name="state_focus">Focus!</string> | ||||
|     <plurals name="state_focus_remaining"> | ||||
|         <item quantity="zero">Focus one more time!</item> | ||||
|  | @ -65,6 +66,8 @@ | |||
|     </plurals> | ||||
|     <string name="state_done">Done!</string> | ||||
|     <string name="state_take_a_break">Take a break!</string> | ||||
|     <string name="custom_name">Custom</string> | ||||
|     <string name="custom_description">Select how long you want to study</string> | ||||
| 
 | ||||
|     <!-- Settings --> | ||||
|     <string name="settings_temp_description">Looks like you found the settings screen! In the future, this will enable you to edit your preferenes such as light/dark mode, end sessions automatically when we detect you are gone etc.</string> <!-- TODO Remove this description line once implemented. --> | ||||
|  |  | |||
		Reference in a new issue
	
	 GitHub Enterprise
							GitHub Enterprise