Merge pull request #93 from SELab1/custom_timer

Custom timer
This commit is contained in:
Tibo De Peuter 2023-05-03 22:09:06 +02:00 committed by GitHub Enterprise
commit ee7914d0c9
23 changed files with 250 additions and 18 deletions

View file

@ -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,

View file

@ -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()

View file

@ -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()
}

View file

@ -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)
}

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.data
class EditTimerState {
}

View file

@ -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"
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -22,4 +22,8 @@ class EndlessTimerInfo(
)
}
override fun <T> accept(visitor: TimerInfoVisitor<T>): T {
return visitor.visitEndlessTimerInfo(this)
}
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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) {
}
}

View file

@ -0,0 +1,2 @@
package be.ugent.sel.studeez.screens.timer_edit

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit
class TimerEditViewModel {
}

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit
abstract class AbstractTimerEditScreen {
}

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens
class BreakTimerEditScreen {
}

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit
class CustomTimerEditScreen {
}

View file

@ -0,0 +1,4 @@
package be.ugent.sel.studeez.screens.timer_edit.editScreens
class EndlessTimerEditScreen {
}

View file

@ -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) {}

View file

@ -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 = {}
)
}

View file

@ -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()
}

View file

@ -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. -->