16
16
17
17
package com.example.jetcaster.ui.home
18
18
19
+ import androidx.compose.foundation.Box
19
20
import androidx.compose.foundation.Icon
20
21
import androidx.compose.foundation.Text
21
22
import androidx.compose.foundation.background
@@ -47,6 +48,7 @@ import androidx.compose.material.icons.filled.Search
47
48
import androidx.compose.runtime.Composable
48
49
import androidx.compose.runtime.collectAsState
49
50
import androidx.compose.runtime.getValue
51
+ import androidx.compose.runtime.launchInComposition
50
52
import androidx.compose.runtime.remember
51
53
import androidx.compose.ui.Alignment
52
54
import androidx.compose.ui.Modifier
@@ -65,12 +67,15 @@ import com.example.jetcaster.data.PodcastWithExtraInfo
65
67
import com.example.jetcaster.ui.home.discover.Discover
66
68
import com.example.jetcaster.ui.theme.JetcasterTheme
67
69
import com.example.jetcaster.ui.theme.Keyline1
68
- import com.example.jetcaster.util.DominantColorVerticalGradient
70
+ import com.example.jetcaster.util.DynamicThemePrimaryColorsFromImage
69
71
import com.example.jetcaster.util.Pager
70
72
import com.example.jetcaster.util.PagerState
71
73
import com.example.jetcaster.util.ToggleFollowPodcastIconButton
74
+ import com.example.jetcaster.util.constrastAgainst
72
75
import com.example.jetcaster.util.quantityStringResource
76
+ import com.example.jetcaster.util.rememberDominantColorState
73
77
import com.example.jetcaster.util.statusBarPadding
78
+ import com.example.jetcaster.util.verticalGradientScrim
74
79
import dev.chrisbanes.accompanist.coil.CoilImage
75
80
import java.time.Duration
76
81
import java.time.LocalDateTime
@@ -129,6 +134,13 @@ fun HomeAppBar(
129
134
}
130
135
}
131
136
137
+ /* *
138
+ * This is the minimum amount of calculated constrast for a color to be used on top of the
139
+ * surface color. These values are defined within the WCAG AA guidelines, and we use a value of
140
+ * 3:1 which is the minimum for user-interface components.
141
+ */
142
+ private const val MinConstastOfPrimaryVsSurface = 3f
143
+
132
144
@Composable
133
145
fun HomeContent (
134
146
featuredPodcasts : List <PodcastWithExtraInfo >,
@@ -140,37 +152,63 @@ fun HomeContent(
140
152
onCategorySelected : (HomeCategory ) -> Unit
141
153
) {
142
154
Column (modifier = modifier) {
143
- Stack (Modifier .fillMaxWidth()) {
155
+ // We dynamically theme this sub-section of the layout to match the selected
156
+ // 'top podcast'
157
+
158
+ val surfaceColor = MaterialTheme .colors.surface
159
+ val dominantColorState = rememberDominantColorState { color ->
160
+ // We want a color which has sufficient contrast against the surface color
161
+ color.constrastAgainst(surfaceColor) >= MinConstastOfPrimaryVsSurface
162
+ }
163
+
164
+ DynamicThemePrimaryColorsFromImage (dominantColorState) {
144
165
val clock = AnimationClockAmbient .current
145
166
val pagerState = remember(clock) { PagerState (clock) }
146
167
147
- DominantColorVerticalGradient (
148
- imageSourceUrl = featuredPodcasts.getOrNull(pagerState.currentPage)
149
- ?.podcast?.imageUrl,
150
- modifier = Modifier .matchParentSize()
151
- )
168
+ val selectedImageUrl = featuredPodcasts.getOrNull(pagerState.currentPage)
169
+ ?.podcast?.imageUrl
152
170
153
- Column (Modifier .fillMaxWidth()) {
154
- HomeAppBar (
155
- modifier = Modifier .fillMaxWidth()
156
- .statusBarPadding()
157
- .preferredHeight(56 .dp) /* TODO: change height to 48.dp in landscape */
171
+ // When the selected image url changes, call updateColorsFromImageUrl() or reset()
172
+ if (selectedImageUrl != null ) {
173
+ launchInComposition(selectedImageUrl) {
174
+ dominantColorState.updateColorsFromImageUrl(selectedImageUrl)
175
+ }
176
+ } else {
177
+ dominantColorState.reset()
178
+ }
179
+
180
+ Stack (Modifier .fillMaxWidth()) {
181
+ Box (
182
+ Modifier .matchParentSize()
183
+ .verticalGradientScrim(
184
+ color = MaterialTheme .colors.primary.copy(alpha = 0.38f ),
185
+ startYPercentage = 1f ,
186
+ endYPercentage = 0f
187
+ )
158
188
)
159
189
160
- if (featuredPodcasts.isNotEmpty()) {
161
- Spacer (Modifier .height(16 .dp))
162
-
163
- FollowedPodcasts (
164
- items = featuredPodcasts,
165
- pagerState = pagerState,
166
- onPodcastUnfollowed = onPodcastUnfollowed,
167
- modifier = Modifier
168
- .padding(start = Keyline1 , top = 16 .dp, end = Keyline1 )
169
- .fillMaxWidth()
170
- .preferredHeight(200 .dp)
190
+ Column (Modifier .fillMaxWidth()) {
191
+ HomeAppBar (
192
+ Modifier .fillMaxWidth()
193
+ .statusBarPadding()
194
+ .preferredHeight(56 .dp) /* TODO: change height to 48.dp in landscape */
171
195
)
172
196
173
- Spacer (Modifier .height(16 .dp))
197
+ if (featuredPodcasts.isNotEmpty()) {
198
+ Spacer (Modifier .height(16 .dp))
199
+
200
+ FollowedPodcasts (
201
+ items = featuredPodcasts,
202
+ pagerState = pagerState,
203
+ onPodcastUnfollowed = onPodcastUnfollowed,
204
+ modifier = Modifier
205
+ .padding(start = Keyline1 , top = 16 .dp, end = Keyline1 )
206
+ .fillMaxWidth()
207
+ .preferredHeight(200 .dp)
208
+ )
209
+
210
+ Spacer (Modifier .height(16 .dp))
211
+ }
174
212
}
175
213
}
176
214
}
0 commit comments