Skip to content

Commit 7d1a8ec

Browse files
authored
Fix volume management for device group (#89)
1 parent 920cb00 commit 7d1a8ec

8 files changed

Lines changed: 395 additions & 124 deletions

File tree

mediarouter-compose/src/main/java/ch/srgssr/androidx/mediarouter/compose/MediaRouteControllerDialog.kt

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import androidx.compose.runtime.Composable
2525
import androidx.compose.runtime.LaunchedEffect
2626
import androidx.compose.runtime.collectAsState
2727
import androidx.compose.runtime.getValue
28-
import androidx.compose.runtime.mutableFloatStateOf
2928
import androidx.compose.runtime.mutableStateOf
3029
import androidx.compose.runtime.remember
3130
import androidx.compose.runtime.setValue
@@ -44,6 +43,7 @@ import androidx.mediarouter.R
4443
import androidx.mediarouter.media.MediaRouteSelector
4544
import androidx.mediarouter.media.MediaRouter
4645
import androidx.mediarouter.media.MediaRouter.RouteInfo
46+
import ch.srgssr.androidx.mediarouter.compose.MediaRouteControllerDialogViewModel.RouteDetail
4747
import coil3.compose.AsyncImage
4848
import coil3.compose.AsyncImagePainter
4949

@@ -76,14 +76,16 @@ fun MediaRouteControllerDialog(
7676
factory = MediaRouteControllerDialogViewModel.Factory(volumeControlEnabled),
7777
)
7878
val showDialog by viewModel.showDialog.collectAsState()
79-
val selectedRoute by viewModel.selectedRoute.collectAsState()
8079
val isDeviceGroupExpanded by viewModel.isDeviceGroupExpanded.collectAsState()
8180
val showPlaybackControl by viewModel.showPlaybackControl.collectAsState()
8281
val showVolumeControl by viewModel.showVolumeControl.collectAsState()
8382
val imageModel by viewModel.imageModel.collectAsState()
8483
val title by viewModel.title.collectAsState()
8584
val subtitle by viewModel.subtitle.collectAsState()
8685
val iconInfo by viewModel.iconInfo.collectAsState()
86+
val routes by viewModel.routes.collectAsState()
87+
val selectedRouteDetail = routes[0]
88+
val groupRouteDetails = routes.drop(1)
8789

8890
LaunchedEffect(showDialog) {
8991
if (!showDialog) {
@@ -92,15 +94,15 @@ fun MediaRouteControllerDialog(
9294
}
9395

9496
ControllerDialog(
95-
route = selectedRoute,
96-
volumeControlEnabled = volumeControlEnabled,
97+
routeDetail = selectedRouteDetail,
9798
imageModel = imageModel,
9899
title = title,
99100
subtitle = subtitle,
100101
iconInfo = iconInfo,
101102
isDeviceGroupExpanded = isDeviceGroupExpanded,
102103
showPlaybackControl = showPlaybackControl,
103104
showVolumeControl = showVolumeControl,
105+
groupRouteDetails = groupRouteDetails,
104106
modifier = modifier,
105107
customControlView = customControlView,
106108
toggleDeviceGroup = viewModel::toggleDeviceGroup,
@@ -110,20 +112,21 @@ fun MediaRouteControllerDialog(
110112
onStopCasting = viewModel::stopCasting,
111113
onDisconnect = viewModel::disconnect,
112114
onDismissRequest = viewModel::hideDialog,
115+
onVolumeChange = viewModel::setRouteVolume,
113116
)
114117
}
115118

116119
@Composable
117120
internal fun ControllerDialog(
118-
route: RouteInfo,
119-
volumeControlEnabled: Boolean,
121+
routeDetail: RouteDetail,
120122
imageModel: Any?,
121123
title: String?,
122124
subtitle: String?,
123125
iconInfo: Pair<ImageVector, String>?,
124126
isDeviceGroupExpanded: Boolean,
125127
showPlaybackControl: Boolean,
126128
showVolumeControl: Boolean,
129+
groupRouteDetails: List<RouteDetail>,
127130
modifier: Modifier = Modifier,
128131
customControlView: @Composable (() -> Unit)?,
129132
toggleDeviceGroup: () -> Unit,
@@ -133,6 +136,7 @@ internal fun ControllerDialog(
133136
onStopCasting: () -> Unit,
134137
onDisconnect: () -> Unit,
135138
onDismissRequest: () -> Unit,
139+
onVolumeChange: (route: RouteInfo, volume: Float) -> Unit,
136140
) {
137141
AlertDialog(
138142
onDismissRequest = onDismissRequest,
@@ -142,7 +146,7 @@ internal fun ControllerDialog(
142146
}
143147
},
144148
modifier = modifier.onKeyEvent(onKeyEvent),
145-
dismissButton = if (route.canDisconnect()) {
149+
dismissButton = if (routeDetail.route.canDisconnect()) {
146150
{
147151
TextButton(onClick = onDisconnect) {
148152
Text(text = stringResource(R.string.mr_controller_disconnect))
@@ -158,7 +162,7 @@ internal fun ControllerDialog(
158162
verticalAlignment = Alignment.CenterVertically,
159163
) {
160164
Text(
161-
text = route.name,
165+
text = routeDetail.route.name,
162166
overflow = TextOverflow.Ellipsis,
163167
maxLines = 1,
164168
)
@@ -173,41 +177,43 @@ internal fun ControllerDialog(
173177
},
174178
text = {
175179
ControllerDialogContent(
176-
route = route,
177-
volumeControlEnabled = volumeControlEnabled,
180+
routeDetail = routeDetail,
178181
imageModel = imageModel,
179182
title = title,
180183
subtitle = subtitle,
181184
iconInfo = iconInfo,
182185
isDeviceGroupExpanded = isDeviceGroupExpanded,
183186
showPlaybackControl = showPlaybackControl,
184187
showVolumeControl = showVolumeControl,
188+
groupRouteDetails = groupRouteDetails,
185189
modifier = Modifier.fillMaxWidth(),
186190
customControlView = customControlView,
187191
onToggleDeviceGroup = toggleDeviceGroup,
188192
onPlaybackTitleClick = onPlaybackTitleClick,
189193
onPlaybackIconClick = onPlaybackIconClick,
194+
onVolumeChange = onVolumeChange,
190195
)
191196
},
192197
)
193198
}
194199

195200
@Composable
196201
private fun ControllerDialogContent(
197-
route: RouteInfo,
198-
volumeControlEnabled: Boolean,
202+
routeDetail: RouteDetail,
199203
imageModel: Any?,
200204
title: String?,
201205
subtitle: String?,
202206
iconInfo: Pair<ImageVector, String>?,
203207
isDeviceGroupExpanded: Boolean,
204208
showPlaybackControl: Boolean,
205209
showVolumeControl: Boolean,
210+
groupRouteDetails: List<RouteDetail>,
206211
modifier: Modifier = Modifier,
207212
customControlView: @Composable (() -> Unit)?,
208213
onToggleDeviceGroup: () -> Unit,
209214
onPlaybackTitleClick: () -> Unit,
210215
onPlaybackIconClick: () -> Unit,
216+
onVolumeChange: (route: RouteInfo, volume: Float) -> Unit,
211217
) {
212218
@Suppress("NoNameShadowing")
213219
val showPlaybackControl = showPlaybackControl && customControlView == null
@@ -242,21 +248,22 @@ private fun ControllerDialogContent(
242248

243249
if (showVolumeControl) {
244250
VolumeControl(
245-
route = route,
251+
routeDetail = routeDetail,
246252
modifier = Modifier.fillMaxWidth(),
247253
isExpanded = isDeviceGroupExpanded,
248254
onExpandCollapseClick = onToggleDeviceGroup,
255+
onVolumeChange = onVolumeChange,
249256
)
250257
}
251258
}
252259

253260
if (isDeviceGroupExpanded) {
254261
DeviceGroup(
255-
routes = route.memberRoutes,
256-
volumeControlEnabled = volumeControlEnabled,
262+
routeDetails = groupRouteDetails,
257263
modifier = Modifier
258264
.fillMaxWidth()
259265
.padding(top = 16.dp),
266+
onVolumeChange = onVolumeChange,
260267
)
261268
}
262269
}
@@ -341,32 +348,30 @@ private fun PlaybackControlRow(
341348

342349
@Composable
343350
private fun VolumeControl(
344-
route: RouteInfo,
351+
routeDetail: RouteDetail,
345352
modifier: Modifier = Modifier,
346353
isExpanded: Boolean,
347354
onExpandCollapseClick: () -> Unit,
355+
onVolumeChange: (route: RouteInfo, volume: Float) -> Unit,
348356
) {
349357
Row(
350358
modifier = modifier,
351359
horizontalArrangement = Arrangement.spacedBy(8.dp),
352360
verticalAlignment = Alignment.CenterVertically,
353361
) {
354-
val (volume, setVolume) = remember { mutableFloatStateOf(route.volume.toFloat()) }
355-
356362
Icon(
357363
imageVector = Icons.Audiotrack,
358364
contentDescription = null,
359365
)
360366

361367
Slider(
362-
value = volume,
363-
onValueChange = setVolume,
368+
value = routeDetail.volume,
369+
onValueChange = { onVolumeChange(routeDetail.route, it) },
364370
modifier = Modifier.weight(1f),
365-
valueRange = 0f..route.volumeMax.toFloat(),
366-
onValueChangeFinished = { route.requestSetVolume(volume.toInt()) },
371+
valueRange = routeDetail.volumeRange,
367372
)
368373

369-
if (route.isGroup && route.memberRoutes.size > 1) {
374+
if (routeDetail.route.isGroup && routeDetail.route.memberRoutes.size > 1) {
370375
IconButton(onClick = onExpandCollapseClick) {
371376
val scale by animateFloatAsState(targetValue = if (isExpanded) -1f else 1f)
372377
val contentDescriptionRes = if (isExpanded) {
@@ -387,27 +392,16 @@ private fun VolumeControl(
387392

388393
@Composable
389394
private fun DeviceGroup(
390-
routes: List<RouteInfo>,
391-
volumeControlEnabled: Boolean,
395+
routeDetails: List<RouteDetail>,
392396
modifier: Modifier = Modifier,
397+
onVolumeChange: (route: RouteInfo, volume: Float) -> Unit,
393398
) {
394399
LazyColumn(
395400
modifier = modifier,
396401
verticalArrangement = Arrangement.spacedBy(16.dp),
397402
) {
398-
items(routes) { route ->
399-
val isVolumeControlEnabled = volumeControlEnabled
400-
&& route.volumeHandling == RouteInfo.PLAYBACK_VOLUME_VARIABLE
401-
val volumeMax = if (isVolumeControlEnabled) route.volumeMax else 100
402-
val volumeRange = 0f..volumeMax.toFloat()
403-
404-
var volume by remember {
405-
mutableFloatStateOf(if (isVolumeControlEnabled) route.volume.toFloat() else 100f)
406-
}
407-
408-
Column(
409-
modifier = Modifier.fillMaxWidth(),
410-
) {
403+
items(routeDetails) { (route, volume, volumeRange) ->
404+
Column(modifier = Modifier.fillMaxWidth()) {
411405
Text(
412406
text = route.name,
413407
maxLines = 1,
@@ -426,19 +420,10 @@ private fun DeviceGroup(
426420

427421
Slider(
428422
value = volume,
429-
onValueChange = {
430-
if (isVolumeControlEnabled) {
431-
volume = it
432-
}
433-
},
423+
onValueChange = { onVolumeChange(route, it) },
434424
modifier = Modifier.weight(1f),
435425
enabled = route.isEnabled,
436426
valueRange = volumeRange,
437-
onValueChangeFinished = {
438-
if (isVolumeControlEnabled) {
439-
route.requestSetVolume(volume.toInt())
440-
}
441-
},
442427
)
443428
}
444429
}

0 commit comments

Comments
 (0)