feat(wearos): add pause/resume workout, water lock, and swipeable workout pages
This commit is contained in:
@@ -50,7 +50,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
// Wear OS Compose
|
// Wear OS Compose
|
||||||
implementation("androidx.wear.compose:compose-material:1.3.1")
|
implementation("androidx.wear.compose:compose-material:1.3.1")
|
||||||
implementation("androidx.wear.compose:compose-foundation:1.3.1")
|
implementation("androidx.wear.compose:compose-foundation:1.5.0")
|
||||||
implementation("androidx.wear.compose:compose-navigation:1.3.1")
|
implementation("androidx.wear.compose:compose-navigation:1.3.1")
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class SwimBuddyWearActivity : ComponentActivity() {
|
|||||||
val completedWorkouts by viewModel.completedWorkouts.collectAsState()
|
val completedWorkouts by viewModel.completedWorkouts.collectAsState()
|
||||||
val selectedWorkout by viewModel.selectedWorkout.collectAsState()
|
val selectedWorkout by viewModel.selectedWorkout.collectAsState()
|
||||||
val isWorkoutActive by viewModel.isWorkoutActive.collectAsState()
|
val isWorkoutActive by viewModel.isWorkoutActive.collectAsState()
|
||||||
|
val isPaused by viewModel.isPaused.collectAsState()
|
||||||
val currentSetIndex by viewModel.currentSetIndex.collectAsState()
|
val currentSetIndex by viewModel.currentSetIndex.collectAsState()
|
||||||
val currentRep by viewModel.currentRep.collectAsState()
|
val currentRep by viewModel.currentRep.collectAsState()
|
||||||
val elapsedSeconds by viewModel.elapsedSeconds.collectAsState()
|
val elapsedSeconds by viewModel.elapsedSeconds.collectAsState()
|
||||||
@@ -29,12 +30,14 @@ class SwimBuddyWearActivity : ComponentActivity() {
|
|||||||
val isResting by viewModel.isResting.collectAsState()
|
val isResting by viewModel.isResting.collectAsState()
|
||||||
val restTimeRemaining by viewModel.restTimeRemaining.collectAsState()
|
val restTimeRemaining by viewModel.restTimeRemaining.collectAsState()
|
||||||
val workoutSummary by viewModel.workoutSummary.collectAsState()
|
val workoutSummary by viewModel.workoutSummary.collectAsState()
|
||||||
|
val isWaterLockActive by viewModel.isWaterLockActive.collectAsState()
|
||||||
|
|
||||||
SwimBuddyApp(
|
SwimBuddyApp(
|
||||||
savedWorkouts = savedWorkouts,
|
savedWorkouts = savedWorkouts,
|
||||||
completedWorkouts = completedWorkouts,
|
completedWorkouts = completedWorkouts,
|
||||||
selectedWorkout = selectedWorkout,
|
selectedWorkout = selectedWorkout,
|
||||||
isWorkoutActive = isWorkoutActive,
|
isWorkoutActive = isWorkoutActive,
|
||||||
|
isPaused = isPaused,
|
||||||
currentSetIndex = currentSetIndex,
|
currentSetIndex = currentSetIndex,
|
||||||
currentRep = currentRep,
|
currentRep = currentRep,
|
||||||
elapsedSeconds = elapsedSeconds,
|
elapsedSeconds = elapsedSeconds,
|
||||||
@@ -47,9 +50,13 @@ class SwimBuddyWearActivity : ComponentActivity() {
|
|||||||
totalDistanceSwum = viewModel.totalDistanceSwum(),
|
totalDistanceSwum = viewModel.totalDistanceSwum(),
|
||||||
currentSetDistanceSwum = viewModel.currentSetDistanceSwum(),
|
currentSetDistanceSwum = viewModel.currentSetDistanceSwum(),
|
||||||
currentSetTotalDistance = viewModel.currentSetTotalDistance(),
|
currentSetTotalDistance = viewModel.currentSetTotalDistance(),
|
||||||
|
isWaterLockActive = isWaterLockActive,
|
||||||
onSelectWorkout = { viewModel.selectWorkout(it) },
|
onSelectWorkout = { viewModel.selectWorkout(it) },
|
||||||
onStartWorkout = { viewModel.startWorkout() },
|
onStartWorkout = { viewModel.startWorkout() },
|
||||||
onCompleteLap = { viewModel.completeLap() },
|
onCompleteLap = { viewModel.completeLap() },
|
||||||
|
onPauseResume = {
|
||||||
|
if (isPaused) viewModel.resumeWorkout() else viewModel.pauseWorkout()
|
||||||
|
},
|
||||||
onEndWorkout = { viewModel.endWorkout() },
|
onEndWorkout = { viewModel.endWorkout() },
|
||||||
onBack = { viewModel.selectWorkout(null) },
|
onBack = { viewModel.selectWorkout(null) },
|
||||||
onDeleteSaved = { viewModel.deleteSavedWorkout(it) },
|
onDeleteSaved = { viewModel.deleteSavedWorkout(it) },
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package nl.guidoit.swimbuddy
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
class WaterLockHelper {
|
||||||
|
|
||||||
|
private val _isActive = MutableStateFlow(false)
|
||||||
|
val isActive: StateFlow<Boolean> = _isActive.asStateFlow()
|
||||||
|
|
||||||
|
fun onWorkoutStarted() {
|
||||||
|
_isActive.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onWorkoutEnded() {
|
||||||
|
_isActive.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
|
|
||||||
private val healthManager = HealthServicesManager(application)
|
private val healthManager = HealthServicesManager(application)
|
||||||
private val storage = WorkoutStorage(application)
|
private val storage = WorkoutStorage(application)
|
||||||
|
private val waterLockHelper = WaterLockHelper()
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
private val appContext = application
|
private val appContext = application
|
||||||
|
|
||||||
@@ -67,8 +68,14 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
private val _workoutSummary = MutableStateFlow<WorkoutSummary?>(null)
|
private val _workoutSummary = MutableStateFlow<WorkoutSummary?>(null)
|
||||||
val workoutSummary: StateFlow<WorkoutSummary?> = _workoutSummary.asStateFlow()
|
val workoutSummary: StateFlow<WorkoutSummary?> = _workoutSummary.asStateFlow()
|
||||||
|
|
||||||
|
private val _isPaused = MutableStateFlow(false)
|
||||||
|
val isPaused: StateFlow<Boolean> = _isPaused.asStateFlow()
|
||||||
|
|
||||||
|
val isWaterLockActive: StateFlow<Boolean> = waterLockHelper.isActive
|
||||||
|
|
||||||
private var startTime = 0L
|
private var startTime = 0L
|
||||||
private var lapStartTime = 0L
|
private var lapStartTime = 0L
|
||||||
|
private var pausedElapsedSeconds = 0f
|
||||||
private var lapDetector: LapDetector? = null
|
private var lapDetector: LapDetector? = null
|
||||||
private var currentStrokeCount = 0L
|
private var currentStrokeCount = 0L
|
||||||
|
|
||||||
@@ -137,8 +144,11 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
healthManager.startSwimmingWorkout(workout.poolLength)
|
healthManager.startSwimmingWorkout(workout.poolLength)
|
||||||
_isWorkoutActive.value = true
|
_isWorkoutActive.value = true
|
||||||
|
_isPaused.value = false
|
||||||
|
waterLockHelper.onWorkoutStarted()
|
||||||
startTime = System.currentTimeMillis()
|
startTime = System.currentTimeMillis()
|
||||||
lapStartTime = startTime
|
lapStartTime = startTime
|
||||||
|
pausedElapsedSeconds = 0f
|
||||||
_currentSetIndex.value = 0
|
_currentSetIndex.value = 0
|
||||||
_currentRep.value = 0
|
_currentRep.value = 0
|
||||||
_completedLaps.value = emptyList()
|
_completedLaps.value = emptyList()
|
||||||
@@ -152,6 +162,27 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pauseWorkout() {
|
||||||
|
if (!_isWorkoutActive.value || _isPaused.value) return
|
||||||
|
viewModelScope.launch {
|
||||||
|
healthManager.pauseWorkout()
|
||||||
|
_isPaused.value = true
|
||||||
|
pausedElapsedSeconds = _elapsedSeconds.value
|
||||||
|
HapticFeedback.play(appContext, HapticFeedback.Type.NOTIFICATION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resumeWorkout() {
|
||||||
|
if (!_isWorkoutActive.value || !_isPaused.value) return
|
||||||
|
viewModelScope.launch {
|
||||||
|
healthManager.resumeWorkout()
|
||||||
|
_isPaused.value = false
|
||||||
|
startTime = System.currentTimeMillis() - (pausedElapsedSeconds * 1000).toLong()
|
||||||
|
lapStartTime = System.currentTimeMillis()
|
||||||
|
HapticFeedback.play(appContext, HapticFeedback.Type.START)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startLapDetection() {
|
private fun startLapDetection() {
|
||||||
lapDetector = LapDetector(appContext) {
|
lapDetector = LapDetector(appContext) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
@@ -167,7 +198,9 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
private fun startElapsedTimeTracking() {
|
private fun startElapsedTimeTracking() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
while (_isWorkoutActive.value) {
|
while (_isWorkoutActive.value) {
|
||||||
|
if (!_isPaused.value) {
|
||||||
_elapsedSeconds.value = (System.currentTimeMillis() - startTime) / 1000f
|
_elapsedSeconds.value = (System.currentTimeMillis() - startTime) / 1000f
|
||||||
|
}
|
||||||
kotlinx.coroutines.delay(1000)
|
kotlinx.coroutines.delay(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +292,8 @@ class WorkoutViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
healthManager.endWorkout()
|
healthManager.endWorkout()
|
||||||
_isWorkoutActive.value = false
|
_isWorkoutActive.value = false
|
||||||
|
_isPaused.value = false
|
||||||
|
waterLockHelper.onWorkoutEnded()
|
||||||
_currentSetIndex.value = 0
|
_currentSetIndex.value = 0
|
||||||
_currentRep.value = 0
|
_currentRep.value = 0
|
||||||
_elapsedSeconds.value = 0f
|
_elapsedSeconds.value = 0f
|
||||||
|
|||||||
+10
-7
@@ -151,14 +151,17 @@ fun ActiveWorkoutScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onEndWorkout,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
backgroundColor = Color.Red
|
||||||
|
)
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.end),
|
text = stringResource(R.string.end_workout),
|
||||||
modifier = Modifier
|
color = Color.White
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 4.dp),
|
|
||||||
color = Color.Red,
|
|
||||||
style = MaterialTheme.typography.caption3,
|
|
||||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package nl.guidoit.swimbuddy.presentation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.wear.compose.material.Button
|
||||||
|
import androidx.wear.compose.material.ButtonDefaults
|
||||||
|
import androidx.wear.compose.material.MaterialTheme
|
||||||
|
import androidx.wear.compose.material.Text
|
||||||
|
import nl.guidoit.swimbuddy.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ControlsScreen(
|
||||||
|
isPaused: Boolean,
|
||||||
|
isWaterLockActive: Boolean,
|
||||||
|
onPauseResume: () -> Unit,
|
||||||
|
onEndWorkout: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.SpaceEvenly
|
||||||
|
) {
|
||||||
|
if (isWaterLockActive) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "\uD83D\uDCA7",
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.water_lock),
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
color = Color.Cyan
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onPauseResume,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
backgroundColor = Color(0xFFFF9800)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (isPaused) stringResource(R.string.resume) else stringResource(R.string.pause),
|
||||||
|
color = Color.Black
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onEndWorkout,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
backgroundColor = Color.Red
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.end_workout),
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
package nl.guidoit.swimbuddy.presentation
|
package nl.guidoit.swimbuddy.presentation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
|
||||||
|
import androidx.wear.compose.foundation.pager.HorizontalPager
|
||||||
|
import androidx.wear.compose.foundation.pager.rememberPagerState
|
||||||
import nl.guidoit.swimbuddy.data.StoredWorkout
|
import nl.guidoit.swimbuddy.data.StoredWorkout
|
||||||
import nl.guidoit.swimbuddy.data.WorkoutSummary
|
import nl.guidoit.swimbuddy.data.WorkoutSummary
|
||||||
|
|
||||||
@@ -10,6 +15,7 @@ fun SwimBuddyApp(
|
|||||||
completedWorkouts: List<StoredWorkout>,
|
completedWorkouts: List<StoredWorkout>,
|
||||||
selectedWorkout: StoredWorkout?,
|
selectedWorkout: StoredWorkout?,
|
||||||
isWorkoutActive: Boolean,
|
isWorkoutActive: Boolean,
|
||||||
|
isPaused: Boolean,
|
||||||
currentSetIndex: Int,
|
currentSetIndex: Int,
|
||||||
currentRep: Int,
|
currentRep: Int,
|
||||||
elapsedSeconds: Float,
|
elapsedSeconds: Float,
|
||||||
@@ -22,9 +28,11 @@ fun SwimBuddyApp(
|
|||||||
totalDistanceSwum: Int,
|
totalDistanceSwum: Int,
|
||||||
currentSetDistanceSwum: Int,
|
currentSetDistanceSwum: Int,
|
||||||
currentSetTotalDistance: Int,
|
currentSetTotalDistance: Int,
|
||||||
|
isWaterLockActive: Boolean,
|
||||||
onSelectWorkout: (StoredWorkout) -> Unit,
|
onSelectWorkout: (StoredWorkout) -> Unit,
|
||||||
onStartWorkout: () -> Unit,
|
onStartWorkout: () -> Unit,
|
||||||
onCompleteLap: () -> Unit,
|
onCompleteLap: () -> Unit,
|
||||||
|
onPauseResume: () -> Unit,
|
||||||
onEndWorkout: () -> Unit,
|
onEndWorkout: () -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onDeleteSaved: (StoredWorkout) -> Unit,
|
onDeleteSaved: (StoredWorkout) -> Unit,
|
||||||
@@ -38,16 +46,8 @@ fun SwimBuddyApp(
|
|||||||
onDismiss = onDismissSummary
|
onDismiss = onDismissSummary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
isWorkoutActive && isResting -> {
|
|
||||||
RestScreen(
|
|
||||||
restTimeRemaining = restTimeRemaining,
|
|
||||||
totalDistanceSwum = totalDistanceSwum,
|
|
||||||
calories = calories,
|
|
||||||
nextSet = selectedWorkout?.sets?.getOrNull(currentSetIndex)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
isWorkoutActive -> {
|
isWorkoutActive -> {
|
||||||
ActiveWorkoutScreen(
|
ActiveWorkoutPager(
|
||||||
workout = selectedWorkout,
|
workout = selectedWorkout,
|
||||||
currentSetIndex = currentSetIndex,
|
currentSetIndex = currentSetIndex,
|
||||||
currentRep = currentRep,
|
currentRep = currentRep,
|
||||||
@@ -55,10 +55,15 @@ fun SwimBuddyApp(
|
|||||||
heartRate = heartRate,
|
heartRate = heartRate,
|
||||||
calories = calories,
|
calories = calories,
|
||||||
lapDetected = lapDetected,
|
lapDetected = lapDetected,
|
||||||
|
isResting = isResting,
|
||||||
|
restTimeRemaining = restTimeRemaining,
|
||||||
totalDistanceSwum = totalDistanceSwum,
|
totalDistanceSwum = totalDistanceSwum,
|
||||||
currentSetDistanceSwum = currentSetDistanceSwum,
|
currentSetDistanceSwum = currentSetDistanceSwum,
|
||||||
currentSetTotalDistance = currentSetTotalDistance,
|
currentSetTotalDistance = currentSetTotalDistance,
|
||||||
|
isPaused = isPaused,
|
||||||
|
isWaterLockActive = isWaterLockActive,
|
||||||
onCompleteLap = onCompleteLap,
|
onCompleteLap = onCompleteLap,
|
||||||
|
onPauseResume = onPauseResume,
|
||||||
onEndWorkout = onEndWorkout
|
onEndWorkout = onEndWorkout
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -80,3 +85,76 @@ fun SwimBuddyApp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalWearFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun ActiveWorkoutPager(
|
||||||
|
workout: StoredWorkout?,
|
||||||
|
currentSetIndex: Int,
|
||||||
|
currentRep: Int,
|
||||||
|
elapsedSeconds: Float,
|
||||||
|
heartRate: Int,
|
||||||
|
calories: Float,
|
||||||
|
lapDetected: Boolean,
|
||||||
|
isResting: Boolean,
|
||||||
|
restTimeRemaining: Float,
|
||||||
|
totalDistanceSwum: Int,
|
||||||
|
currentSetDistanceSwum: Int,
|
||||||
|
currentSetTotalDistance: Int,
|
||||||
|
isPaused: Boolean,
|
||||||
|
isWaterLockActive: Boolean,
|
||||||
|
onCompleteLap: () -> Unit,
|
||||||
|
onPauseResume: () -> Unit,
|
||||||
|
onEndWorkout: () -> Unit
|
||||||
|
) {
|
||||||
|
val pagerState = rememberPagerState(pageCount = { 3 })
|
||||||
|
|
||||||
|
HorizontalPager(
|
||||||
|
state = pagerState,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) { page ->
|
||||||
|
when (page) {
|
||||||
|
0 -> {
|
||||||
|
if (isResting) {
|
||||||
|
RestScreen(
|
||||||
|
restTimeRemaining = restTimeRemaining,
|
||||||
|
totalDistanceSwum = totalDistanceSwum,
|
||||||
|
calories = calories,
|
||||||
|
nextSet = workout?.sets?.getOrNull(currentSetIndex)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ActiveWorkoutScreen(
|
||||||
|
workout = workout,
|
||||||
|
currentSetIndex = currentSetIndex,
|
||||||
|
currentRep = currentRep,
|
||||||
|
elapsedSeconds = elapsedSeconds,
|
||||||
|
heartRate = heartRate,
|
||||||
|
calories = calories,
|
||||||
|
lapDetected = lapDetected,
|
||||||
|
totalDistanceSwum = totalDistanceSwum,
|
||||||
|
currentSetDistanceSwum = currentSetDistanceSwum,
|
||||||
|
currentSetTotalDistance = currentSetTotalDistance,
|
||||||
|
onCompleteLap = onCompleteLap,
|
||||||
|
onEndWorkout = onEndWorkout
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
WorkoutOverviewScreen(
|
||||||
|
workout = workout,
|
||||||
|
currentSetIndex = currentSetIndex,
|
||||||
|
isResting = isResting,
|
||||||
|
restTimeRemaining = restTimeRemaining
|
||||||
|
)
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
ControlsScreen(
|
||||||
|
isPaused = isPaused,
|
||||||
|
isWaterLockActive = isWaterLockActive,
|
||||||
|
onPauseResume = onPauseResume,
|
||||||
|
onEndWorkout = onEndWorkout
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+20
@@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.wear.compose.material.Button
|
import androidx.wear.compose.material.Button
|
||||||
import androidx.wear.compose.material.ButtonDefaults
|
import androidx.wear.compose.material.ButtonDefaults
|
||||||
import androidx.wear.compose.material.MaterialTheme
|
import androidx.wear.compose.material.MaterialTheme
|
||||||
@@ -55,6 +56,7 @@ fun WorkoutDetailScreen(
|
|||||||
}
|
}
|
||||||
items(workout.sets.size) { index ->
|
items(workout.sets.size) { index ->
|
||||||
val set = workout.sets[index]
|
val set = workout.sets[index]
|
||||||
|
Column {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
@@ -69,6 +71,24 @@ fun WorkoutDetailScreen(
|
|||||||
color = MaterialTheme.colors.onSecondary.copy(alpha = 0.7f)
|
color = MaterialTheme.colors.onSecondary.copy(alpha = 0.7f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (set.restType != "none" && set.restDuration > 0) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(start = 14.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "\u23F0",
|
||||||
|
fontSize = 10.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(2.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.rest_duration, set.restDuration.toInt()),
|
||||||
|
style = MaterialTheme.typography.caption2,
|
||||||
|
color = Color(0xFFFF9800)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
package nl.guidoit.swimbuddy.presentation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.wear.compose.material.ListColumn
|
||||||
|
import androidx.wear.compose.material.MaterialTheme
|
||||||
|
import androidx.wear.compose.material.Text
|
||||||
|
import nl.guidoit.swimbuddy.R
|
||||||
|
import nl.guidoit.swimbuddy.data.StoredWorkout
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WorkoutOverviewScreen(
|
||||||
|
workout: StoredWorkout?,
|
||||||
|
currentSetIndex: Int,
|
||||||
|
isResting: Boolean,
|
||||||
|
restTimeRemaining: Float
|
||||||
|
) {
|
||||||
|
if (workout == null) return
|
||||||
|
|
||||||
|
val restMins = (restTimeRemaining / 60).toInt()
|
||||||
|
val restSecs = (restTimeRemaining % 60).toInt()
|
||||||
|
|
||||||
|
ListColumn(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
if (isResting) {
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "\u23F8",
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.rest) + " " + String.format("%d:%02d", restMins, restSecs),
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
color = Color(0xFFFF9800)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(workout.sets.size) { index ->
|
||||||
|
val set = workout.sets[index]
|
||||||
|
val statusColor = when {
|
||||||
|
index < currentSetIndex -> Color.Green
|
||||||
|
index == currentSetIndex -> Color.Cyan
|
||||||
|
else -> Color.Gray.copy(alpha = 0.4f)
|
||||||
|
}
|
||||||
|
val textColor = if (index <= currentSetIndex) {
|
||||||
|
MaterialTheme.colors.onPrimary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colors.onSecondary.copy(alpha = 0.7f)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Canvas(modifier = Modifier.size(8.dp)) {
|
||||||
|
drawCircle(color = statusColor)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "${index + 1}. ${set.stroke}",
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
color = textColor,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "${set.repCount}x${set.distancePerRep}m",
|
||||||
|
style = MaterialTheme.typography.caption2,
|
||||||
|
color = MaterialTheme.colors.onSecondary.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (set.restType != "none" && set.restDuration > 0) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(start = 22.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "\u23F0",
|
||||||
|
fontSize = 10.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(2.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.rest_duration, set.restDuration.toInt()),
|
||||||
|
style = MaterialTheme.typography.caption2,
|
||||||
|
color = Color(0xFFFF9800)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,4 +27,10 @@
|
|||||||
<string name="laps_label">Omgange</string>
|
<string name="laps_label">Omgange</string>
|
||||||
<string name="tap_to_dismiss">Tryk for at lukke</string>
|
<string name="tap_to_dismiss">Tryk for at lukke</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
|
<string name="water_lock">Water Lock</string>
|
||||||
|
<string name="pause">Pause</string>
|
||||||
|
<string name="resume">Genoptag</string>
|
||||||
|
<string name="end_workout">Afslut træning</string>
|
||||||
|
<string name="workout_overview">Oversigt</string>
|
||||||
|
<string name="rest_duration">%ds pause</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,4 +27,10 @@
|
|||||||
<string name="laps_label">Runden</string>
|
<string name="laps_label">Runden</string>
|
||||||
<string name="tap_to_dismiss">Tippen zum Schließen</string>
|
<string name="tap_to_dismiss">Tippen zum Schließen</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
|
<string name="water_lock">Water Lock</string>
|
||||||
|
<string name="pause">Pause</string>
|
||||||
|
<string name="resume">Fortsetzen</string>
|
||||||
|
<string name="end_workout">Training beenden</string>
|
||||||
|
<string name="workout_overview">Übersicht</string>
|
||||||
|
<string name="rest_duration">%ds Pause</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,4 +27,10 @@
|
|||||||
<string name="laps_label">Vueltas</string>
|
<string name="laps_label">Vueltas</string>
|
||||||
<string name="tap_to_dismiss">Toca para cerrar</string>
|
<string name="tap_to_dismiss">Toca para cerrar</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
|
<string name="water_lock">Water Lock</string>
|
||||||
|
<string name="pause">Pausa</string>
|
||||||
|
<string name="resume">Reanudar</string>
|
||||||
|
<string name="end_workout">Finalizar entrenamiento</string>
|
||||||
|
<string name="workout_overview">Resumen</string>
|
||||||
|
<string name="rest_duration">%ds descanso</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,4 +27,10 @@
|
|||||||
<string name="laps_label">Tours</string>
|
<string name="laps_label">Tours</string>
|
||||||
<string name="tap_to_dismiss">Appuyez pour fermer</string>
|
<string name="tap_to_dismiss">Appuyez pour fermer</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
|
<string name="water_lock">Water Lock</string>
|
||||||
|
<string name="pause">Pause</string>
|
||||||
|
<string name="resume">Reprendre</string>
|
||||||
|
<string name="end_workout">Terminer l\'entraînement</string>
|
||||||
|
<string name="workout_overview">Aperçu</string>
|
||||||
|
<string name="rest_duration">%ds repos</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,4 +27,10 @@
|
|||||||
<string name="laps_label">Giri</string>
|
<string name="laps_label">Giri</string>
|
||||||
<string name="tap_to_dismiss">Tocca per chiudere</string>
|
<string name="tap_to_dismiss">Tocca per chiudere</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
|
<string name="water_lock">Water Lock</string>
|
||||||
|
<string name="pause">Pausa</string>
|
||||||
|
<string name="resume">Riprendi</string>
|
||||||
|
<string name="end_workout">Termina allenamento</string>
|
||||||
|
<string name="workout_overview">Panoramica</string>
|
||||||
|
<string name="rest_duration">%ds pausa</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,4 +27,10 @@
|
|||||||
<string name="laps_label">Rondes</string>
|
<string name="laps_label">Rondes</string>
|
||||||
<string name="tap_to_dismiss">Tik om te sluiten</string>
|
<string name="tap_to_dismiss">Tik om te sluiten</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
|
<string name="water_lock">Water Lock</string>
|
||||||
|
<string name="pause">Pauze</string>
|
||||||
|
<string name="resume">Hervat</string>
|
||||||
|
<string name="end_workout">Training beëindigen</string>
|
||||||
|
<string name="workout_overview">Overzicht</string>
|
||||||
|
<string name="rest_duration">%ds rust</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,4 +27,10 @@
|
|||||||
<string name="laps_label">Okrążenia</string>
|
<string name="laps_label">Okrążenia</string>
|
||||||
<string name="tap_to_dismiss">Stuknij aby zamknąć</string>
|
<string name="tap_to_dismiss">Stuknij aby zamknąć</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
|
<string name="water_lock">Water Lock</string>
|
||||||
|
<string name="pause">Pauza</string>
|
||||||
|
<string name="resume">Wznów</string>
|
||||||
|
<string name="end_workout">Zakończ trening</string>
|
||||||
|
<string name="workout_overview">Przegląd</string>
|
||||||
|
<string name="rest_duration">%ds przerwy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,4 +27,10 @@
|
|||||||
<string name="laps_label">Voltas</string>
|
<string name="laps_label">Voltas</string>
|
||||||
<string name="tap_to_dismiss">Toque para fechar</string>
|
<string name="tap_to_dismiss">Toque para fechar</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
|
<string name="water_lock">Water Lock</string>
|
||||||
|
<string name="pause">Pausar</string>
|
||||||
|
<string name="resume">Retomar</string>
|
||||||
|
<string name="end_workout">Terminar treino</string>
|
||||||
|
<string name="workout_overview">Resumo</string>
|
||||||
|
<string name="rest_duration">%ds descanso</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,4 +27,10 @@
|
|||||||
<string name="laps_label">Laps</string>
|
<string name="laps_label">Laps</string>
|
||||||
<string name="tap_to_dismiss">Tap to dismiss</string>
|
<string name="tap_to_dismiss">Tap to dismiss</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
|
<string name="water_lock">Water Lock</string>
|
||||||
|
<string name="pause">Pause</string>
|
||||||
|
<string name="resume">Resume</string>
|
||||||
|
<string name="end_workout">End Workout</string>
|
||||||
|
<string name="workout_overview">Overview</string>
|
||||||
|
<string name="rest_duration">%ds rest</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user