mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-11-04 10:13:59 +00:00
feat(ui): replace IndeterminateProgress with new loading components and enhance dialog structures
This commit is contained in:
parent
daf54b74f4
commit
c57e33fe0d
@ -279,6 +279,10 @@
|
|||||||
<item quantity="one">%1$d plugin installed</item>
|
<item quantity="one">%1$d plugin installed</item>
|
||||||
<item quantity="other">%1$d plugins installed</item>
|
<item quantity="other">%1$d plugins installed</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="plugin_install_sheet_subtitle">Add a plugin from a link or a file on your device.</string>
|
||||||
|
<string name="plugin_install_file_hint">Pick a .smplug file from your device</string>
|
||||||
|
<string name="plugin_install_or">or</string>
|
||||||
|
<string name="plugin_info_dialog_title">Plugin details</string>
|
||||||
<string name="plugin_install_section_title">Install a Plugin</string>
|
<string name="plugin_install_section_title">Install a Plugin</string>
|
||||||
<string name="plugin_url_placeholder">https://example.com/plugin.smplug</string>
|
<string name="plugin_url_placeholder">https://example.com/plugin.smplug</string>
|
||||||
<string name="plugin_action_download">Download</string>
|
<string name="plugin_action_download">Download</string>
|
||||||
@ -572,6 +576,7 @@
|
|||||||
<string name="search_start_typing">Start typing to search</string>
|
<string name="search_start_typing">Start typing to search</string>
|
||||||
<string name="search_start_typing_tracks">Start typing to search tracks</string>
|
<string name="search_start_typing_tracks">Start typing to search tracks</string>
|
||||||
<string name="search_no_results">No results found</string>
|
<string name="search_no_results">No results found</string>
|
||||||
|
<string name="common_no_results">No results</string>
|
||||||
<string name="search_see_all">See all</string>
|
<string name="search_see_all">See all</string>
|
||||||
<string name="search_searching">Searching</string>
|
<string name="search_searching">Searching</string>
|
||||||
<string name="search_loading_tracks">Loading tracks...</string>
|
<string name="search_loading_tracks">Loading tracks...</string>
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.core.remote
|
package dev.krtirtho.spotube.core.remote
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumAlertDialog
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -34,7 +35,6 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumDialog
|
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
import spotube.composeapp.generated.resources.Res
|
import spotube.composeapp.generated.resources.Res
|
||||||
@ -56,21 +56,25 @@ fun ConnectionRequestDialogHost() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pendingRequest?.let { request ->
|
pendingRequest?.let { request ->
|
||||||
MillenniumDialog(
|
MillenniumAlertDialog(
|
||||||
visible = true,
|
visible = true,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
handler.resolveConnectionRequest(request.sessionId, ConnectionRequestResponse.Deny)
|
handler.resolveConnectionRequest(request.sessionId, ConnectionRequestResponse.Deny)
|
||||||
pendingRequest = null
|
pendingRequest = null
|
||||||
},
|
},
|
||||||
title = stringResource(Res.string.remote_request_title)) {
|
title = { Text(stringResource(Res.string.remote_request_title)) },
|
||||||
Text(
|
description = {
|
||||||
text = stringResource(Res.string.remote_request_message, request.deviceName),
|
Text(stringResource(Res.string.remote_request_message, request.deviceName))
|
||||||
style = AppTheme.typography.bodyMedium,
|
},
|
||||||
textAlign = TextAlign.Start)
|
) {
|
||||||
Row(
|
MillenniumButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
onClick = {
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
|
handler.resolveConnectionRequest(request.sessionId, ConnectionRequestResponse.AllowAlways)
|
||||||
verticalAlignment = Alignment.CenterVertically) {
|
pendingRequest = null
|
||||||
|
},
|
||||||
|
style = MillenniumButtonStyle.Quiet) {
|
||||||
|
Text(stringResource(Res.string.remote_request_allow_always))
|
||||||
|
}
|
||||||
MillenniumButton(
|
MillenniumButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
handler.resolveConnectionRequest(request.sessionId, ConnectionRequestResponse.Deny)
|
handler.resolveConnectionRequest(request.sessionId, ConnectionRequestResponse.Deny)
|
||||||
@ -86,15 +90,6 @@ fun ConnectionRequestDialogHost() {
|
|||||||
}) {
|
}) {
|
||||||
Text(stringResource(Res.string.remote_request_allow))
|
Text(stringResource(Res.string.remote_request_allow))
|
||||||
}
|
}
|
||||||
MillenniumButton(
|
|
||||||
onClick = {
|
|
||||||
handler.resolveConnectionRequest(request.sessionId, ConnectionRequestResponse.AllowAlways)
|
|
||||||
pendingRequest = null
|
|
||||||
},
|
|
||||||
style = MillenniumButtonStyle.Quiet) {
|
|
||||||
Text(stringResource(Res.string.remote_request_allow_always))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,15 @@ package dev.krtirtho.spotube.core.ui.component
|
|||||||
|
|
||||||
|
|
||||||
import org.spotube.millennium.composables.MillenniumDialog
|
import org.spotube.millennium.composables.MillenniumDialog
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogBody
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogClose
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogFooter
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogHeader
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogTitle
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
@ -50,6 +59,9 @@ import spotube.composeapp.generated.resources.*
|
|||||||
fun AdaptiveDialogBottomSheet(
|
fun AdaptiveDialogBottomSheet(
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
title: @Composable (() -> Unit)? = null,
|
title: @Composable (() -> Unit)? = null,
|
||||||
|
// Plain-text title for the large-screen dialog's own titlebar; when set it replaces [title]
|
||||||
|
// there so the titlebar isn't left empty above a duplicated in-body title.
|
||||||
|
dialogTitle: String? = null,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
actions: @Composable (() -> Unit)? = null,
|
actions: @Composable (() -> Unit)? = null,
|
||||||
content: @Composable () -> Unit) {
|
content: @Composable () -> Unit) {
|
||||||
@ -59,14 +71,19 @@ fun AdaptiveDialogBottomSheet(
|
|||||||
MillenniumDialog(
|
MillenniumDialog(
|
||||||
visible = true,
|
visible = true,
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = "",
|
modifier = modifier,
|
||||||
modifier = modifier) {
|
paneTitle = dialogTitle ?: "") {
|
||||||
title?.let { it() }
|
MillenniumDialogHeader {
|
||||||
content()
|
if (dialogTitle != null) {
|
||||||
actions?.let {
|
MillenniumDialogTitle(text = dialogTitle, textStyle = AppTheme.typography.titleMedium)
|
||||||
MillenniumSeparator()
|
} else {
|
||||||
it()
|
MillenniumDialogTitle { title?.invoke() }
|
||||||
|
}
|
||||||
|
MillenniumDialogClose(
|
||||||
|
contentDescription = stringResource(Res.string.common_close_dialog))
|
||||||
}
|
}
|
||||||
|
MillenniumDialogBody { content() }
|
||||||
|
actions?.let { MillenniumDialogFooter(content = { it() }) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val sheetState = rememberModalBottomSheetState(initialDetent = SheetDetent.FullyExpanded)
|
val sheetState = rememberModalBottomSheetState(initialDetent = SheetDetent.FullyExpanded)
|
||||||
@ -99,7 +116,13 @@ fun AdaptiveDialogBottomSheet(
|
|||||||
}
|
}
|
||||||
Spacer(Modifier.height(12.dp))
|
Spacer(Modifier.height(12.dp))
|
||||||
}
|
}
|
||||||
content()
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(max = 560.dp)
|
||||||
|
.verticalScroll(rememberScrollState())) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
actions?.let {
|
actions?.let {
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
MillenniumSeparator()
|
MillenniumSeparator()
|
||||||
|
|||||||
@ -23,6 +23,16 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.scaleIn
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -30,6 +40,7 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.foundation.text.input.clearText
|
||||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@ -41,6 +52,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import com.composeunstyled.currentWindowWidthBreakpoint
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.theme.MillenniumTheme
|
||||||
import org.spotube.millennium.adaptive.WindowBreakpoints
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
@ -57,6 +69,14 @@ import dev.krtirtho.spotube.resources.iconsax.IconsaxShare
|
|||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxTrash
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxTrash
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import org.spotube.millennium.composables.MillenniumDropdownMenu
|
import org.spotube.millennium.composables.MillenniumDropdownMenu
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuContent
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuEmpty
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuFilter
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuHeader
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuItem
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuItemIcon
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuSeparator
|
||||||
|
import com.composeunstyled.Text
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
@ -67,6 +87,7 @@ import org.spotube.millennium.composables.MillenniumText
|
|||||||
import org.spotube.millennium.composables.MillenniumTextField
|
import org.spotube.millennium.composables.MillenniumTextField
|
||||||
import spotube.composeapp.generated.resources.Res
|
import spotube.composeapp.generated.resources.Res
|
||||||
import spotube.composeapp.generated.resources.accessibility_selected
|
import spotube.composeapp.generated.resources.accessibility_selected
|
||||||
|
import spotube.composeapp.generated.resources.common_no_results
|
||||||
import spotube.composeapp.generated.resources.search_filter_placeholder
|
import spotube.composeapp.generated.resources.search_filter_placeholder
|
||||||
|
|
||||||
data class AdaptiveMenuItem(
|
data class AdaptiveMenuItem(
|
||||||
@ -99,17 +120,13 @@ fun AdaptiveDropdownBottomSheet(
|
|||||||
|
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
if (isLargeScreen) {
|
if (isLargeScreen) {
|
||||||
MillenniumDropdownMenu(
|
DesktopDropdownMenu(
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onExpandedChange = { expanded = it },
|
onExpandedChange = { expanded = it },
|
||||||
items = items.map { it.label },
|
items = items,
|
||||||
onItemClick = { label ->
|
menuMinWidth = menuMinWidth,
|
||||||
items.firstOrNull { it.label == label && it.enabled }?.onClick?.invoke()
|
filter = filter,
|
||||||
expanded = false
|
trigger = { trigger { expanded = true } })
|
||||||
},
|
|
||||||
minimumWidth = menuMinWidth,
|
|
||||||
anchor = { trigger { expanded = true } }
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
trigger { expanded = true }
|
trigger { expanded = true }
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
@ -134,6 +151,7 @@ private fun AdaptiveBottomSheetContent(
|
|||||||
header: @Composable (() -> Unit)?,
|
header: @Composable (() -> Unit)?,
|
||||||
filter: ((AdaptiveMenuItem, String) -> Boolean)? = null) {
|
filter: ((AdaptiveMenuItem, String) -> Boolean)? = null) {
|
||||||
val hasSelection = items.any { it.selected }
|
val hasSelection = items.any { it.selected }
|
||||||
|
val hasLeading = items.any { it.icon != null || it.leadingContent != null }
|
||||||
val queryState = rememberTextFieldState()
|
val queryState = rememberTextFieldState()
|
||||||
val query = queryState.text.toString()
|
val query = queryState.text.toString()
|
||||||
val filteredItems = if (filter != null && query.isNotBlank()) {
|
val filteredItems = if (filter != null && query.isNotBlank()) {
|
||||||
@ -175,69 +193,134 @@ private fun AdaptiveBottomSheetContent(
|
|||||||
if (item.dividerBefore) {
|
if (item.dividerBefore) {
|
||||||
MillenniumSeparator(modifier = Modifier.padding(vertical = 4.dp))
|
MillenniumSeparator(modifier = Modifier.padding(vertical = 4.dp))
|
||||||
}
|
}
|
||||||
MillenniumListTile(
|
AdaptiveMenuTile(
|
||||||
title = {
|
item = item,
|
||||||
MillenniumText(
|
hasSelection = hasSelection,
|
||||||
item.label,
|
hasLeading = hasLeading,
|
||||||
style = AppTheme.typography.bodyLarge,
|
textStyle = AppTheme.typography.bodyLarge,
|
||||||
color = if (item.enabled) {
|
minimumHeight = 56.dp,
|
||||||
AppTheme.colors.onSurface
|
|
||||||
} else {
|
|
||||||
AppTheme.colors.onSurface.copy(alpha = 0.38f)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
subtitle = item.subtitle?.let { subtitle ->
|
|
||||||
{
|
|
||||||
MillenniumText(
|
|
||||||
subtitle,
|
|
||||||
style = AppTheme.typography.bodySmall,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
leading = when {
|
|
||||||
item.leadingContent != null -> item.leadingContent
|
|
||||||
item.icon != null -> {
|
|
||||||
{
|
|
||||||
MillenniumIcon(
|
|
||||||
imageVector = item.icon,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(22.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.selected -> {
|
|
||||||
{
|
|
||||||
MillenniumIcon(
|
|
||||||
imageVector = FeatherIcons.Check,
|
|
||||||
contentDescription = stringResource(Res.string.accessibility_selected),
|
|
||||||
modifier = Modifier.size(22.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hasSelection -> {{ Spacer(modifier = Modifier.size(22.dp)) }}
|
|
||||||
else -> null
|
|
||||||
},
|
|
||||||
trailing = if (item.selected && (item.icon != null || item.leadingContent != null)) {
|
|
||||||
{
|
|
||||||
MillenniumIcon(
|
|
||||||
imageVector = FeatherIcons.Check,
|
|
||||||
contentDescription = stringResource(Res.string.accessibility_selected),
|
|
||||||
modifier = Modifier.size(22.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
},
|
|
||||||
selected = item.selected,
|
|
||||||
enabled = item.enabled,
|
|
||||||
onClick = {
|
onClick = {
|
||||||
item.onClick()
|
item.onClick()
|
||||||
onDismiss()
|
onDismiss()
|
||||||
},
|
})
|
||||||
minimumHeight = 56.dp
|
}
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** One menu row. The leading slot is always reserved when any sibling has an icon/leading content
|
||||||
|
* (or a selection), so labels and icons stay aligned in a column. */
|
||||||
|
@Composable
|
||||||
|
private fun AdaptiveMenuTile(
|
||||||
|
item: AdaptiveMenuItem,
|
||||||
|
hasSelection: Boolean,
|
||||||
|
hasLeading: Boolean,
|
||||||
|
textStyle: androidx.compose.ui.text.TextStyle,
|
||||||
|
minimumHeight: Dp,
|
||||||
|
onClick: () -> Unit) {
|
||||||
|
if (item.dividerBefore) {
|
||||||
|
MillenniumSeparator(modifier = Modifier.padding(vertical = 4.dp))
|
||||||
|
}
|
||||||
|
val contentColor = if (item.enabled) AppTheme.colors.onSurface
|
||||||
|
else AppTheme.colors.onSurface.copy(alpha = 0.38f)
|
||||||
|
MillenniumListTile(
|
||||||
|
title = { MillenniumText(item.label, style = textStyle, color = contentColor) },
|
||||||
|
subtitle = item.subtitle?.let { subtitle ->
|
||||||
|
{
|
||||||
|
MillenniumText(
|
||||||
|
subtitle,
|
||||||
|
style = AppTheme.typography.bodySmall,
|
||||||
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leading = when {
|
||||||
|
item.leadingContent != null -> item.leadingContent
|
||||||
|
item.icon != null -> {
|
||||||
|
{
|
||||||
|
MillenniumIcon(
|
||||||
|
imageVector = item.icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(22.dp),
|
||||||
|
tint = contentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.selected -> {
|
||||||
|
{
|
||||||
|
MillenniumIcon(
|
||||||
|
imageVector = FeatherIcons.Check,
|
||||||
|
contentDescription = stringResource(Res.string.accessibility_selected),
|
||||||
|
modifier = Modifier.size(22.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasSelection || hasLeading -> {{ Spacer(modifier = Modifier.size(22.dp)) }}
|
||||||
|
else -> null
|
||||||
|
},
|
||||||
|
trailing = if (item.selected && (item.icon != null || item.leadingContent != null)) {
|
||||||
|
{
|
||||||
|
MillenniumIcon(
|
||||||
|
imageVector = FeatherIcons.Check,
|
||||||
|
contentDescription = stringResource(Res.string.accessibility_selected),
|
||||||
|
modifier = Modifier.size(22.dp))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
|
selected = item.selected,
|
||||||
|
enabled = item.enabled,
|
||||||
|
onClick = onClick,
|
||||||
|
minimumHeight = minimumHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Desktop popup built from Millennium's dropdown bricks: native icons, descriptions,
|
||||||
|
* selection indicator, separators and an optional search header. */
|
||||||
|
@Composable
|
||||||
|
private fun DesktopDropdownMenu(
|
||||||
|
expanded: Boolean,
|
||||||
|
onExpandedChange: (Boolean) -> Unit,
|
||||||
|
items: List<AdaptiveMenuItem>,
|
||||||
|
menuMinWidth: Dp,
|
||||||
|
filter: ((AdaptiveMenuItem, String) -> Boolean)?,
|
||||||
|
trigger: @Composable () -> Unit) {
|
||||||
|
val queryState = rememberTextFieldState()
|
||||||
|
LaunchedEffect(expanded) { if (!expanded) queryState.clearText() }
|
||||||
|
val query = queryState.text.toString()
|
||||||
|
val visibleItems = if (filter != null && query.isNotBlank()) {
|
||||||
|
items.filter { filter(it, query) }
|
||||||
|
} else {
|
||||||
|
items
|
||||||
|
}
|
||||||
|
val hasLeading = items.any { it.icon != null || it.leadingContent != null }
|
||||||
|
val iconSize = MillenniumTheme.dropdownMenuTheme.states.default.iconSize
|
||||||
|
|
||||||
|
MillenniumDropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onExpandedChange = onExpandedChange,
|
||||||
|
minimumWidth = menuMinWidth,
|
||||||
|
width = menuMinWidth,
|
||||||
|
anchor = trigger) {
|
||||||
|
if (filter != null) {
|
||||||
|
MillenniumDropdownMenuHeader(showDivider = false) {
|
||||||
|
MillenniumDropdownMenuFilter(
|
||||||
|
state = queryState,
|
||||||
|
placeholder = stringResource(Res.string.search_filter_placeholder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MillenniumDropdownMenuContent {
|
||||||
|
if (visibleItems.isEmpty()) {
|
||||||
|
MillenniumDropdownMenuEmpty(stringResource(Res.string.common_no_results))
|
||||||
|
}
|
||||||
|
visibleItems.forEach { item ->
|
||||||
|
if (item.dividerBefore) MillenniumDropdownMenuSeparator()
|
||||||
|
MillenniumDropdownMenuItem(
|
||||||
|
onClick = item.onClick,
|
||||||
|
enabled = item.enabled,
|
||||||
|
selected = item.selected,
|
||||||
|
leading = item.leadingContent
|
||||||
|
?: item.icon?.let { icon -> { MillenniumDropdownMenuItemIcon(icon) } }
|
||||||
|
?: if (hasLeading) ({ Spacer(Modifier.size(iconSize)) }) else null,
|
||||||
|
description = item.subtitle?.let { subtitle -> { Text(subtitle) } }) {
|
||||||
|
Text(item.label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,11 @@
|
|||||||
package dev.krtirtho.spotube.core.ui.component
|
package dev.krtirtho.spotube.core.ui.component
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogHeader
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogTitle
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogClose
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogBody
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogFooter
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@ -191,19 +196,11 @@ private fun ErrorLogDialog(
|
|||||||
onDismiss: () -> Unit) {
|
onDismiss: () -> Unit) {
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
|
||||||
MillenniumDialog(
|
MillenniumDialog(visible = true, onDismissRequest = onDismiss) {
|
||||||
visible = true,
|
MillenniumDialogHeader {
|
||||||
onDismissRequest = onDismiss,
|
MillenniumDialogTitle(
|
||||||
title = "") {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.error_full_log_title),
|
text = stringResource(Res.string.error_full_log_title),
|
||||||
style = AppTheme.typography.titleLarge,
|
textStyle = AppTheme.typography.titleMedium)
|
||||||
fontWeight = FontWeight.Bold)
|
|
||||||
|
|
||||||
MillenniumIconButton(
|
MillenniumIconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
clipboardManager.setText(AnnotatedString(errorMessage))
|
clipboardManager.setText(AnnotatedString(errorMessage))
|
||||||
@ -213,31 +210,32 @@ private fun ErrorLogDialog(
|
|||||||
imageVector = FeatherIcons.Copy,
|
imageVector = FeatherIcons.Copy,
|
||||||
contentDescription = stringResource(Res.string.error_copy_to_clipboard))
|
contentDescription = stringResource(Res.string.error_copy_to_clipboard))
|
||||||
}
|
}
|
||||||
|
MillenniumDialogClose()
|
||||||
}
|
}
|
||||||
Box(
|
MillenniumDialogBody(scrollable = false) {
|
||||||
modifier = Modifier
|
Box(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.height(400.dp)
|
.fillMaxWidth()
|
||||||
.clip(AppTheme.shapes.small)
|
.height(400.dp)
|
||||||
.background(AppTheme.colors.surfaceContainerHighest.copy(alpha = 0.3f))) {
|
.clip(AppTheme.shapes.small)
|
||||||
SelectionContainer {
|
.background(AppTheme.colors.surfaceContainerHighest.copy(alpha = 0.3f))) {
|
||||||
Text(
|
SelectionContainer {
|
||||||
text = errorMessage,
|
Text(
|
||||||
style = AppTheme.typography.bodySmall.copy(
|
text = errorMessage,
|
||||||
fontFamily = FontFamily.Monospace),
|
style = AppTheme.typography.bodySmall.copy(
|
||||||
color = AppTheme.colors.onSurfaceVariant,
|
fontFamily = FontFamily.Monospace),
|
||||||
modifier = Modifier
|
color = AppTheme.colors.onSurfaceVariant,
|
||||||
.fillMaxSize()
|
modifier = Modifier
|
||||||
.verticalScroll(rememberScrollState())
|
.fillMaxSize()
|
||||||
.padding(12.dp))
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(12.dp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(
|
MillenniumDialogFooter {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.End) {
|
|
||||||
MillenniumButton(
|
MillenniumButton(
|
||||||
onClick = onDismiss,
|
onClick = onDismiss,
|
||||||
style = MillenniumButtonStyle.Quiet) {
|
style = MillenniumButtonStyle.Secondary) {
|
||||||
Text(stringResource(Res.string.common_close))
|
Text(stringResource(Res.string.common_close))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
@ -184,7 +185,7 @@ fun TrackOptionsBottomSheet(
|
|||||||
} else {
|
} else {
|
||||||
AppTheme.colors.onSurface.copy(alpha = 0.38f)
|
AppTheme.colors.onSurface.copy(alpha = 0.38f)
|
||||||
})
|
})
|
||||||
}
|
} ?: Spacer(modifier = Modifier.size(22.dp))
|
||||||
Text(
|
Text(
|
||||||
text = item.label,
|
text = item.label,
|
||||||
style = AppTheme.typography.bodyLarge,
|
style = AppTheme.typography.bodyLarge,
|
||||||
|
|||||||
@ -42,6 +42,8 @@ import androidx.compose.ui.unit.TextUnit
|
|||||||
import com.valentinilk.shimmer.ShimmerBounds
|
import com.valentinilk.shimmer.ShimmerBounds
|
||||||
import com.valentinilk.shimmer.rememberShimmer
|
import com.valentinilk.shimmer.rememberShimmer
|
||||||
import com.valentinilk.shimmer.shimmer
|
import com.valentinilk.shimmer.shimmer
|
||||||
|
import org.spotube.millennium.theme.MillenniumTheme
|
||||||
|
import org.spotube.millennium.theme.millenniumBevelBrush
|
||||||
|
|
||||||
val LocalSkeletonLoading = compositionLocalOf { false }
|
val LocalSkeletonLoading = compositionLocalOf { false }
|
||||||
|
|
||||||
@ -80,11 +82,16 @@ fun SkeletonTree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Modifier.shimmerApply(
|
fun Modifier.shimmerApply(
|
||||||
color: Color = Color.LightGray.copy(alpha = 0.5f) // Adjust alpha/color to match your theme
|
color: Color? = null // defaults to Millennium's recessed skeleton fill
|
||||||
): Modifier = composed {
|
): Modifier = composed {
|
||||||
val isTreeLoading = LocalSkeletonLoading.current
|
val isTreeLoading = LocalSkeletonLoading.current
|
||||||
|
val skeletonBrush = millenniumBevelBrush(MillenniumTheme.colors, sunken = true)
|
||||||
|
|
||||||
if (isTreeLoading) this.background(color) else this
|
when {
|
||||||
|
!isTreeLoading -> this
|
||||||
|
color != null -> this.background(color)
|
||||||
|
else -> this.background(skeletonBrush)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@ -84,7 +84,6 @@ import dev.krtirtho.spotube.core.ui.component.cards.PlayableCard
|
|||||||
import dev.krtirtho.spotube.core.ui.component.dragScrollable
|
import dev.krtirtho.spotube.core.ui.component.dragScrollable
|
||||||
import dev.krtirtho.spotube.core.ui.misc.SkeletonTree
|
import dev.krtirtho.spotube.core.ui.misc.SkeletonTree
|
||||||
import dev.krtirtho.spotube.modules.library.playlist.AddToPlaylistPicker
|
import dev.krtirtho.spotube.modules.library.playlist.AddToPlaylistPicker
|
||||||
import dev.krtirtho.spotube.modules.shell.LocalAppShellBottomInset
|
|
||||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxAddSquare
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxAddSquare
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxPlay
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxPlay
|
||||||
@ -143,8 +142,7 @@ fun ArtistScreen(
|
|||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize(),
|
||||||
.padding(bottom = LocalAppShellBottomInset.current + 24.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
|
|||||||
@ -18,10 +18,10 @@
|
|||||||
package dev.krtirtho.spotube.modules.devices
|
package dev.krtirtho.spotube.modules.devices
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumSpokeSpinner
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumIndeterminateProgress
|
|
||||||
import org.spotube.millennium.theme.MillenniumTheme
|
import org.spotube.millennium.theme.MillenniumTheme
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@ -103,10 +103,9 @@ fun DevicesScreen(
|
|||||||
title = { Text(stringResource(Res.string.devices_title)) },
|
title = { Text(stringResource(Res.string.devices_title)) },
|
||||||
actions = {
|
actions = {
|
||||||
if (isDiscovering && connectingToDevice == null) {
|
if (isDiscovering && connectingToDevice == null) {
|
||||||
MillenniumIndeterminateProgress(
|
MillenniumSpokeSpinner(
|
||||||
modifier = Modifier
|
size = 24.dp,
|
||||||
.size(24.dp)
|
modifier = Modifier.padding(end = 8.dp))
|
||||||
.padding(end = 8.dp))
|
|
||||||
} else {
|
} else {
|
||||||
MillenniumIcon(
|
MillenniumIcon(
|
||||||
imageVector = Iconsax.IconsaxRefreshRight,
|
imageVector = Iconsax.IconsaxRefreshRight,
|
||||||
@ -177,8 +176,7 @@ fun DevicesScreen(
|
|||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically) {
|
verticalAlignment = Alignment.CenterVertically) {
|
||||||
MillenniumIndeterminateProgress(
|
MillenniumSpokeSpinner(size = 24.dp)
|
||||||
modifier = Modifier.size(24.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.devices_connecting),
|
text = stringResource(Res.string.devices_connecting),
|
||||||
style = AppTheme.typography.bodyMedium)
|
style = AppTheme.typography.bodyMedium)
|
||||||
@ -250,8 +248,7 @@ private fun DeviceRow(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
if (isConnecting) {
|
if (isConnecting) {
|
||||||
MillenniumIndeterminateProgress(
|
MillenniumSpokeSpinner(size = 24.dp)
|
||||||
modifier = Modifier.size(24.dp))
|
|
||||||
} else {
|
} else {
|
||||||
MillenniumIcon(
|
MillenniumIcon(
|
||||||
imageVector = Iconsax.IconsaxMirroringScreen,
|
imageVector = Iconsax.IconsaxMirroringScreen,
|
||||||
|
|||||||
@ -18,6 +18,11 @@
|
|||||||
package dev.krtirtho.spotube.modules.devices
|
package dev.krtirtho.spotube.modules.devices
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogHeader
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogTitle
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogClose
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogBody
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogFooter
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumDialog
|
import org.spotube.millennium.composables.MillenniumDialog
|
||||||
import org.spotube.millennium.composables.MillenniumListTile
|
import org.spotube.millennium.composables.MillenniumListTile
|
||||||
@ -82,10 +87,12 @@ fun PlayDestinationPickerHost() {
|
|||||||
PlaybackDestinationAction.PlayNext -> stringResource(Res.string.devices_destination_action_play_next)
|
PlaybackDestinationAction.PlayNext -> stringResource(Res.string.devices_destination_action_play_next)
|
||||||
}
|
}
|
||||||
|
|
||||||
MillenniumDialog(
|
MillenniumDialog(visible = true, onDismissRequest = controller::dismissPicker) {
|
||||||
visible = true,
|
MillenniumDialogHeader {
|
||||||
onDismissRequest = controller::dismissPicker,
|
MillenniumDialogTitle(stringResource(Res.string.devices_destination_title))
|
||||||
title = stringResource(Res.string.devices_destination_title)) {
|
MillenniumDialogClose()
|
||||||
|
}
|
||||||
|
MillenniumDialogBody {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
@ -138,6 +145,9 @@ fun PlayDestinationPickerHost() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MillenniumDialogFooter {
|
||||||
MillenniumButton(
|
MillenniumButton(
|
||||||
onClick = controller::dismissPicker,
|
onClick = controller::dismissPicker,
|
||||||
style = MillenniumButtonStyle.Quiet) {
|
style = MillenniumButtonStyle.Quiet) {
|
||||||
|
|||||||
@ -18,6 +18,8 @@
|
|||||||
package dev.krtirtho.spotube.modules.devices
|
package dev.krtirtho.spotube.modules.devices
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuItem
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxMusicSquareRemove
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
@ -604,11 +606,6 @@ private fun RemoteQueueItemRow(
|
|||||||
MillenniumDropdownMenu(
|
MillenniumDropdownMenu(
|
||||||
expanded = showMenu,
|
expanded = showMenu,
|
||||||
onExpandedChange = { showMenu = it },
|
onExpandedChange = { showMenu = it },
|
||||||
items = listOf(stringResource(Res.string.devices_remote_remove_from_queue)),
|
|
||||||
onItemClick = {
|
|
||||||
onRemoveClick()
|
|
||||||
showMenu = false
|
|
||||||
},
|
|
||||||
anchor = {
|
anchor = {
|
||||||
MillenniumIconButton(
|
MillenniumIconButton(
|
||||||
onClick = { showMenu = true },
|
onClick = { showMenu = true },
|
||||||
@ -620,7 +617,16 @@ private fun RemoteQueueItemRow(
|
|||||||
modifier = Modifier.size(18.dp)
|
modifier = Modifier.size(18.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}) {
|
||||||
|
MillenniumDropdownMenuItem(
|
||||||
|
label = stringResource(Res.string.devices_remote_remove_from_queue),
|
||||||
|
icon = Iconsax.IconsaxMusicSquareRemove,
|
||||||
|
destructive = true,
|
||||||
|
onClick = {
|
||||||
|
onRemoveClick()
|
||||||
|
showMenu = false
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
package dev.krtirtho.spotube.modules.downloads
|
package dev.krtirtho.spotube.modules.downloads
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumStripedProgress
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -42,7 +43,6 @@ import org.spotube.millennium.composables.MillenniumButtonStyle
|
|||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumProgress
|
|
||||||
import org.spotube.millennium.theme.MillenniumTheme
|
import org.spotube.millennium.theme.MillenniumTheme
|
||||||
import org.spotube.millennium.theme.millenniumPanelBrush
|
import org.spotube.millennium.theme.millenniumPanelBrush
|
||||||
import org.spotube.millennium.theme.millenniumSurface
|
import org.spotube.millennium.theme.millenniumSurface
|
||||||
@ -225,9 +225,10 @@ internal fun DownloadListRow(
|
|||||||
},
|
},
|
||||||
maxLines = 1, overflow = TextOverflow.Ellipsis
|
maxLines = 1, overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
if (item.status == DownloadStatus.Downloading) MillenniumProgress(
|
if (item.status == DownloadStatus.Downloading) MillenniumStripedProgress(
|
||||||
item.progress,
|
progress = item.progress,
|
||||||
Modifier.fillMaxWidth().height(4.dp)
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
height = 6.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
MillenniumIconButton(
|
MillenniumIconButton(
|
||||||
|
|||||||
@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.jam
|
package dev.krtirtho.spotube.modules.jam
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuItem
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxUserRemove
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxForbidden
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -421,25 +424,27 @@ internal fun SessionPanel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (participant.canModerate) {
|
if (participant.canModerate) {
|
||||||
val removeLabel = stringResource(Res.string.jam_participants_remove)
|
|
||||||
val banLabel = stringResource(Res.string.jam_participants_ban)
|
|
||||||
// Keep destructive moderation out of the participant's primary row.
|
// Keep destructive moderation out of the participant's primary row.
|
||||||
org.spotube.millennium.composables.MillenniumDropdownMenu(
|
org.spotube.millennium.composables.MillenniumDropdownMenu(
|
||||||
expanded = participant.menuExpanded,
|
expanded = participant.menuExpanded,
|
||||||
onExpandedChange = { expanded -> onMenuChange(if (expanded) participant.id else null) },
|
onExpandedChange = { expanded -> onMenuChange(if (expanded) participant.id else null) },
|
||||||
items = listOf(removeLabel, banLabel),
|
|
||||||
onItemClick = { label ->
|
|
||||||
if (label == removeLabel) onKick(participant.id) else onBan(
|
|
||||||
participant.id
|
|
||||||
)
|
|
||||||
},
|
|
||||||
anchor = {
|
anchor = {
|
||||||
MillenniumButton(
|
MillenniumButton(
|
||||||
onClick = { onMenuChange(participant.id) },
|
onClick = { onMenuChange(participant.id) },
|
||||||
style = MillenniumButtonStyle.Secondary
|
style = MillenniumButtonStyle.Secondary
|
||||||
) { Text(stringResource(Res.string.jam_participants_manage)) }
|
) { Text(stringResource(Res.string.jam_participants_manage)) }
|
||||||
}
|
}
|
||||||
)
|
) {
|
||||||
|
MillenniumDropdownMenuItem(
|
||||||
|
label = stringResource(Res.string.jam_participants_remove),
|
||||||
|
icon = Iconsax.IconsaxUserRemove,
|
||||||
|
onClick = { onKick(participant.id) })
|
||||||
|
MillenniumDropdownMenuItem(
|
||||||
|
label = stringResource(Res.string.jam_participants_ban),
|
||||||
|
icon = Iconsax.IconsaxForbidden,
|
||||||
|
destructive = true,
|
||||||
|
onClick = { onBan(participant.id) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.jam
|
package dev.krtirtho.spotube.modules.jam
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumAlertDialog
|
||||||
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
||||||
import com.composeunstyled.Text
|
import com.composeunstyled.Text
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
@ -47,27 +48,23 @@ fun PlayDestinationPicker(
|
|||||||
onSuggestToJam: () -> Unit) {
|
onSuggestToJam: () -> Unit) {
|
||||||
if (!visible) return
|
if (!visible) return
|
||||||
|
|
||||||
MillenniumDialog(
|
MillenniumAlertDialog(
|
||||||
visible = true,
|
visible = true,
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = stringResource(Res.string.jam_destination_title)) {
|
title = { Text(stringResource(Res.string.jam_destination_title)) },
|
||||||
Text(
|
description = { Text(stringResource(Res.string.jam_destination_message)) }) {
|
||||||
text = stringResource(Res.string.jam_destination_message),
|
MillenniumButton(
|
||||||
style = AppTheme.typography.bodyMedium)
|
onClick = onDismiss,
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
style = MillenniumButtonStyle.Quiet) {
|
||||||
MillenniumButton(
|
Text(stringResource(Res.string.common_cancel))
|
||||||
onClick = onDismiss,
|
}
|
||||||
style = MillenniumButtonStyle.Quiet) {
|
MillenniumButton(
|
||||||
Text(stringResource(Res.string.common_cancel))
|
onClick = onPlayLocally,
|
||||||
}
|
style = MillenniumButtonStyle.Secondary) {
|
||||||
MillenniumButton(
|
Text(stringResource(Res.string.jam_destination_play_here))
|
||||||
onClick = onPlayLocally,
|
}
|
||||||
style = MillenniumButtonStyle.Quiet) {
|
MillenniumButton(onClick = onSuggestToJam) {
|
||||||
Text(stringResource(Res.string.jam_destination_play_here))
|
Text(stringResource(Res.string.jam_destination_suggest))
|
||||||
}
|
|
||||||
MillenniumButton(onClick = onSuggestToJam) {
|
|
||||||
Text(stringResource(Res.string.jam_destination_suggest))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
package dev.krtirtho.spotube.modules.library.local_tracks
|
package dev.krtirtho.spotube.modules.library.local_tracks
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumCircularProgress
|
||||||
|
import org.spotube.millennium.composables.MillenniumSpokeSpinner
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -51,7 +53,6 @@ import org.spotube.millennium.composables.MillenniumButtonStyle
|
|||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumIndeterminateProgress
|
|
||||||
import org.spotube.millennium.composables.MillenniumListTile
|
import org.spotube.millennium.composables.MillenniumListTile
|
||||||
import org.spotube.millennium.theme.MillenniumTheme
|
import org.spotube.millennium.theme.MillenniumTheme
|
||||||
import org.spotube.millennium.theme.millenniumPanelBrush
|
import org.spotube.millennium.theme.millenniumPanelBrush
|
||||||
@ -113,7 +114,7 @@ internal fun LocalTracksContent(
|
|||||||
Modifier.fillMaxSize().padding(bottom = inset),
|
Modifier.fillMaxSize().padding(bottom = inset),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
MillenniumIndeterminateProgress()
|
MillenniumCircularProgress(size = 40.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
state.currentFolderPath == null && state.visibleFolders.isNotEmpty() -> LazyColumn(
|
state.currentFolderPath == null && state.visibleFolders.isNotEmpty() -> LazyColumn(
|
||||||
@ -264,7 +265,7 @@ internal fun LocalTracksBreadcrumb(
|
|||||||
style = AppTheme.typography.labelSmall, color = AppTheme.colors.onSurfaceVariant,
|
style = AppTheme.typography.labelSmall, color = AppTheme.colors.onSurfaceVariant,
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
)
|
)
|
||||||
if (state.isRefreshing) MillenniumIndeterminateProgress(Modifier.size(14.dp))
|
if (state.isRefreshing) MillenniumSpokeSpinner(size = 14.dp)
|
||||||
if (state.currentFolderPath == null && onManageFolders != null) MillenniumIconButton(
|
if (state.currentFolderPath == null && onManageFolders != null) MillenniumIconButton(
|
||||||
onClick = onManageFolders,
|
onClick = onManageFolders,
|
||||||
modifier = Modifier.size(28.dp), style = MillenniumIconButtonStyle.Quiet
|
modifier = Modifier.size(28.dp), style = MillenniumIconButtonStyle.Quiet
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
package dev.krtirtho.spotube.modules.library.playlist
|
package dev.krtirtho.spotube.modules.library.playlist
|
||||||
|
|
||||||
|
|
||||||
|
import dev.krtirtho.spotube.core.ui.component.AdaptiveDialogBottomSheet
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
@ -154,34 +155,25 @@ fun AddToPlaylistPicker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLargeScreen) {
|
val pickerTitle = stringResource(Res.string.library_add_to_playlist_title)
|
||||||
MillenniumDialog(
|
AdaptiveDialogBottomSheet(
|
||||||
visible = true,
|
onDismiss = onDismiss,
|
||||||
onDismissRequest = onDismiss,
|
modifier = modifier,
|
||||||
title = stringResource(Res.string.library_add_to_playlist_title),
|
dialogTitle = pickerTitle,
|
||||||
modifier = modifier) {
|
title = { Text(pickerTitle, style = AppTheme.typography.headlineSmall) },
|
||||||
Box(modifier = Modifier.fillMaxWidth().height(400.dp)) {
|
actions = {
|
||||||
content()
|
Row(
|
||||||
}
|
modifier = Modifier.fillMaxWidth(),
|
||||||
MillenniumButton(
|
horizontalArrangement = Arrangement.End) {
|
||||||
onClick = onDismiss,
|
MillenniumButton(
|
||||||
style = MillenniumButtonStyle.Secondary) {
|
onClick = onDismiss,
|
||||||
Text(stringResource(Res.string.common_cancel))
|
style = MillenniumButtonStyle.Secondary) {
|
||||||
}
|
Text(stringResource(Res.string.common_cancel))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
val sheetState = rememberModalBottomSheetState(initialDetent = SheetDetent.FullyExpanded)
|
|
||||||
MillenniumModalBottomSheet(state = sheetState) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.library_add_to_playlist_title),
|
|
||||||
style = AppTheme.typography.headlineSmall,
|
|
||||||
modifier = Modifier.padding(bottom = 16.dp))
|
|
||||||
content()
|
|
||||||
}
|
}
|
||||||
|
}) {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth().height(400.dp)) {
|
||||||
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.library.playlist
|
package dev.krtirtho.spotube.modules.library.playlist
|
||||||
|
|
||||||
|
import dev.krtirtho.spotube.core.ui.component.AdaptiveDialogBottomSheet
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
@ -162,90 +163,42 @@ fun PlaylistFormSheet(
|
|||||||
|
|
||||||
val handleImagePick = { imagePicker.launch() }
|
val handleImagePick = { imagePicker.launch() }
|
||||||
|
|
||||||
if (isLargeScreen) {
|
val formTitle = stringResource(
|
||||||
MillenniumDialog(
|
if (isEditing) Res.string.library_playlist_form_edit_title else Res.string.library_playlist_form_create_title)
|
||||||
visible = true,
|
AdaptiveDialogBottomSheet(
|
||||||
onDismissRequest = onDismiss,
|
onDismiss = onDismiss,
|
||||||
title = stringResource(if (isEditing) Res.string.library_playlist_form_edit_title else Res.string.library_playlist_form_create_title),
|
modifier = modifier,
|
||||||
modifier = modifier
|
dialogTitle = formTitle,
|
||||||
) {
|
title = { Text(formTitle, style = AppTheme.typography.headlineSmall) },
|
||||||
PlaylistFormFields(
|
actions = {
|
||||||
nameState = nameState,
|
Row(
|
||||||
descriptionState = descriptionState,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
isPublic = isPublic,
|
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End)) {
|
||||||
onIsPublicChange = { isPublic = it },
|
MillenniumButton(
|
||||||
isCollaborating = isCollaborating,
|
onClick = onDismiss,
|
||||||
onIsCollaboratingChange = { isCollaborating = it },
|
style = MillenniumButtonStyle.Secondary) {
|
||||||
imageBase64 = imageBase64,
|
Text(stringResource(Res.string.common_cancel))
|
||||||
imagePreviewUrl = imagePreviewUrl,
|
}
|
||||||
onPickImage = handleImagePick
|
|
||||||
)
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
|
||||||
MillenniumButton(
|
MillenniumButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
onSubmit(currentData)
|
onSubmit(currentData)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
},
|
},
|
||||||
enabled = name.isNotBlank()
|
enabled = name.isNotBlank()) {
|
||||||
) {
|
|
||||||
Text(stringResource(if (isEditing) Res.string.common_save else Res.string.library_playlist_form_create))
|
Text(stringResource(if (isEditing) Res.string.common_save else Res.string.library_playlist_form_create))
|
||||||
}
|
}
|
||||||
MillenniumButton(
|
|
||||||
onClick = onDismiss,
|
|
||||||
style = MillenniumButtonStyle.Secondary
|
|
||||||
) {
|
|
||||||
Text(stringResource(Res.string.common_cancel))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}) {
|
||||||
} else {
|
PlaylistFormFields(
|
||||||
val sheetState = rememberModalBottomSheetState(initialDetent = SheetDetent.FullyExpanded)
|
nameState = nameState,
|
||||||
MillenniumModalBottomSheet(state = sheetState) {
|
descriptionState = descriptionState,
|
||||||
Column(
|
isPublic = isPublic,
|
||||||
modifier = Modifier
|
onIsPublicChange = { isPublic = it },
|
||||||
.fillMaxWidth()
|
isCollaborating = isCollaborating,
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
onIsCollaboratingChange = { isCollaborating = it },
|
||||||
) {
|
imageBase64 = imageBase64,
|
||||||
Text(
|
imagePreviewUrl = imagePreviewUrl,
|
||||||
text = stringResource(if (isEditing) Res.string.library_playlist_form_edit_title else Res.string.library_playlist_form_create_title),
|
onPickImage = handleImagePick)
|
||||||
style = AppTheme.typography.headlineSmall,
|
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
|
||||||
)
|
|
||||||
PlaylistFormFields(
|
|
||||||
nameState = nameState,
|
|
||||||
descriptionState = descriptionState,
|
|
||||||
isPublic = isPublic,
|
|
||||||
onIsPublicChange = { isPublic = it },
|
|
||||||
isCollaborating = isCollaborating,
|
|
||||||
onIsCollaboratingChange = { isCollaborating = it },
|
|
||||||
imageBase64 = imageBase64,
|
|
||||||
imagePreviewUrl = imagePreviewUrl,
|
|
||||||
onPickImage = handleImagePick
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End)
|
|
||||||
) {
|
|
||||||
MillenniumButton(
|
|
||||||
onClick = onDismiss,
|
|
||||||
style = MillenniumButtonStyle.Secondary
|
|
||||||
) {
|
|
||||||
Text(stringResource(Res.string.common_cancel))
|
|
||||||
}
|
|
||||||
MillenniumButton(
|
|
||||||
onClick = {
|
|
||||||
onSubmit(currentData)
|
|
||||||
onDismiss()
|
|
||||||
},
|
|
||||||
enabled = name.isNotBlank()
|
|
||||||
) {
|
|
||||||
Text(stringResource(if (isEditing) Res.string.common_save else Res.string.library_playlist_form_create))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,10 +18,10 @@
|
|||||||
package dev.krtirtho.spotube.modules.lyrics
|
package dev.krtirtho.spotube.modules.lyrics
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumBarsLoader
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumIndeterminateProgress
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -138,7 +138,7 @@ fun LyricsScreen(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
MillenniumIndeterminateProgress(modifier = Modifier.fillMaxWidth(0.4f))
|
MillenniumBarsLoader(size = 48.dp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,13 +18,15 @@
|
|||||||
package dev.krtirtho.spotube.modules.playlist
|
package dev.krtirtho.spotube.modules.playlist
|
||||||
|
|
||||||
|
|
||||||
|
import dev.krtirtho.spotube.core.ui.component.AdaptiveDialogBottomSheet
|
||||||
|
import org.spotube.millennium.composables.MillenniumCircularProgress
|
||||||
|
import org.spotube.millennium.composables.MillenniumSpokeSpinner
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumDialog
|
import org.spotube.millennium.composables.MillenniumDialog
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumIndeterminateProgress
|
|
||||||
import org.spotube.millennium.composables.MillenniumModalBottomSheet
|
import org.spotube.millennium.composables.MillenniumModalBottomSheet
|
||||||
import org.spotube.millennium.composables.MillenniumTextField
|
import org.spotube.millennium.composables.MillenniumTextField
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -169,7 +171,7 @@ fun AddTracksToPlaylistDialog(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
contentAlignment = Alignment.Center) {
|
contentAlignment = Alignment.Center) {
|
||||||
MillenniumIndeterminateProgress()
|
MillenniumCircularProgress(size = 40.dp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hasSearched && searchResults.isEmpty() -> {
|
hasSearched && searchResults.isEmpty() -> {
|
||||||
@ -209,33 +211,23 @@ fun AddTracksToPlaylistDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLargeScreen) {
|
val dialogTitle = stringResource(Res.string.playlist_add_tracks_title)
|
||||||
MillenniumDialog(
|
AdaptiveDialogBottomSheet(
|
||||||
visible = true,
|
onDismiss = onDismiss,
|
||||||
onDismissRequest = onDismiss,
|
modifier = modifier,
|
||||||
title = stringResource(Res.string.playlist_add_tracks_title),
|
dialogTitle = dialogTitle,
|
||||||
modifier = modifier) {
|
title = { Text(dialogTitle, style = AppTheme.typography.headlineSmall) },
|
||||||
Box(modifier = Modifier.fillMaxWidth().height(500.dp)) {
|
actions = {
|
||||||
content()
|
Row(
|
||||||
}
|
modifier = Modifier.fillMaxWidth(),
|
||||||
MillenniumButton(onClick = onDismiss) {
|
horizontalArrangement = Arrangement.End) {
|
||||||
Text(stringResource(Res.string.playlist_add_tracks_done))
|
MillenniumButton(onClick = onDismiss) {
|
||||||
}
|
Text(stringResource(Res.string.playlist_add_tracks_done))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
val sheetState = rememberModalBottomSheetState(initialDetent = SheetDetent.FullyExpanded)
|
|
||||||
MillenniumModalBottomSheet(state = sheetState) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
.height(600.dp)) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(Res.string.playlist_add_tracks_title),
|
|
||||||
style = AppTheme.typography.headlineSmall,
|
|
||||||
modifier = Modifier.padding(bottom = 16.dp))
|
|
||||||
content()
|
|
||||||
}
|
}
|
||||||
|
}) {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth().height(500.dp)) {
|
||||||
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,8 +289,7 @@ private fun TrackSearchResultRow(
|
|||||||
modifier = Modifier.size(24.dp))
|
modifier = Modifier.size(24.dp))
|
||||||
}
|
}
|
||||||
isAdding -> {
|
isAdding -> {
|
||||||
MillenniumIndeterminateProgress(
|
MillenniumSpokeSpinner(size = 24.dp)
|
||||||
modifier = Modifier.size(24.dp))
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
MillenniumButton(
|
MillenniumButton(
|
||||||
|
|||||||
@ -18,13 +18,15 @@
|
|||||||
package dev.krtirtho.spotube.modules.plugin
|
package dev.krtirtho.spotube.modules.plugin
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumCircularProgress
|
||||||
|
import org.spotube.millennium.composables.MillenniumDotsLoader
|
||||||
|
import org.spotube.millennium.composables.MillenniumSpokeSpinner
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumDialog
|
import org.spotube.millennium.composables.MillenniumDialog
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumIndeterminateProgress
|
|
||||||
import org.spotube.millennium.composables.MillenniumSeparator
|
import org.spotube.millennium.composables.MillenniumSeparator
|
||||||
import org.spotube.millennium.composables.MillenniumSegmentedButton
|
import org.spotube.millennium.composables.MillenniumSegmentedButton
|
||||||
import org.spotube.millennium.composables.MillenniumSegmentedButtonDefaults
|
import org.spotube.millennium.composables.MillenniumSegmentedButtonDefaults
|
||||||
@ -85,6 +87,22 @@ import dev.krtirtho.spotube.core.ui.component.HeaderDisplayMode
|
|||||||
import dev.krtirtho.spotube.getPlatform
|
import dev.krtirtho.spotube.getPlatform
|
||||||
import dev.krtirtho.spotube.modules.plugin.components.DefaultAbilityPluginGrid
|
import dev.krtirtho.spotube.modules.plugin.components.DefaultAbilityPluginGrid
|
||||||
import dev.krtirtho.spotube.modules.plugin.components.PluginCard
|
import dev.krtirtho.spotube.modules.plugin.components.PluginCard
|
||||||
|
import dev.krtirtho.spotube.modules.plugin.components.PluginDialogActions
|
||||||
|
import dev.krtirtho.spotube.modules.plugin.components.PluginDialogBody
|
||||||
|
import dev.krtirtho.spotube.modules.plugin.components.PluginDialogTitle
|
||||||
|
import dev.krtirtho.spotube.modules.plugin.components.PluginIconTile
|
||||||
|
import dev.krtirtho.spotube.modules.plugin.components.PluginLogoTile
|
||||||
|
import dev.krtirtho.spotube.modules.plugin.components.PluginPanel
|
||||||
|
import dev.krtirtho.spotube.modules.plugin.components.PluginPill
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxBoxAdd
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.User
|
||||||
|
import compose.icons.FeatherIcons
|
||||||
|
import compose.icons.feathericons.ChevronRight
|
||||||
|
import spotube.composeapp.generated.resources.plugin_install_sheet_subtitle
|
||||||
|
import spotube.composeapp.generated.resources.plugin_install_file_hint
|
||||||
|
import spotube.composeapp.generated.resources.plugin_install_or
|
||||||
|
import spotube.composeapp.generated.resources.plugin_info_dialog_title
|
||||||
|
import spotube.composeapp.generated.resources.plugin_version_label
|
||||||
import dev.krtirtho.spotube.modules.plugin.components.PluginInstallDialog
|
import dev.krtirtho.spotube.modules.plugin.components.PluginInstallDialog
|
||||||
import dev.krtirtho.spotube.modules.plugin.components.PluginLogo
|
import dev.krtirtho.spotube.modules.plugin.components.PluginLogo
|
||||||
import dev.krtirtho.spotube.modules.plugin.components.PluginPermissionDialog
|
import dev.krtirtho.spotube.modules.plugin.components.PluginPermissionDialog
|
||||||
@ -267,7 +285,7 @@ internal fun PluginScreenContent(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) { MillenniumIndeterminateProgress(modifier = Modifier.widthIn(max = 160.dp)) }
|
) { MillenniumCircularProgress(size = 40.dp) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is PluginUiState.Data -> {
|
is PluginUiState.Data -> {
|
||||||
@ -492,7 +510,7 @@ internal fun PluginScreenContent(
|
|||||||
Modifier.fillMaxWidth().padding(16.dp),
|
Modifier.fillMaxWidth().padding(16.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
MillenniumIndeterminateProgress(Modifier.widthIn(max = 120.dp))
|
MillenniumDotsLoader()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -524,7 +542,7 @@ internal fun PluginScreenContent(
|
|||||||
.padding(vertical = 16.dp),
|
.padding(vertical = 16.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
MillenniumIndeterminateProgress(modifier = Modifier.widthIn(max = 120.dp))
|
MillenniumDotsLoader()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -606,24 +624,17 @@ private fun InstallSheet(
|
|||||||
AdaptiveDialogBottomSheet(
|
AdaptiveDialogBottomSheet(
|
||||||
onDismiss = onDismiss,
|
onDismiss = onDismiss,
|
||||||
title = {
|
title = {
|
||||||
Row(
|
PluginDialogTitle(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
title = stringResource(Res.string.plugin_install_section_title),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
icon = Iconsax.IconsaxBoxAdd)
|
||||||
) {
|
},
|
||||||
MillenniumIcon(
|
dialogTitle = stringResource(Res.string.plugin_install_section_title)) {
|
||||||
Iconsax.IconsaxImportArrow2Bulk,
|
PluginDialogBody {
|
||||||
contentDescription = null,
|
Text(
|
||||||
modifier = Modifier.size(18.dp),
|
stringResource(Res.string.plugin_install_sheet_subtitle),
|
||||||
tint = AppTheme.colors.primary
|
style = AppTheme.typography.bodySmall,
|
||||||
)
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
Text(
|
|
||||||
stringResource(Res.string.plugin_install_section_title),
|
|
||||||
style = AppTheme.typography.titleSmall,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
||||||
val urlState = rememberTextFieldState(urlInput)
|
val urlState = rememberTextFieldState(urlInput)
|
||||||
LaunchedEffect(urlInput) {
|
LaunchedEffect(urlInput) {
|
||||||
if (urlState.text.toString() != urlInput) {
|
if (urlState.text.toString() != urlInput) {
|
||||||
@ -636,51 +647,82 @@ private fun InstallSheet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
PluginPanel {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
Column(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
modifier = Modifier.fillMaxWidth().padding(14.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
) {
|
Row(
|
||||||
MillenniumTextField(
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
state = urlState,
|
horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
label = stringResource(Res.string.plugin_section_url_title),
|
PluginIconTile(Iconsax.IconsaxLink)
|
||||||
modifier = Modifier.weight(1f),
|
Text(
|
||||||
placeholder = stringResource(Res.string.plugin_url_placeholder),
|
stringResource(Res.string.plugin_section_url_title),
|
||||||
error = urlError?.text())
|
style = AppTheme.typography.titleSmall,
|
||||||
MillenniumIconButton(
|
fontWeight = FontWeight.SemiBold)
|
||||||
onClick = onSubmitUrl,
|
}
|
||||||
enabled = !isLoadingUrl,
|
MillenniumTextField(
|
||||||
style = MillenniumIconButtonStyle.Secondary) {
|
state = urlState,
|
||||||
if (isLoadingUrl) {
|
label = "",
|
||||||
MillenniumIndeterminateProgress(modifier = Modifier.size(16.dp))
|
modifier = Modifier.fillMaxWidth(),
|
||||||
} else {
|
placeholder = stringResource(Res.string.plugin_url_placeholder),
|
||||||
MillenniumIcon(
|
error = urlError?.text())
|
||||||
Iconsax.IconsaxImportArrow2Bulk,
|
MillenniumButton(
|
||||||
contentDescription = stringResource(Res.string.plugin_action_download))
|
onClick = onSubmitUrl,
|
||||||
|
enabled = !isLoadingUrl,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
style = MillenniumButtonStyle.Primary) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
if (isLoadingUrl) {
|
||||||
|
MillenniumSpokeSpinner(size = 16.dp, color = MillenniumTheme.colors.onAccent)
|
||||||
|
} else {
|
||||||
|
MillenniumIcon(
|
||||||
|
Iconsax.IconsaxImportArrow2Bulk,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp))
|
||||||
|
}
|
||||||
|
Text(stringResource(Res.string.plugin_action_download))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MillenniumSeparator()
|
Row(
|
||||||
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.plugin_section_file_title),
|
|
||||||
style = AppTheme.typography.labelLarge,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
|
||||||
)
|
|
||||||
MillenniumButton(
|
|
||||||
onClick = onPickFile,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
style = MillenniumButtonStyle.Secondary) {
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
|
MillenniumSeparator(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
stringResource(Res.string.plugin_install_or),
|
||||||
|
style = AppTheme.typography.labelSmall,
|
||||||
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
|
MillenniumSeparator(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginPanel(modifier = Modifier.clickable(onClick = onPickFile)) {
|
||||||
Row(
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(14.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
) {
|
PluginIconTile(Iconsax.IconsaxExportArrowBulk)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
Text(
|
||||||
|
stringResource(Res.string.plugin_action_install_from_file),
|
||||||
|
style = AppTheme.typography.titleSmall,
|
||||||
|
fontWeight = FontWeight.SemiBold)
|
||||||
|
Text(
|
||||||
|
stringResource(Res.string.plugin_install_file_hint),
|
||||||
|
style = AppTheme.typography.labelSmall,
|
||||||
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
|
}
|
||||||
MillenniumIcon(
|
MillenniumIcon(
|
||||||
Iconsax.IconsaxExportArrowBulk,
|
FeatherIcons.ChevronRight,
|
||||||
contentDescription = stringResource(Res.string.plugin_action_install_from_file)
|
contentDescription = null,
|
||||||
)
|
modifier = Modifier.size(18.dp),
|
||||||
Text(stringResource(Res.string.plugin_action_install_from_file))
|
tint = AppTheme.colors.onSurfaceVariant)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -692,84 +734,82 @@ private fun PluginInfoDialog(
|
|||||||
plugin: PluginEntry,
|
plugin: PluginEntry,
|
||||||
logoPath: Path?,
|
logoPath: Path?,
|
||||||
onDismiss: () -> Unit) {
|
onDismiss: () -> Unit) {
|
||||||
MillenniumDialog(
|
AdaptiveDialogBottomSheet(
|
||||||
visible = true,
|
onDismiss = onDismiss,
|
||||||
onDismissRequest = onDismiss,
|
title = { PluginDialogTitle(stringResource(Res.string.plugin_info_dialog_title)) },
|
||||||
title = plugin.name) {
|
dialogTitle = stringResource(Res.string.plugin_info_dialog_title),
|
||||||
Box(
|
actions = {
|
||||||
modifier = Modifier
|
PluginDialogActions(
|
||||||
.size(48.dp)
|
secondaryLabel = stringResource(Res.string.common_close),
|
||||||
.clip(RoundedCornerShape(12.dp))
|
onSecondary = onDismiss)
|
||||||
.background(AppTheme.colors.primary.copy(alpha = 0.12f))
|
}) {
|
||||||
) {
|
PluginDialogBody {
|
||||||
PluginLogo(
|
PluginPanel {
|
||||||
plugin = plugin,
|
Row(
|
||||||
logoPath = logoPath,
|
modifier = Modifier.fillMaxWidth().padding(14.dp),
|
||||||
contentDescription = plugin.name,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxSize(),
|
horizontalArrangement = Arrangement.spacedBy(14.dp)) {
|
||||||
fallback = {
|
PluginLogoTile(plugin = plugin, logoPath = logoPath)
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Column(
|
||||||
MillenniumIcon(
|
modifier = Modifier.weight(1f),
|
||||||
Iconsax.IconsaxBox,
|
verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
contentDescription = null,
|
Text(
|
||||||
modifier = Modifier.size(24.dp),
|
plugin.name,
|
||||||
tint = AppTheme.colors.primary
|
style = AppTheme.typography.titleMedium,
|
||||||
)
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis)
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
|
PluginPill(plugin.author, icon = Iconsax.User)
|
||||||
|
PluginPill(
|
||||||
|
stringResource(Res.string.plugin_version_label, plugin.version),
|
||||||
|
tint = AppTheme.colors.primary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
if (plugin.description.isNotBlank()) {
|
||||||
Column(
|
Text(
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
plugin.description,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.padding(start = 14.dp, end = 14.dp, bottom = 14.dp),
|
||||||
) {
|
style = AppTheme.typography.bodySmall,
|
||||||
if (plugin.description.isNotBlank()) {
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
Text(
|
|
||||||
plugin.description,
|
|
||||||
style = AppTheme.typography.bodyMedium,
|
|
||||||
color = AppTheme.colors.onSurface
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
DetailRow(stringResource(Res.string.plugin_info_version), plugin.version)
|
|
||||||
DetailRow(stringResource(Res.string.plugin_info_api_version), plugin.apiVersion)
|
|
||||||
DetailRow(stringResource(Res.string.plugin_info_author), plugin.author)
|
|
||||||
if (plugin.license.isNotBlank()) {
|
|
||||||
DetailRow(stringResource(Res.string.plugin_info_license), plugin.license)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin.capabilities.isNotEmpty()) {
|
|
||||||
DetailChipsRow(
|
|
||||||
stringResource(Res.string.plugin_info_capabilities),
|
|
||||||
plugin.capabilities.map { it.name })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin.abilities.isNotEmpty()) {
|
|
||||||
DetailChipsRow(
|
|
||||||
stringResource(Res.string.plugin_info_abilities),
|
|
||||||
plugin.abilities.map { ability -> ability.displayLabel() })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin.repository.isNotBlank()) {
|
|
||||||
ClickableDetailRow(label = stringResource(Res.string.plugin_info_repository), value = plugin.repository) {
|
|
||||||
openUrlInBrowser(plugin.repository)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plugin.contact.isNotBlank()) {
|
PluginPanel {
|
||||||
DetailRow(stringResource(Res.string.plugin_info_contact), plugin.contact)
|
Column(
|
||||||
}
|
modifier = Modifier.fillMaxWidth().padding(14.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
if (plugin.bugs.isNotBlank()) {
|
DetailRow(stringResource(Res.string.plugin_info_api_version), plugin.apiVersion)
|
||||||
ClickableDetailRow(label = stringResource(Res.string.plugin_info_report_bugs), value = plugin.bugs) {
|
if (plugin.license.isNotBlank()) {
|
||||||
openUrlInBrowser(plugin.bugs)
|
DetailRow(stringResource(Res.string.plugin_info_license), plugin.license)
|
||||||
|
}
|
||||||
|
if (plugin.capabilities.isNotEmpty()) {
|
||||||
|
DetailChipsRow(
|
||||||
|
stringResource(Res.string.plugin_info_capabilities),
|
||||||
|
plugin.capabilities.map { it.name })
|
||||||
|
}
|
||||||
|
if (plugin.abilities.isNotEmpty()) {
|
||||||
|
DetailChipsRow(
|
||||||
|
stringResource(Res.string.plugin_info_abilities),
|
||||||
|
plugin.abilities.map { ability -> ability.displayLabel() })
|
||||||
|
}
|
||||||
|
if (plugin.repository.isNotBlank()) {
|
||||||
|
ClickableDetailRow(label = stringResource(Res.string.plugin_info_repository), value = plugin.repository) {
|
||||||
|
openUrlInBrowser(plugin.repository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (plugin.contact.isNotBlank()) {
|
||||||
|
DetailRow(stringResource(Res.string.plugin_info_contact), plugin.contact)
|
||||||
|
}
|
||||||
|
if (plugin.bugs.isNotBlank()) {
|
||||||
|
ClickableDetailRow(label = stringResource(Res.string.plugin_info_report_bugs), value = plugin.bugs) {
|
||||||
|
openUrlInBrowser(plugin.bugs)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
|
||||||
MillenniumButton(onClick = onDismiss) {
|
|
||||||
Text(stringResource(Res.string.common_close))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -779,27 +819,30 @@ private fun SupportDialog(
|
|||||||
supportText: String?,
|
supportText: String?,
|
||||||
isLoading: Boolean,
|
isLoading: Boolean,
|
||||||
onDismiss: () -> Unit) {
|
onDismiss: () -> Unit) {
|
||||||
MillenniumDialog(
|
AdaptiveDialogBottomSheet(
|
||||||
visible = true,
|
onDismiss = onDismiss,
|
||||||
onDismissRequest = onDismiss,
|
title = { PluginDialogTitle(stringResource(Res.string.plugin_support_dialog_title, plugin.name)) },
|
||||||
title = stringResource(Res.string.plugin_support_dialog_title, plugin.name)) {
|
dialogTitle = stringResource(Res.string.plugin_support_dialog_title, plugin.name),
|
||||||
if (isLoading) {
|
actions = {
|
||||||
Box(
|
PluginDialogActions(
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp),
|
secondaryLabel = stringResource(Res.string.common_close),
|
||||||
contentAlignment = Alignment.Center
|
onSecondary = onDismiss)
|
||||||
) {
|
}) {
|
||||||
MillenniumIndeterminateProgress(modifier = Modifier.widthIn(max = 160.dp))
|
PluginDialogBody {
|
||||||
}
|
if (isLoading) {
|
||||||
} else {
|
Box(
|
||||||
Text(
|
modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp),
|
||||||
supportText ?: "",
|
contentAlignment = Alignment.Center) {
|
||||||
style = AppTheme.typography.bodyMedium,
|
MillenniumCircularProgress(size = 36.dp)
|
||||||
color = AppTheme.colors.onSurface
|
}
|
||||||
)
|
} else {
|
||||||
}
|
PluginPanel {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
Text(
|
||||||
MillenniumButton(onClick = onDismiss) {
|
supportText ?: "",
|
||||||
Text(stringResource(Res.string.common_close))
|
modifier = Modifier.padding(14.dp),
|
||||||
|
style = AppTheme.typography.bodyMedium,
|
||||||
|
color = AppTheme.colors.onSurface)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -972,7 +1015,7 @@ private fun DiscoverRepoCard(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
if (isInstalling) {
|
if (isInstalling) {
|
||||||
MillenniumIndeterminateProgress(modifier = Modifier.size(16.dp))
|
MillenniumSpokeSpinner(size = 16.dp)
|
||||||
} else {
|
} else {
|
||||||
MillenniumIcon(
|
MillenniumIcon(
|
||||||
Iconsax.IconsaxAdd,
|
Iconsax.IconsaxAdd,
|
||||||
|
|||||||
@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.krtirtho.spotube.modules.plugin.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
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.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.composeunstyled.Text
|
||||||
|
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
||||||
|
import dev.krtirtho.spotube.modules.plugin.PluginEntry
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxBoxAdd
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxInformation
|
||||||
|
import okio.Path
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
|
import org.spotube.millennium.theme.MillenniumTheme
|
||||||
|
import org.spotube.millennium.theme.millenniumPanelBrush
|
||||||
|
import org.spotube.millennium.theme.millenniumSurface
|
||||||
|
|
||||||
|
// Shared building blocks for the plugin dialogs/sheets so they all read as one family and
|
||||||
|
// match the panel/tile language used by the plugin cards and the rest of the app.
|
||||||
|
|
||||||
|
/** Body column for plugin dialogs; the adaptive dialog/sheet around it handles scrolling. */
|
||||||
|
@Composable
|
||||||
|
internal fun PluginDialogBody(content: @Composable ColumnScope.() -> Unit) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(14.dp),
|
||||||
|
content = content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Title for the phone bottom sheet; the desktop dialog shows the same text in its titlebar. */
|
||||||
|
@Composable
|
||||||
|
internal fun PluginDialogTitle(
|
||||||
|
title: String,
|
||||||
|
icon: ImageVector? = null) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
if (icon != null) {
|
||||||
|
PluginIconTile(icon, size = 40.dp)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style = AppTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = AppTheme.colors.onSurface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rounded, softly bordered panel — the same surface the plugin cards use. */
|
||||||
|
@Composable
|
||||||
|
internal fun PluginPanel(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable ColumnScope.() -> Unit) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.millenniumSurface(
|
||||||
|
background = millenniumPanelBrush(MillenniumTheme.colors),
|
||||||
|
shape = RoundedCornerShape(14.dp),
|
||||||
|
borderColor = AppTheme.colors.outlineVariant,
|
||||||
|
borderWidth = 1.dp),
|
||||||
|
content = content)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun PluginIconTile(
|
||||||
|
icon: ImageVector,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
size: Dp = 38.dp,
|
||||||
|
tint: Color = AppTheme.colors.primary) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(size)
|
||||||
|
.clip(RoundedCornerShape(size / 3.4f))
|
||||||
|
.background(tint.copy(alpha = 0.14f)),
|
||||||
|
contentAlignment = Alignment.Center) {
|
||||||
|
MillenniumIcon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(size * 0.5f),
|
||||||
|
tint = tint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun PluginLogoTile(
|
||||||
|
plugin: PluginEntry,
|
||||||
|
logoPath: Path?,
|
||||||
|
size: Dp = 56.dp) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(size)
|
||||||
|
.clip(RoundedCornerShape(size / 3.4f))
|
||||||
|
.background(AppTheme.colors.primary.copy(alpha = 0.12f)),
|
||||||
|
contentAlignment = Alignment.Center) {
|
||||||
|
PluginLogo(
|
||||||
|
plugin = plugin,
|
||||||
|
logoPath = logoPath,
|
||||||
|
contentDescription = plugin.name,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
fallback = {
|
||||||
|
MillenniumIcon(
|
||||||
|
imageVector = Iconsax.IconsaxBoxAdd,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = AppTheme.colors.primary,
|
||||||
|
modifier = Modifier.size(size * 0.5f))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun PluginPill(
|
||||||
|
text: String,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
tint: Color = AppTheme.colors.onSurfaceVariant) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(50))
|
||||||
|
.background(tint.copy(alpha = 0.12f))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 3.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
if (icon != null) {
|
||||||
|
MillenniumIcon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(12.dp),
|
||||||
|
tint = tint)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style = AppTheme.typography.labelSmall,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = tint,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tinted info strip; [isWarning] switches it to the error tone. */
|
||||||
|
@Composable
|
||||||
|
internal fun PluginCallout(text: String, isWarning: Boolean = false) {
|
||||||
|
val tint = if (isWarning) AppTheme.colors.error else AppTheme.colors.primary
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.background(tint.copy(alpha = 0.10f))
|
||||||
|
.padding(12.dp),
|
||||||
|
verticalAlignment = Alignment.Top,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
|
MillenniumIcon(
|
||||||
|
imageVector = Iconsax.IconsaxInformation,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
tint = tint)
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style = AppTheme.typography.bodySmall,
|
||||||
|
color = AppTheme.colors.onSurface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun PluginSectionLabel(text: String, badge: String? = null) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 2.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style = AppTheme.typography.labelLarge,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = AppTheme.colors.onSurface)
|
||||||
|
if (badge != null) {
|
||||||
|
PluginPill(badge, tint = AppTheme.colors.primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Equal-width secondary + primary action pair. */
|
||||||
|
@Composable
|
||||||
|
internal fun PluginDialogActions(
|
||||||
|
secondaryLabel: String,
|
||||||
|
onSecondary: () -> Unit,
|
||||||
|
primaryLabel: String? = null,
|
||||||
|
onPrimary: (() -> Unit)? = null,
|
||||||
|
primaryEnabled: Boolean = true) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
|
MillenniumButton(
|
||||||
|
onClick = onSecondary,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = if (primaryLabel != null) MillenniumButtonStyle.Secondary else MillenniumButtonStyle.Primary) {
|
||||||
|
Text(secondaryLabel)
|
||||||
|
}
|
||||||
|
if (primaryLabel != null && onPrimary != null) {
|
||||||
|
MillenniumButton(
|
||||||
|
onClick = onPrimary,
|
||||||
|
enabled = primaryEnabled,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = MillenniumButtonStyle.Primary) {
|
||||||
|
Text(primaryLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.plugin.components
|
package dev.krtirtho.spotube.modules.plugin.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import org.spotube.millennium.composables.MillenniumCircularProgress
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -32,10 +32,6 @@ import androidx.compose.foundation.layout.widthIn
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
|
||||||
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
|
||||||
|
|
||||||
import com.composeunstyled.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -52,19 +48,22 @@ import coil3.compose.AsyncImage
|
|||||||
import coil3.compose.LocalPlatformContext
|
import coil3.compose.LocalPlatformContext
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
import coil3.request.crossfade
|
import coil3.request.crossfade
|
||||||
import dev.krtirtho.spotube.modules.plugin.GitHubRepo
|
import com.composeunstyled.Text
|
||||||
|
import dev.krtirtho.spotube.core.ui.component.AdaptiveDialogBottomSheet
|
||||||
|
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
||||||
import dev.krtirtho.spotube.modules.plugin.GitHubRelease
|
import dev.krtirtho.spotube.modules.plugin.GitHubRelease
|
||||||
|
import dev.krtirtho.spotube.modules.plugin.GitHubRepo
|
||||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxArrowDown4
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxArrowDown4
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxBox
|
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumDialog
|
|
||||||
import org.spotube.millennium.composables.MillenniumDropdownMenu
|
import org.spotube.millennium.composables.MillenniumDropdownMenu
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuContent
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuItem
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIndeterminateProgress
|
|
||||||
import spotube.composeapp.generated.resources.Res
|
import spotube.composeapp.generated.resources.Res
|
||||||
|
import spotube.composeapp.generated.resources.common_cancel
|
||||||
import spotube.composeapp.generated.resources.plugin_action_install
|
import spotube.composeapp.generated.resources.plugin_action_install
|
||||||
import spotube.composeapp.generated.resources.plugin_install_no_releases
|
import spotube.composeapp.generated.resources.plugin_install_no_releases
|
||||||
import spotube.composeapp.generated.resources.plugin_install_prerelease
|
import spotube.composeapp.generated.resources.plugin_install_prerelease
|
||||||
@ -88,21 +87,24 @@ fun PluginInstallDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MillenniumDialog(
|
AdaptiveDialogBottomSheet(
|
||||||
visible = true,
|
onDismiss = onDismiss,
|
||||||
onDismissRequest = onDismiss,
|
title = { PluginDialogTitle(stringResource(Res.string.plugin_action_install)) },
|
||||||
title = repo.fullName) {
|
dialogTitle = stringResource(Res.string.plugin_action_install),
|
||||||
Row(
|
actions = {
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
PluginDialogActions(
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
secondaryLabel = stringResource(Res.string.common_cancel),
|
||||||
) {
|
onSecondary = onDismiss,
|
||||||
Box(
|
primaryLabel = stringResource(Res.string.plugin_action_install),
|
||||||
modifier = Modifier
|
onPrimary = { selectedRelease?.let(onInstall) },
|
||||||
.size(48.dp)
|
primaryEnabled = selectedRelease != null && !isLoadingReleases)
|
||||||
.clip(RoundedCornerShape(12.dp))
|
}) {
|
||||||
.background(AppTheme.colors.primary.copy(alpha = 0.12f))
|
PluginDialogBody {
|
||||||
) {
|
PluginPanel {
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(14.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(14.dp)) {
|
||||||
val platformContext = LocalPlatformContext.current
|
val platformContext = LocalPlatformContext.current
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = ImageRequest.Builder(platformContext)
|
model = ImageRequest.Builder(platformContext)
|
||||||
@ -110,131 +112,99 @@ fun PluginInstallDialog(
|
|||||||
.crossfade(true)
|
.crossfade(true)
|
||||||
.build(),
|
.build(),
|
||||||
contentDescription = repo.owner.login,
|
contentDescription = repo.owner.login,
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.size(56.dp).clip(RoundedCornerShape(16.dp)))
|
||||||
)
|
Column(
|
||||||
}
|
modifier = Modifier.weight(1f),
|
||||||
}
|
verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Text(
|
||||||
Text(
|
repo.fullName,
|
||||||
repo.fullName,
|
style = AppTheme.typography.titleMedium,
|
||||||
style = AppTheme.typography.titleMedium,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontWeight = FontWeight.SemiBold,
|
maxLines = 1,
|
||||||
maxLines = 1,
|
overflow = TextOverflow.Ellipsis)
|
||||||
overflow = TextOverflow.Ellipsis
|
if (!repo.description.isNullOrBlank()) {
|
||||||
)
|
|
||||||
if (!repo.description.isNullOrBlank()) {
|
|
||||||
Text(
|
|
||||||
repo.description,
|
|
||||||
style = AppTheme.typography.bodySmall,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoadingReleases) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxWidth().height(120.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
MillenniumIndeterminateProgress(modifier = Modifier.widthIn(max = 160.dp))
|
|
||||||
}
|
|
||||||
} else if (releases.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.plugin_install_no_releases),
|
|
||||||
style = AppTheme.typography.bodyMedium,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.plugin_install_select_release_label),
|
|
||||||
style = AppTheme.typography.labelMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
color = AppTheme.colors.onSurface
|
|
||||||
)
|
|
||||||
|
|
||||||
MillenniumDropdownMenu(
|
|
||||||
expanded = expanded,
|
|
||||||
onExpandedChange = { expanded = it },
|
|
||||||
items = releases.map { it.tagName },
|
|
||||||
onItemClick = { tagName ->
|
|
||||||
selectedRelease = releases.firstOrNull { it.tagName == tagName }
|
|
||||||
expanded = false
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
anchor = {
|
|
||||||
MillenniumButton(
|
|
||||||
onClick = { expanded = true },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
style = MillenniumButtonStyle.Secondary
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
Text(
|
Text(
|
||||||
selectedRelease?.tagName
|
repo.description,
|
||||||
?: stringResource(Res.string.plugin_install_select_release_placeholder),
|
|
||||||
style = AppTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
MillenniumIcon(
|
|
||||||
imageVector = Iconsax.IconsaxArrowDown4,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
selectedRelease?.let { release ->
|
|
||||||
if (release.prerelease) {
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.plugin_install_prerelease),
|
|
||||||
style = AppTheme.typography.labelSmall,
|
|
||||||
color = AppTheme.colors.error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (!release.body.isNullOrBlank()) {
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.plugin_install_release_notes),
|
|
||||||
style = AppTheme.typography.labelMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
color = AppTheme.colors.onSurface
|
|
||||||
)
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(8.dp))
|
|
||||||
.background(AppTheme.colors.surfaceVariant.copy(alpha = 0.5f))
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(max = 200.dp)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(12.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
release.body,
|
|
||||||
style = AppTheme.typography.bodySmall,
|
style = AppTheme.typography.bodySmall,
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
color = AppTheme.colors.onSurfaceVariant,
|
||||||
)
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
if (isLoadingReleases) {
|
||||||
MillenniumButton(
|
Box(
|
||||||
onClick = { selectedRelease?.let { onInstall(it) } },
|
modifier = Modifier.fillMaxWidth().height(96.dp),
|
||||||
enabled = selectedRelease != null && !isLoadingReleases
|
contentAlignment = Alignment.Center) {
|
||||||
) {
|
MillenniumCircularProgress(size = 36.dp)
|
||||||
Text(stringResource(Res.string.plugin_action_install))
|
}
|
||||||
|
} else if (releases.isEmpty()) {
|
||||||
|
PluginCallout(stringResource(Res.string.plugin_install_no_releases), isWarning = true)
|
||||||
|
} else {
|
||||||
|
PluginSectionLabel(stringResource(Res.string.plugin_install_select_release_label))
|
||||||
|
MillenniumDropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onExpandedChange = { expanded = it },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
anchor = {
|
||||||
|
MillenniumButton(
|
||||||
|
onClick = { expanded = true },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
style = MillenniumButtonStyle.Secondary) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
Text(
|
||||||
|
selectedRelease?.tagName
|
||||||
|
?: stringResource(Res.string.plugin_install_select_release_placeholder),
|
||||||
|
style = AppTheme.typography.bodyMedium)
|
||||||
|
MillenniumIcon(
|
||||||
|
imageVector = Iconsax.IconsaxArrowDown4,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
MillenniumDropdownMenuContent {
|
||||||
|
releases.forEach { release ->
|
||||||
|
MillenniumDropdownMenuItem(
|
||||||
|
label = release.tagName,
|
||||||
|
selected = release.tagName == selectedRelease?.tagName,
|
||||||
|
description = if (release.prerelease) {
|
||||||
|
stringResource(Res.string.plugin_install_prerelease)
|
||||||
|
} else null,
|
||||||
|
onClick = {
|
||||||
|
selectedRelease = release
|
||||||
|
expanded = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedRelease?.let { release ->
|
||||||
|
if (release.prerelease) {
|
||||||
|
PluginCallout(stringResource(Res.string.plugin_install_prerelease), isWarning = true)
|
||||||
|
}
|
||||||
|
if (!release.body.isNullOrBlank()) {
|
||||||
|
PluginSectionLabel(stringResource(Res.string.plugin_install_release_notes))
|
||||||
|
PluginPanel {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(max = 200.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(12.dp)) {
|
||||||
|
Text(
|
||||||
|
release.body,
|
||||||
|
style = AppTheme.typography.bodySmall,
|
||||||
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,47 +17,39 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.plugin.components
|
package dev.krtirtho.spotube.modules.plugin.components
|
||||||
|
|
||||||
|
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
|
||||||
import org.spotube.millennium.composables.MillenniumCard
|
|
||||||
import org.spotube.millennium.composables.MillenniumDialog
|
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
|
|
||||||
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
|
||||||
import com.composeunstyled.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import okio.Path
|
import com.composeunstyled.Text
|
||||||
|
import compose.icons.FeatherIcons
|
||||||
|
import compose.icons.feathericons.ArrowRight
|
||||||
|
import dev.krtirtho.spotube.core.ui.component.AdaptiveDialogBottomSheet
|
||||||
|
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
||||||
import dev.krtirtho.spotube.modules.plugin.PluginCapability
|
import dev.krtirtho.spotube.modules.plugin.PluginCapability
|
||||||
import dev.krtirtho.spotube.modules.plugin.PluginEntry
|
import dev.krtirtho.spotube.modules.plugin.PluginEntry
|
||||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxBoxAdd
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxCheckCircle
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxEye
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxEye
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxWifiSquare
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxWifiSquare
|
||||||
import dev.krtirtho.spotube.resources.iconsax.PhosphorDatabaseDuotone
|
import dev.krtirtho.spotube.resources.iconsax.PhosphorDatabaseDuotone
|
||||||
import dev.krtirtho.spotube.resources.iconsax.User
|
import dev.krtirtho.spotube.resources.iconsax.User
|
||||||
|
import okio.Path
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
|
import org.spotube.millennium.composables.MillenniumSeparator
|
||||||
import spotube.composeapp.generated.resources.Res
|
import spotube.composeapp.generated.resources.Res
|
||||||
|
import spotube.composeapp.generated.resources.common_cancel
|
||||||
|
import spotube.composeapp.generated.resources.common_close
|
||||||
import spotube.composeapp.generated.resources.plugin_permissions_api_diff
|
import spotube.composeapp.generated.resources.plugin_permissions_api_diff
|
||||||
import spotube.composeapp.generated.resources.plugin_permissions_author_version
|
|
||||||
import spotube.composeapp.generated.resources.plugin_permissions_capability_network_desc
|
import spotube.composeapp.generated.resources.plugin_permissions_capability_network_desc
|
||||||
import spotube.composeapp.generated.resources.plugin_permissions_capability_network_title
|
import spotube.composeapp.generated.resources.plugin_permissions_capability_network_title
|
||||||
import spotube.composeapp.generated.resources.plugin_permissions_capability_storage_desc
|
import spotube.composeapp.generated.resources.plugin_permissions_capability_storage_desc
|
||||||
@ -70,8 +62,6 @@ import spotube.composeapp.generated.resources.plugin_permissions_none
|
|||||||
import spotube.composeapp.generated.resources.plugin_permissions_requested_title
|
import spotube.composeapp.generated.resources.plugin_permissions_requested_title
|
||||||
import spotube.composeapp.generated.resources.plugin_permissions_supplied_label
|
import spotube.composeapp.generated.resources.plugin_permissions_supplied_label
|
||||||
import spotube.composeapp.generated.resources.plugin_version_label
|
import spotube.composeapp.generated.resources.plugin_version_label
|
||||||
import spotube.composeapp.generated.resources.common_cancel
|
|
||||||
import spotube.composeapp.generated.resources.common_close
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PluginPermissionDialog(
|
fun PluginPermissionDialog(
|
||||||
@ -83,129 +73,80 @@ fun PluginPermissionDialog(
|
|||||||
logoPath: Path? = null,
|
logoPath: Path? = null,
|
||||||
onConfirm: (() -> Unit)? = null,
|
onConfirm: (() -> Unit)? = null,
|
||||||
onDismiss: () -> Unit) {
|
onDismiss: () -> Unit) {
|
||||||
MillenniumDialog(
|
AdaptiveDialogBottomSheet(
|
||||||
visible = true,
|
onDismiss = onDismiss,
|
||||||
onDismissRequest = onDismiss,
|
title = { PluginDialogTitle(title) },
|
||||||
title = title) {
|
dialogTitle = title,
|
||||||
Row(
|
actions = {
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
PluginDialogActions(
|
||||||
horizontalArrangement = Arrangement.spacedBy(14.dp)
|
secondaryLabel = stringResource(
|
||||||
) {
|
if (confirmLabel == null) Res.string.common_close else Res.string.common_cancel),
|
||||||
Box(
|
onSecondary = onDismiss,
|
||||||
modifier = Modifier
|
primaryLabel = confirmLabel,
|
||||||
.size(48.dp)
|
onPrimary = onConfirm)
|
||||||
.clip(RoundedCornerShape(12.dp))
|
}) {
|
||||||
.background(AppTheme.colors.primary.copy(alpha = 0.12f)),
|
PluginDialogBody {
|
||||||
contentAlignment = Alignment.Center
|
PluginPanel {
|
||||||
) {
|
Row(
|
||||||
PluginLogo(
|
modifier = Modifier.fillMaxWidth().padding(14.dp),
|
||||||
plugin = pluginInfo,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
logoPath = logoPath,
|
horizontalArrangement = Arrangement.spacedBy(14.dp)) {
|
||||||
contentDescription = pluginInfo.name,
|
PluginLogoTile(plugin = pluginInfo, logoPath = logoPath)
|
||||||
modifier = Modifier.fillMaxSize(),
|
Column(
|
||||||
fallback = {
|
modifier = Modifier.weight(1f),
|
||||||
MillenniumIcon(
|
verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
imageVector = Iconsax.IconsaxBoxAdd,
|
Text(
|
||||||
contentDescription = null,
|
pluginInfo.name,
|
||||||
tint = AppTheme.colors.primary,
|
style = AppTheme.typography.titleMedium,
|
||||||
modifier = Modifier.size(24.dp)
|
fontWeight = FontWeight.SemiBold,
|
||||||
)
|
maxLines = 1,
|
||||||
})
|
overflow = TextOverflow.Ellipsis)
|
||||||
}
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
PluginPill(pluginInfo.author, icon = Iconsax.User)
|
||||||
Text(
|
PluginPill(
|
||||||
pluginInfo.name,
|
stringResource(Res.string.plugin_version_label, pluginInfo.version),
|
||||||
style = AppTheme.typography.titleMedium,
|
tint = AppTheme.colors.primary)
|
||||||
fontWeight = FontWeight.SemiBold,
|
}
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
|
||||||
) {
|
|
||||||
MillenniumIcon(
|
|
||||||
Iconsax.User,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(13.dp),
|
|
||||||
tint = AppTheme.colors.onSurfaceVariant
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
pluginInfo.author,
|
|
||||||
style = AppTheme.typography.labelSmall,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
Res.string.plugin_permissions_author_version,
|
|
||||||
stringResource(Res.string.plugin_version_label, pluginInfo.version)
|
|
||||||
),
|
|
||||||
style = AppTheme.typography.labelSmall,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pluginInfo.description.isNotBlank()) {
|
|
||||||
Text(
|
|
||||||
pluginInfo.description,
|
|
||||||
style = AppTheme.typography.bodySmall,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant,
|
|
||||||
maxLines = 3,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
MillenniumCard(contentPadding = PaddingValues(14.dp)) {
|
|
||||||
Text(
|
|
||||||
message,
|
|
||||||
style = AppTheme.typography.bodySmall,
|
|
||||||
color = AppTheme.colors.onSurface
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
existingPlugin?.let {
|
|
||||||
InstalledComparisonCard(
|
|
||||||
installedPlugin = it,
|
|
||||||
incomingPlugin = pluginInfo
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.plugin_permissions_requested_title),
|
|
||||||
style = AppTheme.typography.labelMedium,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
color = AppTheme.colors.onSurface
|
|
||||||
)
|
|
||||||
|
|
||||||
if (pluginInfo.capabilities.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.plugin_permissions_none),
|
|
||||||
style = AppTheme.typography.bodySmall,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
pluginInfo.capabilities.forEach { capability ->
|
|
||||||
CapabilityRow(capability)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
|
||||||
MillenniumButton(
|
|
||||||
onClick = onDismiss,
|
|
||||||
style = MillenniumButtonStyle.Secondary) {
|
|
||||||
Text(
|
|
||||||
if (confirmLabel == null) {
|
|
||||||
stringResource(Res.string.common_close)
|
|
||||||
} else {
|
|
||||||
stringResource(Res.string.common_cancel)
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
if (pluginInfo.description.isNotBlank()) {
|
||||||
|
Text(
|
||||||
|
pluginInfo.description,
|
||||||
|
modifier = Modifier.padding(start = 14.dp, end = 14.dp, bottom = 14.dp),
|
||||||
|
style = AppTheme.typography.bodySmall,
|
||||||
|
color = AppTheme.colors.onSurfaceVariant,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (confirmLabel != null && onConfirm != null) {
|
|
||||||
MillenniumButton(onClick = onConfirm) {
|
PluginCallout(text = message, isWarning = confirmLabel == null)
|
||||||
Text(confirmLabel)
|
|
||||||
|
existingPlugin?.let {
|
||||||
|
InstalledComparisonPanel(installedPlugin = it, incomingPlugin = pluginInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginSectionLabel(
|
||||||
|
stringResource(Res.string.plugin_permissions_requested_title),
|
||||||
|
badge = pluginInfo.capabilities.size.takeIf { it > 0 }?.toString())
|
||||||
|
|
||||||
|
PluginPanel {
|
||||||
|
if (pluginInfo.capabilities.isEmpty()) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(14.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
PluginIconTile(Iconsax.IconsaxCheckCircle)
|
||||||
|
Text(
|
||||||
|
stringResource(Res.string.plugin_permissions_none),
|
||||||
|
style = AppTheme.typography.bodySmall,
|
||||||
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pluginInfo.capabilities.forEachIndexed { index, capability ->
|
||||||
|
if (index > 0) MillenniumSeparator()
|
||||||
|
CapabilityRow(capability)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,57 +154,58 @@ fun PluginPermissionDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InstalledComparisonCard(
|
private fun InstalledComparisonPanel(
|
||||||
installedPlugin: PluginEntry,
|
installedPlugin: PluginEntry,
|
||||||
incomingPlugin: PluginEntry) {
|
incomingPlugin: PluginEntry) {
|
||||||
MillenniumCard(
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
PluginSectionLabel(stringResource(Res.string.plugin_permissions_compare_title))
|
||||||
contentPadding = PaddingValues(14.dp)) {
|
PluginPanel {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth().padding(14.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
) {
|
Row(
|
||||||
Text(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
stringResource(Res.string.plugin_permissions_compare_title),
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
style = AppTheme.typography.labelMedium,
|
horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||||
fontWeight = FontWeight.SemiBold)
|
VersionStat(
|
||||||
Row(
|
label = stringResource(Res.string.plugin_permissions_installed_label),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
value = installedPlugin.version)
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
MillenniumIcon(
|
||||||
) {
|
imageVector = FeatherIcons.ArrowRight,
|
||||||
VersionStat(
|
contentDescription = null,
|
||||||
label = stringResource(Res.string.plugin_permissions_installed_label),
|
modifier = Modifier.size(18.dp),
|
||||||
value = installedPlugin.version)
|
tint = AppTheme.colors.onSurfaceVariant)
|
||||||
VersionStat(
|
VersionStat(
|
||||||
label = stringResource(Res.string.plugin_permissions_supplied_label),
|
label = stringResource(Res.string.plugin_permissions_supplied_label),
|
||||||
value = incomingPlugin.version)
|
value = incomingPlugin.version,
|
||||||
|
highlighted = true)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
Res.string.plugin_permissions_api_diff,
|
||||||
|
installedPlugin.apiVersion,
|
||||||
|
incomingPlugin.apiVersion),
|
||||||
|
style = AppTheme.typography.labelSmall,
|
||||||
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
}
|
}
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
Res.string.plugin_permissions_api_diff,
|
|
||||||
installedPlugin.apiVersion,
|
|
||||||
incomingPlugin.apiVersion
|
|
||||||
),
|
|
||||||
style = AppTheme.typography.labelSmall,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun VersionStat(label: String, value: String) {
|
private fun VersionStat(label: String, value: String, highlighted: Boolean = false) {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
style = AppTheme.typography.labelSmall,
|
style = AppTheme.typography.labelSmall,
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
)
|
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.plugin_version_label, value),
|
stringResource(Res.string.plugin_version_label, value),
|
||||||
style = AppTheme.typography.bodyMedium,
|
style = AppTheme.typography.titleSmall,
|
||||||
fontWeight = FontWeight.Medium
|
fontWeight = FontWeight.SemiBold,
|
||||||
)
|
color = if (highlighted) AppTheme.colors.primary else AppTheme.colors.onSurface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,54 +215,31 @@ private fun CapabilityRow(capability: PluginCapability) {
|
|||||||
PluginCapability.PERSISTENT_STORAGE -> Triple(
|
PluginCapability.PERSISTENT_STORAGE -> Triple(
|
||||||
Iconsax.PhosphorDatabaseDuotone,
|
Iconsax.PhosphorDatabaseDuotone,
|
||||||
stringResource(Res.string.plugin_permissions_capability_storage_title),
|
stringResource(Res.string.plugin_permissions_capability_storage_title),
|
||||||
stringResource(Res.string.plugin_permissions_capability_storage_desc)
|
stringResource(Res.string.plugin_permissions_capability_storage_desc))
|
||||||
)
|
|
||||||
PluginCapability.NETWORK_REQUESTS -> Triple(
|
PluginCapability.NETWORK_REQUESTS -> Triple(
|
||||||
Iconsax.IconsaxWifiSquare,
|
Iconsax.IconsaxWifiSquare,
|
||||||
stringResource(Res.string.plugin_permissions_capability_network_title),
|
stringResource(Res.string.plugin_permissions_capability_network_title),
|
||||||
stringResource(Res.string.plugin_permissions_capability_network_desc)
|
stringResource(Res.string.plugin_permissions_capability_network_desc))
|
||||||
)
|
|
||||||
PluginCapability.WEBVIEW -> Triple(
|
PluginCapability.WEBVIEW -> Triple(
|
||||||
Iconsax.IconsaxEye,
|
Iconsax.IconsaxEye,
|
||||||
stringResource(Res.string.plugin_permissions_capability_webview_title),
|
stringResource(Res.string.plugin_permissions_capability_webview_title),
|
||||||
stringResource(Res.string.plugin_permissions_capability_webview_desc)
|
stringResource(Res.string.plugin_permissions_capability_webview_desc))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MillenniumCard(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp),
|
||||||
contentPadding = PaddingValues(12.dp)) {
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
Row(
|
horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
PluginIconTile(icon)
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
Column(verticalArrangement = Arrangement.spacedBy(1.dp)) {
|
||||||
horizontalArrangement = Arrangement.spacedBy(14.dp)
|
Text(
|
||||||
) {
|
label,
|
||||||
Box(
|
style = AppTheme.typography.bodyMedium,
|
||||||
modifier = Modifier
|
fontWeight = FontWeight.Medium)
|
||||||
.size(34.dp)
|
Text(
|
||||||
.clip(CircleShape)
|
description,
|
||||||
.background(AppTheme.colors.errorContainer.copy(alpha = 0.6f)),
|
style = AppTheme.typography.labelSmall,
|
||||||
contentAlignment = Alignment.Center
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
) {
|
|
||||||
MillenniumIcon(
|
|
||||||
imageVector = icon,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
tint = AppTheme.colors.error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style = AppTheme.typography.bodyMedium,
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
description,
|
|
||||||
style = AppTheme.typography.labelSmall,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,10 +18,10 @@
|
|||||||
package dev.krtirtho.spotube.modules.search
|
package dev.krtirtho.spotube.modules.search
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumMarqueeProgress
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumIndeterminateProgress
|
|
||||||
import org.spotube.millennium.composables.MillenniumListTile
|
import org.spotube.millennium.composables.MillenniumListTile
|
||||||
import org.spotube.millennium.composables.MillenniumAutocomplete
|
import org.spotube.millennium.composables.MillenniumAutocomplete
|
||||||
import org.spotube.millennium.composables.MillenniumText
|
import org.spotube.millennium.composables.MillenniumText
|
||||||
@ -1155,7 +1155,7 @@ private fun SearchLoadingIndicator(
|
|||||||
translationY = bounce
|
translationY = bounce
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
MillenniumIndeterminateProgress(
|
MillenniumMarqueeProgress(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 32.dp, vertical = 16.dp)
|
.padding(horizontal = 32.dp, vertical = 16.dp)
|
||||||
|
|||||||
@ -18,10 +18,10 @@
|
|||||||
package dev.krtirtho.spotube.modules.settings
|
package dev.krtirtho.spotube.modules.settings
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumCircularProgress
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumIndeterminateProgress
|
|
||||||
import org.spotube.millennium.composables.MillenniumSeparator
|
import org.spotube.millennium.composables.MillenniumSeparator
|
||||||
import org.spotube.millennium.composables.MillenniumSidebarItem
|
import org.spotube.millennium.composables.MillenniumSidebarItem
|
||||||
import org.spotube.millennium.theme.MillenniumTheme
|
import org.spotube.millennium.theme.MillenniumTheme
|
||||||
@ -288,7 +288,7 @@ private fun SettingsTabContent(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center) {
|
contentAlignment = Alignment.Center) {
|
||||||
MillenniumIndeterminateProgress()
|
MillenniumCircularProgress(size = 40.dp)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
|||||||
@ -18,6 +18,12 @@
|
|||||||
package dev.krtirtho.spotube.modules.settings.components
|
package dev.krtirtho.spotube.modules.settings.components
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogHeader
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogTitle
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogClose
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogBody
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogFooter
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogDescription
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import com.composeunstyled.currentWindowWidthBreakpoint
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
import org.spotube.millennium.adaptive.WindowBreakpoints
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
@ -238,26 +244,30 @@ internal fun TextInputSettingCard(
|
|||||||
val normalizedValue = normalize(draft)
|
val normalizedValue = normalize(draft)
|
||||||
val errorMessage = validate(normalizedValue)
|
val errorMessage = validate(normalizedValue)
|
||||||
|
|
||||||
MillenniumDialog(
|
MillenniumDialog(visible = true, onDismissRequest = { isDialogOpen = false }) {
|
||||||
visible = true,
|
MillenniumDialogHeader {
|
||||||
onDismissRequest = { isDialogOpen = false },
|
MillenniumDialogTitle(dialogTitle)
|
||||||
title = dialogTitle
|
MillenniumDialogClose()
|
||||||
) {
|
}
|
||||||
if (dialogDescription != null) {
|
MillenniumDialogBody {
|
||||||
Text(
|
if (dialogDescription != null) {
|
||||||
text = dialogDescription,
|
MillenniumDialogDescription(dialogDescription)
|
||||||
style = AppTheme.typography.bodySmall,
|
}
|
||||||
color = AppTheme.colors.onSurfaceVariant
|
MillenniumTextField(
|
||||||
|
state = dialogState,
|
||||||
|
label = title,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
placeholder = placeholder,
|
||||||
|
error = errorMessage
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
MillenniumTextField(
|
MillenniumDialogFooter {
|
||||||
state = dialogState,
|
MillenniumButton(
|
||||||
label = title,
|
onClick = { isDialogOpen = false },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
style = MillenniumButtonStyle.Secondary
|
||||||
placeholder = placeholder,
|
) {
|
||||||
error = errorMessage
|
Text(stringResource(Res.string.common_cancel))
|
||||||
)
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
|
||||||
MillenniumButton(
|
MillenniumButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (errorMessage == null) {
|
if (errorMessage == null) {
|
||||||
@ -269,12 +279,6 @@ internal fun TextInputSettingCard(
|
|||||||
) {
|
) {
|
||||||
Text(stringResource(Res.string.common_save))
|
Text(stringResource(Res.string.common_save))
|
||||||
}
|
}
|
||||||
MillenniumButton(
|
|
||||||
onClick = { isDialogOpen = false },
|
|
||||||
style = MillenniumButtonStyle.Secondary
|
|
||||||
) {
|
|
||||||
Text(stringResource(Res.string.common_cancel))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,11 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.settings.sections
|
package dev.krtirtho.spotube.modules.settings.sections
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogHeader
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogTitle
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogClose
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogBody
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogFooter
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@ -151,10 +156,12 @@ private fun AppearanceSettingCard(
|
|||||||
mutableStateOf(selectedPalette)
|
mutableStateOf(selectedPalette)
|
||||||
}
|
}
|
||||||
|
|
||||||
MillenniumDialog(
|
MillenniumDialog(visible = true, onDismissRequest = { isDialogOpen = false }) {
|
||||||
visible = true,
|
MillenniumDialogHeader {
|
||||||
onDismissRequest = { isDialogOpen = false },
|
MillenniumDialogTitle(stringResource(Res.string.settings_palette_dialog_title))
|
||||||
title = stringResource(Res.string.settings_palette_dialog_title)) {
|
MillenniumDialogClose()
|
||||||
|
}
|
||||||
|
MillenniumDialogBody {
|
||||||
MillenniumText(
|
MillenniumText(
|
||||||
stringResource(Res.string.settings_palette_dialog_description),
|
stringResource(Res.string.settings_palette_dialog_description),
|
||||||
color = AppTheme.colors.onSurfaceVariant)
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
@ -174,9 +181,13 @@ private fun AppearanceSettingCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
PalettePreview(draftPalette)
|
PalettePreview(draftPalette)
|
||||||
|
}
|
||||||
MillenniumSeparator()
|
MillenniumDialogFooter {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
MillenniumButton(
|
||||||
|
onClick = { isDialogOpen = false },
|
||||||
|
style = MillenniumButtonStyle.Secondary) {
|
||||||
|
MillenniumText(stringResource(Res.string.common_cancel))
|
||||||
|
}
|
||||||
MillenniumButton(
|
MillenniumButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
onSaved(draftPalette)
|
onSaved(draftPalette)
|
||||||
@ -184,11 +195,6 @@ private fun AppearanceSettingCard(
|
|||||||
}) {
|
}) {
|
||||||
MillenniumText(stringResource(Res.string.common_save))
|
MillenniumText(stringResource(Res.string.common_save))
|
||||||
}
|
}
|
||||||
MillenniumButton(
|
|
||||||
onClick = { isDialogOpen = false },
|
|
||||||
style = MillenniumButtonStyle.Secondary) {
|
|
||||||
MillenniumText(stringResource(Res.string.common_cancel))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,10 @@
|
|||||||
package dev.krtirtho.spotube.modules.shell
|
package dev.krtirtho.spotube.modules.shell
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuItem
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxCd
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxMoon
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxInformation
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@ -436,27 +440,6 @@ fun AppLargePlayer(
|
|||||||
onExpandedChange = { expanded ->
|
onExpandedChange = { expanded ->
|
||||||
if (!expanded) playerOptionsViewModel.dismissMoreOptionsMenu()
|
if (!expanded) playerOptionsViewModel.dismissMoreOptionsMenu()
|
||||||
},
|
},
|
||||||
items = listOf(goToAlbumLabel, sleepTimerLabel, trackDetailsLabel),
|
|
||||||
onItemClick = { label ->
|
|
||||||
when (label) {
|
|
||||||
goToAlbumLabel -> {
|
|
||||||
playerOptionsViewModel.dismissMoreOptionsMenu()
|
|
||||||
currentTrack?.album?.id?.let { albumId ->
|
|
||||||
navigationCommands.navigateTo(Routes.Album(albumId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sleepTimerLabel -> {
|
|
||||||
playerOptionsViewModel.dismissMoreOptionsMenu()
|
|
||||||
playerOptionsViewModel.showSleepTimerDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
trackDetailsLabel -> {
|
|
||||||
playerOptionsViewModel.dismissMoreOptionsMenu()
|
|
||||||
playerOptionsViewModel.showTrackDetails()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
side = AnchorSide.Top,
|
side = AnchorSide.Top,
|
||||||
anchor = {
|
anchor = {
|
||||||
MillenniumIconButton(
|
MillenniumIconButton(
|
||||||
@ -471,7 +454,31 @@ fun AppLargePlayer(
|
|||||||
contentDescription = stringResource(Res.string.player_more_options_content_description)
|
contentDescription = stringResource(Res.string.player_more_options_content_description)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}) {
|
||||||
|
MillenniumDropdownMenuItem(
|
||||||
|
label = goToAlbumLabel,
|
||||||
|
icon = Iconsax.IconsaxCd,
|
||||||
|
onClick = {
|
||||||
|
playerOptionsViewModel.dismissMoreOptionsMenu()
|
||||||
|
currentTrack?.album?.id?.let { albumId ->
|
||||||
|
navigationCommands.navigateTo(Routes.Album(albumId))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
MillenniumDropdownMenuItem(
|
||||||
|
label = sleepTimerLabel,
|
||||||
|
icon = Iconsax.IconsaxMoon,
|
||||||
|
onClick = {
|
||||||
|
playerOptionsViewModel.dismissMoreOptionsMenu()
|
||||||
|
playerOptionsViewModel.showSleepTimerDialog()
|
||||||
|
})
|
||||||
|
MillenniumDropdownMenuItem(
|
||||||
|
label = trackDetailsLabel,
|
||||||
|
icon = Iconsax.IconsaxInformation,
|
||||||
|
onClick = {
|
||||||
|
playerOptionsViewModel.dismissMoreOptionsMenu()
|
||||||
|
playerOptionsViewModel.showTrackDetails()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
MillenniumIconButton(
|
MillenniumIconButton(
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import androidx.compose.animation.AnimatedVisibility
|
|||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -166,7 +167,11 @@ fun AppSidebar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MillenniumSidebarFooter {
|
// Compact the footer chrome only (padding + gap between items); items keep their size.
|
||||||
|
MillenniumSidebarFooter(
|
||||||
|
contentPadding = PaddingValues(horizontal = 6.dp, vertical = 4.dp),
|
||||||
|
contentSpacing = 2.dp
|
||||||
|
) {
|
||||||
val supportLabel = stringResource(Res.string.support_button_label)
|
val supportLabel = stringResource(Res.string.support_button_label)
|
||||||
MillenniumSidebarItem(
|
MillenniumSidebarItem(
|
||||||
label = supportLabel,
|
label = supportLabel,
|
||||||
|
|||||||
@ -18,11 +18,16 @@
|
|||||||
package dev.krtirtho.spotube.modules.shell
|
package dev.krtirtho.spotube.modules.shell
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogHeader
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogTitle
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogClose
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogBody
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogFooter
|
||||||
|
import org.spotube.millennium.composables.MillenniumMarqueeProgress
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumDialog
|
import org.spotube.millennium.composables.MillenniumDialog
|
||||||
import org.spotube.millennium.composables.MillenniumIndeterminateProgress
|
|
||||||
import org.spotube.millennium.composables.MillenniumTextField
|
import org.spotube.millennium.composables.MillenniumTextField
|
||||||
import org.spotube.millennium.theme.millenniumSurface
|
import org.spotube.millennium.theme.millenniumSurface
|
||||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
@ -128,11 +133,12 @@ private fun SleepTimerDialog(
|
|||||||
onStart: () -> Unit,
|
onStart: () -> Unit,
|
||||||
onCancelTimer: () -> Unit,
|
onCancelTimer: () -> Unit,
|
||||||
onDismiss: () -> Unit) {
|
onDismiss: () -> Unit) {
|
||||||
MillenniumDialog(
|
MillenniumDialog(visible = true, onDismissRequest = onDismiss) {
|
||||||
visible = true,
|
MillenniumDialogHeader {
|
||||||
onDismissRequest = onDismiss,
|
MillenniumDialogTitle(stringResource(Res.string.player_sleep_timer))
|
||||||
title = stringResource(Res.string.player_sleep_timer)) {
|
MillenniumDialogClose()
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
}
|
||||||
|
MillenniumDialogBody {
|
||||||
if (remainingMs != null) {
|
if (remainingMs != null) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.player_sleep_timer_remaining, formatRemainingTime(remainingMs)),
|
text = stringResource(Res.string.player_sleep_timer_remaining, formatRemainingTime(remainingMs)),
|
||||||
@ -152,21 +158,20 @@ private fun SleepTimerDialog(
|
|||||||
label = stringResource(Res.string.player_sleep_timer_minutes_label),
|
label = stringResource(Res.string.player_sleep_timer_minutes_label),
|
||||||
placeholder = stringResource(Res.string.player_sleep_timer_minutes_placeholder),
|
placeholder = stringResource(Res.string.player_sleep_timer_minutes_placeholder),
|
||||||
modifier = Modifier.fillMaxWidth())
|
modifier = Modifier.fillMaxWidth())
|
||||||
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
MillenniumDialogFooter {
|
||||||
if (remainingMs != null) {
|
if (remainingMs != null) {
|
||||||
MillenniumButton(
|
|
||||||
onClick = onCancelTimer,
|
|
||||||
style = MillenniumButtonStyle.Secondary) { Text(stringResource(Res.string.player_sleep_timer_cancel)) }
|
|
||||||
}
|
|
||||||
MillenniumButton(
|
MillenniumButton(
|
||||||
onClick = onDismiss,
|
onClick = onCancelTimer,
|
||||||
style = MillenniumButtonStyle.Secondary) { Text(stringResource(Res.string.common_close)) }
|
style = MillenniumButtonStyle.Quiet) { Text(stringResource(Res.string.player_sleep_timer_cancel)) }
|
||||||
MillenniumButton(
|
}
|
||||||
onClick = onStart,
|
MillenniumButton(
|
||||||
enabled = minutes.toLongOrNull()?.let { it in 1L..600L } == true) {
|
onClick = onDismiss,
|
||||||
Text(stringResource(Res.string.player_sleep_timer_start))
|
style = MillenniumButtonStyle.Secondary) { Text(stringResource(Res.string.common_close)) }
|
||||||
}
|
MillenniumButton(
|
||||||
|
onClick = onStart,
|
||||||
|
enabled = minutes.toLongOrNull()?.let { it in 1L..600L } == true) {
|
||||||
|
Text(stringResource(Res.string.player_sleep_timer_start))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,11 +181,12 @@ private fun SleepTimerDialog(
|
|||||||
private fun TrackDetailsDialog(
|
private fun TrackDetailsDialog(
|
||||||
details: PlayerTrackDetails,
|
details: PlayerTrackDetails,
|
||||||
onDismiss: () -> Unit) {
|
onDismiss: () -> Unit) {
|
||||||
MillenniumDialog(
|
MillenniumDialog(visible = true, onDismissRequest = onDismiss) {
|
||||||
visible = true,
|
MillenniumDialogHeader {
|
||||||
onDismissRequest = onDismiss,
|
MillenniumDialogTitle(stringResource(Res.string.player_track_details))
|
||||||
title = stringResource(Res.string.player_track_details)) {
|
MillenniumDialogClose()
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(14.dp)) {
|
}
|
||||||
|
MillenniumDialogBody(contentSpacing = 14.dp) {
|
||||||
Text(
|
Text(
|
||||||
details.track.title,
|
details.track.title,
|
||||||
style = AppTheme.typography.bodyMedium,
|
style = AppTheme.typography.bodyMedium,
|
||||||
@ -189,8 +195,7 @@ private fun TrackDetailsDialog(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 20.dp),
|
modifier = Modifier.fillMaxWidth().padding(vertical = 20.dp),
|
||||||
horizontalArrangement = Arrangement.Center) {
|
horizontalArrangement = Arrangement.Center) {
|
||||||
MillenniumIndeterminateProgress(
|
MillenniumMarqueeProgress(modifier = Modifier.fillMaxWidth())
|
||||||
modifier = Modifier.fillMaxWidth())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,6 +320,8 @@ private fun TrackDetailsDialog(
|
|||||||
DetailRow(stringResource(Res.string.player_track_details_active_url), activeUrl)
|
DetailRow(stringResource(Res.string.player_track_details_active_url), activeUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
MillenniumDialogFooter {
|
||||||
MillenniumButton(
|
MillenniumButton(
|
||||||
onClick = onDismiss,
|
onClick = onDismiss,
|
||||||
style = MillenniumButtonStyle.Secondary) { Text(stringResource(Res.string.common_close)) }
|
style = MillenniumButtonStyle.Secondary) { Text(stringResource(Res.string.common_close)) }
|
||||||
|
|||||||
@ -18,8 +18,8 @@
|
|||||||
package dev.krtirtho.spotube.modules.shell.alternative_track
|
package dev.krtirtho.spotube.modules.shell.alternative_track
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumSpokeSpinner
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIndeterminateProgress
|
|
||||||
import org.spotube.millennium.composables.MillenniumListTile
|
import org.spotube.millennium.composables.MillenniumListTile
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@ -98,8 +98,7 @@ fun AlternativeTrackContent(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
verticalAlignment = Alignment.CenterVertically) {
|
verticalAlignment = Alignment.CenterVertically) {
|
||||||
MillenniumIndeterminateProgress(
|
MillenniumSpokeSpinner(size = 24.dp)
|
||||||
modifier = Modifier.size(24.dp))
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(Res.string.alternative_track_searching),
|
text = stringResource(Res.string.alternative_track_searching),
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
package dev.krtirtho.spotube.modules.shell.player_queue
|
package dev.krtirtho.spotube.modules.shell.player_queue
|
||||||
|
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenuItem
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -419,11 +420,6 @@ private fun QueueItemRow(
|
|||||||
MillenniumDropdownMenu(
|
MillenniumDropdownMenu(
|
||||||
expanded = showMenu,
|
expanded = showMenu,
|
||||||
onExpandedChange = { showMenu = it },
|
onExpandedChange = { showMenu = it },
|
||||||
items = listOf(stringResource(Res.string.queue_remove_from_queue)),
|
|
||||||
onItemClick = {
|
|
||||||
onRemoveClick()
|
|
||||||
showMenu = false
|
|
||||||
},
|
|
||||||
anchor = {
|
anchor = {
|
||||||
MillenniumIconButton(
|
MillenniumIconButton(
|
||||||
onClick = { showMenu = true },
|
onClick = { showMenu = true },
|
||||||
@ -434,7 +430,16 @@ private fun QueueItemRow(
|
|||||||
contentDescription = stringResource(Res.string.player_more_options_content_description)
|
contentDescription = stringResource(Res.string.player_more_options_content_description)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}) {
|
||||||
|
MillenniumDropdownMenuItem(
|
||||||
|
label = stringResource(Res.string.queue_remove_from_queue),
|
||||||
|
icon = Iconsax.IconsaxMusicSquareRemove,
|
||||||
|
destructive = true,
|
||||||
|
onClick = {
|
||||||
|
onRemoveClick()
|
||||||
|
showMenu = false
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -17,6 +17,12 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.support
|
package dev.krtirtho.spotube.modules.support
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogHeader
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogTitle
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogClose
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogBody
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogDescription
|
||||||
|
import org.spotube.millennium.composables.MillenniumDialogFooter
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -103,49 +109,50 @@ fun SupportDialog(
|
|||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
isReminder: Boolean = false) {
|
isReminder: Boolean = false) {
|
||||||
if (!visible) return
|
if (!visible) return
|
||||||
MillenniumDialog(
|
MillenniumDialog(visible = true, onDismissRequest = onDismissRequest) {
|
||||||
visible = true,
|
MillenniumDialogHeader {
|
||||||
onDismissRequest = onDismissRequest,
|
MillenniumDialogTitle(stringResource(Res.string.support_dialog_title))
|
||||||
title = stringResource(Res.string.support_dialog_title)) {
|
MillenniumDialogClose()
|
||||||
Text(
|
|
||||||
stringResource(Res.string.support_dialog_message),
|
|
||||||
style = AppTheme.typography.bodyMedium,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant)
|
|
||||||
if (isReminder) {
|
|
||||||
Text(
|
|
||||||
stringResource(Res.string.support_dialog_reminder_hint),
|
|
||||||
style = AppTheme.typography.bodySmall,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant)
|
|
||||||
}
|
}
|
||||||
donationOptions.forEach { option ->
|
MillenniumDialogBody {
|
||||||
MillenniumButton(
|
MillenniumDialogDescription(stringResource(Res.string.support_dialog_message))
|
||||||
onClick = {
|
if (isReminder) {
|
||||||
openDonation(option)
|
Text(
|
||||||
onDismissRequest()
|
stringResource(Res.string.support_dialog_reminder_hint),
|
||||||
},
|
style = AppTheme.typography.bodySmall,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
color = AppTheme.colors.onSurfaceVariant)
|
||||||
style = MillenniumButtonStyle.Secondary) {
|
}
|
||||||
Row(
|
donationOptions.forEach { option ->
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
MillenniumButton(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
onClick = {
|
||||||
MillenniumIcon(
|
openDonation(option)
|
||||||
imageVector = option.icon,
|
onDismissRequest()
|
||||||
contentDescription = null,
|
},
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
tint = if (option.id == "patreon") {
|
style = MillenniumButtonStyle.Secondary) {
|
||||||
AppTheme.colors.onSurface
|
Row(
|
||||||
} else {
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
Color.Unspecified
|
horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
})
|
MillenniumIcon(
|
||||||
Text(stringResource(option.title))
|
imageVector = option.icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = if (option.id == "patreon") {
|
||||||
|
AppTheme.colors.onSurface
|
||||||
|
} else {
|
||||||
|
Color.Unspecified
|
||||||
|
})
|
||||||
|
Text(stringResource(option.title))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MillenniumButton(
|
MillenniumDialogFooter {
|
||||||
onClick = onDismissRequest,
|
MillenniumButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
onClick = onDismissRequest,
|
||||||
style = MillenniumButtonStyle.Quiet) {
|
style = MillenniumButtonStyle.Quiet) {
|
||||||
Text(stringResource(Res.string.support_dialog_dismiss))
|
Text(stringResource(Res.string.support_dialog_dismiss))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,19 +17,14 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.update
|
package dev.krtirtho.spotube.modules.update
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import com.composeunstyled.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import com.composeunstyled.Text
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.mikepenz.markdown.m3.Markdown
|
import com.mikepenz.markdown.m3.Markdown
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import org.spotube.millennium.composables.MillenniumAlertDialog
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumDialog
|
|
||||||
import spotube.composeapp.generated.resources.Res
|
import spotube.composeapp.generated.resources.Res
|
||||||
import spotube.composeapp.generated.resources.app_update_dialog_no_notes
|
import spotube.composeapp.generated.resources.app_update_dialog_no_notes
|
||||||
import spotube.composeapp.generated.resources.app_update_dialog_title
|
import spotube.composeapp.generated.resources.app_update_dialog_title
|
||||||
@ -47,28 +42,26 @@ fun AppUpdateDialog(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onIgnore: () -> Unit,
|
onIgnore: () -> Unit,
|
||||||
onUpdate: () -> Unit) {
|
onUpdate: () -> Unit) {
|
||||||
MillenniumDialog(
|
MillenniumAlertDialog(
|
||||||
visible = true,
|
visible = true,
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = stringResource(Res.string.app_update_dialog_title, update.tag)) {
|
title = { Text(stringResource(Res.string.app_update_dialog_title, update.tag)) },
|
||||||
if (update.releaseNotesMarkdown.isNotBlank()) {
|
content = {
|
||||||
SelectionContainer {
|
if (update.releaseNotesMarkdown.isNotBlank()) {
|
||||||
Markdown(
|
SelectionContainer {
|
||||||
content = update.releaseNotesMarkdown,
|
Markdown(content = update.releaseNotesMarkdown)
|
||||||
modifier = Modifier.padding(bottom = 8.dp))
|
}
|
||||||
|
} else {
|
||||||
|
Text(stringResource(Res.string.app_update_dialog_no_notes))
|
||||||
}
|
}
|
||||||
} else {
|
}) {
|
||||||
Text(stringResource(Res.string.app_update_dialog_no_notes))
|
MillenniumButton(
|
||||||
|
onClick = onIgnore,
|
||||||
|
style = MillenniumButtonStyle.Secondary) {
|
||||||
|
Text(stringResource(Res.string.update_action_ignore))
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
MillenniumButton(onClick = onUpdate) {
|
||||||
MillenniumButton(
|
Text(stringResource(Res.string.update_action_update))
|
||||||
onClick = onIgnore,
|
|
||||||
style = MillenniumButtonStyle.Secondary) {
|
|
||||||
Text(stringResource(Res.string.update_action_ignore))
|
|
||||||
}
|
|
||||||
MillenniumButton(onClick = onUpdate) {
|
|
||||||
Text(stringResource(Res.string.update_action_update))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.update
|
package dev.krtirtho.spotube.modules.update
|
||||||
|
|
||||||
|
import org.spotube.millennium.composables.MillenniumAlertDialog
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -30,7 +31,6 @@ import com.mikepenz.markdown.m3.Markdown
|
|||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
import org.spotube.millennium.composables.MillenniumButtonStyle
|
||||||
import org.spotube.millennium.composables.MillenniumDialog
|
|
||||||
import spotube.composeapp.generated.resources.Res
|
import spotube.composeapp.generated.resources.Res
|
||||||
import spotube.composeapp.generated.resources.plugin_update_dialog_current_version
|
import spotube.composeapp.generated.resources.plugin_update_dialog_current_version
|
||||||
import spotube.composeapp.generated.resources.plugin_update_dialog_no_notes
|
import spotube.composeapp.generated.resources.plugin_update_dialog_no_notes
|
||||||
@ -50,33 +50,35 @@ fun PluginUpdateDialog(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onIgnore: () -> Unit,
|
onIgnore: () -> Unit,
|
||||||
onUpdate: () -> Unit) {
|
onUpdate: () -> Unit) {
|
||||||
MillenniumDialog(
|
MillenniumAlertDialog(
|
||||||
visible = true,
|
visible = true,
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = stringResource(
|
title = {
|
||||||
Res.string.plugin_update_dialog_title,
|
Text(
|
||||||
update.pluginName,
|
stringResource(
|
||||||
update.info.latestVersion)) {
|
Res.string.plugin_update_dialog_title,
|
||||||
Text(
|
update.pluginName,
|
||||||
text = stringResource(Res.string.plugin_update_dialog_current_version, update.currentVersion),
|
update.info.latestVersion))
|
||||||
color = AppTheme.colors.onSurfaceVariant,
|
},
|
||||||
modifier = Modifier.padding(bottom = 8.dp))
|
description = {
|
||||||
if (update.info.releaseNotes.isNotBlank()) {
|
Text(stringResource(Res.string.plugin_update_dialog_current_version, update.currentVersion))
|
||||||
SelectionContainer {
|
},
|
||||||
Markdown(content = update.info.releaseNotes)
|
content = {
|
||||||
|
if (update.info.releaseNotes.isNotBlank()) {
|
||||||
|
SelectionContainer {
|
||||||
|
Markdown(content = update.info.releaseNotes)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(stringResource(Res.string.plugin_update_dialog_no_notes))
|
||||||
}
|
}
|
||||||
} else {
|
}) {
|
||||||
Text(stringResource(Res.string.plugin_update_dialog_no_notes))
|
MillenniumButton(
|
||||||
|
onClick = onIgnore,
|
||||||
|
style = MillenniumButtonStyle.Secondary) {
|
||||||
|
Text(stringResource(Res.string.update_action_ignore))
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
MillenniumButton(onClick = onUpdate) {
|
||||||
MillenniumButton(
|
Text(stringResource(Res.string.update_action_update))
|
||||||
onClick = onIgnore,
|
|
||||||
style = MillenniumButtonStyle.Secondary) {
|
|
||||||
Text(stringResource(Res.string.update_action_ignore))
|
|
||||||
}
|
|
||||||
MillenniumButton(onClick = onUpdate) {
|
|
||||||
Text(stringResource(Res.string.update_action_update))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user