mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-11-04 10:13:59 +00:00
refactor: replace currentWindowAdaptiveInfo with currentWindowWidthBreakpoint for adaptive layouts
This commit is contained in:
parent
bb9f225fd7
commit
caad76ba95
@ -16,13 +16,18 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
|
<string name="queue_title">Queue</string>
|
||||||
|
<string name="queue_clear_queue">Clear Queue</string>
|
||||||
|
<string name="queue_filter_queue">Filter queue...</string>
|
||||||
|
<string name="queue_no_queue_entries">No queue entries</string>
|
||||||
|
|
||||||
<string name="settings_screen_title">Settings</string>
|
<string name="settings_screen_title">Settings</string>
|
||||||
|
|
||||||
<string name="settings_section_plugins">Plugins</string>
|
<string name="settings_section_plugins">Plugins</string>
|
||||||
<string name="settings_section_language_region">Language and Region</string>
|
<string name="settings_section_language_region">Language and Region</string>
|
||||||
<string name="settings_section_appearance">Appearance</string>
|
<string name="settings_section_appearance">Appearance</string>
|
||||||
<string name="settings_section_playback">Playback</string>
|
<string name="settings_section_playback">Playback</string>
|
||||||
<string name="settings_section_downloads">Downloads</string>
|
<string name="settings_section_downloads">Downloads</string>f
|
||||||
<string name="settings_section_caching">Caching</string>
|
<string name="settings_section_caching">Caching</string>
|
||||||
<string name="settings_section_desktop">Desktop</string>
|
<string name="settings_section_desktop">Desktop</string>
|
||||||
<string name="settings_section_updates">Updates</string>
|
<string name="settings_section_updates">Updates</string>
|
||||||
|
|||||||
@ -76,7 +76,8 @@ interface NavigationItem {
|
|||||||
|
|
||||||
private val tabs = listOf<NavigationItem>(
|
private val tabs = listOf<NavigationItem>(
|
||||||
NavigationItem.Tab<Any>("Home", Iconsax.IconsaxHome, Routes.Home),
|
NavigationItem.Tab<Any>("Home", Iconsax.IconsaxHome, Routes.Home),
|
||||||
NavigationItem.Tab<Any>("Search", Iconsax.IconsaxSearch, Routes.Search))
|
NavigationItem.Tab<Any>("Search", Iconsax.IconsaxSearch, Routes.Search)
|
||||||
|
)
|
||||||
|
|
||||||
val sidebarTabs = tabs + listOf(
|
val sidebarTabs = tabs + listOf(
|
||||||
NavigationItem.Group(
|
NavigationItem.Group(
|
||||||
@ -89,7 +90,8 @@ val sidebarTabs = tabs + listOf(
|
|||||||
LibraryTab.Playlists
|
LibraryTab.Playlists
|
||||||
),
|
),
|
||||||
NavigationItem.Tab("Albums", Iconsax.IconsaxCd, Routes.Library, LibraryTab.Albums),
|
NavigationItem.Tab("Albums", Iconsax.IconsaxCd, Routes.Library, LibraryTab.Albums),
|
||||||
NavigationItem.Tab("Artists", Iconsax.User, Routes.Library, LibraryTab.Artists))
|
NavigationItem.Tab("Artists", Iconsax.User, Routes.Library, LibraryTab.Artists)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
NavigationItem.Group(
|
NavigationItem.Group(
|
||||||
"On Device",
|
"On Device",
|
||||||
@ -105,23 +107,23 @@ val sidebarTabs = tabs + listOf(
|
|||||||
Iconsax.IconsaxDirectboxReceive,
|
Iconsax.IconsaxDirectboxReceive,
|
||||||
Routes.Library,
|
Routes.Library,
|
||||||
LibraryTab.Downloads
|
LibraryTab.Downloads
|
||||||
))
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
NavigationItem.Group(
|
NavigationItem.Group(
|
||||||
"Connect",
|
"Connect",
|
||||||
items = listOf(
|
items = listOf(
|
||||||
NavigationItem.Tab("Devices", Iconsax.IconsaxMirrorScreenRegular, Routes.Devices),
|
NavigationItem.Tab("Devices", Iconsax.IconsaxMirrorScreenRegular, Routes.Devices),
|
||||||
NavigationItem.Tab("Group Jam", Iconsax.IconsaxSoundTwotone, Routes.Jam))))
|
NavigationItem.Tab("Group Jam", Iconsax.IconsaxSoundTwotone, Routes.Jam)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
val bottombarTabs = tabs + listOf(
|
val bottombarTabs = tabs + listOf(
|
||||||
NavigationItem.Tab<Any>("Library", Iconsax.IconsaxMusicLibrary, Routes.Library),
|
NavigationItem.Tab<Any>("Library", Iconsax.IconsaxMusicLibrary, Routes.Library),
|
||||||
NavigationItem.Tab("Settings", Iconsax.IconsaxSetting2, Routes.Settings)
|
NavigationItem.Tab("Settings", Iconsax.IconsaxSetting2, Routes.Settings)
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(KoinExperimentalAPI::class)
|
||||||
KoinExperimentalAPI::class,
|
|
||||||
ExperimentalCoroutinesApi::class,
|
|
||||||
ExperimentalSharedTransitionApi::class
|
|
||||||
)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun App(
|
fun App(
|
||||||
content: @Composable () -> Unit = {}
|
content: @Composable () -> Unit = {}
|
||||||
@ -156,7 +158,8 @@ fun App(
|
|||||||
AppShell(navigator, navigationState) {
|
AppShell(navigator, navigationState) {
|
||||||
SharedTransitionLayout {
|
SharedTransitionLayout {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalSharedTransitionScope provides this@SharedTransitionLayout) {
|
LocalSharedTransitionScope provides this@SharedTransitionLayout
|
||||||
|
) {
|
||||||
Column {
|
Column {
|
||||||
NavDisplay(
|
NavDisplay(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|||||||
@ -32,16 +32,14 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
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
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
|
|
||||||
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
|
||||||
import com.composeunstyled.SheetDetent
|
import com.composeunstyled.SheetDetent
|
||||||
import com.composeunstyled.rememberModalBottomSheetState
|
import com.composeunstyled.rememberModalBottomSheetState
|
||||||
import org.spotube.millennium.adaptive.currentWindowAdaptiveInfo
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.krtirtho.spotube.resources.iconsax.FluentDismiss
|
import dev.krtirtho.spotube.resources.iconsax.FluentDismiss
|
||||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||||
@ -51,11 +49,9 @@ fun AdaptiveDialogBottomSheet(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
title: @Composable (() -> Unit)? = null,
|
title: @Composable (() -> Unit)? = null,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
breakpointDp: Float = 600f,
|
|
||||||
actions: @Composable (() -> Unit)? = null,
|
actions: @Composable (() -> Unit)? = null,
|
||||||
content: @Composable () -> Unit) {
|
content: @Composable () -> Unit) {
|
||||||
val adaptiveInfo = currentWindowAdaptiveInfo()
|
val isLargeScreen = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium
|
||||||
val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp
|
|
||||||
|
|
||||||
if (isLargeScreen) {
|
if (isLargeScreen) {
|
||||||
MillenniumDialog(
|
MillenniumDialog(
|
||||||
|
|||||||
@ -18,17 +18,8 @@
|
|||||||
package dev.krtirtho.spotube.core.ui.component
|
package dev.krtirtho.spotube.core.ui.component
|
||||||
|
|
||||||
|
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
|
||||||
import org.spotube.millennium.composables.MillenniumModalBottomSheet
|
|
||||||
import org.spotube.millennium.composables.MillenniumSeparator
|
|
||||||
import org.spotube.millennium.composables.MillenniumTextField
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.hoverable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.interaction.collectIsHoveredAsState
|
import androidx.compose.foundation.interaction.collectIsHoveredAsState
|
||||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||||
@ -42,23 +33,9 @@ import androidx.compose.foundation.layout.height
|
|||||||
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.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
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.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
|
||||||
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
|
||||||
import com.composeunstyled.DropdownMenuPanel
|
|
||||||
import com.composeunstyled.SheetDetent
|
|
||||||
import com.composeunstyled.Text
|
|
||||||
import com.composeunstyled.UnstyledDropdownMenu
|
|
||||||
import com.composeunstyled.UnstyledDropdownMenuItem
|
|
||||||
import com.composeunstyled.rememberModalBottomSheetState
|
|
||||||
import org.spotube.millennium.adaptive.currentWindowAdaptiveInfo
|
|
||||||
import org.spotube.millennium.theme.MillenniumTheme
|
|
||||||
import org.spotube.millennium.theme.gelBrush
|
|
||||||
import org.spotube.millennium.theme.millenniumPanelBrush
|
|
||||||
import org.spotube.millennium.theme.millenniumSurface
|
|
||||||
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
|
||||||
@ -69,21 +46,37 @@ import androidx.compose.runtime.setValue
|
|||||||
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.draw.clip
|
||||||
import androidx.compose.ui.draw.shadow
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
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
|
||||||
import androidx.compose.ui.unit.DpOffset
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.composeunstyled.DropdownMenuPanel
|
||||||
|
import com.composeunstyled.SheetDetent
|
||||||
|
import com.composeunstyled.Text
|
||||||
|
import com.composeunstyled.UnstyledDropdownMenu
|
||||||
|
import com.composeunstyled.UnstyledDropdownMenuItem
|
||||||
|
import com.composeunstyled.rememberModalBottomSheetState
|
||||||
import compose.icons.FeatherIcons
|
import compose.icons.FeatherIcons
|
||||||
import compose.icons.feathericons.Check
|
import compose.icons.feathericons.Check
|
||||||
|
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
||||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax3DotsMore
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax3DotsMore
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxFilterSearch
|
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxHeart
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxHeart
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxShare
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxShare
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxTrash
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxTrash
|
||||||
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
|
import org.spotube.millennium.composables.MillenniumModalBottomSheet
|
||||||
|
import org.spotube.millennium.composables.MillenniumSeparator
|
||||||
|
import org.spotube.millennium.composables.MillenniumTextField
|
||||||
|
import org.spotube.millennium.theme.MillenniumTheme
|
||||||
|
import org.spotube.millennium.theme.gelBrush
|
||||||
|
import org.spotube.millennium.theme.millenniumPanelBrush
|
||||||
|
import org.spotube.millennium.theme.millenniumSurface
|
||||||
|
|
||||||
data class AdaptiveMenuItem(
|
data class AdaptiveMenuItem(
|
||||||
val icon: ImageVector? = null,
|
val icon: ImageVector? = null,
|
||||||
@ -106,14 +99,12 @@ fun AdaptiveDropdownBottomSheet(
|
|||||||
items: List<AdaptiveMenuItem>,
|
items: List<AdaptiveMenuItem>,
|
||||||
trigger: @Composable (onClick: () -> Unit) -> Unit,
|
trigger: @Composable (onClick: () -> Unit) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
breakpointDp: Float = 600f,
|
|
||||||
menuMinWidth: Dp = 200.dp,
|
menuMinWidth: Dp = 200.dp,
|
||||||
header: @Composable (() -> Unit)? = null,
|
header: @Composable (() -> Unit)? = null,
|
||||||
headerDisplayMode: HeaderDisplayMode = HeaderDisplayMode.Always,
|
headerDisplayMode: HeaderDisplayMode = HeaderDisplayMode.Always,
|
||||||
filter: ((AdaptiveMenuItem, String) -> Boolean)? = null) {
|
filter: ((AdaptiveMenuItem, String) -> Boolean)? = null) {
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
val adaptiveInfo = currentWindowAdaptiveInfo()
|
val isLargeScreen = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium
|
||||||
val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp
|
|
||||||
|
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
if (isLargeScreen) {
|
if (isLargeScreen) {
|
||||||
|
|||||||
@ -31,7 +31,8 @@ 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
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -89,8 +90,8 @@ fun CollectionDetails(
|
|||||||
showFollowButton: Boolean = true,
|
showFollowButton: Boolean = true,
|
||||||
onEdit: (() -> Unit)? = null,
|
onEdit: (() -> Unit)? = null,
|
||||||
sharedElementKey: String? = null) {
|
sharedElementKey: String? = null) {
|
||||||
BoxWithConstraints(modifier = modifier.fillMaxWidth()) {
|
Box(modifier = modifier.fillMaxWidth()) {
|
||||||
val isCompact = maxWidth < 600.dp
|
val isCompact = currentWindowWidthBreakpoint() isBelow WindowBreakpoints.Medium
|
||||||
|
|
||||||
val sharedTransitionScope = LocalSharedTransitionScope.current
|
val sharedTransitionScope = LocalSharedTransitionScope.current
|
||||||
val animatedVisibilityScope = LocalAnimatedVisibilityScope.current
|
val animatedVisibilityScope = LocalAnimatedVisibilityScope.current
|
||||||
|
|||||||
@ -41,7 +41,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
|
|
||||||
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.adaptive.currentWindowAdaptiveInfo
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
@ -104,8 +105,7 @@ private fun FeaturedCarouselContent(
|
|||||||
val centerIndex = items.size / 2
|
val centerIndex = items.size / 2
|
||||||
var selectedIndex by remember { mutableIntStateOf(centerIndex) }
|
var selectedIndex by remember { mutableIntStateOf(centerIndex) }
|
||||||
val state = rememberCoverflowState(centerIndex) { index -> selectedIndex = index }
|
val state = rememberCoverflowState(centerIndex) { index -> selectedIndex = index }
|
||||||
val adaptiveInfo = currentWindowAdaptiveInfo()
|
val isSmallScreen = currentWindowWidthBreakpoint() isBelow WindowBreakpoints.Medium
|
||||||
val isSmallScreen = adaptiveInfo.windowSizeClass.minWidthDp <= 600
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|||||||
@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.core.ui.component
|
|
||||||
|
|
||||||
|
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
|
||||||
import org.spotube.millennium.composables.MillenniumButtonStyle
|
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.RowScope
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
|
|
||||||
import com.composeunstyled.LocalTextStyle
|
|
||||||
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
|
||||||
import com.composeunstyled.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The shared navigation item used by every sidebar in the app (the main app sidebar
|
|
||||||
* and the settings sidebar), so they stay visually identical.
|
|
||||||
*
|
|
||||||
* @param icon leading icon of the item.
|
|
||||||
* @param badge optional overlay drawn on the icon (for example a download indicator).
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun SidebarItem(
|
|
||||||
label: String,
|
|
||||||
icon: ImageVector,
|
|
||||||
selected: Boolean,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
expanded: Boolean = true,
|
|
||||||
badge: (@Composable BoxScope.() -> Unit)? = null) {
|
|
||||||
val itemContent: @Composable () -> Unit = {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Box(modifier = Modifier.size(16.dp)) {
|
|
||||||
MillenniumIcon(
|
|
||||||
imageVector = icon,
|
|
||||||
contentDescription = label,
|
|
||||||
tint = if (selected) {
|
|
||||||
AppTheme.colors.primary
|
|
||||||
} else {
|
|
||||||
AppTheme.colors.onSurfaceVariant
|
|
||||||
})
|
|
||||||
badge?.invoke(this)
|
|
||||||
}
|
|
||||||
AnimatedVisibility(visible = expanded, enter = fadeIn(), exit = fadeOut()) {
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
singleLine = true,
|
|
||||||
style = LocalTextStyle.current.copy(
|
|
||||||
fontSize = AppTheme.typography.labelMedium.fontSize,
|
|
||||||
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Medium,
|
|
||||||
color = if (selected) {
|
|
||||||
AppTheme.colors.onSecondaryContainer
|
|
||||||
} else {
|
|
||||||
AppTheme.colors.onSurfaceVariant
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = if (expanded) Modifier.weight(1f) else Modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val buttonModifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 7.dp, vertical = 2.dp)
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
MillenniumButton(
|
|
||||||
onClick = onClick,
|
|
||||||
modifier = buttonModifier,
|
|
||||||
style = MillenniumButtonStyle.Secondary,
|
|
||||||
content = itemContent)
|
|
||||||
} else {
|
|
||||||
MillenniumButton(
|
|
||||||
onClick = onClick,
|
|
||||||
modifier = buttonModifier,
|
|
||||||
style = MillenniumButtonStyle.Quiet,
|
|
||||||
content = itemContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The shared section heading used above groups of [SidebarItem]s.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun SidebarSectionLabel(
|
|
||||||
text: String,
|
|
||||||
expanded: Boolean = true) {
|
|
||||||
AnimatedVisibility(visible = expanded) {
|
|
||||||
Text(
|
|
||||||
text = text.uppercase(),
|
|
||||||
style = AppTheme.typography.labelSmall,
|
|
||||||
color = AppTheme.colors.onSurfaceVariant,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 24.dp, vertical = 8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -18,6 +18,9 @@
|
|||||||
package dev.krtirtho.spotube.core.ui.component
|
package dev.krtirtho.spotube.core.ui.component
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
@ -78,7 +81,6 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.graphics.luminance
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalWindowInfo
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@ -207,11 +209,10 @@ fun TrackList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val windowInfo = LocalWindowInfo.current
|
val widthBreakpoint = currentWindowWidthBreakpoint()
|
||||||
val maxWidth = with(density) { windowInfo.containerSize.width.toDp() }
|
val isCompact = widthBreakpoint isBelow WindowBreakpoints.Medium
|
||||||
val isCompact = maxWidth < CompactTrackListBreakpoint
|
|
||||||
val isDesktop = remember { getPlatform().isDesktop() }
|
val isDesktop = remember { getPlatform().isDesktop() }
|
||||||
val showIndex = maxWidth >= LargeTrackListBreakpoint
|
val showIndex = widthBreakpoint isAtLeast WindowBreakpoints.Expanded
|
||||||
val showAlbum = !isCompact
|
val showAlbum = !isCompact
|
||||||
val useDropdownForOptions = isDesktop && !isCompact
|
val useDropdownForOptions = isDesktop && !isCompact
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
|
|
||||||
import dev.krtirtho.spotube.modules.settings.Theme
|
import dev.krtirtho.spotube.modules.settings.Theme
|
||||||
import dev.krtirtho.spotube.modules.settings.UserSettings
|
import dev.krtirtho.spotube.modules.settings.UserSettings
|
||||||
|
import org.spotube.millennium.composables.MillenniumTooltipHost
|
||||||
import org.spotube.millennium.theme.MillenniumColors
|
import org.spotube.millennium.theme.MillenniumColors
|
||||||
import org.spotube.millennium.theme.MillenniumPalette
|
import org.spotube.millennium.theme.MillenniumPalette
|
||||||
import org.spotube.millennium.theme.MillenniumTheme
|
import org.spotube.millennium.theme.MillenniumTheme
|
||||||
@ -75,7 +76,8 @@ data class AppColors(
|
|||||||
val onError: Color,
|
val onError: Color,
|
||||||
val errorContainer: Color,
|
val errorContainer: Color,
|
||||||
val onErrorContainer: Color,
|
val onErrorContainer: Color,
|
||||||
val scrim: Color)
|
val scrim: Color
|
||||||
|
)
|
||||||
|
|
||||||
/** Maps Millennium's semantic palette onto the app's role names. */
|
/** Maps Millennium's semantic palette onto the app's role names. */
|
||||||
fun appColorsFrom(m: MillenniumColors): AppColors = AppColors(
|
fun appColorsFrom(m: MillenniumColors): AppColors = AppColors(
|
||||||
@ -106,7 +108,8 @@ fun appColorsFrom(m: MillenniumColors): AppColors = AppColors(
|
|||||||
onError = m.background,
|
onError = m.background,
|
||||||
errorContainer = m.danger.copy(alpha = 0.16f),
|
errorContainer = m.danger.copy(alpha = 0.16f),
|
||||||
onErrorContainer = m.danger,
|
onErrorContainer = m.danger,
|
||||||
scrim = m.scrim)
|
scrim = m.scrim
|
||||||
|
)
|
||||||
|
|
||||||
/** Named text styles used by the app, independent of Material typography. */
|
/** Named text styles used by the app, independent of Material typography. */
|
||||||
@Immutable
|
@Immutable
|
||||||
@ -123,16 +126,19 @@ data class AppTypography(
|
|||||||
val bodySmall: TextStyle,
|
val bodySmall: TextStyle,
|
||||||
val labelLarge: TextStyle,
|
val labelLarge: TextStyle,
|
||||||
val labelMedium: TextStyle,
|
val labelMedium: TextStyle,
|
||||||
val labelSmall: TextStyle)
|
val labelSmall: TextStyle
|
||||||
|
)
|
||||||
|
|
||||||
private fun style(
|
private fun style(
|
||||||
size: Int,
|
size: Int,
|
||||||
lineHeight: Int,
|
lineHeight: Int,
|
||||||
weight: FontWeight = FontWeight.Normal): TextStyle = TextStyle(
|
weight: FontWeight = FontWeight.Normal
|
||||||
|
): TextStyle = TextStyle(
|
||||||
fontFamily = FontFamily.SansSerif,
|
fontFamily = FontFamily.SansSerif,
|
||||||
fontSize = size.sp,
|
fontSize = size.sp,
|
||||||
lineHeight = lineHeight.sp,
|
lineHeight = lineHeight.sp,
|
||||||
fontWeight = weight)
|
fontWeight = weight
|
||||||
|
)
|
||||||
|
|
||||||
val AppDefaultTypography = AppTypography(
|
val AppDefaultTypography = AppTypography(
|
||||||
displaySmall = style(36, 44),
|
displaySmall = style(36, 44),
|
||||||
@ -147,21 +153,25 @@ val AppDefaultTypography = AppTypography(
|
|||||||
bodySmall = style(12, 16),
|
bodySmall = style(12, 16),
|
||||||
labelLarge = style(14, 20, FontWeight.Medium),
|
labelLarge = style(14, 20, FontWeight.Medium),
|
||||||
labelMedium = style(12, 16, FontWeight.Medium),
|
labelMedium = style(12, 16, FontWeight.Medium),
|
||||||
labelSmall = style(11, 16, FontWeight.Medium))
|
labelSmall = style(11, 16, FontWeight.Medium)
|
||||||
|
)
|
||||||
|
|
||||||
/** Corner shapes used by the app. */
|
/** Corner shapes used by the app. */
|
||||||
@Immutable
|
@Immutable
|
||||||
data class AppShapes(
|
data class AppShapes(
|
||||||
val small: Shape,
|
val small: Shape,
|
||||||
val medium: Shape,
|
val medium: Shape,
|
||||||
val large: Shape)
|
val large: Shape
|
||||||
|
)
|
||||||
|
|
||||||
val AppDefaultShapes = AppShapes(
|
val AppDefaultShapes = AppShapes(
|
||||||
small = RoundedCornerShape(8.dp),
|
small = RoundedCornerShape(8.dp),
|
||||||
medium = RoundedCornerShape(12.dp),
|
medium = RoundedCornerShape(12.dp),
|
||||||
large = RoundedCornerShape(16.dp))
|
large = RoundedCornerShape(16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
val LocalAppColors = staticCompositionLocalOf { appColorsFrom(org.spotube.millennium.theme.MillenniumLightColors) }
|
val LocalAppColors =
|
||||||
|
staticCompositionLocalOf { appColorsFrom(org.spotube.millennium.theme.MillenniumLightColors) }
|
||||||
val LocalAppTypography = staticCompositionLocalOf { AppDefaultTypography }
|
val LocalAppTypography = staticCompositionLocalOf { AppDefaultTypography }
|
||||||
val LocalAppShapes = staticCompositionLocalOf { AppDefaultShapes }
|
val LocalAppShapes = staticCompositionLocalOf { AppDefaultShapes }
|
||||||
|
|
||||||
@ -182,7 +192,8 @@ object AppTheme {
|
|||||||
@Composable
|
@Composable
|
||||||
fun SpotubeTheme(
|
fun SpotubeTheme(
|
||||||
settings: UserSettings,
|
settings: UserSettings,
|
||||||
content: @Composable () -> Unit) {
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
val isDarkTheme = when (settings.theme) {
|
val isDarkTheme = when (settings.theme) {
|
||||||
Theme.LIGHT -> false
|
Theme.LIGHT -> false
|
||||||
Theme.DARK -> true
|
Theme.DARK -> true
|
||||||
@ -192,11 +203,14 @@ fun SpotubeTheme(
|
|||||||
|
|
||||||
MillenniumTheme(dark = isDarkTheme, config = MillenniumThemeConfig(palette = palette)) {
|
MillenniumTheme(dark = isDarkTheme, config = MillenniumThemeConfig(palette = palette)) {
|
||||||
val colors = appColorsFrom(MillenniumTheme.colors)
|
val colors = appColorsFrom(MillenniumTheme.colors)
|
||||||
CompositionLocalProvider(
|
MillenniumTooltipHost {
|
||||||
LocalAppColors provides colors,
|
CompositionLocalProvider(
|
||||||
LocalAppTypography provides AppDefaultTypography,
|
LocalAppColors provides colors,
|
||||||
LocalAppShapes provides AppDefaultShapes,
|
LocalAppTypography provides AppDefaultTypography,
|
||||||
content = content)
|
LocalAppShapes provides AppDefaultShapes,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,7 +29,8 @@ import org.spotube.millennium.theme.MillenniumTheme
|
|||||||
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
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -327,8 +328,8 @@ private fun ArtistHeaderCard(
|
|||||||
onFollowClick: () -> Unit,
|
onFollowClick: () -> Unit,
|
||||||
isBlacklisted: Boolean,
|
isBlacklisted: Boolean,
|
||||||
onBlacklistClick: () -> Unit) {
|
onBlacklistClick: () -> Unit) {
|
||||||
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
val isCompact = maxWidth < 600.dp
|
val isCompact = currentWindowWidthBreakpoint() isBelow WindowBreakpoints.Medium
|
||||||
|
|
||||||
MillenniumCard(
|
MillenniumCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@ -42,7 +42,8 @@ import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
|||||||
import com.composeunstyled.SheetDetent
|
import com.composeunstyled.SheetDetent
|
||||||
import com.composeunstyled.Text
|
import com.composeunstyled.Text
|
||||||
import com.composeunstyled.rememberModalBottomSheetState
|
import com.composeunstyled.rememberModalBottomSheetState
|
||||||
import org.spotube.millennium.adaptive.currentWindowAdaptiveInfo
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
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
|
||||||
@ -74,12 +75,10 @@ fun AddToPlaylistPicker(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onPlaylistSelected: (String) -> Unit,
|
onPlaylistSelected: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
libraryRepository: LibraryRepository = koinInject(),
|
libraryRepository: LibraryRepository = koinInject()) {
|
||||||
breakpointDp: Float = 600f) {
|
|
||||||
if (!visible) return
|
if (!visible) return
|
||||||
|
|
||||||
val adaptiveInfo = currentWindowAdaptiveInfo()
|
val isLargeScreen = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium
|
||||||
val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp
|
|
||||||
|
|
||||||
var playlists by remember { mutableStateOf<List<MetadataPlaylist>>(emptyList()) }
|
var playlists by remember { mutableStateOf<List<MetadataPlaylist>>(emptyList()) }
|
||||||
var isLoading by remember { mutableStateOf(true) }
|
var isLoading by remember { mutableStateOf(true) }
|
||||||
|
|||||||
@ -41,7 +41,8 @@ import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
|||||||
import com.composeunstyled.SheetDetent
|
import com.composeunstyled.SheetDetent
|
||||||
import com.composeunstyled.Text
|
import com.composeunstyled.Text
|
||||||
import com.composeunstyled.rememberModalBottomSheetState
|
import com.composeunstyled.rememberModalBottomSheetState
|
||||||
import org.spotube.millennium.adaptive.currentWindowAdaptiveInfo
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.foundation.text.input.TextFieldState
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -82,12 +83,10 @@ fun PlaylistFormSheet(
|
|||||||
onSubmit: (PlaylistFormData) -> Unit,
|
onSubmit: (PlaylistFormData) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
initialData: PlaylistFormData? = null,
|
initialData: PlaylistFormData? = null,
|
||||||
isEditing: Boolean = false,
|
isEditing: Boolean = false) {
|
||||||
breakpointDp: Float = 600f) {
|
|
||||||
if (!visible) return
|
if (!visible) return
|
||||||
|
|
||||||
val adaptiveInfo = currentWindowAdaptiveInfo()
|
val isLargeScreen = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium
|
||||||
val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp
|
|
||||||
|
|
||||||
var name by remember(initialData) { mutableStateOf(initialData?.name ?: "") }
|
var name by remember(initialData) { mutableStateOf(initialData?.name ?: "") }
|
||||||
var description by remember(initialData) { mutableStateOf(initialData?.description ?: "") }
|
var description by remember(initialData) { mutableStateOf(initialData?.description ?: "") }
|
||||||
|
|||||||
@ -44,7 +44,8 @@ import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
|||||||
import com.composeunstyled.SheetDetent
|
import com.composeunstyled.SheetDetent
|
||||||
import com.composeunstyled.Text
|
import com.composeunstyled.Text
|
||||||
import com.composeunstyled.rememberModalBottomSheetState
|
import com.composeunstyled.rememberModalBottomSheetState
|
||||||
import org.spotube.millennium.adaptive.currentWindowAdaptiveInfo
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
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
|
||||||
@ -79,12 +80,10 @@ fun AddTracksToPlaylistDialog(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
searchRepository: SearchRepository = koinInject(),
|
searchRepository: SearchRepository = koinInject(),
|
||||||
libraryRepository: LibraryRepository = koinInject(),
|
libraryRepository: LibraryRepository = koinInject()) {
|
||||||
breakpointDp: Float = 600f) {
|
|
||||||
if (!visible) return
|
if (!visible) return
|
||||||
|
|
||||||
val adaptiveInfo = currentWindowAdaptiveInfo()
|
val isLargeScreen = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium
|
||||||
val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp
|
|
||||||
|
|
||||||
var query by remember { mutableStateOf("") }
|
var query by remember { mutableStateOf("") }
|
||||||
var searchResults by remember { mutableStateOf<List<MetadataTrack>>(emptyList()) }
|
var searchResults by remember { mutableStateOf<List<MetadataTrack>>(emptyList()) }
|
||||||
|
|||||||
@ -17,8 +17,10 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.settings
|
package dev.krtirtho.spotube.modules.settings
|
||||||
|
|
||||||
|
|
||||||
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@ -67,8 +69,8 @@ private val SettingsDialogMinWidth = 720.dp
|
|||||||
fun SettingsScreen(settingsViewModel: SettingsViewModel) {
|
fun SettingsScreen(settingsViewModel: SettingsViewModel) {
|
||||||
val navigatorCommands: NavigationCommands = koinInject()
|
val navigatorCommands: NavigationCommands = koinInject()
|
||||||
|
|
||||||
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
if (maxWidth >= SettingsDialogMinWidth) {
|
if (currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Expanded) {
|
||||||
SettingsDialog(
|
SettingsDialog(
|
||||||
settingsViewModel = settingsViewModel,
|
settingsViewModel = settingsViewModel,
|
||||||
onDismiss = { navigatorCommands.pop(Routes.Settings) })
|
onDismiss = { navigatorCommands.pop(Routes.Settings) })
|
||||||
|
|||||||
@ -23,6 +23,7 @@ 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.MillenniumIndeterminateProgress
|
||||||
import org.spotube.millennium.composables.MillenniumSeparator
|
import org.spotube.millennium.composables.MillenniumSeparator
|
||||||
|
import org.spotube.millennium.composables.MillenniumSidebarItem
|
||||||
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
|
||||||
@ -62,7 +63,6 @@ import dev.krtirtho.spotube.PlatformType
|
|||||||
import dev.krtirtho.spotube.getPlatform
|
import dev.krtirtho.spotube.getPlatform
|
||||||
import dev.krtirtho.spotube.core.discovery.rememberLocalNetworkPermissionRequester
|
import dev.krtirtho.spotube.core.discovery.rememberLocalNetworkPermissionRequester
|
||||||
import dev.krtirtho.spotube.core.navigation.NavigationCommands
|
import dev.krtirtho.spotube.core.navigation.NavigationCommands
|
||||||
import dev.krtirtho.spotube.core.ui.component.SidebarItem
|
|
||||||
import dev.krtirtho.spotube.modules.blacklist.BlacklistContent
|
import dev.krtirtho.spotube.modules.blacklist.BlacklistContent
|
||||||
import dev.krtirtho.spotube.modules.plugin.PluginScreenContent
|
import dev.krtirtho.spotube.modules.plugin.PluginScreenContent
|
||||||
import dev.krtirtho.spotube.modules.settings.sections.appearanceSection
|
import dev.krtirtho.spotube.modules.settings.sections.appearanceSection
|
||||||
@ -247,11 +247,12 @@ private fun SettingsSidebar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(tabs, key = { it.name }) { tab ->
|
items(tabs, key = { it.name }) { tab ->
|
||||||
SidebarItem(
|
MillenniumSidebarItem(
|
||||||
label = stringResource(tab.titleRes),
|
label = stringResource(tab.titleRes),
|
||||||
icon = tab.icon,
|
onClick = { onSelect(tab) },
|
||||||
selected = tab == selectedTab,
|
selected = tab == selectedTab,
|
||||||
onClick = { onSelect(tab) })
|
contentDescription = stringResource(tab.titleRes),
|
||||||
|
icon = tab.icon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,11 @@
|
|||||||
package dev.krtirtho.spotube.modules.settings.components
|
package dev.krtirtho.spotube.modules.settings.components
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumButton
|
import org.spotube.millennium.composables.MillenniumButton
|
||||||
@ -27,7 +32,6 @@ import org.spotube.millennium.composables.MillenniumRadioGroup
|
|||||||
import org.spotube.millennium.composables.MillenniumSwitch
|
import org.spotube.millennium.composables.MillenniumSwitch
|
||||||
import org.spotube.millennium.composables.MillenniumTextField
|
import org.spotube.millennium.composables.MillenniumTextField
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
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.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -92,8 +96,8 @@ internal fun <T> SelectionSettingCard(
|
|||||||
filter: ((AdaptiveMenuItem, String) -> Boolean)? = null) {
|
filter: ((AdaptiveMenuItem, String) -> Boolean)? = null) {
|
||||||
var isDialogOpen by remember { mutableStateOf(false) }
|
var isDialogOpen by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
BoxWithConstraints {
|
Box {
|
||||||
val isWideLayout = maxWidth >= inlineSelectorMinWidth
|
val isWideLayout = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium
|
||||||
|
|
||||||
SettingCardItem(
|
SettingCardItem(
|
||||||
title = title,
|
title = title,
|
||||||
@ -173,8 +177,8 @@ internal fun TextInputSettingCard(
|
|||||||
enabled: Boolean = true) {
|
enabled: Boolean = true) {
|
||||||
var isDialogOpen by remember { mutableStateOf(false) }
|
var isDialogOpen by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
BoxWithConstraints {
|
Box {
|
||||||
val isWideLayout = maxWidth >= inlineTextFieldMinWidth
|
val isWideLayout = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium
|
||||||
val inlineState = rememberTextFieldState(value)
|
val inlineState = rememberTextFieldState(value)
|
||||||
|
|
||||||
LaunchedEffect(inlineState, isWideLayout) {
|
LaunchedEffect(inlineState, isWideLayout) {
|
||||||
|
|||||||
@ -23,8 +23,6 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraintsScope
|
|
||||||
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.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
@ -32,43 +30,29 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
|
|
||||||
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
|
||||||
import org.spotube.millennium.composables.MillenniumSnackbarHost
|
|
||||||
import org.spotube.millennium.composables.MillenniumSnackbarHostState
|
|
||||||
import org.spotube.millennium.composables.MillenniumSeparator
|
|
||||||
import org.spotube.millennium.theme.MillenniumTheme
|
|
||||||
import com.composeunstyled.Sheet
|
|
||||||
import com.composeunstyled.SheetDetent
|
|
||||||
import com.composeunstyled.UnstyledBottomSheet
|
|
||||||
import com.composeunstyled.rememberBottomSheetState
|
|
||||||
import com.composeunstyled.Text
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.snapshotFlow
|
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.chrisbanes.haze.hazeSource
|
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.composeunstyled.Sheet
|
||||||
|
import com.composeunstyled.SheetDetent
|
||||||
|
import com.composeunstyled.UnstyledBottomSheet
|
||||||
|
import com.composeunstyled.rememberBottomSheetState
|
||||||
|
import dev.chrisbanes.haze.hazeSource
|
||||||
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import dev.krtirtho.spotube.core.navigation.NavigationCommands
|
import dev.krtirtho.spotube.core.navigation.NavigationCommands
|
||||||
import dev.krtirtho.spotube.core.navigation.NavigationState
|
import dev.krtirtho.spotube.core.navigation.NavigationState
|
||||||
import dev.krtirtho.spotube.core.navigation.Navigator
|
import dev.krtirtho.spotube.core.navigation.Navigator
|
||||||
@ -86,10 +70,13 @@ import dev.krtirtho.spotube.modules.shell.player_queue.QueueSheet
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
import org.spotube.millennium.composables.MillenniumSeparator
|
||||||
|
import org.spotube.millennium.composables.MillenniumSnackbarHost
|
||||||
|
import org.spotube.millennium.composables.MillenniumSnackbarHostState
|
||||||
|
import org.spotube.millennium.theme.MillenniumTheme
|
||||||
|
|
||||||
val LocalAppShellBottomInset = staticCompositionLocalOf { 0.dp }
|
val LocalAppShellBottomInset = staticCompositionLocalOf { 0.dp }
|
||||||
|
|
||||||
@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppShell(
|
fun AppShell(
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
@ -158,7 +145,6 @@ fun AppShell(
|
|||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
Row(modifier = Modifier.weight(1f)) {
|
Row(modifier = Modifier.weight(1f)) {
|
||||||
AppSidebar(
|
AppSidebar(
|
||||||
hazeState = bgHazeState,
|
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
navigationState = navigationState
|
navigationState = navigationState
|
||||||
)
|
)
|
||||||
@ -214,7 +200,7 @@ fun AppShell(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@ -278,7 +264,7 @@ fun AppShell(
|
|||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalSharedTransitionApi::class, ExperimentalFoundationApi::class)
|
ExperimentalSharedTransitionApi::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun BoxWithConstraintsScope.CompactPlayerOverlay(
|
private fun CompactPlayerOverlay(
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
navigationState: NavigationState,
|
navigationState: NavigationState,
|
||||||
viewModel: AppShellViewModel,
|
viewModel: AppShellViewModel,
|
||||||
|
|||||||
@ -17,9 +17,10 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.shell
|
package dev.krtirtho.spotube.modules.shell
|
||||||
|
|
||||||
|
|
||||||
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.platform.LocalWindowInfo
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -63,9 +64,7 @@ class AppShellViewModel : ViewModel() {
|
|||||||
if (maxWidth != null) {
|
if (maxWidth != null) {
|
||||||
return maxWidth >= sidebarMinWidth
|
return maxWidth >= sidebarMinWidth
|
||||||
}
|
}
|
||||||
val density = LocalDensity.current
|
return currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Expanded
|
||||||
val windowInfo = LocalWindowInfo.current
|
|
||||||
return with(density) { windowInfo.containerSize.width.toDp() >= sidebarMinWidth }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bottomOverlayInset(useSidebar: Boolean): Dp =
|
fun bottomOverlayInset(useSidebar: Boolean): Dp =
|
||||||
|
|||||||
@ -22,7 +22,6 @@ 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.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
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.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -35,11 +34,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.composeunstyled.Text
|
import com.composeunstyled.Text
|
||||||
import dev.chrisbanes.haze.HazeState
|
|
||||||
import dev.krtirtho.spotube.NavigationItem
|
import dev.krtirtho.spotube.NavigationItem
|
||||||
import dev.krtirtho.spotube.core.navigation.NavigationState
|
import dev.krtirtho.spotube.core.navigation.NavigationState
|
||||||
import dev.krtirtho.spotube.core.navigation.Navigator
|
import dev.krtirtho.spotube.core.navigation.Navigator
|
||||||
@ -67,7 +66,6 @@ import spotube.composeapp.generated.resources.cookie_regular
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppSidebar(
|
fun AppSidebar(
|
||||||
hazeState: HazeState,
|
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
navigationState: NavigationState,
|
navigationState: NavigationState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@ -80,6 +78,7 @@ fun AppSidebar(
|
|||||||
MillenniumSidebar(
|
MillenniumSidebar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
collapsed = !expanded,
|
collapsed = !expanded,
|
||||||
|
shape = RectangleShape,
|
||||||
onCollapsedChange = { expanded = !it },
|
onCollapsedChange = { expanded = !it },
|
||||||
) {
|
) {
|
||||||
MillenniumSidebarHeader {
|
MillenniumSidebarHeader {
|
||||||
|
|||||||
@ -17,7 +17,11 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.shell.alternative_track
|
package dev.krtirtho.spotube.modules.shell.alternative_track
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -32,8 +36,8 @@ fun AlternativeTrackSheet(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
content: @Composable () -> Unit) {
|
content: @Composable () -> Unit) {
|
||||||
BoxWithConstraints(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
if (maxWidth >= SlidingSheetBreakpoint) {
|
if (currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Expanded) {
|
||||||
EndDrawerSheet(isVisible, onDismiss, content)
|
EndDrawerSheet(isVisible, onDismiss, content)
|
||||||
} else {
|
} else {
|
||||||
BottomSheetContent(isVisible, onDismiss, content)
|
BottomSheetContent(isVisible, onDismiss, content)
|
||||||
|
|||||||
@ -18,18 +18,8 @@
|
|||||||
package dev.krtirtho.spotube.modules.shell.player_queue
|
package dev.krtirtho.spotube.modules.shell.player_queue
|
||||||
|
|
||||||
|
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
|
||||||
import org.spotube.millennium.composables.MillenniumCard
|
|
||||||
import org.spotube.millennium.composables.MillenniumDropdownMenu
|
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
|
||||||
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
|
||||||
import org.spotube.millennium.composables.MillenniumListTile
|
|
||||||
import org.spotube.millennium.composables.MillenniumSeparator
|
|
||||||
import org.spotube.millennium.composables.MillenniumTextField
|
|
||||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
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
|
||||||
@ -45,14 +35,10 @@ import androidx.compose.foundation.layout.width
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
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.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -60,29 +46,47 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
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 coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
import com.composeunstyled.Text
|
||||||
import dev.krtirtho.spotube.core.jam.JamParticipant
|
import dev.krtirtho.spotube.core.jam.JamParticipant
|
||||||
import dev.krtirtho.spotube.core.ui.component.AdaptiveDialogBottomSheet
|
import dev.krtirtho.spotube.core.ui.component.AdaptiveDialogBottomSheet
|
||||||
|
import dev.krtirtho.spotube.core.ui.theming.AppTheme
|
||||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax3DotsMore
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax3DotsMore
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxDragHandle
|
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxFilterSearch
|
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxCloseSquare
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxCloseSquare
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxDragHandle
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxMusicSquareRemove
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxMusicSquareRemove
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxTrash
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxTrash
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxUserRemove
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxUserRemove
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
import org.spotube.millennium.composables.MillenniumDropdownMenu
|
||||||
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
|
import org.spotube.millennium.composables.MillenniumIconButtonStyle
|
||||||
|
import org.spotube.millennium.composables.MillenniumListTile
|
||||||
|
import org.spotube.millennium.composables.MillenniumSeparator
|
||||||
|
import org.spotube.millennium.composables.MillenniumTextField
|
||||||
|
import org.spotube.millennium.composables.MillenniumTooltip
|
||||||
import sh.calvin.reorderable.ReorderableItem
|
import sh.calvin.reorderable.ReorderableItem
|
||||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
|
import spotube.composeapp.generated.resources.Res
|
||||||
|
import spotube.composeapp.generated.resources.queue_clear_queue
|
||||||
|
import spotube.composeapp.generated.resources.queue_filter_queue
|
||||||
|
import spotube.composeapp.generated.resources.queue_no_queue_entries
|
||||||
|
import spotube.composeapp.generated.resources.queue_title
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PlayerQueueContent(
|
fun PlayerQueueContent(
|
||||||
viewModel: PlayerQueueContentViewModel = koinViewModel<PlayerQueueContentViewModel>(),
|
viewModel: PlayerQueueContentViewModel = koinViewModel<PlayerQueueContentViewModel>(),
|
||||||
modifier: Modifier = Modifier) {
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
val state by viewModel.queueContentUiState.collectAsState()
|
val state by viewModel.queueContentUiState.collectAsState()
|
||||||
val displayItems = state.displayItems
|
val displayItems = state.displayItems
|
||||||
val filterQuery = state.filterQuery
|
val filterQuery = state.filterQuery
|
||||||
@ -103,14 +107,17 @@ fun PlayerQueueContent(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Queue",
|
text = stringResource(Res.string.queue_title),
|
||||||
style = AppTheme.typography.titleLarge)
|
style = AppTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(5.dp),
|
horizontalArrangement = Arrangement.spacedBy(5.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically) {
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
val filterState = rememberTextFieldState(filterQuery)
|
val filterState = rememberTextFieldState(filterQuery)
|
||||||
LaunchedEffect(filterState.text) {
|
LaunchedEffect(filterState.text) {
|
||||||
viewModel.setQueueFilter(filterState.text.toString())
|
viewModel.setQueueFilter(filterState.text.toString())
|
||||||
@ -118,42 +125,49 @@ fun PlayerQueueContent(
|
|||||||
MillenniumTextField(
|
MillenniumTextField(
|
||||||
state = filterState,
|
state = filterState,
|
||||||
label = "",
|
label = "",
|
||||||
placeholder = "Filter queue...",
|
placeholder = stringResource(Res.string.queue_filter_queue),
|
||||||
modifier = Modifier.weight(1f))
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
if (!isReadOnly) {
|
if (!isReadOnly) {
|
||||||
MillenniumIconButton(
|
MillenniumTooltip(stringResource(Res.string.queue_clear_queue)) {
|
||||||
onClick = viewModel::clearQueue,
|
MillenniumIconButton(
|
||||||
style = MillenniumIconButtonStyle.Quiet) {
|
onClick = viewModel::clearQueue,
|
||||||
MillenniumIcon(Iconsax.IconsaxTrash, contentDescription = "Clear Queue")
|
style = MillenniumIconButtonStyle.Quiet
|
||||||
|
) {
|
||||||
|
MillenniumIcon(
|
||||||
|
Iconsax.IconsaxTrash,
|
||||||
|
contentDescription = stringResource(Res.string.queue_clear_queue)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayItems.isEmpty()) {
|
if (displayItems.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = "No queue entries",
|
text = stringResource(Res.string.queue_no_queue_entries),
|
||||||
style = AppTheme.typography.bodyMedium,
|
style = AppTheme.typography.bodyMedium,
|
||||||
color = AppTheme.colors.onSurfaceVariant)
|
color = AppTheme.colors.onSurfaceVariant
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
MillenniumCard {
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
contentPadding = PaddingValues(bottom = 8.dp)) {
|
contentPadding = PaddingValues(bottom = 8.dp)
|
||||||
items(displayItems, key = { it.id }) { item ->
|
) {
|
||||||
ReorderableItem(reorderableLazyListState, key = item.id) { isDragging ->
|
items(displayItems, key = { it.id }) { item ->
|
||||||
val elevation by animateDpAsState(if (isDragging) 8.dp else 0.dp)
|
ReorderableItem(reorderableLazyListState, key = item.id) { isDragging ->
|
||||||
QueueItemRow(
|
QueueItemRow(
|
||||||
item = item,
|
item = item,
|
||||||
reorderScope = if (isFiltered || isReadOnly) null else this,
|
reorderScope = if (isFiltered || isReadOnly) null else this,
|
||||||
onPlayClick = { viewModel.playQueueItem(item.originalIndex) },
|
onPlayClick = { viewModel.playQueueItem(item.originalIndex) },
|
||||||
onRemoveClick = { viewModel.removeQueueItem(item.originalIndex) },
|
onRemoveClick = { viewModel.removeQueueItem(item.originalIndex) },
|
||||||
onDragStarted = { viewModel.onDragStart() },
|
onDragStarted = { viewModel.onDragStart() },
|
||||||
onDragStopped = { viewModel.onDragStop() },
|
onDragStopped = { viewModel.onDragStop() },
|
||||||
showOptions = !isReadOnly,
|
showOptions = !isReadOnly,
|
||||||
enabled = !isReadOnly,
|
enabled = !isReadOnly,
|
||||||
onParticipantClick = { selectedParticipant = it })
|
onParticipantClick = { selectedParticipant = it })
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,32 +193,38 @@ private fun ParticipantDialog(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onKick: () -> Unit,
|
onKick: () -> Unit,
|
||||||
onBan: () -> Unit,
|
onBan: () -> Unit,
|
||||||
onRemoveSuggestions: () -> Unit) {
|
onRemoveSuggestions: () -> Unit
|
||||||
|
) {
|
||||||
AdaptiveDialogBottomSheet(
|
AdaptiveDialogBottomSheet(
|
||||||
onDismiss = onDismiss,
|
onDismiss = onDismiss,
|
||||||
title = { Text(participant.displayName, style = AppTheme.typography.titleLarge) }) {
|
title = { Text(participant.displayName, style = AppTheme.typography.titleLarge) }) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
modifier = Modifier.padding(vertical = 8.dp)) {
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
|
) {
|
||||||
ParticipantAvatar(participant, size = 40)
|
ParticipantAvatar(participant, size = 40)
|
||||||
Text(
|
Text(
|
||||||
text = participant.displayName,
|
text = participant.displayName,
|
||||||
style = AppTheme.typography.bodyLarge)
|
style = AppTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
if (participant.isHost) {
|
if (participant.isHost) {
|
||||||
Text(
|
Text(
|
||||||
text = "Host",
|
text = "Host",
|
||||||
style = AppTheme.typography.labelSmall,
|
style = AppTheme.typography.labelSmall,
|
||||||
color = AppTheme.colors.primary)
|
color = AppTheme.colors.primary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isJamHost && !participant.isHost) {
|
if (isJamHost && !participant.isHost) {
|
||||||
MillenniumSeparator(
|
MillenniumSeparator(
|
||||||
modifier = Modifier.fillMaxWidth())
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
MillenniumListTile(
|
MillenniumListTile(
|
||||||
onClick = {
|
onClick = {
|
||||||
onKick()
|
onKick()
|
||||||
@ -214,7 +234,8 @@ private fun ParticipantDialog(
|
|||||||
MillenniumIcon(
|
MillenniumIcon(
|
||||||
imageVector = Iconsax.IconsaxCloseSquare,
|
imageVector = Iconsax.IconsaxCloseSquare,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = AppTheme.colors.error)
|
tint = AppTheme.colors.error
|
||||||
|
)
|
||||||
},
|
},
|
||||||
title = { Text("Kick") },
|
title = { Text("Kick") },
|
||||||
subtitle = { Text("Remove them from the session") })
|
subtitle = { Text("Remove them from the session") })
|
||||||
@ -227,7 +248,8 @@ private fun ParticipantDialog(
|
|||||||
MillenniumIcon(
|
MillenniumIcon(
|
||||||
imageVector = Iconsax.IconsaxUserRemove,
|
imageVector = Iconsax.IconsaxUserRemove,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = AppTheme.colors.error)
|
tint = AppTheme.colors.error
|
||||||
|
)
|
||||||
},
|
},
|
||||||
title = { Text("Ban") },
|
title = { Text("Ban") },
|
||||||
subtitle = { Text("Kick and prevent them from rejoining") })
|
subtitle = { Text("Kick and prevent them from rejoining") })
|
||||||
@ -239,7 +261,8 @@ private fun ParticipantDialog(
|
|||||||
leading = {
|
leading = {
|
||||||
MillenniumIcon(
|
MillenniumIcon(
|
||||||
imageVector = Iconsax.IconsaxMusicSquareRemove,
|
imageVector = Iconsax.IconsaxMusicSquareRemove,
|
||||||
contentDescription = null)
|
contentDescription = null
|
||||||
|
)
|
||||||
},
|
},
|
||||||
title = { Text("Remove suggestions") },
|
title = { Text("Remove suggestions") },
|
||||||
subtitle = { Text("Remove every track they added to the queue") })
|
subtitle = { Text("Remove every track they added to the queue") })
|
||||||
@ -255,11 +278,13 @@ private fun ParticipantAvatar(participant: JamParticipant, size: Int) {
|
|||||||
.size(size.dp)
|
.size(size.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(AppTheme.colors.primaryContainer),
|
.background(AppTheme.colors.primaryContainer),
|
||||||
contentAlignment = Alignment.Center) {
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = participant.displayName.firstOrNull()?.uppercase()?.take(1) ?: "?",
|
text = participant.displayName.firstOrNull()?.uppercase()?.take(1) ?: "?",
|
||||||
style = AppTheme.typography.labelMedium,
|
style = AppTheme.typography.labelMedium,
|
||||||
color = AppTheme.colors.onPrimaryContainer)
|
color = AppTheme.colors.onPrimaryContainer
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +298,8 @@ private fun QueueItemRow(
|
|||||||
onDragStopped: () -> Unit,
|
onDragStopped: () -> Unit,
|
||||||
showOptions: Boolean = true,
|
showOptions: Boolean = true,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
onParticipantClick: (JamParticipant) -> Unit = {}) {
|
onParticipantClick: (JamParticipant) -> Unit = {}
|
||||||
|
) {
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
MillenniumListTile(
|
MillenniumListTile(
|
||||||
@ -284,8 +310,9 @@ private fun QueueItemRow(
|
|||||||
leading = {
|
leading = {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(72.dp),
|
.height(56.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically) {
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
MillenniumIcon(
|
MillenniumIcon(
|
||||||
Iconsax.IconsaxDragHandle,
|
Iconsax.IconsaxDragHandle,
|
||||||
contentDescription = if (reorderScope != null) "Reorder" else null,
|
contentDescription = if (reorderScope != null) "Reorder" else null,
|
||||||
@ -296,7 +323,8 @@ private fun QueueItemRow(
|
|||||||
with(reorderScope) {
|
with(reorderScope) {
|
||||||
Modifier.draggableHandle(
|
Modifier.draggableHandle(
|
||||||
onDragStarted = { onDragStarted() },
|
onDragStarted = { onDragStarted() },
|
||||||
onDragStopped = onDragStopped)
|
onDragStopped = onDragStopped
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
@ -305,21 +333,24 @@ private fun QueueItemRow(
|
|||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(48.dp)
|
.size(40.dp)
|
||||||
.clip(AppTheme.shapes.small)
|
.clip(AppTheme.shapes.small)
|
||||||
.background(AppTheme.colors.surfaceVariant),
|
.background(AppTheme.colors.surfaceVariant),
|
||||||
contentAlignment = Alignment.Center) {
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
if (item.imageUrl != null) {
|
if (item.imageUrl != null) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = item.imageUrl,
|
model = item.imageUrl,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentScale = ContentScale.Crop)
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "${item.originalIndex + 1}",
|
text = "${item.originalIndex + 1}",
|
||||||
style = AppTheme.typography.labelMedium,
|
style = AppTheme.typography.labelMedium,
|
||||||
color = AppTheme.colors.onSurfaceVariant)
|
color = AppTheme.colors.onSurfaceVariant
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -334,7 +365,8 @@ private fun QueueItemRow(
|
|||||||
AppTheme.colors.onSecondaryContainer
|
AppTheme.colors.onSecondaryContainer
|
||||||
} else {
|
} else {
|
||||||
AppTheme.colors.onSurface
|
AppTheme.colors.onSurface
|
||||||
})
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
subtitle = {
|
subtitle = {
|
||||||
Text(
|
Text(
|
||||||
@ -342,13 +374,15 @@ private fun QueueItemRow(
|
|||||||
style = AppTheme.typography.bodySmall,
|
style = AppTheme.typography.bodySmall,
|
||||||
color = AppTheme.colors.onSurfaceVariant,
|
color = AppTheme.colors.onSurfaceVariant,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis)
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
},
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
Text(
|
Text(
|
||||||
text = item.durationLabel,
|
text = item.durationLabel,
|
||||||
style = AppTheme.typography.bodySmall,
|
style = AppTheme.typography.bodySmall,
|
||||||
color = AppTheme.colors.onSurfaceVariant)
|
color = AppTheme.colors.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
|
||||||
@ -359,11 +393,13 @@ private fun QueueItemRow(
|
|||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(AppTheme.colors.surfaceVariant)
|
.background(AppTheme.colors.surfaceVariant)
|
||||||
.clickable { onParticipantClick(participant) },
|
.clickable { onParticipantClick(participant) },
|
||||||
contentAlignment = Alignment.Center) {
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = participant.displayName.firstOrNull()?.uppercase()?.take(1) ?: "?",
|
text = participant.displayName.firstOrNull()?.uppercase()?.take(1) ?: "?",
|
||||||
style = AppTheme.typography.labelSmall,
|
style = AppTheme.typography.labelSmall,
|
||||||
color = AppTheme.colors.onSurfaceVariant)
|
color = AppTheme.colors.onSurfaceVariant
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
}
|
}
|
||||||
@ -380,10 +416,12 @@ private fun QueueItemRow(
|
|||||||
anchor = {
|
anchor = {
|
||||||
MillenniumIconButton(
|
MillenniumIconButton(
|
||||||
onClick = { showMenu = true },
|
onClick = { showMenu = true },
|
||||||
style = MillenniumIconButtonStyle.Quiet) {
|
style = MillenniumIconButtonStyle.Quiet
|
||||||
|
) {
|
||||||
MillenniumIcon(
|
MillenniumIcon(
|
||||||
Iconsax.Iconsax3DotsMore,
|
Iconsax.Iconsax3DotsMore,
|
||||||
contentDescription = "More options")
|
contentDescription = "More options"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,15 +17,17 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.modules.shell.player_queue
|
package dev.krtirtho.spotube.modules.shell.player_queue
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.composeunstyled.DrawerPlacement
|
import com.composeunstyled.DrawerPlacement
|
||||||
import com.composeunstyled.SheetDetent
|
import com.composeunstyled.SheetDetent
|
||||||
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
import com.composeunstyled.rememberModalBottomSheetState
|
import com.composeunstyled.rememberModalBottomSheetState
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import org.spotube.millennium.composables.MillenniumDrawer
|
import org.spotube.millennium.composables.MillenniumDrawer
|
||||||
import org.spotube.millennium.composables.MillenniumDrawerValue
|
import org.spotube.millennium.composables.MillenniumDrawerValue
|
||||||
import org.spotube.millennium.composables.MillenniumModalBottomSheet
|
import org.spotube.millennium.composables.MillenniumModalBottomSheet
|
||||||
@ -38,13 +40,14 @@ fun QueueSheet(
|
|||||||
isVisible: Boolean,
|
isVisible: Boolean,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
content: @Composable () -> Unit) {
|
content: @Composable () -> Unit
|
||||||
BoxWithConstraints(modifier = modifier) {
|
) {
|
||||||
if (maxWidth >= SlidingSheetBreakpoint) {
|
val isExpanded = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Expanded
|
||||||
EndDrawerSheet(isVisible, onDismiss, content)
|
|
||||||
} else {
|
if (isExpanded) {
|
||||||
BottomSheetContent(isVisible, onDismiss, content)
|
EndDrawerSheet(isVisible, onDismiss, content)
|
||||||
}
|
} else {
|
||||||
|
BottomSheetContent(isVisible, onDismiss, content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +55,8 @@ fun QueueSheet(
|
|||||||
internal fun EndDrawerSheet(
|
internal fun EndDrawerSheet(
|
||||||
isVisible: Boolean,
|
isVisible: Boolean,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
content: @Composable () -> Unit) {
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
val drawerState = rememberMillenniumDrawerState()
|
val drawerState = rememberMillenniumDrawerState()
|
||||||
LaunchedEffect(isVisible) {
|
LaunchedEffect(isVisible) {
|
||||||
drawerState.targetValue =
|
drawerState.targetValue =
|
||||||
@ -66,7 +70,10 @@ internal fun EndDrawerSheet(
|
|||||||
MillenniumDrawer(
|
MillenniumDrawer(
|
||||||
state = drawerState,
|
state = drawerState,
|
||||||
placement = DrawerPlacement.End,
|
placement = DrawerPlacement.End,
|
||||||
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 12.dp)) {
|
shape = RectangleShape,
|
||||||
|
maximumWidth = 400.dp,
|
||||||
|
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 12.dp)
|
||||||
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +82,8 @@ internal fun EndDrawerSheet(
|
|||||||
internal fun BottomSheetContent(
|
internal fun BottomSheetContent(
|
||||||
isVisible: Boolean,
|
isVisible: Boolean,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
content: @Composable () -> Unit) {
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
if (!isVisible) return
|
if (!isVisible) return
|
||||||
val sheetState = rememberModalBottomSheetState(initialDetent = SheetDetent.FullyExpanded)
|
val sheetState = rememberModalBottomSheetState(initialDetent = SheetDetent.FullyExpanded)
|
||||||
LaunchedEffect(sheetState.bottomSheetState.currentDetent) {
|
LaunchedEffect(sheetState.bottomSheetState.currentDetent) {
|
||||||
@ -85,7 +93,8 @@ internal fun BottomSheetContent(
|
|||||||
}
|
}
|
||||||
MillenniumModalBottomSheet(
|
MillenniumModalBottomSheet(
|
||||||
state = sheetState,
|
state = sheetState,
|
||||||
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp)) {
|
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp)
|
||||||
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
package dev.krtirtho.spotube.modules.welcome.pages
|
package dev.krtirtho.spotube.modules.welcome.pages
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import com.composeunstyled.currentWindowWidthBreakpoint
|
||||||
|
import org.spotube.millennium.adaptive.WindowBreakpoints
|
||||||
import org.spotube.millennium.composables.MillenniumCard
|
import org.spotube.millennium.composables.MillenniumCard
|
||||||
import org.spotube.millennium.composables.MillenniumIcon
|
import org.spotube.millennium.composables.MillenniumIcon
|
||||||
import org.spotube.millennium.composables.MillenniumIconButton
|
import org.spotube.millennium.composables.MillenniumIconButton
|
||||||
@ -38,8 +43,8 @@ fun HowItWorksPage(
|
|||||||
onTogglePlayback: () -> Unit,
|
onTogglePlayback: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
BoxWithConstraints(modifier.fillMaxSize()) {
|
Box(modifier.fillMaxSize()) {
|
||||||
val wide = maxWidth >= 760.dp
|
val wide = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Expanded
|
||||||
if (wide) {
|
if (wide) {
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxSize().padding(40.dp),
|
Modifier.fillMaxSize().padding(40.dp),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user