diff --git a/Jetcaster/app/build.gradle b/Jetcaster/app/build.gradle index a3d24acdbb..f0e3b8bf28 100644 --- a/Jetcaster/app/build.gradle +++ b/Jetcaster/app/build.gradle @@ -109,6 +109,8 @@ dependencies { implementation Libs.Accompanist.coil + implementation Libs.Accompanist.insets + implementation Libs.OkHttp.okhttp implementation Libs.OkHttp.logging diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt index 9cceb6758b..881e241727 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt @@ -21,7 +21,7 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.platform.setContent import androidx.core.view.WindowCompat import com.example.jetcaster.ui.theme.JetcasterTheme -import com.example.jetcaster.util.ProvideDisplayInsets +import dev.chrisbanes.accompanist.insets.ProvideWindowInsets class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -32,7 +32,7 @@ class MainActivity : ComponentActivity() { setContent { JetcasterTheme { - ProvideDisplayInsets { + ProvideWindowInsets { JetcasterApp() } } 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 9fc5610c9f..40aa9bfa29 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 @@ -17,7 +17,6 @@ package com.example.jetcaster.ui.home import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -33,22 +32,24 @@ import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredHeightIn import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface import androidx.compose.material.Tab import androidx.compose.material.TabConstants.defaultTabIndicatorOffset import androidx.compose.material.TabPosition import androidx.compose.material.TabRow +import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.Search import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedTask +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Providers import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -76,9 +77,9 @@ import com.example.jetcaster.util.ToggleFollowPodcastIconButton import com.example.jetcaster.util.constrastAgainst import com.example.jetcaster.util.quantityStringResource import com.example.jetcaster.util.rememberDominantColorState -import com.example.jetcaster.util.statusBarsHeight import com.example.jetcaster.util.verticalGradientScrim import dev.chrisbanes.accompanist.coil.CoilImage +import dev.chrisbanes.accompanist.insets.statusBarsHeight import java.time.Duration import java.time.LocalDateTime import java.time.OffsetDateTime @@ -119,7 +120,7 @@ fun HomeAppBar( }, backgroundColor = backgroundColor, actions = { - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { IconButton( onClick = { /* TODO: Open search */ }, icon = { Icon(Icons.Filled.Search) } @@ -170,7 +171,7 @@ fun HomeContent( // When the selected image url changes, call updateColorsFromImageUrl() or reset() if (selectedImageUrl != null) { - LaunchedTask(selectedImageUrl) { + LaunchedEffect(selectedImageUrl) { dominantColorState.updateColorsFromImageUrl(selectedImageUrl) } } else { @@ -338,17 +339,15 @@ private fun FollowedPodcastCarouselItem( ) } - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - ToggleFollowPodcastIconButton( - onClick = onUnfollowedClick, - isFollowed = true, /* All podcasts are followed in this feed */ - modifier = Modifier.align(Alignment.BottomEnd) - ) - } + ToggleFollowPodcastIconButton( + onClick = onUnfollowedClick, + isFollowed = true, /* All podcasts are followed in this feed */ + modifier = Modifier.align(Alignment.BottomEnd) + ) } if (lastEpisodeDate != null) { - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = lastUpdated(lastEpisodeDate), style = MaterialTheme.typography.caption, diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt index be277cb0db..c30b226b7c 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt @@ -16,9 +16,7 @@ package com.example.jetcaster.ui.home.category -import androidx.compose.foundation.AmbientContentColor import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -32,21 +30,23 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth -import androidx.compose.foundation.lazy.ExperimentalLazyDsl import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRowForIndexed -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.AmbientContentColor +import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.PlaylistAdd import androidx.compose.material.icons.rounded.PlayCircleFilled import androidx.compose.material.ripple.RippleIndication import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -73,7 +73,6 @@ import dev.chrisbanes.accompanist.coil.CoilImage import java.time.format.DateTimeFormatter import java.time.format.FormatStyle -@OptIn(ExperimentalLazyDsl::class) @Composable fun PodcastCategory( categoryId: Long, @@ -166,29 +165,27 @@ fun EpisodeListItem( ) } - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Text( - text = episode.title, - maxLines = 2, - style = MaterialTheme.typography.subtitle1, - modifier = Modifier.constrainAs(episodeTitle) { - linkTo( - start = parent.start, - end = image.start, - startMargin = Keyline1, - endMargin = 16.dp, - bias = 0f - ) - top.linkTo(parent.top, 16.dp) + Text( + text = episode.title, + maxLines = 2, + style = MaterialTheme.typography.subtitle1, + modifier = Modifier.constrainAs(episodeTitle) { + linkTo( + start = parent.start, + end = image.start, + startMargin = Keyline1, + endMargin = 16.dp, + bias = 0f + ) + top.linkTo(parent.top, 16.dp) - width = Dimension.preferredWrapContent - } - ) - } + width = Dimension.preferredWrapContent + } + ) val titleImageBarrier = createBottomBarrier(podcastTitle, image) - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = podcast.title, maxLines = 2, @@ -208,25 +205,23 @@ fun EpisodeListItem( ) } - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Image( - asset = Icons.Rounded.PlayCircleFilled, - contentScale = ContentScale.Fit, - colorFilter = ColorFilter.tint(AmbientContentColor.current), - modifier = Modifier - .clickable(indication = RippleIndication(bounded = false, radius = 24.dp)) { - /* TODO */ - } - .preferredSize(36.dp) - .constrainAs(playIcon) { - start.linkTo(parent.start, Keyline1) - top.linkTo(titleImageBarrier, margin = 16.dp) - bottom.linkTo(parent.bottom, 16.dp) - } - ) - } + Image( + asset = Icons.Rounded.PlayCircleFilled, + contentScale = ContentScale.Fit, + colorFilter = ColorFilter.tint(AmbientContentColor.current), + modifier = Modifier + .clickable(indication = RippleIndication(bounded = false, radius = 24.dp)) { + /* TODO */ + } + .preferredSize(36.dp) + .constrainAs(playIcon) { + start.linkTo(parent.start, Keyline1) + top.linkTo(titleImageBarrier, margin = 16.dp) + bottom.linkTo(parent.bottom, 16.dp) + } + ) - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = when { episode.duration != null -> { @@ -326,24 +321,20 @@ private fun TopPodcastRowItem( ) } - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - ToggleFollowPodcastIconButton( - onClick = onToggleFollowClicked, - isFollowed = isFollowed, - modifier = Modifier.align(Alignment.BottomEnd) - ) - } - } - - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Text( - text = podcastTitle, - style = MaterialTheme.typography.body2, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(top = 8.dp).fillMaxWidth() + ToggleFollowPodcastIconButton( + onClick = onToggleFollowClicked, + isFollowed = isFollowed, + modifier = Modifier.align(Alignment.BottomEnd) ) } + + Text( + text = podcastTitle, + style = MaterialTheme.typography.body2, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 8.dp).fillMaxWidth() + ) } } diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt index 29da31062a..802996cc47 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt @@ -22,8 +22,6 @@ import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.TransitionDefinition import androidx.compose.animation.core.transitionDefinition import androidx.compose.animation.core.tween -import androidx.compose.foundation.Text -import androidx.compose.foundation.contentColor import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -35,6 +33,7 @@ import androidx.compose.material.ScrollableTabRow import androidx.compose.material.Surface import androidx.compose.material.Tab import androidx.compose.material.TabPosition +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.emptyContent diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt index 7de9a3843d..6f0685d496 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt @@ -17,10 +17,10 @@ package com.example.jetcaster.util import androidx.compose.animation.animate -import androidx.compose.foundation.AmbientContentColor import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentColor +import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme @@ -52,7 +52,7 @@ fun ToggleFollowPodcastIconButton( tint = animate( when { isFollowed -> AmbientContentColor.current - else -> AmbientEmphasisLevels.current.high.applyEmphasis(Color.Black) + else -> Color.Black.copy(alpha = ContentAlpha.high) } ), modifier = Modifier diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Insets.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Insets.kt deleted file mode 100644 index c75d9ee4f2..0000000000 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Insets.kt +++ /dev/null @@ -1,610 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress("NOTHING_TO_INLINE", "unused") - -package com.example.jetcaster.util - -import android.view.View -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Providers -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.onCommit -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.runtime.staticAmbientOf -import androidx.compose.ui.LayoutModifier -import androidx.compose.ui.Measurable -import androidx.compose.ui.MeasureScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.layout.IntrinsicMeasurable -import androidx.compose.ui.layout.IntrinsicMeasureScope -import androidx.compose.ui.platform.DensityAmbient -import androidx.compose.ui.platform.LayoutDirectionAmbient -import androidx.compose.ui.platform.ViewAmbient -import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.offset -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.WindowInsetsCompat.Type - -/** - * Taken from https://goo.gle/compose-insets. Requires androidx.core:core v1.5.0-alpha02+ - */ - -/** - * Main holder of our inset values. - */ -@Stable -class DisplayInsets { - /** - * Inset values which match [WindowInsetsCompat.Type.systemBars] - */ - val systemBars = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.systemGestures] - */ - val systemGestures = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.navigationBars] - */ - val navigationBars = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.statusBars] - */ - val statusBars = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.ime] - */ - val ime = Insets() -} - -@Stable -class Insets { - /** - * The left dimension of these insets in pixels. - */ - var left by mutableStateOf(0) - internal set - - /** - * The top dimension of these insets in pixels. - */ - var top by mutableStateOf(0) - internal set - - /** - * The right dimension of these insets in pixels. - */ - var right by mutableStateOf(0) - internal set - - /** - * The bottom dimension of these insets in pixels. - */ - var bottom by mutableStateOf(0) - internal set - - /** - * Whether the insets are currently visible. - */ - var isVisible by mutableStateOf(true) - internal set -} - -val InsetsAmbient = staticAmbientOf() - -/** - * Applies any [WindowInsetsCompat] values to [InsetsAmbient], which are then available - * within [content]. - * - * @param consumeWindowInsets Whether to consume any [WindowInsetsCompat]s which are dispatched to - * the host view. Defaults to `true`. - */ -@Composable -fun ProvideDisplayInsets( - consumeWindowInsets: Boolean = true, - content: @Composable () -> Unit -) { - val view = ViewAmbient.current - - val displayInsets = remember { DisplayInsets() } - - onCommit(view) { - ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets -> - displayInsets.systemBars.updateFrom(windowInsets, Type.systemBars()) - displayInsets.systemGestures.updateFrom(windowInsets, Type.systemGestures()) - displayInsets.statusBars.updateFrom(windowInsets, Type.statusBars()) - displayInsets.navigationBars.updateFrom(windowInsets, Type.navigationBars()) - displayInsets.ime.updateFrom(windowInsets, Type.ime()) - - if (consumeWindowInsets) WindowInsetsCompat.CONSUMED else windowInsets - } - - // Add an OnAttachStateChangeListener to request an inset pass each time we're attached - // to the window - val attachListener = object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) = v.requestApplyInsets() - override fun onViewDetachedFromWindow(v: View) = Unit - } - view.addOnAttachStateChangeListener(attachListener) - - if (view.isAttachedToWindow) { - // If the view is already attached, we can request an inset pass now - view.requestApplyInsets() - } - - onDispose { - view.removeOnAttachStateChangeListener(attachListener) - } - } - - Providers(InsetsAmbient provides displayInsets) { - content() - } -} - -/** - * Updates our mutable state backed [Insets] from an Android system insets. - */ -private fun Insets.updateFrom(windowInsets: WindowInsetsCompat, type: Int) { - val insets = windowInsets.getInsets(type) - left = insets.left - top = insets.top - right = insets.right - bottom = insets.bottom - - isVisible = windowInsets.isVisible(type) -} - -/** - * Selectively apply additional space which matches the width/height of any system bars present - * on the respective edges of the screen. - * - * @param enabled Whether to apply padding using the system bars dimensions on the respective edges. - * Defaults to `true`. - */ -fun Modifier.systemBarsPadding(enabled: Boolean = true) = composed { - insetsPadding( - insets = InsetsAmbient.current.systemBars, - left = enabled, - top = enabled, - right = enabled, - bottom = enabled - ) -} - -/** - * Apply additional space which matches the height of the status bars height along the top edge - * of the content. - */ -fun Modifier.statusBarsPadding() = composed { - insetsPadding(insets = InsetsAmbient.current.statusBars, top = true) -} - -/** - * Apply additional space which matches the height of the navigation bars height - * along the [bottom] edge of the content, and additional space which matches the width of - * the navigation bars on the respective [left] and [right] edges. - * - * @param bottom Whether to apply padding to the bottom edge, which matches the navigation bars - * height (if present) at the bottom edge of the screen. Defaults to `true`. - * @param left Whether to apply padding to the left edge, which matches the navigation bars width - * (if present) on the left edge of the screen. Defaults to `true`. - * @param right Whether to apply padding to the right edge, which matches the navigation bars width - * (if present) on the right edge of the screen. Defaults to `true`. - */ -fun Modifier.navigationBarsPadding( - bottom: Boolean = true, - left: Boolean = true, - right: Boolean = true -) = composed { - insetsPadding( - insets = InsetsAmbient.current.navigationBars, - left = left, - right = right, - bottom = bottom - ) -} - -/** - * Declare the height of the content to match the height of the status bars exactly. - * - * This is very handy when used with `Spacer` to push content below the status bars: - * ``` - * Column { - * Spacer(Modifier.statusBarHeight()) - * - * // Content to be drawn below status bars (y-axis) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the status bars: - * ``` - * Spacer( - * Modifier.statusBarHeight() - * .fillMaxWidth() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param additional Any additional height to add to the status bars size. - */ -fun Modifier.statusBarsHeight(additional: Dp = 0.dp) = composed { - InsetsSizeModifier( - insets = InsetsAmbient.current.statusBars, - heightSide = VerticalSide.Top, - additionalHeight = additional - ) -} - -/** - * Declare the height of the content to match the height of the status bars exactly. - * - * This is very handy when used with `Spacer` to push content below the status bars: - * ``` - * Column { - * Spacer(Modifier.statusBarHeight()) - * - * // Content to be drawn below status bars (y-axis) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the status bars: - * ``` - * Spacer( - * Modifier.statusBarHeight() - * .fillMaxWidth() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - */ -inline fun Modifier.statusBarsHeight() = statusBarsHeightPlus(0.dp) - -/** - * Declare the height of the content to match the height of the status bars, plus some - * additional height passed in via [additional]. - * - * As an example, this could be used with `Spacer` to push content below the status bar - * and app bars: - * - * ``` - * Column { - * Spacer(Modifier.statusBarHeightPlus(56.dp)) - * - * // Content to be drawn below status bars and app bar (y-axis) - * } - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param additional Any additional height to add to the status bars size. - */ -fun Modifier.statusBarsHeightPlus(additional: Dp) = composed { - InsetsSizeModifier( - insets = InsetsAmbient.current.statusBars, - heightSide = VerticalSide.Top, - additionalHeight = additional - ) -} - -/** - * Declare the preferred height of the content to match the height of the navigation bars when - * present at the bottom of the screen. - * - * This is very handy when used with `Spacer` to push content below the navigation bars: - * ``` - * Column { - * // Content to be drawn above status bars (y-axis) - * Spacer(Modifier.navigationBarHeight()) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the navigation bars: - * ``` - * Spacer( - * Modifier.navigationBarHeight() - * .fillMaxWidth() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - */ -inline fun Modifier.navigationBarsHeight() = navigationBarsHeightPlus(0.dp) - -/** - * Declare the height of the content to match the height of the navigation bars, plus some - * additional height passed in via [additional] - * - * As an example, this could be used with `Spacer` to push content above the navigation bar - * and bottom app bars: - * - * ``` - * Column { - * // Content to be drawn above navigation bars and bottom app bar (y-axis) - * - * Spacer(Modifier.statusBarHeightPlus(48.dp)) - * } - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param additional Any additional height to add to the status bars size. - */ -fun Modifier.navigationBarsHeightPlus(additional: Dp) = composed { - InsetsSizeModifier( - insets = InsetsAmbient.current.navigationBars, - heightSide = VerticalSide.Bottom, - additionalHeight = additional - ) -} - -enum class HorizontalSide { Left, Right } -enum class VerticalSide { Top, Bottom } - -/** - * Declare the preferred width of the content to match the width of the navigation bars, - * on the given [side]. - * - * This is very handy when used with `Spacer` to push content inside from any vertical - * navigation bars (typically when the device is in landscape): - * ``` - * Row { - * Spacer(Modifier.navigationBarWidth(HorizontalSide.Left)) - * - * // Content to be inside the navigation bars (x-axis) - * - * Spacer(Modifier.navigationBarWidth(HorizontalSide.Right)) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the navigation bars: - * ``` - * Spacer( - * Modifier.navigationBarWidth(HorizontalSide.Left) - * .fillMaxHeight() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param side The navigation bar side to use as the source for the width. - */ -inline fun Modifier.navigationBarWidth(side: HorizontalSide) = navigationBarsWidthPlus(side, 0.dp) - -/** - * Declare the preferred width of the content to match the width of the navigation bars, - * on the given [side], plus additional width passed in via [additional]. - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param side The navigation bar side to use as the source for the width. - * @param additional Any additional width to add to the status bars size. - */ -fun Modifier.navigationBarsWidthPlus( - side: HorizontalSide, - additional: Dp -) = composed { - InsetsSizeModifier( - insets = InsetsAmbient.current.navigationBars, - widthSide = side, - additionalWidth = additional - ) -} - -/** - * Returns the current insets converted into a [InnerPadding]. - * - * @param start Whether to apply the inset on the start dimension. - * @param top Whether to apply the inset on the top dimension. - * @param end Whether to apply the inset on the end dimension. - * @param bottom Whether to apply the inset on the bottom dimension. - */ -@Composable -fun Insets.toInnerPadding( - start: Boolean = true, - top: Boolean = true, - end: Boolean = true, - bottom: Boolean = true -): PaddingValues = with(DensityAmbient.current) { - val layoutDirection = LayoutDirectionAmbient.current - PaddingValues( - start = when { - start && layoutDirection == LayoutDirection.Ltr -> this@toInnerPadding.left.toDp() - start && layoutDirection == LayoutDirection.Rtl -> this@toInnerPadding.right.toDp() - else -> 0.dp - }, - top = when { - top -> this@toInnerPadding.top.toDp() - else -> 0.dp - }, - end = when { - end && layoutDirection == LayoutDirection.Ltr -> this@toInnerPadding.right.toDp() - end && layoutDirection == LayoutDirection.Rtl -> this@toInnerPadding.left.toDp() - else -> 0.dp - }, - bottom = when { - bottom -> this@toInnerPadding.bottom.toDp() - else -> 0.dp - } - ) -} - -/** - * Allows conditional setting of [insets] on each dimension. - */ -private inline fun Modifier.insetsPadding( - insets: Insets, - left: Boolean = false, - top: Boolean = false, - right: Boolean = false, - bottom: Boolean = false -) = this then InsetsPaddingModifier(insets, left, top, right, bottom) - -private data class InsetsPaddingModifier( - private val insets: Insets, - private val applyLeft: Boolean = false, - private val applyTop: Boolean = false, - private val applyRight: Boolean = false, - private val applyBottom: Boolean = false -) : LayoutModifier { - override fun MeasureScope.measure( - measurable: Measurable, - constraints: Constraints - ): MeasureScope.MeasureResult { - val left = if (applyLeft) insets.left else 0 - val top = if (applyTop) insets.top else 0 - val right = if (applyRight) insets.right else 0 - val bottom = if (applyBottom) insets.bottom else 0 - val horizontal = left + right - val vertical = top + bottom - - val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) - - val width = (placeable.width + horizontal) - .coerceIn(constraints.minWidth, constraints.maxWidth) - val height = (placeable.height + vertical) - .coerceIn(constraints.minHeight, constraints.maxHeight) - return layout(width, height) { - placeable.place(left, top) - } - } -} - -private data class InsetsSizeModifier( - private val insets: Insets, - private val widthSide: HorizontalSide? = null, - private val additionalWidth: Dp = 0.dp, - private val heightSide: VerticalSide? = null, - private val additionalHeight: Dp = 0.dp -) : LayoutModifier { - private val Density.targetConstraints: Constraints - get() { - val additionalWidthPx = additionalWidth.toIntPx() - val additionalHeightPx = additionalHeight.toIntPx() - return Constraints( - minWidth = additionalWidthPx + when (widthSide) { - HorizontalSide.Left -> insets.left - HorizontalSide.Right -> insets.right - null -> 0 - }, - minHeight = additionalHeightPx + when (heightSide) { - VerticalSide.Top -> insets.top - VerticalSide.Bottom -> insets.bottom - null -> 0 - }, - maxWidth = when (widthSide) { - HorizontalSide.Left -> insets.left + additionalWidthPx - HorizontalSide.Right -> insets.right + additionalWidthPx - null -> Constraints.Infinity - }, - maxHeight = when (heightSide) { - VerticalSide.Top -> insets.top + additionalHeightPx - VerticalSide.Bottom -> insets.bottom + additionalHeightPx - null -> Constraints.Infinity - } - ) - } - - override fun MeasureScope.measure( - measurable: Measurable, - constraints: Constraints - ): MeasureScope.MeasureResult { - val wrappedConstraints = targetConstraints.let { targetConstraints -> - val resolvedMinWidth = if (widthSide != null) { - targetConstraints.minWidth - } else { - constraints.minWidth.coerceAtMost(targetConstraints.maxWidth) - } - val resolvedMaxWidth = if (widthSide != null) { - targetConstraints.maxWidth - } else { - constraints.maxWidth.coerceAtLeast(targetConstraints.minWidth) - } - val resolvedMinHeight = if (heightSide != null) { - targetConstraints.minHeight - } else { - constraints.minHeight.coerceAtMost(targetConstraints.maxHeight) - } - val resolvedMaxHeight = if (heightSide != null) { - targetConstraints.maxHeight - } else { - constraints.maxHeight.coerceAtLeast(targetConstraints.minHeight) - } - Constraints( - resolvedMinWidth, - resolvedMaxWidth, - resolvedMinHeight, - resolvedMaxHeight - ) - } - val placeable = measurable.measure(wrappedConstraints) - return layout(placeable.width, placeable.height) { - placeable.place(0, 0) - } - } - - override fun IntrinsicMeasureScope.minIntrinsicWidth( - measurable: IntrinsicMeasurable, - height: Int - ) = measurable.minIntrinsicWidth(height).let { - val constraints = targetConstraints - it.coerceIn(constraints.minWidth, constraints.maxWidth) - } - - override fun IntrinsicMeasureScope.maxIntrinsicWidth( - measurable: IntrinsicMeasurable, - height: Int - ) = measurable.maxIntrinsicWidth(height).let { - val constraints = targetConstraints - it.coerceIn(constraints.minWidth, constraints.maxWidth) - } - - override fun IntrinsicMeasureScope.minIntrinsicHeight( - measurable: IntrinsicMeasurable, - width: Int - ) = measurable.minIntrinsicHeight(width).let { - val constraints = targetConstraints - it.coerceIn(constraints.minHeight, constraints.maxHeight) - } - - override fun IntrinsicMeasureScope.maxIntrinsicHeight( - measurable: IntrinsicMeasurable, - width: Int - ) = measurable.maxIntrinsicHeight(width).let { - val constraints = targetConstraints - it.coerceIn(constraints.minHeight, constraints.maxHeight) - } -} diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Pager.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Pager.kt index c2cf1d42c6..08a1fd72a8 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Pager.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Pager.kt @@ -31,11 +31,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.ui.Alignment -import androidx.compose.ui.Layout -import androidx.compose.ui.Measurable import androidx.compose.ui.Modifier -import androidx.compose.ui.ParentDataModifier import androidx.compose.ui.gesture.scrollorientationlocking.Orientation +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.ParentDataModifier import androidx.compose.ui.unit.Density import kotlin.math.roundToInt diff --git a/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt b/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt index dc674ad645..8b275cf407 100644 --- a/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt +++ b/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt @@ -29,8 +29,9 @@ object Libs { const val material = "com.google.android.material:material:1.1.0" object Accompanist { - private const val version = "0.3.2" + private const val version = "0.3.3.compose-6953474-SNAPSHOT" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" + const val insets = "dev.chrisbanes.accompanist:accompanist-insets:$version" } object Kotlin { @@ -41,7 +42,7 @@ object Libs { } object Coroutines { - private const val version = "1.3.9" + private const val version = "1.4.0" const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version" const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version" const val test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version" @@ -54,15 +55,15 @@ object Libs { } object AndroidX { - const val appcompat = "androidx.appcompat:appcompat:1.2.0-rc01" + const val appcompat = "androidx.appcompat:appcompat:1.2.0" const val palette = "androidx.palette:palette:1.0.0" const val core = "androidx.core:core:1.5.0-alpha04" const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha04" object Compose { - private const val snapshot = "" - const val version = "1.0.0-alpha06" + private const val snapshot = "6953474" + const val version = "1.0.0-SNAPSHOT" @get:JvmStatic val snapshotUrl: String @@ -77,7 +78,6 @@ object Libs { const val materialIconsExtended = "androidx.compose.material:material-icons-extended:${version}" const val tooling = "androidx.ui:ui-tooling:${version}" - const val test = "androidx.ui:ui-test:${version}" } object Test {