From caad76ba95810447783274c74fffe155ded961f8 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 11 Oct 2026 21:08:11 +0600 Subject: [PATCH] refactor: replace currentWindowAdaptiveInfo with currentWindowWidthBreakpoint for adaptive layouts --- .../composeResources/values/strings.xml | 7 +- .../kotlin/dev/krtirtho/spotube/App.kt | 23 ++- .../ui/component/AdaptiveDialogBottomSheet.kt | 10 +- .../component/AdaptiveDropdownBottomSheet.kt | 49 ++--- .../core/ui/component/CollectionDetails.kt | 7 +- .../core/ui/component/FeaturedCarousel.kt | 6 +- .../spotube/core/ui/component/Sidebar.kt | 133 ------------ .../spotube/core/ui/component/TrackList.kt | 11 +- .../spotube/core/ui/theming/AppTheme.kt | 44 ++-- .../spotube/modules/artist/ArtistScreen.kt | 7 +- .../library/playlist/AddToPlaylistPicker.kt | 9 +- .../library/playlist/PlaylistFormSheet.kt | 9 +- .../playlist/AddTracksToPlaylistDialog.kt | 9 +- .../modules/settings/SettingsScreen.kt | 8 +- .../modules/settings/SettingsSidebarLayout.kt | 9 +- .../components/InteractiveSettingCards.kt | 14 +- .../spotube/modules/shell/AppShell.kt | 42 ++-- .../modules/shell/AppShellViewModel.kt | 9 +- .../spotube/modules/shell/AppSidebar.kt | 5 +- .../AlternativeTrackSheet.kt | 10 +- .../shell/player_queue/PlayerQueueContent.kt | 192 +++++++++++------- .../modules/shell/player_queue/QueueSheet.kt | 33 +-- .../modules/welcome/pages/HowItWorksPage.kt | 9 +- 23 files changed, 289 insertions(+), 366 deletions(-) delete mode 100644 composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/Sidebar.kt diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index a38b2ac1..01b95038 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -16,13 +16,18 @@ --> + Queue + Clear Queue + Filter queue... + No queue entries + Settings Plugins Language and Region Appearance Playback - Downloads + Downloadsf Caching Desktop Updates diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/App.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/App.kt index f7005f50..1ab1f561 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/App.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/App.kt @@ -76,7 +76,8 @@ interface NavigationItem { private val tabs = listOf( NavigationItem.Tab("Home", Iconsax.IconsaxHome, Routes.Home), - NavigationItem.Tab("Search", Iconsax.IconsaxSearch, Routes.Search)) + NavigationItem.Tab("Search", Iconsax.IconsaxSearch, Routes.Search) +) val sidebarTabs = tabs + listOf( NavigationItem.Group( @@ -89,7 +90,8 @@ val sidebarTabs = tabs + listOf( LibraryTab.Playlists ), 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( "On Device", @@ -105,23 +107,23 @@ val sidebarTabs = tabs + listOf( Iconsax.IconsaxDirectboxReceive, Routes.Library, LibraryTab.Downloads - )) + ) + ) ), NavigationItem.Group( "Connect", items = listOf( 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( NavigationItem.Tab("Library", Iconsax.IconsaxMusicLibrary, Routes.Library), NavigationItem.Tab("Settings", Iconsax.IconsaxSetting2, Routes.Settings) ) -@OptIn( - KoinExperimentalAPI::class, - ExperimentalCoroutinesApi::class, - ExperimentalSharedTransitionApi::class -) +@OptIn(KoinExperimentalAPI::class) @Composable fun App( content: @Composable () -> Unit = {} @@ -156,7 +158,8 @@ fun App( AppShell(navigator, navigationState) { SharedTransitionLayout { CompositionLocalProvider( - LocalSharedTransitionScope provides this@SharedTransitionLayout) { + LocalSharedTransitionScope provides this@SharedTransitionLayout + ) { Column { NavDisplay( modifier = Modifier.fillMaxSize(), diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/AdaptiveDialogBottomSheet.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/AdaptiveDialogBottomSheet.kt index 24b21b1a..7d3c25a2 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/AdaptiveDialogBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/AdaptiveDialogBottomSheet.kt @@ -32,16 +32,14 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height 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.rememberModalBottomSheetState -import org.spotube.millennium.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.composeunstyled.currentWindowWidthBreakpoint +import org.spotube.millennium.adaptive.WindowBreakpoints import androidx.compose.ui.unit.dp import dev.krtirtho.spotube.resources.iconsax.FluentDismiss import dev.krtirtho.spotube.resources.iconsax.Iconsax @@ -51,11 +49,9 @@ fun AdaptiveDialogBottomSheet( onDismiss: () -> Unit, title: @Composable (() -> Unit)? = null, modifier: Modifier = Modifier, - breakpointDp: Float = 600f, actions: @Composable (() -> Unit)? = null, content: @Composable () -> Unit) { - val adaptiveInfo = currentWindowAdaptiveInfo() - val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp + val isLargeScreen = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium if (isLargeScreen) { MillenniumDialog( diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/AdaptiveDropdownBottomSheet.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/AdaptiveDropdownBottomSheet.kt index acb5b3a4..e8646ed2 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/AdaptiveDropdownBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/AdaptiveDropdownBottomSheet.kt @@ -18,17 +18,8 @@ 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.border import androidx.compose.foundation.clickable -import androidx.compose.foundation.hoverable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsHoveredAsState 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.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape 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.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -69,21 +46,37 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color 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.unit.Dp -import androidx.compose.ui.unit.DpOffset 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.Check +import dev.krtirtho.spotube.core.ui.theming.AppTheme import dev.krtirtho.spotube.resources.iconsax.Iconsax 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.IconsaxShare 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( val icon: ImageVector? = null, @@ -106,14 +99,12 @@ fun AdaptiveDropdownBottomSheet( items: List, trigger: @Composable (onClick: () -> Unit) -> Unit, modifier: Modifier = Modifier, - breakpointDp: Float = 600f, menuMinWidth: Dp = 200.dp, header: @Composable (() -> Unit)? = null, headerDisplayMode: HeaderDisplayMode = HeaderDisplayMode.Always, filter: ((AdaptiveMenuItem, String) -> Boolean)? = null) { var expanded by remember { mutableStateOf(false) } - val adaptiveInfo = currentWindowAdaptiveInfo() - val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp + val isLargeScreen = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium Box(modifier = modifier) { if (isLargeScreen) { diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/CollectionDetails.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/CollectionDetails.kt index 2315074e..7356b4b5 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/CollectionDetails.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/CollectionDetails.kt @@ -31,7 +31,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement 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.PaddingValues import androidx.compose.foundation.layout.Row @@ -89,8 +90,8 @@ fun CollectionDetails( showFollowButton: Boolean = true, onEdit: (() -> Unit)? = null, sharedElementKey: String? = null) { - BoxWithConstraints(modifier = modifier.fillMaxWidth()) { - val isCompact = maxWidth < 600.dp + Box(modifier = modifier.fillMaxWidth()) { + val isCompact = currentWindowWidthBreakpoint() isBelow WindowBreakpoints.Medium val sharedTransitionScope = LocalSharedTransitionScope.current val animatedVisibilityScope = LocalAnimatedVisibilityScope.current diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/FeaturedCarousel.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/FeaturedCarousel.kt index 75f09d3a..142358fe 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/FeaturedCarousel.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/FeaturedCarousel.kt @@ -41,7 +41,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import dev.krtirtho.spotube.core.ui.theming.AppTheme 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.getValue import androidx.compose.runtime.mutableIntStateOf @@ -104,8 +105,7 @@ private fun FeaturedCarouselContent( val centerIndex = items.size / 2 var selectedIndex by remember { mutableIntStateOf(centerIndex) } val state = rememberCoverflowState(centerIndex) { index -> selectedIndex = index } - val adaptiveInfo = currentWindowAdaptiveInfo() - val isSmallScreen = adaptiveInfo.windowSizeClass.minWidthDp <= 600 + val isSmallScreen = currentWindowWidthBreakpoint() isBelow WindowBreakpoints.Medium Box( modifier = modifier diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/Sidebar.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/Sidebar.kt deleted file mode 100644 index 755dc1bd..00000000 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/Sidebar.kt +++ /dev/null @@ -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 . - */ - -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)) - } -} diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/TrackList.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/TrackList.kt index 7fbd1f4b..b15a985a 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/TrackList.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/component/TrackList.kt @@ -18,6 +18,9 @@ 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.fadeIn 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.layout.ContentScale 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.TextDecoration import androidx.compose.ui.text.style.TextOverflow @@ -207,11 +209,10 @@ fun TrackList( } } - val windowInfo = LocalWindowInfo.current - val maxWidth = with(density) { windowInfo.containerSize.width.toDp() } - val isCompact = maxWidth < CompactTrackListBreakpoint + val widthBreakpoint = currentWindowWidthBreakpoint() + val isCompact = widthBreakpoint isBelow WindowBreakpoints.Medium val isDesktop = remember { getPlatform().isDesktop() } - val showIndex = maxWidth >= LargeTrackListBreakpoint + val showIndex = widthBreakpoint isAtLeast WindowBreakpoints.Expanded val showAlbum = !isCompact val useDropdownForOptions = isDesktop && !isCompact diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/theming/AppTheme.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/theming/AppTheme.kt index b58b713f..11584429 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/theming/AppTheme.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/ui/theming/AppTheme.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.unit.sp import dev.krtirtho.spotube.modules.settings.Theme import dev.krtirtho.spotube.modules.settings.UserSettings +import org.spotube.millennium.composables.MillenniumTooltipHost import org.spotube.millennium.theme.MillenniumColors import org.spotube.millennium.theme.MillenniumPalette import org.spotube.millennium.theme.MillenniumTheme @@ -75,7 +76,8 @@ data class AppColors( val onError: Color, val errorContainer: Color, val onErrorContainer: Color, - val scrim: Color) + val scrim: Color +) /** Maps Millennium's semantic palette onto the app's role names. */ fun appColorsFrom(m: MillenniumColors): AppColors = AppColors( @@ -106,7 +108,8 @@ fun appColorsFrom(m: MillenniumColors): AppColors = AppColors( onError = m.background, errorContainer = m.danger.copy(alpha = 0.16f), onErrorContainer = m.danger, - scrim = m.scrim) + scrim = m.scrim +) /** Named text styles used by the app, independent of Material typography. */ @Immutable @@ -123,16 +126,19 @@ data class AppTypography( val bodySmall: TextStyle, val labelLarge: TextStyle, val labelMedium: TextStyle, - val labelSmall: TextStyle) + val labelSmall: TextStyle +) private fun style( size: Int, lineHeight: Int, - weight: FontWeight = FontWeight.Normal): TextStyle = TextStyle( + weight: FontWeight = FontWeight.Normal +): TextStyle = TextStyle( fontFamily = FontFamily.SansSerif, fontSize = size.sp, lineHeight = lineHeight.sp, - fontWeight = weight) + fontWeight = weight +) val AppDefaultTypography = AppTypography( displaySmall = style(36, 44), @@ -147,21 +153,25 @@ val AppDefaultTypography = AppTypography( bodySmall = style(12, 16), labelLarge = style(14, 20, 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. */ @Immutable data class AppShapes( val small: Shape, val medium: Shape, - val large: Shape) + val large: Shape +) val AppDefaultShapes = AppShapes( small = RoundedCornerShape(8.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 LocalAppShapes = staticCompositionLocalOf { AppDefaultShapes } @@ -182,7 +192,8 @@ object AppTheme { @Composable fun SpotubeTheme( settings: UserSettings, - content: @Composable () -> Unit) { + content: @Composable () -> Unit +) { val isDarkTheme = when (settings.theme) { Theme.LIGHT -> false Theme.DARK -> true @@ -192,11 +203,14 @@ fun SpotubeTheme( MillenniumTheme(dark = isDarkTheme, config = MillenniumThemeConfig(palette = palette)) { val colors = appColorsFrom(MillenniumTheme.colors) - CompositionLocalProvider( - LocalAppColors provides colors, - LocalAppTypography provides AppDefaultTypography, - LocalAppShapes provides AppDefaultShapes, - content = content) + MillenniumTooltipHost { + CompositionLocalProvider( + LocalAppColors provides colors, + LocalAppTypography provides AppDefaultTypography, + LocalAppShapes provides AppDefaultShapes, + content = content + ) + } } } diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/artist/ArtistScreen.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/artist/ArtistScreen.kt index f274c19e..a7ab61be 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/artist/ArtistScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/artist/ArtistScreen.kt @@ -29,7 +29,8 @@ import org.spotube.millennium.theme.MillenniumTheme import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement 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.PaddingValues import androidx.compose.foundation.layout.Row @@ -327,8 +328,8 @@ private fun ArtistHeaderCard( onFollowClick: () -> Unit, isBlacklisted: Boolean, onBlacklistClick: () -> Unit) { - BoxWithConstraints(modifier = Modifier.fillMaxWidth()) { - val isCompact = maxWidth < 600.dp + Box(modifier = Modifier.fillMaxWidth()) { + val isCompact = currentWindowWidthBreakpoint() isBelow WindowBreakpoints.Medium MillenniumCard( modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/library/playlist/AddToPlaylistPicker.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/library/playlist/AddToPlaylistPicker.kt index 3fc8122c..694bb012 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/library/playlist/AddToPlaylistPicker.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/library/playlist/AddToPlaylistPicker.kt @@ -42,7 +42,8 @@ import dev.krtirtho.spotube.core.ui.theming.AppTheme import com.composeunstyled.SheetDetent import com.composeunstyled.Text 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.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -74,12 +75,10 @@ fun AddToPlaylistPicker( onDismiss: () -> Unit, onPlaylistSelected: (String) -> Unit, modifier: Modifier = Modifier, - libraryRepository: LibraryRepository = koinInject(), - breakpointDp: Float = 600f) { + libraryRepository: LibraryRepository = koinInject()) { if (!visible) return - val adaptiveInfo = currentWindowAdaptiveInfo() - val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp + val isLargeScreen = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium var playlists by remember { mutableStateOf>(emptyList()) } var isLoading by remember { mutableStateOf(true) } diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/library/playlist/PlaylistFormSheet.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/library/playlist/PlaylistFormSheet.kt index ba7f9e85..7bd7bc80 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/library/playlist/PlaylistFormSheet.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/library/playlist/PlaylistFormSheet.kt @@ -41,7 +41,8 @@ import dev.krtirtho.spotube.core.ui.theming.AppTheme import com.composeunstyled.SheetDetent import com.composeunstyled.Text 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.rememberTextFieldState import androidx.compose.runtime.Composable @@ -82,12 +83,10 @@ fun PlaylistFormSheet( onSubmit: (PlaylistFormData) -> Unit, modifier: Modifier = Modifier, initialData: PlaylistFormData? = null, - isEditing: Boolean = false, - breakpointDp: Float = 600f) { + isEditing: Boolean = false) { if (!visible) return - val adaptiveInfo = currentWindowAdaptiveInfo() - val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp + val isLargeScreen = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium var name by remember(initialData) { mutableStateOf(initialData?.name ?: "") } var description by remember(initialData) { mutableStateOf(initialData?.description ?: "") } diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/playlist/AddTracksToPlaylistDialog.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/playlist/AddTracksToPlaylistDialog.kt index b50e255f..7e59fbc0 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/playlist/AddTracksToPlaylistDialog.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/playlist/AddTracksToPlaylistDialog.kt @@ -44,7 +44,8 @@ import dev.krtirtho.spotube.core.ui.theming.AppTheme import com.composeunstyled.SheetDetent import com.composeunstyled.Text 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.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -79,12 +80,10 @@ fun AddTracksToPlaylistDialog( onDismiss: () -> Unit, modifier: Modifier = Modifier, searchRepository: SearchRepository = koinInject(), - libraryRepository: LibraryRepository = koinInject(), - breakpointDp: Float = 600f) { + libraryRepository: LibraryRepository = koinInject()) { if (!visible) return - val adaptiveInfo = currentWindowAdaptiveInfo() - val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp + val isLargeScreen = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium var query by remember { mutableStateOf("") } var searchResults by remember { mutableStateOf>(emptyList()) } diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsScreen.kt index add9858d..9a059b31 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsScreen.kt @@ -17,8 +17,10 @@ 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.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize @@ -67,8 +69,8 @@ private val SettingsDialogMinWidth = 720.dp fun SettingsScreen(settingsViewModel: SettingsViewModel) { val navigatorCommands: NavigationCommands = koinInject() - BoxWithConstraints(modifier = Modifier.fillMaxSize()) { - if (maxWidth >= SettingsDialogMinWidth) { + Box(modifier = Modifier.fillMaxSize()) { + if (currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Expanded) { SettingsDialog( settingsViewModel = settingsViewModel, onDismiss = { navigatorCommands.pop(Routes.Settings) }) diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsSidebarLayout.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsSidebarLayout.kt index 0104944a..b64109b0 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsSidebarLayout.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsSidebarLayout.kt @@ -23,6 +23,7 @@ import org.spotube.millennium.composables.MillenniumIconButton import org.spotube.millennium.composables.MillenniumIconButtonStyle import org.spotube.millennium.composables.MillenniumIndeterminateProgress import org.spotube.millennium.composables.MillenniumSeparator +import org.spotube.millennium.composables.MillenniumSidebarItem import org.spotube.millennium.theme.MillenniumTheme import org.spotube.millennium.theme.millenniumPanelBrush import org.spotube.millennium.theme.millenniumSurface @@ -62,7 +63,6 @@ import dev.krtirtho.spotube.PlatformType import dev.krtirtho.spotube.getPlatform import dev.krtirtho.spotube.core.discovery.rememberLocalNetworkPermissionRequester 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.plugin.PluginScreenContent import dev.krtirtho.spotube.modules.settings.sections.appearanceSection @@ -247,11 +247,12 @@ private fun SettingsSidebar( ) } items(tabs, key = { it.name }) { tab -> - SidebarItem( + MillenniumSidebarItem( label = stringResource(tab.titleRes), - icon = tab.icon, + onClick = { onSelect(tab) }, selected = tab == selectedTab, - onClick = { onSelect(tab) }) + contentDescription = stringResource(tab.titleRes), + icon = tab.icon) } } } diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/components/InteractiveSettingCards.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/components/InteractiveSettingCards.kt index 98cfc5bc..557f5639 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/components/InteractiveSettingCards.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/components/InteractiveSettingCards.kt @@ -18,6 +18,11 @@ 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 org.spotube.millennium.composables.MillenniumIcon 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.MillenniumTextField import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -92,8 +96,8 @@ internal fun SelectionSettingCard( filter: ((AdaptiveMenuItem, String) -> Boolean)? = null) { var isDialogOpen by remember { mutableStateOf(false) } - BoxWithConstraints { - val isWideLayout = maxWidth >= inlineSelectorMinWidth + Box { + val isWideLayout = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium SettingCardItem( title = title, @@ -173,8 +177,8 @@ internal fun TextInputSettingCard( enabled: Boolean = true) { var isDialogOpen by remember { mutableStateOf(false) } - BoxWithConstraints { - val isWideLayout = maxWidth >= inlineTextFieldMinWidth + Box { + val isWideLayout = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Medium val inlineState = rememberTextFieldState(value) LaunchedEffect(inlineState, isWideLayout) { diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppShell.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppShell.kt index 9beb0e79..bdd89d61 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppShell.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppShell.kt @@ -23,8 +23,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource 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.Row 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.fillMaxWidth import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.offset -import androidx.compose.ui.graphics.Color -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.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Alignment 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.unit.dp -import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.rememberHazeState import androidx.compose.ui.window.Dialog 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.NavigationState 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 org.koin.compose.koinInject 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 } -@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalFoundationApi::class) @Composable fun AppShell( navigator: Navigator, @@ -158,7 +145,6 @@ fun AppShell( Column(modifier = Modifier.fillMaxSize()) { Row(modifier = Modifier.weight(1f)) { AppSidebar( - hazeState = bgHazeState, navigator = navigator, navigationState = navigationState ) @@ -214,7 +200,7 @@ fun AppShell( } } } else { - BoxWithConstraints(modifier = Modifier.fillMaxSize()) { + Box(modifier = Modifier.fillMaxSize()) { Box( modifier = Modifier .fillMaxSize() @@ -278,7 +264,7 @@ fun AppShell( @OptIn( ExperimentalSharedTransitionApi::class, ExperimentalFoundationApi::class) @Composable -private fun BoxWithConstraintsScope.CompactPlayerOverlay( +private fun CompactPlayerOverlay( navigator: Navigator, navigationState: NavigationState, viewModel: AppShellViewModel, diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppShellViewModel.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppShellViewModel.kt index 4139fe18..1e8fd536 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppShellViewModel.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppShellViewModel.kt @@ -17,9 +17,10 @@ package dev.krtirtho.spotube.modules.shell + +import com.composeunstyled.currentWindowWidthBreakpoint +import org.spotube.millennium.adaptive.WindowBreakpoints import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalWindowInfo import androidx.lifecycle.ViewModel import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -63,9 +64,7 @@ class AppShellViewModel : ViewModel() { if (maxWidth != null) { return maxWidth >= sidebarMinWidth } - val density = LocalDensity.current - val windowInfo = LocalWindowInfo.current - return with(density) { windowInfo.containerSize.width.toDp() >= sidebarMinWidth } + return currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Expanded } fun bottomOverlayInset(useSidebar: Boolean): Dp = diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppSidebar.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppSidebar.kt index e63f2507..1d28b09e 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppSidebar.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/AppSidebar.kt @@ -22,7 +22,6 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable @@ -35,11 +34,11 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.composeunstyled.Text -import dev.chrisbanes.haze.HazeState import dev.krtirtho.spotube.NavigationItem import dev.krtirtho.spotube.core.navigation.NavigationState import dev.krtirtho.spotube.core.navigation.Navigator @@ -67,7 +66,6 @@ import spotube.composeapp.generated.resources.cookie_regular @Composable fun AppSidebar( - hazeState: HazeState, navigator: Navigator, navigationState: NavigationState, modifier: Modifier = Modifier, @@ -80,6 +78,7 @@ fun AppSidebar( MillenniumSidebar( modifier = modifier, collapsed = !expanded, + shape = RectangleShape, onCollapsedChange = { expanded = !it }, ) { MillenniumSidebarHeader { diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/alternative_track/AlternativeTrackSheet.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/alternative_track/AlternativeTrackSheet.kt index d949602b..6162b809 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/alternative_track/AlternativeTrackSheet.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/alternative_track/AlternativeTrackSheet.kt @@ -17,7 +17,11 @@ 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.ui.Modifier import androidx.compose.ui.unit.dp @@ -32,8 +36,8 @@ fun AlternativeTrackSheet( onDismiss: () -> Unit, modifier: Modifier = Modifier, content: @Composable () -> Unit) { - BoxWithConstraints(modifier = modifier) { - if (maxWidth >= SlidingSheetBreakpoint) { + Box(modifier = modifier) { + if (currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Expanded) { EndDrawerSheet(isVisible, onDismiss, content) } else { BottomSheetContent(isVisible, onDismiss, content) diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/player_queue/PlayerQueueContent.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/player_queue/PlayerQueueContent.kt index 33d9a4d2..f845a2d1 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/player_queue/PlayerQueueContent.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/player_queue/PlayerQueueContent.kt @@ -18,18 +18,8 @@ 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.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box 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.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.shape.CircleShape - -import dev.krtirtho.spotube.core.ui.theming.AppTheme - -import com.composeunstyled.Text +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -60,29 +46,47 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.composed import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage +import com.composeunstyled.Text import dev.krtirtho.spotube.core.jam.JamParticipant 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.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.IconsaxDragHandle import dev.krtirtho.spotube.resources.iconsax.IconsaxMusicSquareRemove import dev.krtirtho.spotube.resources.iconsax.IconsaxTrash import dev.krtirtho.spotube.resources.iconsax.IconsaxUserRemove +import org.jetbrains.compose.resources.stringResource 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.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 fun PlayerQueueContent( viewModel: PlayerQueueContentViewModel = koinViewModel(), - modifier: Modifier = Modifier) { + modifier: Modifier = Modifier +) { val state by viewModel.queueContentUiState.collectAsState() val displayItems = state.displayItems val filterQuery = state.filterQuery @@ -103,14 +107,17 @@ fun PlayerQueueContent( modifier = Modifier .fillMaxSize() .padding(horizontal = 8.dp, vertical = 4.dp), - verticalArrangement = Arrangement.spacedBy(10.dp)) { + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { Text( - text = "Queue", - style = AppTheme.typography.titleLarge) + text = stringResource(Res.string.queue_title), + style = AppTheme.typography.titleLarge + ) Row( horizontalArrangement = Arrangement.spacedBy(5.dp), - verticalAlignment = Alignment.CenterVertically) { + verticalAlignment = Alignment.CenterVertically + ) { val filterState = rememberTextFieldState(filterQuery) LaunchedEffect(filterState.text) { viewModel.setQueueFilter(filterState.text.toString()) @@ -118,42 +125,49 @@ fun PlayerQueueContent( MillenniumTextField( state = filterState, label = "", - placeholder = "Filter queue...", - modifier = Modifier.weight(1f)) + placeholder = stringResource(Res.string.queue_filter_queue), + modifier = Modifier.weight(1f) + ) if (!isReadOnly) { - MillenniumIconButton( - onClick = viewModel::clearQueue, - style = MillenniumIconButtonStyle.Quiet) { - MillenniumIcon(Iconsax.IconsaxTrash, contentDescription = "Clear Queue") + MillenniumTooltip(stringResource(Res.string.queue_clear_queue)) { + MillenniumIconButton( + onClick = viewModel::clearQueue, + style = MillenniumIconButtonStyle.Quiet + ) { + MillenniumIcon( + Iconsax.IconsaxTrash, + contentDescription = stringResource(Res.string.queue_clear_queue) + ) + } } } } if (displayItems.isEmpty()) { Text( - text = "No queue entries", + text = stringResource(Res.string.queue_no_queue_entries), style = AppTheme.typography.bodyMedium, - color = AppTheme.colors.onSurfaceVariant) + color = AppTheme.colors.onSurfaceVariant + ) } else { - MillenniumCard { - LazyColumn( - modifier = Modifier.fillMaxSize(), - state = lazyListState, - contentPadding = PaddingValues(bottom = 8.dp)) { - items(displayItems, key = { it.id }) { item -> - ReorderableItem(reorderableLazyListState, key = item.id) { isDragging -> - val elevation by animateDpAsState(if (isDragging) 8.dp else 0.dp) - QueueItemRow( - item = item, - reorderScope = if (isFiltered || isReadOnly) null else this, - onPlayClick = { viewModel.playQueueItem(item.originalIndex) }, - onRemoveClick = { viewModel.removeQueueItem(item.originalIndex) }, - onDragStarted = { viewModel.onDragStart() }, - onDragStopped = { viewModel.onDragStop() }, - showOptions = !isReadOnly, - enabled = !isReadOnly, - onParticipantClick = { selectedParticipant = it }) - } + + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = lazyListState, + contentPadding = PaddingValues(bottom = 8.dp) + ) { + items(displayItems, key = { it.id }) { item -> + ReorderableItem(reorderableLazyListState, key = item.id) { isDragging -> + QueueItemRow( + item = item, + reorderScope = if (isFiltered || isReadOnly) null else this, + onPlayClick = { viewModel.playQueueItem(item.originalIndex) }, + onRemoveClick = { viewModel.removeQueueItem(item.originalIndex) }, + onDragStarted = { viewModel.onDragStart() }, + onDragStopped = { viewModel.onDragStop() }, + showOptions = !isReadOnly, + enabled = !isReadOnly, + onParticipantClick = { selectedParticipant = it }) } } } @@ -179,32 +193,38 @@ private fun ParticipantDialog( onDismiss: () -> Unit, onKick: () -> Unit, onBan: () -> Unit, - onRemoveSuggestions: () -> Unit) { + onRemoveSuggestions: () -> Unit +) { AdaptiveDialogBottomSheet( onDismiss = onDismiss, title = { Text(participant.displayName, style = AppTheme.typography.titleLarge) }) { Column( modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(4.dp)) { + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp), - modifier = Modifier.padding(vertical = 8.dp)) { + modifier = Modifier.padding(vertical = 8.dp) + ) { ParticipantAvatar(participant, size = 40) Text( text = participant.displayName, - style = AppTheme.typography.bodyLarge) + style = AppTheme.typography.bodyLarge + ) if (participant.isHost) { Text( text = "Host", style = AppTheme.typography.labelSmall, - color = AppTheme.colors.primary) + color = AppTheme.colors.primary + ) } } if (isJamHost && !participant.isHost) { MillenniumSeparator( - modifier = Modifier.fillMaxWidth()) + modifier = Modifier.fillMaxWidth() + ) MillenniumListTile( onClick = { onKick() @@ -214,7 +234,8 @@ private fun ParticipantDialog( MillenniumIcon( imageVector = Iconsax.IconsaxCloseSquare, contentDescription = null, - tint = AppTheme.colors.error) + tint = AppTheme.colors.error + ) }, title = { Text("Kick") }, subtitle = { Text("Remove them from the session") }) @@ -227,7 +248,8 @@ private fun ParticipantDialog( MillenniumIcon( imageVector = Iconsax.IconsaxUserRemove, contentDescription = null, - tint = AppTheme.colors.error) + tint = AppTheme.colors.error + ) }, title = { Text("Ban") }, subtitle = { Text("Kick and prevent them from rejoining") }) @@ -239,7 +261,8 @@ private fun ParticipantDialog( leading = { MillenniumIcon( imageVector = Iconsax.IconsaxMusicSquareRemove, - contentDescription = null) + contentDescription = null + ) }, title = { Text("Remove suggestions") }, subtitle = { Text("Remove every track they added to the queue") }) @@ -255,11 +278,13 @@ private fun ParticipantAvatar(participant: JamParticipant, size: Int) { .size(size.dp) .clip(CircleShape) .background(AppTheme.colors.primaryContainer), - contentAlignment = Alignment.Center) { + contentAlignment = Alignment.Center + ) { Text( text = participant.displayName.firstOrNull()?.uppercase()?.take(1) ?: "?", style = AppTheme.typography.labelMedium, - color = AppTheme.colors.onPrimaryContainer) + color = AppTheme.colors.onPrimaryContainer + ) } } @@ -273,7 +298,8 @@ private fun QueueItemRow( onDragStopped: () -> Unit, showOptions: Boolean = true, enabled: Boolean = true, - onParticipantClick: (JamParticipant) -> Unit = {}) { + onParticipantClick: (JamParticipant) -> Unit = {} +) { var showMenu by remember { mutableStateOf(false) } MillenniumListTile( @@ -284,8 +310,9 @@ private fun QueueItemRow( leading = { Row( modifier = Modifier - .height(72.dp), - verticalAlignment = Alignment.CenterVertically) { + .height(56.dp), + verticalAlignment = Alignment.CenterVertically + ) { MillenniumIcon( Iconsax.IconsaxDragHandle, contentDescription = if (reorderScope != null) "Reorder" else null, @@ -296,7 +323,8 @@ private fun QueueItemRow( with(reorderScope) { Modifier.draggableHandle( onDragStarted = { onDragStarted() }, - onDragStopped = onDragStopped) + onDragStopped = onDragStopped + ) } } else { Modifier @@ -305,21 +333,24 @@ private fun QueueItemRow( Box( modifier = Modifier - .size(48.dp) + .size(40.dp) .clip(AppTheme.shapes.small) .background(AppTheme.colors.surfaceVariant), - contentAlignment = Alignment.Center) { + contentAlignment = Alignment.Center + ) { if (item.imageUrl != null) { AsyncImage( model = item.imageUrl, contentDescription = null, modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop) + contentScale = ContentScale.Crop + ) } else { Text( text = "${item.originalIndex + 1}", style = AppTheme.typography.labelMedium, - color = AppTheme.colors.onSurfaceVariant) + color = AppTheme.colors.onSurfaceVariant + ) } } } @@ -334,7 +365,8 @@ private fun QueueItemRow( AppTheme.colors.onSecondaryContainer } else { AppTheme.colors.onSurface - }) + } + ) }, subtitle = { Text( @@ -342,13 +374,15 @@ private fun QueueItemRow( style = AppTheme.typography.bodySmall, color = AppTheme.colors.onSurfaceVariant, maxLines = 1, - overflow = TextOverflow.Ellipsis) + overflow = TextOverflow.Ellipsis + ) }, trailing = { Text( text = item.durationLabel, style = AppTheme.typography.bodySmall, - color = AppTheme.colors.onSurfaceVariant) + color = AppTheme.colors.onSurfaceVariant + ) Spacer(modifier = Modifier.width(4.dp)) @@ -359,11 +393,13 @@ private fun QueueItemRow( .clip(CircleShape) .background(AppTheme.colors.surfaceVariant) .clickable { onParticipantClick(participant) }, - contentAlignment = Alignment.Center) { + contentAlignment = Alignment.Center + ) { Text( text = participant.displayName.firstOrNull()?.uppercase()?.take(1) ?: "?", style = AppTheme.typography.labelSmall, - color = AppTheme.colors.onSurfaceVariant) + color = AppTheme.colors.onSurfaceVariant + ) } Spacer(modifier = Modifier.width(4.dp)) } @@ -380,10 +416,12 @@ private fun QueueItemRow( anchor = { MillenniumIconButton( onClick = { showMenu = true }, - style = MillenniumIconButtonStyle.Quiet) { + style = MillenniumIconButtonStyle.Quiet + ) { MillenniumIcon( Iconsax.Iconsax3DotsMore, - contentDescription = "More options") + contentDescription = "More options" + ) } }) } diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/player_queue/QueueSheet.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/player_queue/QueueSheet.kt index 7bd75267..6175b72a 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/player_queue/QueueSheet.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/shell/player_queue/QueueSheet.kt @@ -17,15 +17,17 @@ package dev.krtirtho.spotube.modules.shell.player_queue -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.unit.dp import com.composeunstyled.DrawerPlacement import com.composeunstyled.SheetDetent +import com.composeunstyled.currentWindowWidthBreakpoint import com.composeunstyled.rememberModalBottomSheetState +import org.spotube.millennium.adaptive.WindowBreakpoints import org.spotube.millennium.composables.MillenniumDrawer import org.spotube.millennium.composables.MillenniumDrawerValue import org.spotube.millennium.composables.MillenniumModalBottomSheet @@ -38,13 +40,14 @@ fun QueueSheet( isVisible: Boolean, onDismiss: () -> Unit, modifier: Modifier = Modifier, - content: @Composable () -> Unit) { - BoxWithConstraints(modifier = modifier) { - if (maxWidth >= SlidingSheetBreakpoint) { - EndDrawerSheet(isVisible, onDismiss, content) - } else { - BottomSheetContent(isVisible, onDismiss, content) - } + content: @Composable () -> Unit +) { + val isExpanded = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Expanded + + if (isExpanded) { + EndDrawerSheet(isVisible, onDismiss, content) + } else { + BottomSheetContent(isVisible, onDismiss, content) } } @@ -52,7 +55,8 @@ fun QueueSheet( internal fun EndDrawerSheet( isVisible: Boolean, onDismiss: () -> Unit, - content: @Composable () -> Unit) { + content: @Composable () -> Unit +) { val drawerState = rememberMillenniumDrawerState() LaunchedEffect(isVisible) { drawerState.targetValue = @@ -66,7 +70,10 @@ internal fun EndDrawerSheet( MillenniumDrawer( state = drawerState, 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() } } @@ -75,7 +82,8 @@ internal fun EndDrawerSheet( internal fun BottomSheetContent( isVisible: Boolean, onDismiss: () -> Unit, - content: @Composable () -> Unit) { + content: @Composable () -> Unit +) { if (!isVisible) return val sheetState = rememberModalBottomSheetState(initialDetent = SheetDetent.FullyExpanded) LaunchedEffect(sheetState.bottomSheetState.currentDetent) { @@ -85,7 +93,8 @@ internal fun BottomSheetContent( } MillenniumModalBottomSheet( state = sheetState, - contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp)) { + contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp) + ) { content() } } diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/welcome/pages/HowItWorksPage.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/welcome/pages/HowItWorksPage.kt index 80b6b4eb..c88cff74 100644 --- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/welcome/pages/HowItWorksPage.kt +++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/welcome/pages/HowItWorksPage.kt @@ -1,6 +1,11 @@ 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.MillenniumIcon import org.spotube.millennium.composables.MillenniumIconButton @@ -38,8 +43,8 @@ fun HowItWorksPage( onTogglePlayback: () -> Unit, modifier: Modifier = Modifier ) { - BoxWithConstraints(modifier.fillMaxSize()) { - val wide = maxWidth >= 760.dp + Box(modifier.fillMaxSize()) { + val wide = currentWindowWidthBreakpoint() isAtLeast WindowBreakpoints.Expanded if (wide) { Row( Modifier.fillMaxSize().padding(40.dp),