Skip to content

Commit

Permalink
Merge pull request #1103 from kul3r4/speaker-sample
Browse files Browse the repository at this point in the history
Simplify layout of Speaker Sample
  • Loading branch information
kul3r4 authored May 20, 2024
2 parents 80dc6b7 + 5cfc6a4 commit a47e848
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 145 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ Samples

* **[WearOAuth](WearOAuth)** (Kotlin) - Demonstrates how developers can authenticate a user on their Wear OS app via the user's mobile/phone device without requiring a mobile app (Wear OS companion app handles the request on the mobile side). The sample uses OAuth. [Guide](https://developer.android.com/training/wearables/apps/auth-wear)

* **[WearSpeakerSample](WearSpeakerSample)** (Compose/Kotlin) - Demonstrates audio recording and playback if the wearable device has a speaker. This is also an advanced Compose sample, handling permissions, use of [effects](https://developer.android.com/jetpack/compose/side-effects), animations and [ConstraintLayout for Compose](https://developer.android.com/jetpack/compose/layouts/constraintlayout). [Guide](https://developer.android.com/training/wearables/wearable-sounds)
* **[WearSpeakerSample](WearSpeakerSample)** (Compose/Kotlin) - Demonstrates audio recording and playback if the wearable device has a speaker. This is also demonstrate how to handling permissions. [Guide](https://developer.android.com/training/wearables/wearable-sounds)

* **[WearTilesKotlin](WearTilesKotlin)** (Kotlin) - Demonstrates tiles using the new AndroidX library. [Guide](https://developer.android.com/training/articles/wear-tiles)
2 changes: 0 additions & 2 deletions WearSpeakerSample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ play the recorded voice, if the wearable device is connected to a speaker

This sample is also written entirely with Jetpack Compose, using [Wear-specific components provided
by AndroidX](https://developer.android.com/jetpack/androidx/releases/wear-compose).
The Compose UI is fairly advanced, using [ConstraintLayout for Compose](https://developer.android.com/jetpack/compose/layouts/constraintlayout).
The sample also handles permissions and uses the [effect APIs](https://developer.android.com/jetpack/compose/side-effects) where necessary.
If you are looking for a simpler example of Compose on [WearOS], refer to [ComposeStarter](../ComposeStarter).

This sample doesn't have any companion phone app so you need to install this directly
Expand Down
2 changes: 0 additions & 2 deletions WearSpeakerSample/wear/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ dependencies {

implementation libs.androidx.ui.test.manifest

implementation libs.androidx.constraintlayout.compose

coreLibraryDesugaring libs.desugar.jdk.libs

// Testing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,19 @@

package com.example.android.wearable.speaker

import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.ConstraintSet
import androidx.constraintlayout.compose.Dimension
import androidx.wear.compose.material.Button
import androidx.wear.compose.material.CircularProgressIndicator
import androidx.wear.compose.material.Icon
Expand All @@ -52,81 +50,38 @@ fun ControlDashboard(
recordingProgress: Float,
modifier: Modifier = Modifier
) {
val circle = Any()
val mic = Any()
val play = Any()

val constraintSet = createConstraintSet(
circle = circle,
mic = mic,
play = play
)

// We are using ConstraintLayout here for the circular constraints
// In general, ConstraintLayout is less necessary for Compose than it was for Views
ConstraintLayout(
constraintSet = constraintSet,
modifier = modifier
Box(
contentAlignment = Alignment.Center,
modifier = modifier.fillMaxSize()
) {
Spacer(
modifier = modifier.layoutId(circle)
)
CircularProgressIndicator(
modifier = Modifier.fillMaxSize(),
progress = recordingProgress
)

ControlDashboardButton(
buttonState = controlDashboardUiState.micState,
onClick = onMicClicked,
layoutId = mic,
imageVector = Icons.Filled.Mic,
contentDescription = if (controlDashboardUiState.micState.expanded) {
stringResource(id = R.string.stop_recording)
} else {
stringResource(id = R.string.record)
}
)

ControlDashboardButton(
buttonState = controlDashboardUiState.playState,
onClick = onPlayClicked,
layoutId = play,
imageVector = Icons.Filled.PlayArrow,
contentDescription = stringResource(id = R.string.play_recording)
)
}
}

/**
* Creates the [ConstraintSet].
*
* The [circle], [mic], [play] are used as keys for the constraints.
*/
@Composable
private fun createConstraintSet(
circle: Any,
mic: Any,
play: Any
): ConstraintSet {
val iconCircleRadius = 32.dp
val iconMinimizedSize = 48.dp
// Show the progress indicator only when recording
if (controlDashboardUiState.micState.expanded) {
CircularProgressIndicator(
progress = recordingProgress,
modifier = modifier.fillMaxSize()
)
}

return ConstraintSet {
val circleRef = createRefFor(circle)
val micRef = createRefFor(mic)
val playRef = createRefFor(play)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
ControlDashboardButton(
buttonState = controlDashboardUiState.micState,
onClick = onMicClicked,
imageVector = Icons.Filled.Mic,
contentDescription = if (controlDashboardUiState.micState.expanded) {
stringResource(id = R.string.stop_recording)
} else {
stringResource(id = R.string.record)
}
)

constrain(circleRef) { centerTo(parent) }
constrain(playRef) {
width = Dimension.value(iconMinimizedSize)
height = Dimension.value(iconMinimizedSize)
circular(circleRef, 90f, iconCircleRadius)
}
constrain(micRef) {
width = Dimension.value(iconMinimizedSize)
height = Dimension.value(iconMinimizedSize)
circular(circleRef, 270f, iconCircleRadius)
ControlDashboardButton(
buttonState = controlDashboardUiState.playState,
onClick = onPlayClicked,
imageVector = Icons.Filled.PlayArrow,
contentDescription = stringResource(id = R.string.play_recording)
)
}
}
}
Expand All @@ -138,28 +93,18 @@ private fun createConstraintSet(
private fun ControlDashboardButton(
buttonState: ControlDashboardButtonUiState,
onClick: () -> Unit,
layoutId: Any,
imageVector: ImageVector,
contentDescription: String,
modifier: Modifier = Modifier
) {
val iconPadding = 8.dp
// TODO: Replace with a version of IconButton?
// https://issuetracker.google.com/issues/203123015

Button(
modifier = modifier
.fillMaxSize()
.layoutId(layoutId),
modifier = modifier,
enabled = buttonState.enabled && buttonState.visible,
onClick = onClick
) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
modifier = Modifier
.fillMaxSize()
.padding(iconPadding)
contentDescription = contentDescription
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,9 @@
*/
package com.example.android.wearable.speaker

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices
import com.google.android.horologist.compose.layout.ScreenScaffold

Expand All @@ -49,49 +42,12 @@ fun SpeakerRecordingScreen(
isPermissionDenied = isPermissionDenied
)

// The progress bar should only be visible when actively recording
val isProgressVisible =
when (playbackState) {
PlaybackState.PlayingVoice,
is PlaybackState.Ready -> false
PlaybackState.Recording -> true
}

// We are using ConstraintLayout here to center the ControlDashboard, and align the progress
// indicator to it.
// In general, ConstraintLayout is less necessary for Compose than it was for Views
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val (controlDashboard, progressBar) = createRefs()

ControlDashboard(
controlDashboardUiState = controlDashboardUiState,
onMicClicked = onMicClicked,
onPlayClicked = onPlayClicked,
recordingProgress = recordingProgress,
modifier = Modifier
.constrainAs(controlDashboard) {
centerTo(parent)
}
)
AnimatedVisibility(
visible = isProgressVisible,
modifier = Modifier
.constrainAs(progressBar) {
width = Dimension.fillToConstraints
top.linkTo(controlDashboard.bottom, 5.dp)
start.linkTo(controlDashboard.start)
end.linkTo(controlDashboard.end)
}
) {
LinearProgressIndicator(
progress = {
recordingProgress
}
)
}
}
ControlDashboard(
controlDashboardUiState = controlDashboardUiState,
onMicClicked = onMicClicked,
onPlayClicked = onPlayClicked,
recordingProgress = recordingProgress
)
}
}

Expand Down

0 comments on commit a47e848

Please sign in to comment.