1 /*
<lambda>null2  * Copyright 2019 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 androidx.compose.material.demos
18 
19 import android.os.Bundle
20 import androidx.activity.ComponentActivity
21 import androidx.activity.compose.setContent
22 import androidx.compose.animation.core.FastOutSlowInEasing
23 import androidx.compose.foundation.background
24 import androidx.compose.foundation.layout.Box
25 import androidx.compose.foundation.layout.fillMaxWidth
26 import androidx.compose.foundation.layout.height
27 import androidx.compose.foundation.layout.padding
28 import androidx.compose.foundation.lazy.LazyColumn
29 import androidx.compose.foundation.lazy.LazyListState
30 import androidx.compose.foundation.lazy.rememberLazyListState
31 import androidx.compose.foundation.shape.CircleShape
32 import androidx.compose.foundation.shape.RoundedCornerShape
33 import androidx.compose.material.BottomAppBar
34 import androidx.compose.material.Colors
35 import androidx.compose.material.ExtendedFloatingActionButton
36 import androidx.compose.material.FabPosition
37 import androidx.compose.material.MaterialTheme
38 import androidx.compose.material.Scaffold
39 import androidx.compose.material.Text
40 import androidx.compose.material.TopAppBar
41 import androidx.compose.material.lightColors
42 import androidx.compose.runtime.Composable
43 import androidx.compose.runtime.LaunchedEffect
44 import androidx.compose.runtime.MutableState
45 import androidx.compose.runtime.mutableFloatStateOf
46 import androidx.compose.runtime.snapshotFlow
47 import androidx.compose.ui.Alignment
48 import androidx.compose.ui.Modifier
49 import androidx.compose.ui.graphics.Color
50 import androidx.compose.ui.graphics.lerp
51 import androidx.compose.ui.graphics.toArgb
52 import androidx.compose.ui.unit.dp
53 import kotlinx.coroutines.flow.collect
54 
55 /**
56  * Demo activity that animates the primary, secondary, and background colours in the [MaterialTheme]
57  * as the user scrolls. This has the effect of going from a 'light' theme to a 'dark' theme.
58  */
59 class DynamicThemeActivity : ComponentActivity() {
60     private val scrollFraction = mutableFloatStateOf(0f)
61 
62     override fun onCreate(savedInstanceState: Bundle?) {
63         super.onCreate(savedInstanceState)
64         setContent {
65             val palette = interpolateTheme(scrollFraction.floatValue)
66             val darkenedPrimary = palette.darkenedPrimary
67             @Suppress("DEPRECATION")
68             window.statusBarColor = darkenedPrimary
69             @Suppress("DEPRECATION")
70             window.navigationBarColor = darkenedPrimary
71 
72             DynamicThemeApp(scrollFraction, palette)
73         }
74     }
75 
76     private val Colors.darkenedPrimary: Int
77         get() {
78             return with(primary) {
79                     copy(red = red * 0.75f, green = green * 0.75f, blue = blue * 0.75f)
80                 }
81                 .toArgb()
82         }
83 }
84 
85 private typealias ScrollFraction = MutableState<Float>
86 
87 private val LazyListState.scrollOffset: Float
88     get() {
89         val total = layoutInfo.totalItemsCount
90         if (total == 0 || layoutInfo.visibleItemsInfo.isEmpty()) {
91             return 0f
92         } else {
93             val itemSize = layoutInfo.visibleItemsInfo.first().size
94             val currentOffset = firstVisibleItemIndex * itemSize + firstVisibleItemScrollOffset
95             return (currentOffset.toFloat() / (total * itemSize)).coerceIn(0f, 1f)
96         }
97     }
98 
99 @Composable
DynamicThemeAppnull100 private fun DynamicThemeApp(scrollFraction: ScrollFraction, palette: Colors) {
101     MaterialTheme(palette) {
102         val state = rememberLazyListState()
103         LaunchedEffect(state) {
104             snapshotFlow { state.scrollOffset }.collect { scrollFraction.value = it }
105         }
106         Scaffold(
107             topBar = { TopAppBar({ Text("Scroll down!") }) },
108             bottomBar = { BottomAppBar(cutoutShape = CircleShape) {} },
109             floatingActionButton = { Fab(scrollFraction) },
110             floatingActionButtonPosition = FabPosition.Center,
111             isFloatingActionButtonDocked = true,
112             content = { innerPadding ->
113                 LazyColumn(state = state, contentPadding = innerPadding) { items(20) { Card(it) } }
114             }
115         )
116     }
117 }
118 
119 @Composable
Fabnull120 private fun Fab(scrollFraction: ScrollFraction) {
121     val fabText = emojiForScrollFraction(scrollFraction.value)
122     ExtendedFloatingActionButton(
123         text = { Text(fabText, style = MaterialTheme.typography.h5) },
124         onClick = {}
125     )
126 }
127 
128 @Composable
Cardnull129 private fun Card(index: Int) {
130     val shapeColor = lerp(Color(0xFF303030), Color.White, index / 19f)
131     val textColor = lerp(Color.White, Color(0xFF303030), index / 19f)
132     // TODO: ideally this would be a Card but currently Surface consumes every
133     // colour from the Material theme to work out text colour, so we end up doing a
134     // large amount of work here when the top level theme changes
135     Box(
136         Modifier.padding(25.dp)
137             .fillMaxWidth()
138             .height(150.dp)
139             .background(shapeColor, RoundedCornerShape(10.dp)),
140         contentAlignment = Alignment.Center
141     ) {
142         Text("Card ${index + 1}", color = textColor)
143     }
144 }
145 
interpolateThemenull146 private fun interpolateTheme(fraction: Float): Colors {
147     val interpolatedFraction = FastOutSlowInEasing.transform(fraction)
148 
149     val primary = lerp(Color(0xFF6200EE), Color(0xFF303030), interpolatedFraction)
150     val secondary = lerp(Color(0xFF03DAC6), Color(0xFFBB86FC), interpolatedFraction)
151     val background = lerp(Color.White, Color(0xFF121212), interpolatedFraction)
152 
153     return lightColors(primary = primary, secondary = secondary, background = background)
154 }
155 
156 /** 'Animate' the emoji in the FAB from 'sun' to 'moon' as we darken the theme */
emojiForScrollFractionnull157 private fun emojiForScrollFraction(fraction: Float): String {
158     return when {
159         // Sun
160         fraction < 1 / 7f -> "\u2600"
161         // Sun behind small cloud
162         fraction < 2 / 7f -> "\uD83C\uDF24"
163         // Sun behind cloud
164         fraction < 3 / 7f -> "\uD83C\uDF25"
165         // Cloud
166         fraction < 4 / 7f -> "\u2601"
167         // Cloud with rain
168         fraction < 5 / 7f -> "\uD83C\uDF27"
169         // Cloud with lightning
170         fraction < 6 / 7f -> "\uD83C\uDF29"
171         // Moon
172         else -> "\uD83C\uDF15"
173     }
174 }
175