Fix style issues after leaving timer and rotation breaking timers

This commit is contained in:
William Brawner 2020-07-04 18:15:10 -07:00
parent cb284b800d
commit f21754a0be
12 changed files with 267 additions and 173 deletions

View file

@ -9,7 +9,8 @@ data class IntervalDuration(
val seconds: Long = 0 val seconds: Long = 0
) { ) {
override fun toString(): String { override fun toString(): String {
return "%02d:%02d:%02d".format(hours, minutes, seconds) return if (hours > 0) "%02d:%02d:%02d".format(hours, minutes, seconds)
else "%02d:%02d".format(minutes, seconds)
} }
} }
@ -19,9 +20,10 @@ fun IntervalDuration.toMillis(): Long {
TimeUnit.SECONDS.toMillis(seconds) TimeUnit.SECONDS.toMillis(seconds)
} }
private const val SECONDS_IN_HOUR = 3600
private const val SECONDS_IN_MINUTE = 60
fun Long.toIntervalDuration(): IntervalDuration { fun Long.toIntervalDuration(): IntervalDuration {
val SECONDS_IN_HOUR = 3600
val SECONDS_IN_MINUTE = 60
if (this < 1000) { if (this < 1000) {
return IntervalDuration(0, 0, 0) return IntervalDuration(0, 0, 0)

View file

@ -2,7 +2,6 @@ package com.wbrawner.trainterval
import android.app.Application import android.app.Application
import androidx.room.Room import androidx.room.Room
import com.wbrawner.trainterval.activetimer.ActiveTimerViewModel
import com.wbrawner.trainterval.timerform.TimerFormViewModel import com.wbrawner.trainterval.timerform.TimerFormViewModel
import com.wbrawner.trainterval.timerlist.TimerListViewModel import com.wbrawner.trainterval.timerlist.TimerListViewModel
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
@ -46,10 +45,6 @@ val traintervalModule = module {
TimerFormViewModel(get(parameters = { parametersOf("TimerFormStore") }), get()) TimerFormViewModel(get(parameters = { parametersOf("TimerFormStore") }), get())
} }
factory {
ActiveTimerViewModel(get(parameters = { parametersOf("ActiveTimerStore") }), get())
}
factory<Logger> { params -> factory<Logger> { params ->
AndroidLogger(params.component1()) AndroidLogger(params.component1())
} }

View file

@ -7,25 +7,31 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.wbrawner.trainterval.Logger
import com.wbrawner.trainterval.R import com.wbrawner.trainterval.R
import com.wbrawner.trainterval.model.IntervalTimerDao
import kotlinx.android.synthetic.main.fragment_active_timer.* import kotlinx.android.synthetic.main.fragment_active_timer.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf
class ActiveTimerFragment : Fragment() { class ActiveTimerFragment : Fragment() {
private var coroutineScope: CoroutineScope? = null private var coroutineScope: CoroutineScope? = null
private val activeTimerViewModel: ActiveTimerViewModel by inject() private val activeTimerViewModel: ActiveTimerViewModel by activityViewModels()
private val logger: Logger by inject(parameters = { parametersOf("ActiveTimerStore") })
private val timerDao: IntervalTimerDao by inject()
private var timerId: Long = 0 private var timerId: Long = 0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
timerId = requireArguments().getLong("timerId") timerId = requireArguments().getLong(EXTRA_TIMER_ID)
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
@ -56,35 +62,46 @@ class ActiveTimerFragment : Fragment() {
is IntervalTimerActiveState.ExitState -> findNavController().navigateUp() is IntervalTimerActiveState.ExitState -> findNavController().navigateUp()
} }
}) })
activeTimerViewModel.init(timerId) activeTimerViewModel.init(logger, timerDao, timerId)
} }
skipPreviousButton.setOnClickListener { skipPreviousButton.setOnClickListener {
coroutineScope!!.launch { activeTimerViewModel.goBack()
activeTimerViewModel.goBack()
}
} }
playPauseButton.setOnClickListener { playPauseButton.setOnClickListener {
coroutineScope!!.launch { activeTimerViewModel.toggleTimer()
activeTimerViewModel.toggleTimer()
}
} }
skipNextButton.setOnClickListener { skipNextButton.setOnClickListener {
coroutineScope!!.launch { activeTimerViewModel.skipAhead()
activeTimerViewModel.skipAhead()
}
} }
} }
private fun renderLoading() { private fun renderLoading() {
progressBar.visibility = View.VISIBLE progressBar.visibility = View.VISIBLE
timerLayout.visibility = View.GONE timerLayout.referencedIds.forEach {
view?.findViewById<View>(it)?.visibility = View.GONE
}
} }
private fun renderTimer(state: IntervalTimerActiveState.TimerRunningState) { private fun renderTimer(state: IntervalTimerActiveState.TimerRunningState) {
progressBar.visibility = View.GONE progressBar.visibility = View.GONE
timerLayout.visibility = View.VISIBLE timerLayout.referencedIds.forEach {
view?.findViewById<View>(it)?.visibility = View.VISIBLE
}
(activity as? AppCompatActivity)?.supportActionBar?.title = state.timerName
val backgroundColor = resources.getColor(state.timerBackground, context?.theme)
timerBackground.setBackgroundColor(backgroundColor)
playPauseButton.setImageDrawable(requireContext().getDrawable(state.playPauseIcon)) playPauseButton.setImageDrawable(requireContext().getDrawable(state.playPauseIcon))
timeRemaining.text = state.timeRemaining timeRemaining.text = state.timeRemaining
timerSets.text = getString(
R.string.timer_sets_formatted,
state.currentSet,
state.totalSets
)
timerRounds.text = getString(
R.string.timer_rounds_formatted,
state.currentRound,
state.totalRounds
)
} }
override fun onDestroyView() { override fun onDestroyView() {

View file

@ -1,8 +1,10 @@
package com.wbrawner.trainterval.activetimer package com.wbrawner.trainterval.activetimer
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wbrawner.trainterval.Logger import com.wbrawner.trainterval.Logger
import com.wbrawner.trainterval.R import com.wbrawner.trainterval.R
import com.wbrawner.trainterval.activetimer.IntervalTimerActiveState.LoadingState import com.wbrawner.trainterval.activetimer.IntervalTimerActiveState.LoadingState
@ -11,93 +13,83 @@ import com.wbrawner.trainterval.model.IntervalTimer
import com.wbrawner.trainterval.model.IntervalTimerDao import com.wbrawner.trainterval.model.IntervalTimerDao
import com.wbrawner.trainterval.model.Phase import com.wbrawner.trainterval.model.Phase
import com.wbrawner.trainterval.toIntervalDuration import com.wbrawner.trainterval.toIntervalDuration
import kotlinx.coroutines.* import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
class ActiveTimerViewModel( class ActiveTimerViewModel : ViewModel() {
private val logger: Logger,
private val timerDao: IntervalTimerDao
) : ViewModel() {
val timerState: MutableLiveData<IntervalTimerActiveState> = MutableLiveData(LoadingState) val timerState: MutableLiveData<IntervalTimerActiveState> = MutableLiveData(LoadingState)
private var timerJob: Job? = null private var timerJob: Job? = null
private lateinit var timer: IntervalTimer private lateinit var timer: IntervalTimer
private lateinit var logger: Logger
private var timerComplete = false private var timerComplete = false
private var timerRunning = false
private var currentPhase = Phase.WARM_UP private var currentPhase = Phase.WARM_UP
private var currentSet = 1 private var currentSet = 1
private var currentRound = 1 private var currentRound = 1
private var timeRemaining: Long = 0 private var timeRemaining: Long = 0
suspend fun init(timerId: Long) { suspend fun init(
logger.d(message = "Initializing with Timer id $timerId") logger: Logger,
timer = timerDao.getById(timerId) timerDao: IntervalTimerDao,
timeRemaining = timer.warmUpDuration timerId: Long
timerState.postValue( ) {
TimerRunningState( this.logger = logger
timerRunning, if (timerJob == null || timer.id != timerId) {
timeRemaining.toIntervalDuration().toString(), logger.d(message = "Initializing with Timer id $timerId")
currentSet, timer = timerDao.getById(timerId)
timer.sets, timeRemaining = timer.warmUpDuration
currentRound,
timer.cycles
)
)
}
suspend fun toggleTimer() {
if (timerRunning) {
timerJob?.cancel()
timerRunning = false
timerState.postValue( timerState.postValue(
TimerRunningState( TimerRunningState(
timerRunning, timer,
timeRemaining.toIntervalDuration().toString(), timeRemaining,
currentSet, currentSet,
timer.sets,
currentRound, currentRound,
timer.cycles currentPhase,
timerJob != null
) )
) )
} else {
startTimer()
} }
} }
private suspend fun startTimer() { fun toggleTimer() {
coroutineScope { if (timerJob != null) {
timerJob = launch { timerJob?.cancel()
timerRunning = true timerJob = null
timerState.postValue( timerState.postValue(
TimerRunningState( TimerRunningState(
timerRunning, timer,
timeRemaining.toIntervalDuration().toString(), timeRemaining,
currentSet, currentSet,
timer.sets, currentRound,
currentRound, currentPhase,
timer.cycles timerJob != null
)
) )
while (coroutineContext.isActive && timerRunning) { )
} else {
viewModelScope.launch {
startTimer()
}
}
}
private fun startTimer() {
viewModelScope.launch {
timerJob = launch {
updateTimer()
while (coroutineContext.isActive && timerJob != null) {
delay(1_000) delay(1_000)
timeRemaining -= 1_000 timeRemaining -= 1_000
if (timeRemaining <= 0) { if (timeRemaining <= 0) {
goForward() goForward()
} }
timerState.postValue( updateTimer()
TimerRunningState(
timerRunning,
timeRemaining.toIntervalDuration().toString(),
currentSet,
timer.sets,
currentRound,
timer.cycles
)
)
} }
} }
} }
} }
suspend fun skipAhead() { fun skipAhead() {
timerJob?.cancel() timerJob?.cancel()
when (currentPhase) { when (currentPhase) {
Phase.COOL_DOWN -> { Phase.COOL_DOWN -> {
@ -107,11 +99,26 @@ class ActiveTimerViewModel(
goForward() goForward()
} }
} }
if (timerRunning) { if (timerJob != null) {
startTimer() startTimer()
} else {
updateTimer()
} }
} }
private fun updateTimer() {
timerState.postValue(
TimerRunningState(
timer,
timeRemaining,
currentSet,
currentRound,
currentPhase,
timerJob != null
)
)
}
private fun goForward() { private fun goForward() {
timerComplete = currentPhase == Phase.COOL_DOWN timerComplete = currentPhase == Phase.COOL_DOWN
when (currentPhase) { when (currentPhase) {
@ -147,12 +154,14 @@ class ActiveTimerViewModel(
timeRemaining = timer.lowIntensityDuration timeRemaining = timer.lowIntensityDuration
} }
Phase.COOL_DOWN -> { Phase.COOL_DOWN -> {
timerRunning = false timeRemaining = 0
timerJob?.cancel()
timerJob = null
} }
} }
} }
suspend fun goBack() { fun goBack() {
timerJob?.cancel() timerJob?.cancel()
when (currentPhase) { when (currentPhase) {
Phase.WARM_UP -> { Phase.WARM_UP -> {
@ -169,11 +178,11 @@ class ActiveTimerViewModel(
timeRemaining = timer.restDuration timeRemaining = timer.restDuration
} }
else -> { else -> {
currentSet--
currentPhase = Phase.HIGH_INTENSITY currentPhase = Phase.HIGH_INTENSITY
timeRemaining = timer.highIntensityDuration timeRemaining = timer.highIntensityDuration
} }
} }
timeRemaining = timer.highIntensityDuration
} }
Phase.HIGH_INTENSITY -> { Phase.HIGH_INTENSITY -> {
currentPhase = Phase.LOW_INTENSITY currentPhase = Phase.LOW_INTENSITY
@ -190,8 +199,10 @@ class ActiveTimerViewModel(
timeRemaining = timer.highIntensityDuration timeRemaining = timer.highIntensityDuration
} }
} }
if (timerRunning) { if (timerJob != null) {
startTimer() startTimer()
} else {
updateTimer()
} }
} }
} }
@ -202,14 +213,33 @@ class ActiveTimerViewModel(
sealed class IntervalTimerActiveState { sealed class IntervalTimerActiveState {
object LoadingState : IntervalTimerActiveState() object LoadingState : IntervalTimerActiveState()
class TimerRunningState( class TimerRunningState(
timerRunning: Boolean, val timerName: String,
val timeRemaining: String, val timeRemaining: String,
val currentSet: Int, val currentSet: Int,
val totalSets: Int, val totalSets: Int,
val currentRound: Int, val currentRound: Int,
val totalRounds: Int, val totalRounds: Int,
val timerComplete: Boolean = false, @ColorRes val timerBackground: Int,
@DrawableRes val playPauseIcon: Int = if (timerRunning) R.drawable.ic_pause else R.drawable.ic_play_arrow @DrawableRes val playPauseIcon: Int
) : IntervalTimerActiveState() ) : IntervalTimerActiveState() {
constructor(
timer: IntervalTimer,
timeRemaining: Long,
currentSet: Int,
currentRound: Int,
phase: Phase,
timerRunning: Boolean
) : this(
timerName = timer.name,
timeRemaining = timeRemaining.toIntervalDuration().toString(),
currentSet = currentSet,
currentRound = currentRound,
totalSets = timer.sets,
totalRounds = timer.cycles,
timerBackground = phase.colorRes,
playPauseIcon = if (timerRunning) R.drawable.ic_pause else R.drawable.ic_play_arrow
)
}
object ExitState : IntervalTimerActiveState() object ExitState : IntervalTimerActiveState()
} }

View file

@ -1,6 +1,8 @@
package com.wbrawner.trainterval.model package com.wbrawner.trainterval.model
import androidx.annotation.ColorRes
import androidx.room.* import androidx.room.*
import com.wbrawner.trainterval.R
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -18,12 +20,12 @@ data class IntervalTimer(
val cycles: Int = 1 val cycles: Int = 1
) )
enum class Phase { enum class Phase(@ColorRes val colorRes: Int) {
WARM_UP, WARM_UP(R.color.colorSurface),
LOW_INTENSITY, LOW_INTENSITY(R.color.colorSurfaceLowIntensity),
HIGH_INTENSITY, HIGH_INTENSITY(R.color.colorSurfaceHighIntensity),
REST, REST(R.color.colorSurfaceRest),
COOL_DOWN, COOL_DOWN(R.color.colorSurfaceCoolDown),
} }
@Dao @Dao

View file

@ -1,21 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity"> app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
<androidx.fragment.app.FragmentContainerView app:layout_constraintLeft_toLeftOf="parent"
android:id="@+id/nav_host_fragment" app:layout_constraintRight_toRightOf="parent"
android:name="androidx.navigation.fragment.NavHostFragment" app:layout_constraintTop_toTopOf="parent"
android:layout_width="0dp" app:navGraph="@navigation/nav_graph"
android:layout_height="0dp" tools:context=".MainActivity" />
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,9 +1,12 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/timerBackground"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:fitsSystemWindows="true"
android:keepScreenOn="true"
android:orientation="vertical" android:orientation="vertical"
tools:context="com.wbrawner.trainterval.activetimer.ActiveTimerFragment"> tools:context="com.wbrawner.trainterval.activetimer.ActiveTimerFragment">
@ -12,82 +15,125 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="#00000000" android:background="#00000000"
android:clipChildren="false"
android:clipToPadding="false"
android:elevation="0dp" android:elevation="0dp"
app:elevation="0dp"> android:padding="16dp"
app:elevation="0dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="@drawable/background_rounded_corners" android:background="@drawable/background_rounded_corners"
app:layout_scrollFlags="scroll|enterAlways" app:layout_scrollFlags="scroll|enterAlways"
app:title="@string/app_name" /> app:title="@string/app_name" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.Group
android:id="@+id/timerLayout" android:id="@+id/timerLayout"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_weight="1" app:constraint_referenced_ids="playPauseButton,timerSets,timerRounds,timeRemaining,skipNextButton,skipPreviousButton" />
android:visibility="gone"
tools:visibility="visible">
<TextView <TextView
android:id="@+id/timeRemaining" 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:textAlignment="center" android:padding="16dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline2" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@+id/timerRounds"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:text="00:00" /> tools:text="Set: 4/5" />
<ImageButton <TextView
android:id="@+id/skipPreviousButton" 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:backgroundTint="@android:color/transparent" android:padding="16dp"
android:contentDescription="@string/skip_previous" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:src="@drawable/ic_skip_previous" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/playPauseButton" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toEndOf="@+id/timerSets"
app:layout_constraintStart_toStartOf="parent" tools:text="Round: 4/5" />
app:layout_constraintTop_toBottomOf="@+id/timeRemaining" />
<ImageButton <androidx.constraintlayout.widget.Barrier
android:id="@+id/playPauseButton" android:id="@+id/timerInfo"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:backgroundTint="@android:color/transparent" app:barrierDirection="top"
android:contentDescription="@string/start_timer" app:constraint_referenced_ids="timerSets,timerRounds" />
app:layout_constraintEnd_toStartOf="@+id/skipNextButton"
app:layout_constraintStart_toEndOf="@+id/skipPreviousButton"
app:layout_constraintTop_toBottomOf="@+id/timeRemaining"
tools:src="@drawable/ic_play_arrow" />
<ImageButton <TextView
android:id="@+id/skipNextButton" android:id="@+id/timeRemaining"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:backgroundTint="@android:color/transparent" android:fontFamily="monospace"
android:contentDescription="@string/skip_next" android:textAlignment="center"
android:src="@drawable/ic_skip_next" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline2"
app:layout_constraintEnd_toEndOf="parent" android:textColor="@color/colorOnSurface"
app:layout_constraintStart_toEndOf="@+id/playPauseButton" app:layout_constraintBottom_toTopOf="@+id/playPauseButton"
app:layout_constraintTop_toBottomOf="@+id/timeRemaining" /> app:layout_constraintEnd_toEndOf="parent"
</androidx.constraintlayout.widget.ConstraintLayout> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout"
app:layout_constraintVertical_chainStyle="packed"
tools:text="00:00" />
<ImageButton
android:id="@+id/skipPreviousButton"
android:layout_width="64dp"
android:layout_height="64dp"
android:backgroundTint="@android:color/transparent"
android:contentDescription="@string/skip_previous"
android:scaleType="fitCenter"
android:src="@drawable/ic_skip_previous"
app:layout_constraintBottom_toTopOf="@+id/timerInfo"
app:layout_constraintEnd_toStartOf="@+id/playPauseButton"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/timeRemaining"
app:layout_constraintVertical_bias="0.0" />
<ImageButton
android:id="@+id/playPauseButton"
android:layout_width="64dp"
android:layout_height="64dp"
android:backgroundTint="@android:color/transparent"
android:contentDescription="@string/start_timer"
android:scaleType="fitCenter"
app:layout_constraintBottom_toTopOf="@+id/timerInfo"
app:layout_constraintEnd_toStartOf="@+id/skipNextButton"
app:layout_constraintStart_toEndOf="@+id/skipPreviousButton"
app:layout_constraintTop_toBottomOf="@+id/timeRemaining"
tools:src="@drawable/ic_play_arrow" />
<ImageButton
android:id="@+id/skipNextButton"
android:layout_width="64dp"
android:layout_height="64dp"
android:backgroundTint="@android:color/transparent"
android:contentDescription="@string/skip_next"
android:scaleType="fitCenter"
android:src="@drawable/ic_skip_next"
app:layout_constraintBottom_toTopOf="@+id/timerInfo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/playPauseButton"
app:layout_constraintTop_toBottomOf="@+id/timeRemaining"
app:layout_constraintVertical_bias="0.0" />
<ProgressBar <ProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_weight="1" android:layout_weight="1"
android:visibility="gone" /> android:visibility="gone"
</LinearLayout> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -5,6 +5,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:fillViewport="true" android:fillViewport="true"
android:fitsSystemWindows="true"
tools:context="com.wbrawner.trainterval.timerform.TimerFormFragment"> tools:context="com.wbrawner.trainterval.timerform.TimerFormFragment">
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView

View file

@ -6,6 +6,7 @@
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false" android:clipToPadding="false"
android:fitsSystemWindows="true"
android:orientation="vertical" android:orientation="vertical"
tools:context="com.wbrawner.trainterval.timerlist.TimerListFragment"> tools:context="com.wbrawner.trainterval.timerlist.TimerListFragment">

View file

@ -1,9 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="colorPrimary">#6200EE</color> <color name="colorPrimary">#FFEB3B</color>
<color name="colorPrimaryDark">#3700B3</color> <color name="colorPrimaryDark">#F9A825</color>
<color name="colorAccent">#03DAC5</color> <color name="colorAccent">#F57C00</color>
<color name="colorOnSurface">#111111</color> <color name="colorOnSurface">#111111</color>
<color name="colorSurface">#FFFFFF</color> <color name="colorSurface">#FFFFFF</color>
<color name="colorSurfaceStroke">#F1F1F1</color> <color name="colorSurfaceStroke">#F1F1F1</color>
<color name="colorSurfaceWarmUp">@color/colorSurface</color>
<color name="colorSurfaceLowIntensity">#F06292</color>
<color name="colorSurfaceHighIntensity">#81C784</color>
<color name="colorSurfaceRest">#FFF176</color>
<color name="colorSurfaceCoolDown">#64B5F6</color>
</resources> </resources>

View file

@ -18,4 +18,6 @@
<string name="rest_duration">Rest Duration</string> <string name="rest_duration">Rest Duration</string>
<string name="title_item_list">Items</string> <string name="title_item_list">Items</string>
<string name="title_item_detail">Item Detail</string> <string name="title_item_detail">Item Detail</string>
<string name="timer_sets_formatted">Set: %1$d/%2$d</string>
<string name="timer_rounds_formatted">Round: %1$d/%2$d</string>
</resources> </resources>

View file

@ -6,9 +6,8 @@
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
<item name="android:windowLightStatusBar">true</item> <item name="android:windowTranslucentStatus">true</item>
<item name="android:statusBarColor">?android:windowBackground</item> <item name="android:windowTranslucentNavigation">true</item>
<item name="statusBarBackground">?android:windowBackground</item>
</style> </style>
<style name="AppTheme.NoActionBar"> <style name="AppTheme.NoActionBar">