Skip to content

Commit

Permalink
rememberActivePlaceholderState API (#2263)
Browse files Browse the repository at this point in the history
  • Loading branch information
yschimke authored Jun 10, 2024
1 parent 0badef1 commit 73b1d8c
Show file tree
Hide file tree
Showing 15 changed files with 150 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

@file:OptIn(ExperimentalWearMaterialApi::class)

package com.google.android.horologist.ai.sample.wear.prompt.settings

import androidx.compose.runtime.Composable
Expand All @@ -23,6 +25,7 @@ import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.wear.compose.foundation.lazy.items
import androidx.wear.compose.material.ExperimentalWearMaterialApi
import androidx.wear.compose.material.Text
import androidx.wear.compose.ui.tooling.preview.WearPreviewLargeRound
import com.google.android.horologist.ai.ui.model.ModelInstanceUiModel
Expand All @@ -32,6 +35,7 @@ import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults.It
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults.listTextPadding
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults.padding
import com.google.android.horologist.compose.layout.ScreenScaffold
import com.google.android.horologist.compose.layout.rememberActivePlaceholderState
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
import com.google.android.horologist.compose.material.ResponsiveListHeader
import com.google.android.horologist.compose.material.ToggleChip
Expand Down Expand Up @@ -64,11 +68,17 @@ private fun SettingsScreen(
),
)

val placeholderState = rememberActivePlaceholderState { uiState.models != null }

ScreenScaffold(scrollState = columnState, modifier = modifier) {
ScalingLazyColumn(columnState = columnState) {
if (uiState.models == null) {
items(3) {
PlaceholderChip()
PlaceholderChip(
placeholderState = placeholderState,
icon = false,
secondaryLabel = false,
)
}
} else {
item {
Expand Down
7 changes: 5 additions & 2 deletions auth/composables/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ android {

kotlinOptions {
jvmTarget = "11"
freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlin.RequiresOptIn"
freeCompilerArgs = freeCompilerArgs + "-opt-in=com.google.android.horologist.annotations.ExperimentalHorologistApi"
freeCompilerArgs += listOf(
"-opt-in=com.google.android.horologist.annotations.ExperimentalHorologistApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.wear.compose.material.ExperimentalWearMaterialApi",
)
}

composeOptions {
Expand Down
2 changes: 1 addition & 1 deletion composables/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package com.google.android.horologist.composables {
}

public final class PlaceholderChipKt {
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void PlaceholderChip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.wear.compose.material.ChipColors colors, optional boolean enabled, optional String contentDescription);
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void PlaceholderChip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.wear.compose.material.PlaceholderState placeholderState, optional boolean secondaryLabel, optional boolean icon, optional androidx.wear.compose.material.ChipColors colors, optional boolean enabled, optional String contentDescription);
}

public final class ProgressIndicatorSegment {
Expand Down
10 changes: 5 additions & 5 deletions composables/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ android {

kotlinOptions {
jvmTarget = "11"
freeCompilerArgs = freeCompilerArgs +
listOf(
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=com.google.android.horologist.annotations.ExperimentalHorologistApi",
)
freeCompilerArgs += listOf(
"-opt-in=com.google.android.horologist.annotations.ExperimentalHorologistApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.wear.compose.material.ExperimentalWearMaterialApi",
)
}

composeOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.paint
Expand All @@ -41,10 +40,11 @@ import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.ExperimentalWearMaterialApi
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.PlaceholderDefaults
import androidx.wear.compose.material.PlaceholderState
import androidx.wear.compose.material.placeholder
import androidx.wear.compose.material.placeholderShimmer
import androidx.wear.compose.material.rememberPlaceholderState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.rememberActivePlaceholderState

/**
* A placeholder chip to be displayed while the contents of the [Chip] is being loaded.
Expand All @@ -55,12 +55,13 @@ import com.google.android.horologist.annotations.ExperimentalHorologistApi
public fun PlaceholderChip(
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
placeholderState: PlaceholderState = rememberActivePlaceholderState { false },
secondaryLabel: Boolean = true,
icon: Boolean = true,
colors: ChipColors = ChipDefaults.primaryChipColors(),
enabled: Boolean = false,
contentDescription: String = stringResource(id = R.string.horologist_placeholderchip_content_description),
) {
val chipPlaceholderState = rememberPlaceholderState { false }

Chip(
modifier = modifier
.height(ChipDefaults.Height)
Expand All @@ -70,7 +71,7 @@ public fun PlaceholderChip(
painter = colors.background(enabled = enabled).value,
contentScale = ContentScale.Crop,
)
.placeholderShimmer(chipPlaceholderState)
.placeholderShimmer(placeholderState)
.semantics {
this.contentDescription = contentDescription
},
Expand All @@ -84,38 +85,40 @@ public fun PlaceholderChip(
.clip(RoundedCornerShape(12.dp))
.fillMaxWidth()
.height(12.dp)
.placeholder(chipPlaceholderState),
.placeholder(placeholderState),
)
Spacer(Modifier.size(8.dp))
}
},
secondaryLabel = {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(end = 30.dp)
.clip(RoundedCornerShape(12.dp))
.height(12.dp)
.placeholder(chipPlaceholderState),
)
secondaryLabel = if (secondaryLabel) {
{
Box(
modifier = Modifier
.fillMaxWidth()
.padding(end = 30.dp)
.clip(RoundedCornerShape(12.dp))
.height(12.dp)
.placeholder(placeholderState),
)
}
} else {
null
},
icon = {
Box(
modifier = Modifier
.clip(CircleShape)
.size(ChipDefaults.LargeIconSize)
.placeholder(chipPlaceholderState),
)
icon = if (icon) {
{
Box(
modifier = Modifier
.clip(CircleShape)
.size(ChipDefaults.LargeIconSize)
.placeholder(placeholderState),
)
}
} else {
null
},
colors = PlaceholderDefaults.placeholderChipColors(
originalChipColors = colors,
placeholderState = chipPlaceholderState,
placeholderState = placeholderState,
),
)

if (!chipPlaceholderState.isShowContent) {
LaunchedEffect(chipPlaceholderState) {
chipPlaceholderState.startPlaceholderAnimation()
}
}
}
4 changes: 4 additions & 0 deletions compose-layout/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ package com.google.android.horologist.compose.layout {
method @androidx.compose.runtime.Composable public static void PagerScaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional androidx.compose.foundation.pager.PagerState? pagerState, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
}

public final class PlaceholderKt {
method @androidx.compose.runtime.Composable public static androidx.wear.compose.material.PlaceholderState rememberActivePlaceholderState(kotlin.jvm.functions.Function0<java.lang.Boolean> isContentReady);
}

public final class ResponsiveTimeTextKt {
method @androidx.compose.runtime.Composable public static void ResponsiveTimeText(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.TimeSource timeSource, optional androidx.compose.ui.text.TextStyle timeTextStyle, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit>? startLinearContent, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? startCurvedContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? endLinearContent, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit>? endCurvedContent, optional kotlin.jvm.functions.Function0<kotlin.Unit> textLinearSeparator, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> textCurvedSeparator);
method @androidx.compose.runtime.Composable public static androidx.compose.foundation.layout.PaddingValues responsivePaddingDefaults();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2024 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:OptIn(ExperimentalWearMaterialApi::class, ExperimentalWearFoundationApi::class)

package com.google.android.horologist.compose.layout

import androidx.compose.runtime.Composable
import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
import androidx.wear.compose.foundation.OnFocusChange
import androidx.wear.compose.material.ExperimentalWearMaterialApi
import androidx.wear.compose.material.PlaceholderState
import androidx.wear.compose.material.rememberPlaceholderState
import kotlinx.coroutines.launch

@Composable
fun rememberActivePlaceholderState(isContentReady: () -> Boolean): PlaceholderState {
val placeholderState = rememberPlaceholderState {
isContentReady()
}

OnFocusChange { focused ->
if (focused) {
if (!placeholderState.isShowContent) {
launch {
placeholderState.startPlaceholderAnimation()
}
}
}
}

return placeholderState
}
6 changes: 5 additions & 1 deletion compose-material/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ android {

kotlinOptions {
jvmTarget = "11"
freeCompilerArgs = freeCompilerArgs + "-opt-in=com.google.android.horologist.annotations.ExperimentalHorologistApi"
freeCompilerArgs += listOf(
"-opt-in=com.google.android.horologist.annotations.ExperimentalHorologistApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.wear.compose.material.ExperimentalWearMaterialApi",
)
}

composeOptions {
Expand Down
2 changes: 1 addition & 1 deletion media/ui/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ package com.google.android.horologist.media.ui.components.controls {
package com.google.android.horologist.media.ui.components.display {

public final class LoadingMediaDisplayKt {
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void LoadingMediaDisplay(optional androidx.compose.ui.Modifier modifier);
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void LoadingMediaDisplay(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.PlaceholderState placeholderState);
}

public final class MessageMediaDisplayKt {
Expand Down
1 change: 1 addition & 0 deletions media/ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ android {
com.google.android.horologist.annotations.ExperimentalHorologistApi
kotlin.RequiresOptIn
kotlinx.coroutines.ExperimentalCoroutinesApi
androidx.wear.compose.material.ExperimentalWearMaterialApi
""".trim().split("\\s+".toRegex()).map {
"-opt-in=$it"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.ExperimentalWearMaterialApi
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.PlaceholderState
import androidx.wear.compose.material.placeholder
import androidx.wear.compose.material.placeholderShimmer
import androidx.wear.compose.material.rememberPlaceholderState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.rememberActivePlaceholderState
import com.google.android.horologist.media.ui.components.animated.MarqueeTextMediaDisplay
import com.google.android.horologist.media.ui.util.isLargeScreen

Expand All @@ -48,9 +48,8 @@ import com.google.android.horologist.media.ui.util.isLargeScreen
@Composable
public fun LoadingMediaDisplay(
modifier: Modifier = Modifier,
placeholderState: PlaceholderState = rememberActivePlaceholderState { false },
) {
// Always shimmer on the placeholder pills.
val placeholderState = rememberPlaceholderState(isContentReady = { false })
val isLargeScreen = LocalConfiguration.current.isLargeScreen

Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Expand Down Expand Up @@ -81,8 +80,4 @@ public fun LoadingMediaDisplay(
.height(12.dp),
)
}

if (!placeholderState.isShowContent) {
LaunchedEffect(placeholderState) { placeholderState.startPlaceholderAnimation() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.wear.compose.material.ChipDefaults
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.composables.PlaceholderChip
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
import com.google.android.horologist.compose.layout.rememberActivePlaceholderState
import com.google.android.horologist.compose.material.Button
import com.google.android.horologist.compose.material.Chip
import com.google.android.horologist.images.coil.CoilPaintable
Expand Down Expand Up @@ -64,11 +65,23 @@ public fun PlaylistStreamingScreen(
PlaylistDownloadScreenState.Failed -> EntityScreenState.Failed
}

// TODO This should be folded into SectionedList
val placeholderState =
rememberActivePlaceholderState { entityScreenState !is EntityScreenState.Loading }

EntityScreen(
columnState = columnState,
entityScreenState = entityScreenState,
headerContent = { DefaultEntityScreenHeader(title = playlistName) },
loadingContent = { items(count = 2) { PlaceholderChip(colors = ChipDefaults.secondaryChipColors()) } },
loadingContent = {
items(count = 2) {
PlaceholderChip(
colors = ChipDefaults.secondaryChipColors(),
placeholderState = placeholderState,
secondaryLabel = false,
)
}
},
mediaContent = { mediaUiModel ->
val mediaTitle = mediaUiModel.title ?: defaultMediaTitle
Chip(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.google.android.horologist.composables.Section
import com.google.android.horologist.composables.SectionedList
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
import com.google.android.horologist.compose.layout.ScreenScaffold
import com.google.android.horologist.compose.layout.rememberActivePlaceholderState
import com.google.android.horologist.compose.material.Chip
import com.google.android.horologist.compose.material.Title
import com.google.android.horologist.images.coil.CoilPaintable
Expand Down Expand Up @@ -59,6 +60,10 @@ public fun <T> PlaylistsScreen(
playlistContent: @Composable (playlist: T) -> Unit,
modifier: Modifier = Modifier,
) {
// TODO This should be folded into SectionedList
val placeholderState =
rememberActivePlaceholderState { playlistsScreenState !is PlaylistsScreenState.Loading }

ScreenScaffold(scrollState = columnState) {
SectionedList(
modifier = modifier,
Expand All @@ -85,7 +90,11 @@ public fun <T> PlaylistsScreen(

loading(count = 4) {
Column {
PlaceholderChip(colors = ChipDefaults.secondaryChipColors())
PlaceholderChip(
colors = ChipDefaults.secondaryChipColors(),
placeholderState = placeholderState,
secondaryLabel = false,
)
}
}
}
Expand Down
Loading

0 comments on commit 73b1d8c

Please sign in to comment.