Merge pull request #86 from SELab1/session_screen_refactor

Session screen refactor
This commit is contained in:
lbarraga 2023-04-28 11:44:29 +02:00 committed by GitHub Enterprise
commit b1955f1338
15 changed files with 340 additions and 259 deletions

View file

@ -1,21 +1,26 @@
package be.ugent.sel.studeez.data.local.models.timer_functional package be.ugent.sel.studeez.data.local.models.timer_functional
import be.ugent.sel.studeez.screens.session.sessionScreens.CustomSessionScreen
import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen
class FunctionalCustomTimer(studyTime: Int) : FunctionalTimer(studyTime) { class FunctionalCustomTimer(studyTime: Int) : FunctionalTimer(studyTime) {
override fun tick() { override fun tick() {
if (time.time == 0) { if (!hasEnded()) {
view = StudyState.DONE
} else {
time.minOne() time.minOne()
} }
} }
override fun hasEnded(): Boolean { override fun hasEnded(): Boolean {
return view == StudyState.DONE
}
override fun hasCurrentCountdownEnded(): Boolean {
return time.time == 0 return time.time == 0
} }
override fun hasCurrentCountdownEnded(): Boolean {
return hasEnded()
}
override fun <T> accept(visitor: FunctionalTimerVisitor<T>): T {
return visitor.visitFunctionalCustomTimer(this)
}
} }

View file

@ -1,6 +1,6 @@
package be.ugent.sel.studeez.data.local.models.timer_functional package be.ugent.sel.studeez.data.local.models.timer_functional
class FunctionalEndlessTimer() : FunctionalTimer(0) { class FunctionalEndlessTimer : FunctionalTimer(0) {
override fun hasEnded(): Boolean { override fun hasEnded(): Boolean {
return false return false
@ -13,4 +13,8 @@ class FunctionalEndlessTimer() : FunctionalTimer(0) {
override fun tick() { override fun tick() {
time.plusOne() time.plusOne()
} }
override fun <T> accept(visitor: FunctionalTimerVisitor<T>): T {
return visitor.visitFunctionalEndlessTimer(this)
}
} }

View file

@ -1,5 +1,8 @@
package be.ugent.sel.studeez.data.local.models.timer_functional package be.ugent.sel.studeez.data.local.models.timer_functional
import be.ugent.sel.studeez.screens.session.sessionScreens.BreakSessionScreen
import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen
class FunctionalPomodoroTimer( class FunctionalPomodoroTimer(
private var studyTime: Int, private var studyTime: Int,
private var breakTime: Int, repeats: Int private var breakTime: Int, repeats: Int
@ -9,18 +12,15 @@ class FunctionalPomodoroTimer(
var isInBreak = false var isInBreak = false
override fun tick() { override fun tick() {
if (time.time == 0 && breaksRemaining == 0) { if (hasEnded()) {
view = StudyState.DONE
return return
} }
if (time.time == 0) { if (hasCurrentCountdownEnded()) {
if (isInBreak) { if (isInBreak) {
breaksRemaining-- breaksRemaining--
view = StudyState.FOCUS_REMAINING
time.time = studyTime time.time = studyTime
} else { } else {
view = StudyState.BREAK
time.time = breakTime time.time = breakTime
} }
isInBreak = !isInBreak isInBreak = !isInBreak
@ -29,10 +29,18 @@ class FunctionalPomodoroTimer(
} }
override fun hasEnded(): Boolean { override fun hasEnded(): Boolean {
return breaksRemaining == 0 && time.time == 0 return !hasBreaksRemaining() && hasCurrentCountdownEnded()
}
private fun hasBreaksRemaining(): Boolean {
return breaksRemaining > 0
} }
override fun hasCurrentCountdownEnded(): Boolean { override fun hasCurrentCountdownEnded(): Boolean {
return time.time == 0 return time.time == 0
} }
override fun <T> accept(visitor: FunctionalTimerVisitor<T>): T {
return visitor.visitFunctionalBreakTimer(this)
}
} }

View file

@ -2,7 +2,6 @@ package be.ugent.sel.studeez.data.local.models.timer_functional
abstract class FunctionalTimer(initialValue: Int) { abstract class FunctionalTimer(initialValue: Int) {
val time: Time = Time(initialValue) val time: Time = Time(initialValue)
var view: StudyState = StudyState.FOCUS
fun getHoursMinutesSeconds(): HoursMinutesSeconds { fun getHoursMinutesSeconds(): HoursMinutesSeconds {
return time.getAsHMS() return time.getAsHMS()
@ -14,8 +13,5 @@ abstract class FunctionalTimer(initialValue: Int) {
abstract fun hasCurrentCountdownEnded(): Boolean abstract fun hasCurrentCountdownEnded(): Boolean
enum class StudyState { abstract fun <T> accept(visitor: FunctionalTimerVisitor<T>): T
FOCUS, DONE, BREAK, FOCUS_REMAINING
}
} }

View file

@ -0,0 +1,11 @@
package be.ugent.sel.studeez.data.local.models.timer_functional
interface FunctionalTimerVisitor<T> {
fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): T
fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): T
fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): T
}

View file

@ -0,0 +1,63 @@
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.ui.platform.LocalContext
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.GetSessionScreen
data class SessionActions(
val getTimer: () -> FunctionalTimer,
val getTask: () -> String,
val prepareMediaPlayer: () -> Unit,
val releaseMediaPlayer: () -> Unit,
)
private fun getSessionActions(
viewModel: SessionViewModel,
mediaplayer: MediaPlayer,
): SessionActions {
return SessionActions(
getTimer = viewModel::getTimer,
getTask = viewModel::getTask,
prepareMediaPlayer = mediaplayer::prepareAsync,
releaseMediaPlayer = mediaplayer::release,
)
}
@Composable
fun SessionRoute(
open: (String) -> Unit,
viewModel: SessionViewModel,
) {
val context = LocalContext.current
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val mediaplayer = MediaPlayer()
mediaplayer.setDataSource(context, uri)
mediaplayer.setOnCompletionListener {
mediaplayer.stop()
//if (timerEnd) {
// mediaplayer.release()
//}
}
mediaplayer.setOnPreparedListener {
// mediaplayer.start()
}
val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen())
//val sessionScreen = when (val timer = viewModel.getTimer()) {
// is FunctionalCustomTimer -> CustomSessionScreen(timer)
// is FunctionalPomodoroTimer -> BreakSessionScreen(timer)
// is FunctionalEndlessTimer -> EndlessSessionScreen()
// else -> throw java.lang.IllegalArgumentException("Unknown Timer")
//}
sessionScreen(
open = open,
sessionActions = getSessionActions(viewModel, mediaplayer)
)
}

View file

@ -1,212 +0,0 @@
package be.ugent.sel.studeez.screens.session
import android.media.MediaPlayer
import android.media.RingtoneManager
import android.net.Uri
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.platform.LocalContext
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.FunctionalEndlessTimer
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.StudyState
import be.ugent.sel.studeez.navigation.StudeezDestinations
import be.ugent.sel.studeez.resources
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds
var timerEnd = false
data class SessionActions(
val getTimer: () -> FunctionalTimer,
val getTask: () -> String,
val prepareMediaPlayer: () -> Unit,
val releaseMediaPlayer: () -> Unit,
)
fun getSessionActions(
viewModel: SessionViewModel,
mediaplayer: MediaPlayer,
): SessionActions {
return SessionActions(
getTimer = viewModel::getTimer,
getTask = viewModel::getTask,
prepareMediaPlayer = mediaplayer::prepareAsync,
releaseMediaPlayer = mediaplayer::release,
)
}
@Composable
fun SessionRoute(
open: (String) -> Unit,
viewModel: SessionViewModel,
) {
val context = LocalContext.current
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val mediaplayer = MediaPlayer()
mediaplayer.setDataSource(context, uri)
mediaplayer.setOnCompletionListener {
mediaplayer.stop()
if (timerEnd) {
// mediaplayer.release()
}
}
mediaplayer.setOnPreparedListener {
// mediaplayer.start()
}
SessionScreen(
open = open,
sessionActions = getSessionActions(viewModel, mediaplayer),
)
}
@Composable
fun SessionScreen(
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
open(StudeezDestinations.HOME_SCREEN)
// Vanaf hier ook naar report gaan als "end session" knop word ingedrukt
},
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
private fun Timer(
sessionActions: SessionActions,
) {
var tikker by remember { mutableStateOf(false) }
LaunchedEffect(tikker) {
delay(1.seconds)
sessionActions.getTimer().tick()
tikker = !tikker
}
if (
sessionActions.getTimer().hasCurrentCountdownEnded() && !sessionActions.getTimer()
.hasEnded()
) {
// sessionActions.prepareMediaPlayer()
}
if (!timerEnd && sessionActions.getTimer().hasEnded()) {
// sessionActions.prepareMediaPlayer()
timerEnd =
true // Placeholder, vanaf hier moet het report opgestart worden en de sessie afgesloten
}
val hms = sessionActions.getTimer().getHoursMinutesSeconds()
Column {
Text(
text = "${hms.hours} : ${hms.minutes} : ${hms.seconds}",
modifier = Modifier
.fillMaxWidth()
.padding(50.dp),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 40.sp,
)
val stateString: String = when (sessionActions.getTimer().view) {
StudyState.DONE -> resources().getString(R.string.state_done)
StudyState.FOCUS -> resources().getString(R.string.state_focus)
StudyState.BREAK -> resources().getString(R.string.state_take_a_break)
StudyState.FOCUS_REMAINING -> (sessionActions.getTimer() as FunctionalPomodoroTimer?)?.breaksRemaining?.let {
resources().getQuantityString(R.plurals.state_focus_remaining, it, it)
}.toString()
}
Text(
text = stateString,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Light,
fontSize = 30.sp
)
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)
)
}
}
}
}
@Preview
@Composable
fun TimerPreview() {
Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}))
}
@Preview
@Composable
fun SessionPreview() {
SessionScreen(
open = {},
sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {})
)
}

View file

@ -0,0 +1,151 @@
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.*
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.navigation.StudeezDestinations
import be.ugent.sel.studeez.screens.session.SessionActions
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds
abstract class AbstractSessionScreen {
var timerEnd = false
@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
open(StudeezDestinations.HOME_SCREEN)
// Vanaf hier ook naar report gaan als "end session" knop word ingedrukt
},
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()
tikker = !tikker
}
if (
sessionActions.getTimer().hasCurrentCountdownEnded() && !sessionActions.getTimer()
.hasEnded()
) {
// sessionActions.prepareMediaPlayer()
}
if (!timerEnd && sessionActions.getTimer().hasEnded()) {
// sessionActions.prepareMediaPlayer()
timerEnd =
true // Placeholder, vanaf hier moet het report opgestart worden en de sessie afgesloten
}
val hms = sessionActions.getTimer().getHoursMinutesSeconds()
Column {
Text(
text = "${hms.hours} : ${hms.minutes} : ${hms.seconds}",
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
)
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
}
@Preview
@Composable
fun TimerPreview() {
val sessionScreen = object : AbstractSessionScreen() {
@Composable
override fun motivationString(): String = "Test"
}
sessionScreen.Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}))
}

View file

@ -0,0 +1,30 @@
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.FunctionalPomodoroTimer
import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText
class BreakSessionScreen(
private val funPomoDoroTimer: FunctionalPomodoroTimer
): AbstractSessionScreen() {
@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().getQuantityString(
R.plurals.state_focus_remaining,
funPomoDoroTimer.breaksRemaining,
funPomoDoroTimer.breaksRemaining
)
}
}

View file

@ -0,0 +1,21 @@
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.resources
import be.ugent.sel.studeez.R.string as AppText
class CustomSessionScreen(
private val functionalTimer: FunctionalCustomTimer
): AbstractSessionScreen() {
@Composable
override fun motivationString(): String {
if (functionalTimer.hasEnded()) {
return resources().getString(AppText.state_done)
}
return resources().getString(AppText.state_focus)
}
}

View file

@ -0,0 +1,14 @@
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)
}
}

View file

@ -0,0 +1,17 @@
package be.ugent.sel.studeez.screens.session.sessionScreens
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 : FunctionalTimerVisitor<AbstractSessionScreen> {
override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): AbstractSessionScreen =
CustomSessionScreen(functionalCustomTimer)
override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): AbstractSessionScreen =
EndlessSessionScreen()
override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): AbstractSessionScreen =
BreakSessionScreen(functionalPomodoroTimer)
}

View file

@ -36,9 +36,6 @@ class FunctionalCustomTimerUnitTest : FunctionalTimerUnitTest() {
timer = FunctionalCustomTimer(0) timer = FunctionalCustomTimer(0)
timer.tick() timer.tick()
Assert.assertTrue(timer.hasEnded()) Assert.assertTrue(timer.hasEnded())
Assert.assertEquals( Assert.assertTrue(timer.hasEnded())
FunctionalTimer.StudyState.DONE,
timer.view
)
} }
} }

View file

@ -37,10 +37,6 @@ class FunctionalEndlessTimerUnitTest : FunctionalTimerUnitTest() {
for (i in 1..n) { for (i in 1..n) {
timer.tick() timer.tick()
Assert.assertFalse(timer.hasEnded()) Assert.assertFalse(timer.hasEnded())
Assert.assertEquals(
FunctionalTimer.StudyState.FOCUS,
timer.view
)
} }
} }
} }

View file

@ -29,10 +29,6 @@ class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() {
breaks, breaks,
pomodoroTimer.breaksRemaining, pomodoroTimer.breaksRemaining,
) )
Assert.assertEquals(
FunctionalTimer.StudyState.FOCUS,
pomodoroTimer.view,
)
} }
@Test @Test
@ -52,10 +48,6 @@ class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() {
pomodoroTimer = FunctionalPomodoroTimer(0, 0, 0) pomodoroTimer = FunctionalPomodoroTimer(0, 0, 0)
pomodoroTimer.tick() pomodoroTimer.tick()
Assert.assertTrue(pomodoroTimer.hasEnded()) Assert.assertTrue(pomodoroTimer.hasEnded())
Assert.assertEquals(
FunctionalTimer.StudyState.DONE,
pomodoroTimer.view,
)
} }
@Test @Test
@ -65,10 +57,6 @@ class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() {
} }
Assert.assertFalse(pomodoroTimer.hasEnded()) Assert.assertFalse(pomodoroTimer.hasEnded())
Assert.assertTrue(pomodoroTimer.isInBreak) Assert.assertTrue(pomodoroTimer.isInBreak)
Assert.assertEquals(
FunctionalTimer.StudyState.BREAK,
pomodoroTimer.view
)
} }
@Test @Test
@ -77,10 +65,6 @@ class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() {
pomodoroTimer.tick() pomodoroTimer.tick()
} }
Assert.assertTrue(pomodoroTimer.isInBreak) Assert.assertTrue(pomodoroTimer.isInBreak)
Assert.assertEquals(
FunctionalTimer.StudyState.BREAK,
pomodoroTimer.view
)
for (i in 0..breakTime) { for (i in 0..breakTime) {
pomodoroTimer.tick() pomodoroTimer.tick()
} }
@ -90,9 +74,5 @@ class FunctionalPomodoroTimerUnitTest : FunctionalTimerUnitTest() {
breaksRemaining, breaksRemaining,
pomodoroTimer.breaksRemaining pomodoroTimer.breaksRemaining
) )
Assert.assertEquals(
FunctionalTimer.StudyState.FOCUS_REMAINING,
pomodoroTimer.view
)
} }
} }