Skip to content

Commit 9d3ce61

Browse files
authored
Use Split Button on item details screen (#127)
1 parent e37fca6 commit 9d3ce61

8 files changed

Lines changed: 101 additions & 89 deletions

File tree

composeApp/build.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,10 @@ kotlin {
5252
commonMain.dependencies {
5353
implementation(compose.runtime)
5454
implementation(compose.foundation)
55-
// implementation(compose.material)
56-
implementation(compose.material3)
5755
implementation(compose.ui)
5856
implementation(compose.components.resources)
5957
implementation(compose.components.uiToolingPreview)
58+
implementation(libs.material)
6059
implementation(libs.androidx.lifecycle.viewmodel)
6160
implementation(libs.androidx.lifecycle.runtime.compose)
6261
implementation(libs.androidx.navigation3.ui)

composeApp/src/commonMain/kotlin/io/music_assistant/client/ui/compose/common/OverflowMenu.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import androidx.compose.ui.unit.dp
2020
@Composable
2121
fun OverflowMenu(
2222
modifier: Modifier = Modifier,
23-
buttonContent: @Composable (onClick: () -> Unit) -> Unit,
2423
options: List<OverflowMenuOption>,
24+
buttonContent: @Composable (onClick: () -> Unit) -> Unit,
2525
) {
2626
var expanded by remember { mutableStateOf(false) }
2727
Box(
@@ -59,4 +59,4 @@ data class OverflowMenuOption(
5959
val title: String,
6060
val icon: ImageVector? = null,
6161
val onClick: () -> Unit
62-
)
62+
)

composeApp/src/commonMain/kotlin/io/music_assistant/client/ui/compose/home/CollapsibleQueue.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,6 @@ fun CollapsibleQueue(
109109
) {
110110
// Transfer button
111111
OverflowMenu(
112-
modifier = Modifier,
113-
buttonContent = {
114-
OutlinedButton(onClick = it) {
115-
Text("Transfer")
116-
}
117-
},
118112
options = players.filter { p -> p.player.id != queueId }.map { playerData ->
119113
OverflowMenuOption(
120114
title = playerData.player.displayName,
@@ -137,6 +131,11 @@ fun CollapsibleQueue(
137131
onClick = { /* No-op */ }
138132
)
139133
)
134+
},
135+
buttonContent = {
136+
OutlinedButton(onClick = it) {
137+
Text("Transfer")
138+
}
140139
}
141140
)
142141

@@ -411,4 +410,4 @@ fun CollapsibleQueue(
411410
}
412411
}
413412
}
414-
}
413+
}

composeApp/src/commonMain/kotlin/io/music_assistant/client/ui/compose/home/PlayersTopBar.kt

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,15 @@ fun PlayersTopBar(
5454
)
5555

5656
OverflowMenu(
57-
modifier = Modifier,
57+
options = playerDataList.map { data ->
58+
val isLocalPlayer = data.playerId == playersState.localPlayerId
59+
OverflowMenuOption(
60+
title = data.player.displayName + (if (isLocalPlayer) " (local)" else ""),
61+
icon = Icons.Filled.Speaker,
62+
) {
63+
onMoveToPlayer(data.player.id)
64+
}
65+
},
5866
buttonContent = { onClick ->
5967
IconButton(
6068
modifier = Modifier.size(32.dp),
@@ -67,15 +75,6 @@ fun PlayersTopBar(
6775
tint = MaterialTheme.colorScheme.primary,
6876
)
6977
}
70-
},
71-
options = playerDataList.map { data ->
72-
val isLocalPlayer = data.playerId == playersState.localPlayerId
73-
OverflowMenuOption(
74-
title = data.player.displayName + (if (isLocalPlayer) " (local)" else ""),
75-
icon = Icons.Filled.Speaker,
76-
) {
77-
onMoveToPlayer(data.player.id)
78-
}
7978
}
8079
)
8180
}

composeApp/src/commonMain/kotlin/io/music_assistant/client/ui/compose/item/ItemDetailsScreen.kt

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,8 @@ import androidx.compose.foundation.lazy.grid.GridCells
1515
import androidx.compose.foundation.lazy.grid.GridItemSpan
1616
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
1717
import androidx.compose.foundation.lazy.grid.items
18-
import androidx.compose.material.icons.Icons
19-
import androidx.compose.material.icons.automirrored.filled.ViewList
20-
import androidx.compose.material.icons.filled.GridView
2118
import androidx.compose.material3.CircularProgressIndicator
2219
import androidx.compose.material3.ExperimentalMaterial3Api
23-
import androidx.compose.material3.Icon
24-
import androidx.compose.material3.IconButton
2520
import androidx.compose.material3.MaterialTheme
2621
import androidx.compose.material3.Scaffold
2722
import androidx.compose.material3.Text
@@ -238,10 +233,12 @@ private fun ItemChildren(
238233
ItemHeader(
239234
item = item,
240235
serverUrl = serverUrl,
236+
isRowMode = isRowMode,
241237
onBack = onBack,
242238
onPlayClick = onPlayItemClick,
243239
libraryAction = libraryActions,
244-
playlistActions = playlistActions
240+
playlistActions = playlistActions,
241+
onToggleViewMode = onToggleViewMode
245242
)
246243
}
247244

@@ -251,11 +248,7 @@ private fun ItemChildren(
251248
is DataState.Data -> {
252249
if (albumsState.data.isNotEmpty()) {
253250
item(span = { GridItemSpan(maxLineSpan) }) {
254-
SectionHeader(
255-
"Albums",
256-
isRowMode = isRowMode,
257-
onToggleViewMode = onToggleViewMode
258-
)
251+
SectionHeader("Albums")
259252
}
260253
items(
261254
albumsState.data,
@@ -297,11 +290,7 @@ private fun ItemChildren(
297290
val chapters = item.chapters
298291
if (!chapters.isNullOrEmpty()) {
299292
item(span = { GridItemSpan(maxLineSpan) }) {
300-
SectionHeader(
301-
"Chapters",
302-
isRowMode = isRowMode,
303-
onToggleViewMode = null
304-
)
293+
SectionHeader("Chapters",)
305294
}
306295
chapters.forEach { chapter ->
307296
item(span = { GridItemSpan(maxLineSpan) }) {
@@ -325,9 +314,7 @@ private fun ItemChildren(
325314
when (item) {
326315
is AppMediaItem.Podcast -> "Episodes"
327316
else -> "Tracks"
328-
},
329-
isRowMode = isRowMode,
330-
onToggleViewMode = onToggleViewMode
317+
}
331318
)
332319
}
333320
tracksState.data.forEachIndexed { index, track ->
@@ -404,24 +391,14 @@ private fun ItemChildren(
404391
}
405392

406393
@Composable
407-
private fun SectionHeader(title: String, isRowMode: Boolean, onToggleViewMode: (() -> Unit)? = null) {
394+
private fun SectionHeader(title: String) {
408395
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) {
409396
Text(
410397
text = title,
411398
style = MaterialTheme.typography.titleMedium,
412399
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp)
413400
)
414-
415-
if (onToggleViewMode != null) {
416-
IconButton(onClick = onToggleViewMode) {
417-
Icon(
418-
imageVector = if (isRowMode) Icons.Default.GridView else Icons.AutoMirrored.Filled.ViewList,
419-
contentDescription = "Toggle view mode"
420-
)
421-
}
422-
}
423401
}
424-
425402
}
426403

427404
@Composable

composeApp/src/commonMain/kotlin/io/music_assistant/client/ui/compose/item/ItemHeader.kt

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.music_assistant.client.ui.compose.item
22

3-
import androidx.compose.foundation.clickable
3+
import androidx.compose.foundation.layout.Arrangement
44
import androidx.compose.foundation.layout.Box
55
import androidx.compose.foundation.layout.Column
66
import androidx.compose.foundation.layout.Row
@@ -12,18 +12,24 @@ import androidx.compose.foundation.shape.RoundedCornerShape
1212
import androidx.compose.material.icons.Icons
1313
import androidx.compose.material.icons.automirrored.filled.ArrowBack
1414
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
15+
import androidx.compose.material.icons.automirrored.filled.ViewList
1516
import androidx.compose.material.icons.filled.AddToQueue
1617
import androidx.compose.material.icons.filled.ExpandMore
18+
import androidx.compose.material.icons.filled.GridView
19+
import androidx.compose.material.icons.filled.MoreVert
1720
import androidx.compose.material.icons.filled.PlayArrow
1821
import androidx.compose.material.icons.filled.PlaylistAddCircle
1922
import androidx.compose.material.icons.filled.QueuePlayNext
2023
import androidx.compose.material.icons.filled.Radio
2124
import androidx.compose.material3.AlertDialog
22-
import androidx.compose.material3.Button
2325
import androidx.compose.material3.CircularProgressIndicator
26+
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
2427
import androidx.compose.material3.Icon
2528
import androidx.compose.material3.IconButton
2629
import androidx.compose.material3.MaterialTheme
30+
import androidx.compose.material3.SplitButtonDefaults.LeadingButton
31+
import androidx.compose.material3.SplitButtonDefaults.TrailingButton
32+
import androidx.compose.material3.SplitButtonLayout
2733
import androidx.compose.material3.Text
2834
import androidx.compose.material3.TextButton
2935
import androidx.compose.runtime.Composable
@@ -56,18 +62,41 @@ import io.music_assistant.client.ui.compose.common.viewmodel.ActionsViewModel
5662
import kotlinx.coroutines.launch
5763
import org.jetbrains.compose.ui.tooling.preview.Preview
5864

65+
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
5966
@Composable
6067
fun ItemHeader(
6168
item: AppMediaItem,
6269
serverUrl: String? = null,
70+
isRowMode: Boolean = true,
6371
onBack: () -> Unit = {},
6472
onPlayClick: (QueueOption, Boolean) -> Unit = { _, _ -> },
6573
libraryAction: ActionsViewModel.LibraryActions? = null,
66-
playlistActions: ActionsViewModel.PlaylistActions? = null
74+
playlistActions: ActionsViewModel.PlaylistActions? = null,
75+
onToggleViewMode: () -> Unit = {}
6776
) {
6877
Column(modifier = Modifier.fillMaxWidth()) {
69-
IconButton(onClick = onBack) {
70-
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
78+
Row(
79+
modifier = Modifier.fillMaxWidth(),
80+
verticalAlignment = Alignment.CenterVertically,
81+
horizontalArrangement = Arrangement.SpaceBetween
82+
) {
83+
IconButton(onClick = onBack) {
84+
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
85+
}
86+
87+
OverflowMenu(
88+
options = listOf(
89+
OverflowMenuOption(
90+
title = "Toggle view mode",
91+
icon = if (isRowMode) Icons.Default.GridView else Icons.AutoMirrored.Filled.ViewList,
92+
onClick = onToggleViewMode
93+
)
94+
)
95+
) { onClick ->
96+
IconButton(onClick = onClick) {
97+
Icon(imageVector = Icons.Default.MoreVert, null)
98+
}
99+
}
71100
}
72101

73102
Column(
@@ -111,21 +140,37 @@ fun ItemHeader(
111140
verticalAlignment = Alignment.CenterVertically,
112141
modifier = Modifier.padding(top = 16.dp)
113142
) {
114-
Button(
115-
modifier = Modifier.semantics { contentDescription = "Play now" },
116-
onClick = { onPlayClick(QueueOption.REPLACE, false) }) {
117-
Row(verticalAlignment = Alignment.CenterVertically) {
118-
Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null)
119-
Text(modifier = Modifier.padding(start = 8.dp), text = "Play")
143+
SplitButtonLayout(
144+
leadingButton = {
145+
LeadingButton(
146+
modifier = Modifier.semantics { contentDescription = "Play now" },
147+
onClick = { onPlayClick(QueueOption.REPLACE, false) }) {
148+
Row(verticalAlignment = Alignment.CenterVertically) {
149+
Icon(
150+
imageVector = Icons.Default.PlayArrow,
151+
contentDescription = null
152+
)
153+
Text(modifier = Modifier.padding(start = 8.dp), text = "Play")
154+
}
155+
}
156+
},
157+
trailingButton = {
158+
ItemOverflowMenu(
159+
item = item,
160+
onPlayClick = onPlayClick,
161+
libraryActions = libraryAction,
162+
playlistActions = playlistActions
163+
) { onClick ->
164+
TrailingButton(
165+
onClick = onClick
166+
) {
167+
Icon(
168+
imageVector = Icons.Default.ExpandMore,
169+
contentDescription = null
170+
)
171+
}
172+
}
120173
}
121-
}
122-
123-
ItemOverflowMenu(
124-
item = item,
125-
modifier = Modifier.padding(start = 4.dp),
126-
onPlayClick = onPlayClick,
127-
libraryActions = libraryAction,
128-
playlistActions = playlistActions
129174
)
130175
}
131176
}
@@ -135,25 +180,17 @@ fun ItemHeader(
135180
@Composable
136181
private fun ItemOverflowMenu(
137182
item: AppMediaItem,
138-
modifier: Modifier,
139183
onPlayClick: (QueueOption, Boolean) -> Unit,
140184
libraryActions: ActionsViewModel.LibraryActions?,
141-
playlistActions: ActionsViewModel.PlaylistActions?
185+
playlistActions: ActionsViewModel.PlaylistActions?,
186+
button: @Composable (() -> Unit) -> Unit
142187
) {
143188
val coroutineScope = rememberCoroutineScope()
144189
var showPlaylistDialog by rememberSaveable { mutableStateOf(false) }
145190
var playlists by remember { mutableStateOf<List<AppMediaItem.Playlist>>(emptyList()) }
146191
var isLoadingPlaylists by remember { mutableStateOf(false) }
147192

148193
OverflowMenu(
149-
modifier = modifier,
150-
buttonContent = { onClick ->
151-
Icon(
152-
modifier = Modifier.clickable { onClick() },
153-
imageVector = Icons.Default.ExpandMore,
154-
contentDescription = null
155-
)
156-
},
157194
options = buildList {
158195
add(
159196
OverflowMenuOption(
@@ -221,7 +258,8 @@ private fun ItemOverflowMenu(
221258
}
222259
})
223260
}
224-
}
261+
},
262+
buttonContent = button
225263
)
226264

227265
if (showPlaylistDialog) {
@@ -279,7 +317,7 @@ private fun ItemOverflowMenu(
279317
@Preview
280318
@Composable
281319
private fun Preview(item: AppMediaItem.Album = AppMediaItemFixtures.album()) {
282-
ItemHeader(item)
320+
ItemHeader(item,)
283321
}
284322

285323
@Preview

composeApp/src/commonMain/kotlin/io/music_assistant/client/ui/compose/settings/SettingsScreen.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,11 @@ private fun SendspinSection(
795795
}
796796

797797
OverflowMenu(
798-
modifier = Modifier,
798+
options = Codecs.list.map { item ->
799+
OverflowMenuOption(
800+
title = item.uiTitle()
801+
) { viewModel.setSendspinCodecPreference(item) }
802+
},
799803
buttonContent = { onClick ->
800804
Icon(
801805
modifier = Modifier
@@ -808,11 +812,6 @@ private fun SendspinSection(
808812
else
809813
MaterialTheme.colorScheme.onSurfaceVariant
810814
)
811-
},
812-
options = Codecs.list.map { item ->
813-
OverflowMenuOption(
814-
title = item.uiTitle()
815-
) { viewModel.setSendspinCodecPreference(item) }
816815
}
817816
)
818817
}

0 commit comments

Comments
 (0)