Play sounds on each timer phase
This commit is contained in:
parent
46cb0e3c0a
commit
6ecc7e2a38
9 changed files with 67 additions and 25 deletions
BIN
app/src/main/assets/audio/cool.mp3
Normal file
BIN
app/src/main/assets/audio/cool.mp3
Normal file
Binary file not shown.
BIN
app/src/main/assets/audio/high.mp3
Normal file
BIN
app/src/main/assets/audio/high.mp3
Normal file
Binary file not shown.
BIN
app/src/main/assets/audio/low.mp3
Normal file
BIN
app/src/main/assets/audio/low.mp3
Normal file
Binary file not shown.
BIN
app/src/main/assets/audio/rest.mp3
Normal file
BIN
app/src/main/assets/audio/rest.mp3
Normal file
Binary file not shown.
BIN
app/src/main/assets/audio/warm.mp3
Normal file
BIN
app/src/main/assets/audio/warm.mp3
Normal file
Binary file not shown.
|
@ -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<Int>()
|
||||
|
||||
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() {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timerSets"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline3"
|
||||
android:textColor="@color/colorOnSurface"
|
||||
tools:text="5" />
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -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" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timerRounds"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline3"
|
||||
android:textColor="@color/colorOnSurface"
|
||||
tools:text="4" />
|
||||
</LinearLayout>
|
||||
|
||||
|
|
Loading…
Reference in a new issue