Merge pull request #87 from SELab1/timerfix

Timerfix
This commit is contained in:
lbarraga 2023-04-30 12:23:47 +02:00 committed by GitHub Enterprise
commit 6b7ec41f32
23 changed files with 207 additions and 57 deletions

View file

@ -42,5 +42,6 @@
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="TestFunctionName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
</profile>
</component>

2
.idea/misc.xml generated
View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17_PREVIEW" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View file

@ -66,6 +66,7 @@ dependencies {
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.compose.material:material:1.2.0'
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
// ViewModel
@ -97,6 +98,9 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
// Coroutine testing
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
// Mocking
testImplementation 'org.mockito.kotlin:mockito-kotlin:3.2.0'

View file

@ -10,9 +10,15 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.lifecycleScope
import be.ugent.sel.studeez.StudeezApp
import be.ugent.sel.studeez.screens.session.InvisibleSessionManager
import be.ugent.sel.studeez.ui.theme.StudeezTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
var onTimerInvisible: Job? = null
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@ -30,6 +36,18 @@ class MainActivity : ComponentActivity() {
}
}
}
override fun onStop() {
onTimerInvisible = lifecycleScope.launch {
InvisibleSessionManager.updateTimer()
}
super.onStop()
}
override fun onStart() {
onTimerInvisible?.cancel()
super.onStart()
}
}
@Composable

View file

@ -1,6 +1,5 @@
package be.ugent.sel.studeez.common.composable.navbar
import android.util.Log
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Icon

View file

@ -1,9 +1,5 @@
package be.ugent.sel.studeez.data.local.models.timer_functional
import be.ugent.sel.studeez.data.local.models.SessionReport
import be.ugent.sel.studeez.screens.session.sessionScreens.CustomSessionScreen
import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen
class FunctionalCustomTimer(studyTime: Int) : FunctionalTimer(studyTime) {
override fun tick() {

View file

@ -1,8 +1,5 @@
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(
private var studyTime: Int,
private var breakTime: Int, repeats: Int

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.data.local.models.timer_functional
import be.ugent.sel.studeez.data.local.models.SessionReport
import be.ugent.sel.studeez.screens.session.sessionScreens.AbstractSessionScreen
import com.google.firebase.Timestamp
abstract class FunctionalTimer(initialValue: Int) {

View file

@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.common.composable.BasicTextButton
import be.ugent.sel.studeez.common.composable.LabelledInputField

View file

@ -0,0 +1,29 @@
package be.ugent.sel.studeez.screens.session
import android.media.MediaPlayer
import kotlinx.coroutines.delay
import javax.inject.Singleton
import kotlin.time.Duration.Companion.seconds
@Singleton
object InvisibleSessionManager {
private var viewModel: SessionViewModel? = null
private lateinit var mediaPlayer: MediaPlayer
fun setParameters(viewModel: SessionViewModel, mediaplayer: MediaPlayer) {
this.viewModel = viewModel
this.mediaPlayer = mediaplayer
}
suspend fun updateTimer() {
viewModel?.let {
while (!it.getTimer().hasEnded()) {
delay(1.seconds)
it.getTimer().tick()
if (it.getTimer().hasCurrentCountdownEnded()) {
mediaPlayer.start()
}
}
}
}
}

View file

@ -12,7 +12,7 @@ import be.ugent.sel.studeez.screens.session.sessionScreens.GetSessionScreen
data class SessionActions(
val getTimer: () -> FunctionalTimer,
val getTask: () -> String,
val prepareMediaPlayer: () -> Unit,
val startMediaPlayer: () -> Unit,
val releaseMediaPlayer: () -> Unit,
val endSession: () -> Unit
)
@ -26,8 +26,8 @@ private fun getSessionActions(
getTimer = viewModel::getTimer,
getTask = viewModel::getTask,
endSession = { viewModel.endSession(openAndPopUp) },
prepareMediaPlayer = mediaplayer::prepareAsync,
releaseMediaPlayer = mediaplayer::release
startMediaPlayer = mediaplayer::start,
releaseMediaPlayer = mediaplayer::release,
)
}
@ -39,26 +39,15 @@ fun SessionRoute(
) {
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 mediaplayer = MediaPlayer.create(context, uri)
mediaplayer.isLooping = false
val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen())
InvisibleSessionManager.setParameters(
viewModel = viewModel,
mediaplayer = mediaplayer
)
//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")
//}
val sessionScreen: AbstractSessionScreen = viewModel.getTimer().accept(GetSessionScreen(mediaplayer))
sessionScreen(
open = open,

View file

@ -19,15 +19,12 @@ 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,
@ -74,22 +71,10 @@ abstract class AbstractSessionScreen {
LaunchedEffect(tikker) {
delay(1.seconds)
sessionActions.getTimer().tick()
callMediaPlayer()
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(
@ -136,6 +121,8 @@ abstract class AbstractSessionScreen {
@Composable
abstract fun motivationString(): String
abstract fun callMediaPlayer()
}
@Preview
@ -144,6 +131,7 @@ fun TimerPreview() {
val sessionScreen = object : AbstractSessionScreen() {
@Composable
override fun motivationString(): String = "Test"
override fun callMediaPlayer() {}
}
sessionScreen.Timer(sessionActions = SessionActions({ FunctionalEndlessTimer() }, { "Preview" }, {}, {}, {}))

View file

@ -1,5 +1,6 @@
package be.ugent.sel.studeez.screens.session.sessionScreens
import android.media.MediaPlayer
import androidx.compose.runtime.Composable
import be.ugent.sel.studeez.R
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
@ -7,7 +8,8 @@ import be.ugent.sel.studeez.resources
import be.ugent.sel.studeez.R.string as AppText
class BreakSessionScreen(
private val funPomoDoroTimer: FunctionalPomodoroTimer
private val funPomoDoroTimer: FunctionalPomodoroTimer,
private var mediaplayer: MediaPlayer?
): AbstractSessionScreen() {
@Composable
@ -27,4 +29,17 @@ class BreakSessionScreen(
)
}
override fun callMediaPlayer() {
if (funPomoDoroTimer.hasEnded()) {
mediaplayer?.let { it: MediaPlayer ->
it.setOnCompletionListener {
it.release()
mediaplayer = null
}
it.start()
}
} else if (funPomoDoroTimer.hasCurrentCountdownEnded()) {
mediaplayer?.start()
}
}
}

View file

@ -1,5 +1,6 @@
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
@ -7,7 +8,8 @@ import be.ugent.sel.studeez.R.string as AppText
class CustomSessionScreen(
private val functionalTimer: FunctionalCustomTimer
private val functionalTimer: FunctionalCustomTimer,
private var mediaplayer: MediaPlayer?
): AbstractSessionScreen() {
@Composable
@ -18,4 +20,16 @@ class CustomSessionScreen(
return resources().getString(AppText.state_focus)
}
override fun callMediaPlayer() {
if (functionalTimer.hasEnded()) {
mediaplayer?.let { it: MediaPlayer ->
it.setOnCompletionListener {
it.release()
mediaplayer = null
}
it.start()
}
}
}
}

View file

@ -11,4 +11,6 @@ class EndlessSessionScreen : AbstractSessionScreen() {
override fun motivationString(): String {
return resources().getString(AppText.state_focus)
}
override fun callMediaPlayer() {}
}

View file

@ -1,17 +1,18 @@
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 : FunctionalTimerVisitor<AbstractSessionScreen> {
class GetSessionScreen(private val mediaplayer: MediaPlayer?) : FunctionalTimerVisitor<AbstractSessionScreen> {
override fun visitFunctionalCustomTimer(functionalCustomTimer: FunctionalCustomTimer): AbstractSessionScreen =
CustomSessionScreen(functionalCustomTimer)
CustomSessionScreen(functionalCustomTimer, mediaplayer)
override fun visitFunctionalEndlessTimer(functionalEndlessTimer: FunctionalEndlessTimer): AbstractSessionScreen =
EndlessSessionScreen()
override fun visitFunctionalBreakTimer(functionalPomodoroTimer: FunctionalPomodoroTimer): AbstractSessionScreen =
BreakSessionScreen(functionalPomodoroTimer)
BreakSessionScreen(functionalPomodoroTimer, mediaplayer)
}

View file

@ -26,7 +26,7 @@ fun getSessionRecapActions(
return SessionRecapActions(
viewModel::getSessionReport,
{viewModel.saveSession(openAndPopUp)},
{viewModel.saveSession(openAndPopUp)}
{viewModel.discardSession(openAndPopUp)}
)
}

View file

@ -45,6 +45,7 @@
<!-- Sessions -->
<string name="sessions">Sessions</string>
<string name="end_session">End session</string>
<!-- Profile -->
<string name="profile">Profile</string>

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.timer_functional
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalCustomTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
import org.junit.Assert
import org.junit.Test

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.timer_functional
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalEndlessTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
import org.junit.Assert
import org.junit.Test

View file

@ -1,7 +1,6 @@
package be.ugent.sel.studeez.timer_functional
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalPomodoroTimer
import be.ugent.sel.studeez.data.local.models.timer_functional.FunctionalTimer
import org.junit.Assert
import org.junit.Test

View file

@ -0,0 +1,99 @@
package be.ugent.sel.studeez.timer_functional
import android.media.MediaPlayer
import be.ugent.sel.studeez.data.SelectedTimerState
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.InvisibleSessionManager
import be.ugent.sel.studeez.screens.session.SessionViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Test
import org.mockito.kotlin.mock
@ExperimentalCoroutinesApi
class InvisibleSessionManagerTest {
private var timerState: SelectedTimerState = SelectedTimerState()
private lateinit var viewModel: SessionViewModel
private var mediaPlayer: MediaPlayer = mock()
@Test
fun InvisibleEndlessTimerTest() = runTest {
timerState.selectedTimer = FunctionalEndlessTimer()
viewModel = SessionViewModel(timerState, mock())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
val test = launch {
InvisibleSessionManager.updateTimer()
}
Assert.assertEquals(viewModel.getTimer().time.time, 0)
advanceTimeBy(1_000) // Start tikker
advanceTimeBy(10_000_000)
Assert.assertEquals(viewModel.getTimer().time.time, 10000)
test.cancel()
return@runTest
}
@Test
fun InvisiblePomodoroTimerTest() = runTest {
val studyTime = 10
val breakTime = 5
val repeats = 1
timerState.selectedTimer = FunctionalPomodoroTimer(studyTime, breakTime, repeats)
viewModel = SessionViewModel(timerState, mock())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
val test = launch {
InvisibleSessionManager.updateTimer()
}
Assert.assertEquals(viewModel.getTimer().time.time, 10)
advanceTimeBy(1_000) // start tikker
advanceTimeBy(9_000)
Assert.assertEquals(viewModel.getTimer().time.time, 1)
// focus, 9 sec, 1 sec nog
advanceTimeBy(2_000)
Assert.assertEquals(viewModel.getTimer().time.time, 4)
// pauze, 11 sec bezig, 4 seconden nog pauze
advanceTimeBy(5_000)
Assert.assertEquals(viewModel.getTimer().time.time, 9)
// 2e focus, 16 sec, 9 sec in 2e focus nog
advanceTimeBy(13_000)
Assert.assertTrue(viewModel.getTimer().hasEnded())
// Done
test.cancel()
return@runTest
}
@Test
fun InvisibleCustomTimerTest() = runTest {
timerState.selectedTimer = FunctionalCustomTimer(5)
viewModel = SessionViewModel(timerState, mock())
InvisibleSessionManager.setParameters(viewModel, mediaPlayer)
val test = launch {
InvisibleSessionManager.updateTimer()
}
Assert.assertEquals(viewModel.getTimer().time.time, 5)
advanceTimeBy(1_000) // Start tikker
advanceTimeBy(4_000)
Assert.assertEquals(viewModel.getTimer().time.time, 1)
advanceTimeBy(1_000)
Assert.assertEquals(viewModel.getTimer().time.time, 0)
test.cancel()
return@runTest
}
}

View file

@ -7,6 +7,8 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.daemon=true
org.gradle.parallel=true
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects