diff --git a/app/src/main/assets/audio/cool.mp3 b/app/src/main/assets/audio/cool.mp3 new file mode 100644 index 0000000..ea98e2c Binary files /dev/null and b/app/src/main/assets/audio/cool.mp3 differ diff --git a/app/src/main/assets/audio/high.mp3 b/app/src/main/assets/audio/high.mp3 new file mode 100644 index 0000000..2da01bd Binary files /dev/null and b/app/src/main/assets/audio/high.mp3 differ diff --git a/app/src/main/assets/audio/low.mp3 b/app/src/main/assets/audio/low.mp3 new file mode 100644 index 0000000..cc18c07 Binary files /dev/null and b/app/src/main/assets/audio/low.mp3 differ diff --git a/app/src/main/assets/audio/rest.mp3 b/app/src/main/assets/audio/rest.mp3 new file mode 100644 index 0000000..04913a4 Binary files /dev/null and b/app/src/main/assets/audio/rest.mp3 differ diff --git a/app/src/main/assets/audio/warm.mp3 b/app/src/main/assets/audio/warm.mp3 new file mode 100644 index 0000000..67c2870 Binary files /dev/null and b/app/src/main/assets/audio/warm.mp3 differ diff --git a/app/src/main/java/com/wbrawner/trainterval/activetimer/ActiveTimerFragment.kt b/app/src/main/java/com/wbrawner/trainterval/activetimer/ActiveTimerFragment.kt index c9569f9..8f8d25c 100644 --- a/app/src/main/java/com/wbrawner/trainterval/activetimer/ActiveTimerFragment.kt +++ b/app/src/main/java/com/wbrawner/trainterval/activetimer/ActiveTimerFragment.kt @@ -1,26 +1,29 @@ package com.wbrawner.trainterval.activetimer +import android.media.AudioManager +import android.media.SoundPool import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat.getSystemService import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer +import androidx.lifecycle.viewModelScope import androidx.navigation.fragment.findNavController import com.wbrawner.trainterval.Logger import com.wbrawner.trainterval.R import com.wbrawner.trainterval.model.IntervalTimerDao +import com.wbrawner.trainterval.model.Phase import kotlinx.android.synthetic.main.fragment_active_timer.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import org.koin.android.ext.android.inject import org.koin.core.parameter.parametersOf + class ActiveTimerFragment : Fragment() { private var coroutineScope: CoroutineScope? = null @@ -28,11 +31,29 @@ class ActiveTimerFragment : Fragment() { private val logger: Logger by inject(parameters = { parametersOf("ActiveTimerStore") }) private val timerDao: IntervalTimerDao by inject() private var timerId: Long = 0 + private lateinit var soundPool: SoundPool + private val soundIds = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) timerId = requireArguments().getLong(EXTRA_TIMER_ID) setHasOptionsMenu(true) + soundPool = SoundPool.Builder() + .setMaxStreams(Phase.values().size) + .build() + activeTimerViewModel.viewModelScope.launch { + if (soundIds.isEmpty()) { + val assetManager = context?.assets ?: return@launch + withContext(Dispatchers.IO) { + Phase.values().forEachIndexed { index, phase -> + soundIds.add( + index, + soundPool.load(assetManager.openFd("audio/${phase.soundFile}"), 1) + ) + } + } + } + } } override fun onOptionsItemSelected(item: MenuItem): Boolean = @@ -94,6 +115,17 @@ class ActiveTimerFragment : Fragment() { timeRemaining.text = state.timeRemaining timerSets.text = state.currentSet.toString() timerRounds.text = state.currentRound.toString() + state.soundId?.let { + playSound(soundIds[it]) + } + } + + private fun playSound(soundId: Int) { + val context = context ?: return + val audioManager = getSystemService(context, AudioManager::class.java) ?: return + val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat() + val volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / maxVolume + soundPool.play(soundId, volume, volume, 1, 0, 1f) } override fun onDestroyView() { diff --git a/app/src/main/java/com/wbrawner/trainterval/activetimer/ActiveTimerViewModel.kt b/app/src/main/java/com/wbrawner/trainterval/activetimer/ActiveTimerViewModel.kt index f7da846..27dfbb4 100644 --- a/app/src/main/java/com/wbrawner/trainterval/activetimer/ActiveTimerViewModel.kt +++ b/app/src/main/java/com/wbrawner/trainterval/activetimer/ActiveTimerViewModel.kt @@ -42,16 +42,7 @@ class ActiveTimerViewModel : ViewModel() { currentRound = timer.cycles timeRemaining = timer.warmUpDuration currentPhase = Phase.WARM_UP - timerState.postValue( - TimerRunningState( - timer, - timeRemaining, - currentSet, - currentRound, - currentPhase, - timerJob != null - ) - ) + updateTimer() } } @@ -220,6 +211,7 @@ sealed class IntervalTimerActiveState { val timeRemaining: String, val currentSet: Int, val currentRound: Int, + val soundId: Int?, @ColorRes val timerBackground: Int, @DrawableRes val playPauseIcon: Int ) : IntervalTimerActiveState() { @@ -236,6 +228,9 @@ sealed class IntervalTimerActiveState { currentSet = currentSet, currentRound = currentRound, timerBackground = phase.colorRes, + soundId = if (timerRunning && timeRemaining == timer.durationForPhase(phase)) + phase.ordinal + else null, playPauseIcon = if (timerRunning) R.drawable.ic_pause else R.drawable.ic_play_arrow ) } diff --git a/app/src/main/java/com/wbrawner/trainterval/model/IntervalTimer.kt b/app/src/main/java/com/wbrawner/trainterval/model/IntervalTimer.kt index 0d8a718..2fcb578 100644 --- a/app/src/main/java/com/wbrawner/trainterval/model/IntervalTimer.kt +++ b/app/src/main/java/com/wbrawner/trainterval/model/IntervalTimer.kt @@ -18,14 +18,25 @@ data class IntervalTimer( val coolDownDuration: Long = TimeUnit.MINUTES.toMillis(5), val sets: Int = 4, val cycles: Int = 1 -) +) { + fun durationForPhase(phase: Phase) = when (phase) { + Phase.WARM_UP -> warmUpDuration + Phase.LOW_INTENSITY -> lowIntensityDuration + Phase.HIGH_INTENSITY -> highIntensityDuration + Phase.REST -> restDuration + Phase.COOL_DOWN -> coolDownDuration + } +} -enum class Phase(@ColorRes val colorRes: Int) { - WARM_UP(R.color.colorSurface), - LOW_INTENSITY(R.color.colorSurfaceLowIntensity), - HIGH_INTENSITY(R.color.colorSurfaceHighIntensity), - REST(R.color.colorSurfaceRest), - COOL_DOWN(R.color.colorSurfaceCoolDown), +enum class Phase( + @ColorRes val colorRes: Int, + val soundFile: String +) { + WARM_UP(R.color.colorSurface, "warm.mp3"), + LOW_INTENSITY(R.color.colorSurfaceLowIntensity, "low.mp3"), + HIGH_INTENSITY(R.color.colorSurfaceHighIntensity, "high.mp3"), + REST(R.color.colorSurfaceRest, "rest.mp3"), + COOL_DOWN(R.color.colorSurfaceCoolDown, "cool.mp3"), } @Dao diff --git a/app/src/main/res/layout/fragment_active_timer.xml b/app/src/main/res/layout/fragment_active_timer.xml index 90e9ec1..33f6a95 100644 --- a/app/src/main/res/layout/fragment_active_timer.xml +++ b/app/src/main/res/layout/fragment_active_timer.xml @@ -52,13 +52,15 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/label_set" - android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" /> + android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" + android:textColor="@color/colorOnSurface" /> @@ -77,13 +79,15 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/label_round" - android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" /> + android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" + android:textColor="@color/colorOnSurface" />