1 /*
<lambda>null2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.compose.animation.scene.demo
18
19 import androidx.compose.foundation.Canvas
20 import androidx.compose.foundation.ExperimentalFoundationApi
21 import androidx.compose.foundation.layout.Arrangement
22 import androidx.compose.foundation.layout.Box
23 import androidx.compose.foundation.layout.Column
24 import androidx.compose.foundation.layout.Row
25 import androidx.compose.foundation.layout.Spacer
26 import androidx.compose.foundation.layout.fillMaxWidth
27 import androidx.compose.foundation.layout.height
28 import androidx.compose.foundation.layout.size
29 import androidx.compose.foundation.pager.HorizontalPager
30 import androidx.compose.foundation.pager.PagerState
31 import androidx.compose.runtime.Composable
32 import androidx.compose.runtime.LaunchedEffect
33 import androidx.compose.runtime.getValue
34 import androidx.compose.runtime.rememberCoroutineScope
35 import androidx.compose.runtime.snapshotFlow
36 import androidx.compose.ui.Alignment
37 import androidx.compose.ui.Modifier
38 import androidx.compose.ui.graphics.Color
39 import androidx.compose.ui.unit.dp
40 import com.android.compose.animation.scene.ContentScope
41 import com.android.compose.animation.scene.animateSceneFloatAsState
42 import com.android.compose.animation.scene.content.state.TransitionState
43 import kotlin.math.ceil
44 import kotlin.math.roundToInt
45 import kotlinx.coroutines.flow.first
46 import kotlinx.coroutines.launch
47
48 @Composable
49 fun ContentScope.QuickSettingsPager(
50 pagerState: PagerState,
51 tiles: List<QuickSettingsTileViewModel>,
52 nRows: Int,
53 nColumns: Int,
54 modifier: Modifier = Modifier,
55 ) {
56 val nTiles = tiles.size
57 val nTilesPerPage = nRows * nColumns
58
59 // Each page must have exactly nRows rows, unless there is a single page.
60 val nRowsTarget =
61 if (nTiles < nTilesPerPage) ceil(nTiles.toFloat() / nColumns).roundToInt() else nRows
62
63 PagerStateResetter(pagerState)
64
65 Column(modifier.noResizeDuringTransitions()) {
66 Box {
67 GridAnchor(isExpanded = true)
68
69 // The grid expansion progress. To make the Tile() implementation more self-contained we
70 // could have an animateSharedFloatAsState, but this will create a new shared value for
71 // each tile that will all have the same value so we instead create a single shared
72 // value here used by all Tiles.
73 val expansionProgress by
74 animateSceneFloatAsState(
75 1f,
76 QuickSettingsGrid.Values.Expansion,
77 canOverflow = false,
78 )
79
80 HorizontalPager(pagerState, Modifier.fillMaxWidth()) { page ->
81 val firstTileIndex = page * nTilesPerPage
82 QuickSettingsGrid(
83 tiles.subList(
84 firstTileIndex,
85 minOf(tiles.size, firstTileIndex + nTilesPerPage),
86 ),
87 nColumns,
88 isExpanded = true,
89 expansionProgress = { expansionProgress },
90 modifier = QuickSettings.Modifiers.HorizontalPadding,
91 nRowsTarget = nRowsTarget,
92 )
93 }
94 }
95
96 val activeColor = QuickSettingsGrid.Colors.ActiveTileBackground
97 val inactiveColor = QuickSettingsGrid.Colors.InactiveTileBackground
98 PagerIndicators(
99 pagerState,
100 activeColor,
101 inactiveColor,
102 Modifier.align(Alignment.CenterHorizontally),
103 )
104 }
105 }
106
nQuickSettingsPagesnull107 fun nQuickSettingsPages(nTiles: Int, nRows: Int, nColumns: Int): Int {
108 val nTilesPerPage = nRows * nColumns
109 return ceil(nTiles.toFloat() / nTilesPerPage).roundToInt()
110 }
111
112 /**
113 * The invisible anchor used to anchor the movement of the brightness slider and height of the tiles
114 * that are not shared and appearing.
115 */
116 @Composable
GridAnchornull117 fun ContentScope.GridAnchor(isExpanded: Boolean, modifier: Modifier = Modifier) {
118 // The width of this anchor does not matter, but the height is used to anchor the size of the
119 // (dis)appearing tiles.
120 Spacer(modifier.element(QuickSettingsGrid.Elements.GridAnchor).height(tileHeight(isExpanded)))
121 }
122
123 @Composable
124 @OptIn(ExperimentalFoundationApi::class)
ContentScopenull125 private fun ContentScope.PagerIndicators(
126 pagerState: PagerState,
127 activeColor: Color,
128 inactiveColor: Color,
129 modifier: Modifier = Modifier,
130 ) {
131 Row(
132 modifier.element(QuickSettings.Elements.PagerIndicators).height(48.dp),
133 horizontalArrangement = Arrangement.spacedBy(4.dp),
134 verticalAlignment = Alignment.CenterVertically,
135 ) {
136 repeat(pagerState.pageCount) { i ->
137 Canvas(modifier = Modifier.size(6.dp)) {
138 val color = if (pagerState.currentPage == i) activeColor else inactiveColor
139 drawCircle(color)
140 }
141 }
142 }
143 }
144
145 /**
146 * Snaps [pagerState] to the first page if we are transitioning from QS => Shade and the transition
147 * goes past [QuickSettings.TransitionToShadeCommittedProgress].
148 */
149 @Composable
150 @OptIn(ExperimentalFoundationApi::class)
ContentScopenull151 private fun ContentScope.PagerStateResetter(pagerState: PagerState) {
152 if (
153 pagerState.currentPage != 0 &&
154 layoutState.isTransitioning(from = Scenes.QuickSettings, to = Scenes.Shade)
155 ) {
156 val transition = layoutState.transitionState as TransitionState.Transition
157 val scrollScope = rememberCoroutineScope()
158 LaunchedEffect(transition, pagerState) {
159 // Wait for the progress to reach TransitionToShadeCommittedProgress.
160 snapshotFlow { transition.progress }
161 .first { it >= QuickSettings.TransitionToShadeCommittedProgress }
162
163 // Scroll to the first page.
164 scrollScope.launch { pagerState.scrollToPage(0) }
165 }
166 }
167 }
168