diff --git a/Jetcaster/app/build.gradle.kts b/Jetcaster/app/build.gradle.kts index 80b1348d07..311ea2a67e 100644 --- a/Jetcaster/app/build.gradle.kts +++ b/Jetcaster/app/build.gradle.kts @@ -120,6 +120,7 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.androidx.window) + implementation(libs.androidx.window.core) implementation(libs.accompanist.adaptive) diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt index df4f32ce67..c2057df373 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -66,11 +66,19 @@ import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.Posture +import androidx.compose.material3.adaptive.WindowAdaptiveInfo +import androidx.compose.material3.adaptive.allVerticalHingeBounds +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.adaptive.layout.HingePolicy import androidx.compose.material3.adaptive.layout.PaneAdaptedValue +import androidx.compose.material3.adaptive.layout.PaneScaffoldDirective import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.rememberSupportingPaneScaffoldNavigator +import androidx.compose.material3.adaptive.occludingVerticalHingeBounds +import androidx.compose.material3.adaptive.separatingVerticalHingeBounds import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable @@ -82,16 +90,20 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.window.core.layout.WindowHeightSizeClass +import androidx.window.core.layout.WindowWidthSizeClass import coil.compose.AsyncImage import com.example.jetcaster.R import com.example.jetcaster.core.data.model.CategoryInfo @@ -142,6 +154,77 @@ private fun ThreePaneScaffoldNavigator.isMainPaneHidden(): Boolean { return scaffoldValue[SupportingPaneScaffoldRole.Main] == PaneAdaptedValue.Hidden } +/** + * Copied from `calculatePaneScaffoldDirective()` in [PaneScaffoldDirective], with modifications to + * only show 1 pane horizontally if either width or height size class is compact. + */ +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +fun calculateScaffoldDirective( + windowAdaptiveInfo: WindowAdaptiveInfo, + verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating +): PaneScaffoldDirective { + val maxHorizontalPartitions: Int + val verticalSpacerSize: Dp + if (windowAdaptiveInfo.windowSizeClass.isCompact()) { + // Window width or height is compact. Limit to 1 pane horizontally. + maxHorizontalPartitions = 1 + verticalSpacerSize = 0.dp + } else { + when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) { + WindowWidthSizeClass.COMPACT -> { + maxHorizontalPartitions = 1 + verticalSpacerSize = 0.dp + } + WindowWidthSizeClass.MEDIUM -> { + maxHorizontalPartitions = 1 + verticalSpacerSize = 0.dp + } + else -> { + maxHorizontalPartitions = 2 + verticalSpacerSize = 24.dp + } + } + } + val maxVerticalPartitions: Int + val horizontalSpacerSize: Dp + + if (windowAdaptiveInfo.windowPosture.isTabletop) { + maxVerticalPartitions = 2 + horizontalSpacerSize = 24.dp + } else { + maxVerticalPartitions = 1 + horizontalSpacerSize = 0.dp + } + + val defaultPanePreferredWidth = 360.dp + + return PaneScaffoldDirective( + maxHorizontalPartitions, + verticalSpacerSize, + maxVerticalPartitions, + horizontalSpacerSize, + defaultPanePreferredWidth, + getExcludedVerticalBounds(windowAdaptiveInfo.windowPosture, verticalHingePolicy) + ) +} + +/** + * Copied from `getExcludedVerticalBounds()` in [PaneScaffoldDirective] since it is private. + */ +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +private fun getExcludedVerticalBounds(posture: Posture, hingePolicy: HingePolicy): List { + return when (hingePolicy) { + HingePolicy.AvoidSeparating -> posture.separatingVerticalHingeBounds + HingePolicy.AvoidOccluding -> posture.occludingVerticalHingeBounds + HingePolicy.AlwaysAvoid -> posture.allVerticalHingeBounds + else -> emptyList() + } +} + +private fun androidx.window.core.layout.WindowSizeClass.isCompact(): Boolean = + windowWidthSizeClass == WindowWidthSizeClass.COMPACT || + windowHeightSizeClass == WindowHeightSizeClass.COMPACT + @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable fun MainScreen( @@ -150,7 +233,9 @@ fun MainScreen( viewModel: HomeViewModel = hiltViewModel() ) { val viewState by viewModel.state.collectAsStateWithLifecycle() - val navigator = rememberSupportingPaneScaffoldNavigator() + val navigator = rememberSupportingPaneScaffoldNavigator( + scaffoldDirective = calculateScaffoldDirective(currentWindowAdaptiveInfo()) + ) BackHandler(enabled = navigator.canNavigateBack()) { navigator.navigateBack() } diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt index 44f6217b9f..693be0136a 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt @@ -30,12 +30,12 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.PlaylistAdd import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.rounded.PlayCircleFilled +import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -123,7 +123,7 @@ private fun EpisodeListItemFooter( modifier = Modifier .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = false, radius = 24.dp) + indication = rememberRipple(bounded = false, radius = 24.dp) ) { /* TODO */ } .size(48.dp) .padding(6.dp) diff --git a/Jetcaster/gradle/libs.versions.toml b/Jetcaster/gradle/libs.versions.toml index d22948965f..b5647a547d 100644 --- a/Jetcaster/gradle/libs.versions.toml +++ b/Jetcaster/gradle/libs.versions.toml @@ -10,9 +10,7 @@ androidx-appcompat = "1.6.1" androidx-benchmark = "1.2.3" androidx-benchmark-junit4 = "1.2.3" androidx-compose-bom = "2024.03.00" -androidx-compose-latest = "1.7.0-alpha05" -androidx-compose-material3-adaptive = "1.0.0-alpha09" -androidx-compose-material3-latest = "1.3.0-alpha03" +androidx-compose-material3-adaptive = "1.0.0-alpha10" androidx-constraintlayout = "1.0.1" androidx-corektx = "1.13.0-beta01" androidx-glance = "1.0.0" @@ -87,14 +85,14 @@ androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" } androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout" } androidx-compose-material-iconsExtended = { module = "androidx.compose.material:material-icons-extended" } -androidx-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "androidx-compose-material3-latest" } +androidx-compose-material3 = { module = "androidx.compose.material3:material3" } androidx-compose-material3-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "androidx-compose-material3-adaptive" } androidx-compose-material3-adaptive-layout = { group = "androidx.compose.material3.adaptive", name = "adaptive-layout", version.ref = "androidx-compose-material3-adaptive" } androidx-compose-material3-adaptive-navigation = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation", version.ref = "androidx-compose-material3-adaptive" } androidx-compose-material3-window = { module = "androidx.compose.material3:material3-window-size-class" } androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" } androidx-compose-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata" } -androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "androidx-compose-latest" } +androidx-compose-ui = { module = "androidx.compose.ui:ui"} androidx-compose-ui-googlefonts = { module = "androidx.compose.ui:ui-text-google-fonts" } androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" } androidx-compose-ui-test = { module = "androidx.compose.ui:ui-test" } @@ -134,6 +132,7 @@ androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", androidx-tv-foundation = { module = "androidx.tv:tv-foundation", version.ref = "androidx-tv-foundation" } androidx-tv-material = { module = "androidx.tv:tv-material", version.ref = "androidx-tv-material" } androidx-window = { module = "androidx.window:window", version.ref = "androidx-window" } +androidx-window-core = { module = "androidx.window:window-core", version.ref = "androidx-window" } coil-kt-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } core-jdk-desugaring = { module = "com.android.tools:desugar_jdk_libs", version.ref = "jdkDesugar" } google-android-material = { module = "com.google.android.material:material", version.ref = "material" }