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.annotation.StringRes
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier 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.R
import be.ugent.sel.studeez.common.ext.basicButton import be.ugent.sel.studeez.common.ext.basicButton
import be.ugent.sel.studeez.common.ext.card import be.ugent.sel.studeez.common.ext.card
import be.ugent.sel.studeez.common.ext.defaultButtonShape
@Composable @Composable
fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) { fun BasicTextButton(@StringRes text: Int, modifier: Modifier, action: () -> Unit) {
@ -30,7 +30,7 @@ fun BasicButton(
Button( Button(
onClick = onClick, onClick = onClick,
modifier = modifier, modifier = modifier,
shape = RoundedCornerShape(20.dp), shape = defaultButtonShape(),
colors = colors, colors = colors,
border = border, border = border,
) { ) {
@ -47,6 +47,25 @@ fun BasicButtonPreview() {
BasicButton(text = R.string.add_timer, modifier = Modifier.basicButton()) {} 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 @Composable
fun StealthButton( fun StealthButton(
@StringRes text: Int, @StringRes text: Int,

View file

@ -41,10 +41,12 @@ fun BasicField(
fun LabelledInputField( fun LabelledInputField(
value: String, value: String,
onNewValue: (String) -> Unit, onNewValue: (String) -> Unit,
@StringRes label: Int @StringRes label: Int,
singleLine: Boolean = false
) { ) {
OutlinedTextField( OutlinedTextField(
value = value, value = value,
singleLine = singleLine,
onValueChange = onNewValue, onValueChange = onNewValue,
label = { Text(text = stringResource(id = label)) }, label = { Text(text = stringResource(id = label)) },
modifier = Modifier.fieldModifier() 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 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 minutes: Int = (time / (60)) % 60
val seconds: Int = time % 60 val seconds: Int = time % 60
return HoursMinutesSeconds( return HoursMinutesSeconds(hours, minutes, seconds)
hours.toString().padStart(2, '0'),
minutes.toString().padStart(2, '0'),
seconds.toString().padStart(2, '0')
)
} }
} }

View file

@ -6,11 +6,10 @@ import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
class CustomTimerInfo( class CustomTimerInfo(
name: String, name: String,
description: String, description: String,
private val studyTime: Int, var studyTime: Int,
id: String = "" id: String = ""
): TimerInfo(id, name, description) { ): TimerInfo(id, name, description) {
override fun getFunctionalTimer(): FunctionalTimer { override fun getFunctionalTimer(): FunctionalTimer {
return FunctionalCustomTimer(studyTime) 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.FunctionalPomodoroTimer
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.data.local.models.timer_functional.FunctionalTimerVisitor
class PomodoroTimerInfo( class PomodoroTimerInfo(
name: String, name: String,
description: String, description: String,
private val studyTime: Int, val studyTime: Int,
private val breakTime: Int, val breakTime: Int,
private val repeats: Int, val repeats: Int,
id: String = "" id: String = ""
): TimerInfo(id, name, description) { ): 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( abstract class TimerInfo(
val id: String, val id: String,
val name: String, var name: String,
val description: String var description: String
) { ) {
/** /**
@ -21,6 +21,7 @@ abstract class TimerInfo(
* TODO implementaties hebben nog hardgecodeerde strings. * TODO implementaties hebben nog hardgecodeerde strings.
*/ */
abstract fun asJson(): Map<String, Any> 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 { Column {
LazyColumn { 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 // Default Timers, cannot be edited
items(timerOverviewActions.getDefaultTimers()) { items(timerOverviewActions.getDefaultTimers()) {
TimerEntry(timerInfo = it) {} TimerEntry(timerInfo = it) {}

View file

@ -1,14 +1,20 @@
package be.ugent.sel.studeez.screens.timer_selection package be.ugent.sel.studeez.screens.timer_selection
import android.widget.TimePicker
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import be.ugent.sel.studeez.R import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate
import be.ugent.sel.studeez.common.composable.StealthButton import be.ugent.sel.studeez.common.composable.StealthButton
import be.ugent.sel.studeez.common.composable.TimePickerButton
import be.ugent.sel.studeez.common.composable.TimerEntry 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.data.local.models.timer_info.TimerInfo
import be.ugent.sel.studeez.resources import be.ugent.sel.studeez.resources
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -17,6 +23,8 @@ import kotlinx.coroutines.flow.flowOf
data class TimerSelectionActions( data class TimerSelectionActions(
val getAllTimers: () -> Flow<List<TimerInfo>>, val getAllTimers: () -> Flow<List<TimerInfo>>,
val startSession: (TimerInfo) -> Unit, val startSession: (TimerInfo) -> Unit,
val pickDuration: (TimePicker?, Int, Int) -> Unit,
val customTimeStudyTime: Int
) )
fun getTimerSelectionActions( fun getTimerSelectionActions(
@ -26,6 +34,10 @@ fun getTimerSelectionActions(
return TimerSelectionActions( return TimerSelectionActions(
getAllTimers = viewModel::getAllTimers, getAllTimers = viewModel::getAllTimers,
startSession = { viewModel.startSession(open, it) }, 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 popUp = popUp
) { ) {
LazyColumn { LazyColumn {
// Custom timer with duration selection button
item {
CustomTimerEntry(timerSelectionActions)
}
// All timers // All timers
items(timers.value) { timerInfo -> items(timers.value) { timerInfo ->
TimerEntry( 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 @Preview
@Composable @Composable
fun TimerSelectionPreview() { fun TimerSelectionPreview() {
TimerSelectionScreen( TimerSelectionScreen(
timerSelectionActions = TimerSelectionActions({ flowOf() }, {}), timerSelectionActions = TimerSelectionActions({ flowOf() }, {}, { _, _, _ -> {} }, 0),
popUp = {} popUp = {}
) )
} }

View file

@ -1,5 +1,9 @@
package be.ugent.sel.studeez.screens.timer_selection 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.SelectedTimerState
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.domain.LogService import be.ugent.sel.studeez.domain.LogService
@ -17,6 +21,8 @@ class TimerSelectionViewModel @Inject constructor(
logService: LogService logService: LogService
) : StudeezViewModel(logService) { ) : StudeezViewModel(logService) {
var customTimerStudyTime: MutableState<Int> = mutableStateOf(0)
fun getAllTimers() : Flow<List<TimerInfo>> { fun getAllTimers() : Flow<List<TimerInfo>> {
return timerDAO.getAllTimers() return timerDAO.getAllTimers()
} }

View file

@ -57,6 +57,7 @@
<string name="timers">Timers</string> <string name="timers">Timers</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="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">
<item quantity="zero">Focus one more time!</item> <item quantity="zero">Focus one more time!</item>
@ -65,6 +66,8 @@
</plurals> </plurals>
<string name="state_done">Done!</string> <string name="state_done">Done!</string>
<string name="state_take_a_break">Take a break!</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 --> <!-- 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. --> <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. -->