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
|
package com.wbrawner.trainterval.activetimer
|
||||||
|
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.media.SoundPool
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat.getSystemService
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.wbrawner.trainterval.Logger
|
import com.wbrawner.trainterval.Logger
|
||||||
import com.wbrawner.trainterval.R
|
import com.wbrawner.trainterval.R
|
||||||
import com.wbrawner.trainterval.model.IntervalTimerDao
|
import com.wbrawner.trainterval.model.IntervalTimerDao
|
||||||
|
import com.wbrawner.trainterval.model.Phase
|
||||||
import kotlinx.android.synthetic.main.fragment_active_timer.*
|
import kotlinx.android.synthetic.main.fragment_active_timer.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
|
|
||||||
|
|
||||||
class ActiveTimerFragment : Fragment() {
|
class ActiveTimerFragment : Fragment() {
|
||||||
|
|
||||||
private var coroutineScope: CoroutineScope? = null
|
private var coroutineScope: CoroutineScope? = null
|
||||||
|
@ -28,11 +31,29 @@ class ActiveTimerFragment : Fragment() {
|
||||||
private val logger: Logger by inject(parameters = { parametersOf("ActiveTimerStore") })
|
private val logger: Logger by inject(parameters = { parametersOf("ActiveTimerStore") })
|
||||||
private val timerDao: IntervalTimerDao by inject()
|
private val timerDao: IntervalTimerDao by inject()
|
||||||
private var timerId: Long = 0
|
private var timerId: Long = 0
|
||||||
|
private lateinit var soundPool: SoundPool
|
||||||
|
private val soundIds = mutableListOf<Int>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
timerId = requireArguments().getLong(EXTRA_TIMER_ID)
|
timerId = requireArguments().getLong(EXTRA_TIMER_ID)
|
||||||
setHasOptionsMenu(true)
|
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 =
|
override fun onOptionsItemSelected(item: MenuItem): Boolean =
|
||||||
|
@ -94,6 +115,17 @@ class ActiveTimerFragment : Fragment() {
|
||||||
timeRemaining.text = state.timeRemaining
|
timeRemaining.text = state.timeRemaining
|
||||||
timerSets.text = state.currentSet.toString()
|
timerSets.text = state.currentSet.toString()
|
||||||
timerRounds.text = state.currentRound.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() {
|
override fun onDestroyView() {
|
||||||
|
|
|
@ -42,16 +42,7 @@ class ActiveTimerViewModel : ViewModel() {
|
||||||
currentRound = timer.cycles
|
currentRound = timer.cycles
|
||||||
timeRemaining = timer.warmUpDuration
|
timeRemaining = timer.warmUpDuration
|
||||||
currentPhase = Phase.WARM_UP
|
currentPhase = Phase.WARM_UP
|
||||||
timerState.postValue(
|
updateTimer()
|
||||||
TimerRunningState(
|
|
||||||
timer,
|
|
||||||
timeRemaining,
|
|
||||||
currentSet,
|
|
||||||
currentRound,
|
|
||||||
currentPhase,
|
|
||||||
timerJob != null
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +211,7 @@ sealed class IntervalTimerActiveState {
|
||||||
val timeRemaining: String,
|
val timeRemaining: String,
|
||||||
val currentSet: Int,
|
val currentSet: Int,
|
||||||
val currentRound: Int,
|
val currentRound: Int,
|
||||||
|
val soundId: Int?,
|
||||||
@ColorRes val timerBackground: Int,
|
@ColorRes val timerBackground: Int,
|
||||||
@DrawableRes val playPauseIcon: Int
|
@DrawableRes val playPauseIcon: Int
|
||||||
) : IntervalTimerActiveState() {
|
) : IntervalTimerActiveState() {
|
||||||
|
@ -236,6 +228,9 @@ sealed class IntervalTimerActiveState {
|
||||||
currentSet = currentSet,
|
currentSet = currentSet,
|
||||||
currentRound = currentRound,
|
currentRound = currentRound,
|
||||||
timerBackground = phase.colorRes,
|
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
|
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 coolDownDuration: Long = TimeUnit.MINUTES.toMillis(5),
|
||||||
val sets: Int = 4,
|
val sets: Int = 4,
|
||||||
val cycles: Int = 1
|
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) {
|
enum class Phase(
|
||||||
WARM_UP(R.color.colorSurface),
|
@ColorRes val colorRes: Int,
|
||||||
LOW_INTENSITY(R.color.colorSurfaceLowIntensity),
|
val soundFile: String
|
||||||
HIGH_INTENSITY(R.color.colorSurfaceHighIntensity),
|
) {
|
||||||
REST(R.color.colorSurfaceRest),
|
WARM_UP(R.color.colorSurface, "warm.mp3"),
|
||||||
COOL_DOWN(R.color.colorSurfaceCoolDown),
|
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
|
@Dao
|
||||||
|
|
|
@ -52,13 +52,15 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/label_set"
|
android:text="@string/label_set"
|
||||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" />
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
|
android:textColor="@color/colorOnSurface" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/timerSets"
|
android:id="@+id/timerSets"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="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" />
|
tools:text="5" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
@ -77,13 +79,15 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/label_round"
|
android:text="@string/label_round"
|
||||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" />
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
|
android:textColor="@color/colorOnSurface" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/timerRounds"
|
android:id="@+id/timerRounds"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="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" />
|
tools:text="4" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue