Dynamic Gradient.

This commit is contained in:
shabinder 2021-01-01 17:20:33 +05:30
parent 66064c631b
commit df5f7dd2c5
11 changed files with 440 additions and 77 deletions

View File

@ -15,19 +15,19 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Providers
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.example.jetcaster.util.verticalGradientScrim
import com.shabinder.spotiflyer.navigation.ComposeNavigation
import com.shabinder.spotiflyer.navigation.navigateToPlatform
import com.shabinder.spotiflyer.networking.SpotifyService
@ -55,8 +55,7 @@ import com.shabinder.spotiflyer.utils.showDialog as showDialog1
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private var spotifyService : SpotifyService? = null
lateinit var navController: NavHostController
private lateinit var navController: NavHostController
@Inject lateinit var moshi: Moshi
@Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest
@ -70,20 +69,28 @@ class MainActivity : AppCompatActivity() {
ComposeLearnTheme {
Providers(AmbientContentColor provides colorOffWhite) {
ProvideWindowInsets {
Column {
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.87f)
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.6f)
navController = rememberNavController()
val gradientColor by sharedViewModel.gradientColor.collectAsState()
Column(
modifier = Modifier.fillMaxSize().verticalGradientScrim(
color = gradientColor.copy(alpha = 0.38f),
startYPercentage = 1f,
endYPercentage = 0f,
fixedHeight = 700f,
)
) {
// Draw a scrim over the status bar which matches the app bar
Spacer(
Modifier.background(appBarColor).fillMaxWidth().statusBarsHeight()
Modifier.background(appBarColor).fillMaxWidth()
.statusBarsHeight()
)
AppBar(
backgroundColor = appBarColor,
modifier = Modifier.fillMaxWidth()
)
navController = rememberNavController()
ComposeNavigation(navController)
}
}
@ -141,7 +148,7 @@ class MainActivity : AppCompatActivity() {
}
/**
* Adding my own new Spotify Web Api Requests!
* Adding my own Spotify Web Api Requests!
* */
private fun implementSpotifyService(token: String) {
val httpClient: OkHttpClient.Builder = OkHttpClient.Builder()
@ -154,17 +161,17 @@ class MainActivity : AppCompatActivity() {
chain.proceed(request)
}).addInterceptor(NetworkInterceptor())
val retrofit = Retrofit.Builder()
.baseUrl("https://api.spotify.com/v1/")
.client(httpClient.build())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
spotifyService = retrofit.create(SpotifyService::class.java)
sharedViewModel.spotifyService.value = spotifyService
val retrofit = Retrofit.Builder().run{
baseUrl("https://api.spotify.com/v1/")
client(httpClient.build())
addConverterFactory(MoshiConverterFactory.create(moshi))
build()
}
sharedViewModel.spotifyService.value = retrofit.create(SpotifyService::class.java)
}
fun authenticateSpotify() {
if(sharedViewModel.spotifyService.value == null){
sharedViewModel.viewModelScope.launch {
log("Spotify Authentication","Started")
val token = spotifyServiceTokenRequest.getToken()
@ -175,6 +182,7 @@ class MainActivity : AppCompatActivity() {
log("Spotify Token", token.value.toString())
}
}
}
private fun handleIntentFromExternalActivity(intent: Intent? = getIntent()) {
@ -227,7 +235,8 @@ fun AppBar(
}
}
},
modifier = modifier
modifier = modifier,
elevation = 0.dp
)
}

View File

@ -17,6 +17,8 @@
package com.shabinder.spotiflyer
import androidx.compose.material.MaterialTheme
import androidx.compose.ui.graphics.Color
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -25,8 +27,13 @@ import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.models.PlatformQueryResult
import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.networking.SpotifyService
import com.shabinder.spotiflyer.ui.colorPrimary
import com.shabinder.spotiflyer.ui.colorPrimaryDark
import com.shabinder.spotiflyer.ui.home.HomeCategory
import dagger.hilt.android.scopes.ActivityRetainedScoped
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ActivityRetainedScoped
class SharedViewModel @ViewModelInject constructor(
@ -34,6 +41,17 @@ class SharedViewModel @ViewModelInject constructor(
val gaanaInterface : GaanaInterface,
val ytDownloader: YoutubeDownloader
) : ViewModel() {
var intentString = MutableLiveData<String>()
var spotifyService = MutableLiveData<SpotifyService>()
var spotifyService = MutableStateFlow<SpotifyService?>(null)
private val _gradientColor = MutableStateFlow(colorPrimaryDark)
val gradientColor : StateFlow<Color>
get() = _gradientColor
fun updateGradientColor(color: Color) {
_gradientColor.value = color
}
fun resetGradient() {
_gradientColor.value = colorPrimaryDark
}
}

View File

@ -2,6 +2,7 @@ package com.shabinder.spotiflyer.ui.home
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
@ -21,24 +22,26 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import androidx.navigation.NavController
import androidx.navigation.compose.navigate
import com.shabinder.spotiflyer.MainActivity
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.navigation.navigateToPlatform
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
import com.shabinder.spotiflyer.ui.colorAccent
import com.shabinder.spotiflyer.ui.colorPrimary
import com.shabinder.spotiflyer.utils.mainActivity
import com.shabinder.spotiflyer.utils.openPlatform
import com.shabinder.spotiflyer.utils.sharedViewModel
import com.shabinder.spotiflyer.utils.showDialog
import dev.chrisbanes.accompanist.glide.GlideImage
import kotlinx.coroutines.flow.StateFlow
@Composable
fun Home(navController: NavController, modifier: Modifier = Modifier) {
@ -47,7 +50,6 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
val link by viewModel.link.collectAsState()
val selectedCategory by viewModel.selectedCategory.collectAsState()
AuthenticationBanner(viewModel,modifier)
@ -57,7 +59,7 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
navController,
modifier
)
val selectedCategory by viewModel.selectedCategory.collectAsState()
HomeTabBar(
selectedCategory,
@ -68,10 +70,13 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
when(selectedCategory){
HomeCategory.About -> AboutColumn()
HomeCategory.History -> HistoryColumn()
HomeCategory.History -> HistoryColumn(viewModel.downloadRecordList,navController)
}
}
//Update Download List
viewModel.getDownloadRecordList()
//reset Gradient
sharedViewModel.resetGradient()
}
@ -186,7 +191,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = modifier.fillMaxWidth().padding(8.dp)
modifier = modifier.fillMaxSize().padding(8.dp).weight(1f)
) {
Text(
text = stringResource(id = R.string.made_with_love),
@ -206,8 +211,52 @@ fun AboutColumn(modifier: Modifier = Modifier) {
}
@Composable
fun HistoryColumn() {
//TODO("Not yet implemented")
fun HistoryColumn(
downloadRecordList: StateFlow<List<DownloadRecord>>,
navController: NavController
) {
val list by downloadRecordList.collectAsState()
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
content = {
items(list) {
DownloadRecordItem(item = it,navController = navController)
}
},
modifier = Modifier.padding(top = 8.dp).fillMaxSize()
)
}
@Composable
fun DownloadRecordItem(item: DownloadRecord,navController: NavController) {
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(end = 8.dp)) {
val imgUri = item.coverUrl.toUri().buildUpon().scheme("https").build()
GlideImage(
data = imgUri,
//Loading Placeholder Makes Scrolling very stuttery
// loading = { Image(vectorResource(id = R.drawable.ic_song_placeholder)) },
error = { Image(vectorResource(id = R.drawable.ic_musicplaceholder)) },
contentScale = ContentScale.Inside,
// fadeIn = true,
modifier = Modifier.preferredHeight(75.dp).preferredWidth(90.dp)
)
Column(modifier = Modifier.padding(horizontal = 8.dp).preferredHeight(60.dp).weight(1f),verticalArrangement = Arrangement.SpaceEvenly) {
Text(item.name,maxLines = 1,overflow = TextOverflow.Ellipsis,style = SpotiFlyerTypography.h6,color = colorAccent)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom,
modifier = Modifier.padding(horizontal = 8.dp).fillMaxSize()
){
Text(item.type,fontSize = 13.sp)
Text("Tracks: ${item.totalFiles}",fontSize = 13.sp)
}
}
Image(
imageVector = vectorResource(id = R.drawable.ic_share_open),
modifier = Modifier.clickable(onClick = { navController.navigateToPlatform(item.link) })
)
}
}
@Composable
@ -236,7 +285,7 @@ fun HomeTabBar(
TabRow(
selectedTabIndex = selectedIndex,
indicator = indicator,
modifier = modifier
modifier = modifier,
) {
categories.forEachIndexed { index, category ->
Tab(
@ -289,14 +338,16 @@ fun SearchPanel(
RoundedCornerShape(30.dp)
),
backgroundColor = Color.Black,
textStyle = AmbientTextStyle.current.merge(TextStyle(fontSize = 20.sp,color = Color.White)),
textStyle = AmbientTextStyle.current.merge(TextStyle(fontSize = 18.sp,color = Color.White)),
shape = RoundedCornerShape(size = 30.dp),
activeColor = Color.Transparent,
inactiveColor = Color.Transparent
)
OutlinedButton(
modifier = Modifier.padding(12.dp).wrapContentWidth(),
onClick = {navController.navigateToPlatform(link)},
onClick = {
navController.navigateToPlatform(link)
},
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent)))
){
Text(text = "Search",style = SpotiFlyerTypography.h6,modifier = Modifier.padding(4.dp))

View File

@ -1,10 +1,17 @@
package com.shabinder.spotiflyer.ui.home
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.utils.sharedViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class HomeViewModel: ViewModel() {
class HomeViewModel : ViewModel() {
private val _link = MutableStateFlow("")
val link:StateFlow<String>
@ -30,6 +37,21 @@ class HomeViewModel: ViewModel() {
_selectedCategory.value = s
}
private val _downloadRecordList = MutableStateFlow<List<DownloadRecord>>(listOf())
val downloadRecordList: StateFlow<List<DownloadRecord>>
get() = _downloadRecordList
fun getDownloadRecordList() {
viewModelScope.launch {
withContext(Dispatchers.IO){
_downloadRecordList.value = sharedViewModel.databaseDAO.getRecord().toMutableList()
}
}
}
init {
getDownloadRecordList()
}
}
enum class HomeCategory {

View File

@ -3,62 +3,123 @@ package com.shabinder.spotiflyer.ui.tracklist
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.viewModel
import androidx.navigation.NavController
import com.bumptech.glide.RequestManager
import androidx.core.net.toUri
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.models.PlatformQueryResult
import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
import com.shabinder.spotiflyer.ui.colorAccent
import dev.chrisbanes.accompanist.glide.GlideImage
import com.shabinder.spotiflyer.ui.utils.calculateDominantColor
import com.shabinder.spotiflyer.utils.sharedViewModel
import dev.chrisbanes.accompanist.coil.CoilImage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/*
* UI for List of Tracks to be universally used.
* */
**/
@Composable
fun TrackList(
result: PlatformQueryResult,
source: Source,
modifier: Modifier = Modifier
){
val coroutineScope = rememberCoroutineScope()
Box(modifier = modifier.fillMaxSize()){
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
content = {
item {
CoverImage(result.title,result.coverUrl,coroutineScope)
}
items(result.trackList) {
TrackCard(track = it)
}
},
modifier = Modifier.fillMaxSize()
modifier = Modifier.fillMaxSize(),
)
DownloadAllButton(
onClick = {},
modifier = Modifier.padding(bottom = 24.dp).align(Alignment.BottomCenter)
)
}
}
@Composable
fun CoverImage(
title: String,
coverURL: String,
scope: CoroutineScope,
modifier: Modifier = Modifier,
) {
Column(
modifier.padding(vertical = 8.dp).fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
CoilImage(
data = coverURL,
contentScale = ContentScale.Crop,
loading = { Image(vectorResource(id = R.drawable.ic_musicplaceholder)) },
modifier = Modifier
.preferredWidth(210.dp)
.preferredHeight(230.dp)
.clip(MaterialTheme.shapes.medium)
)
Text(
text = title,
style = SpotiFlyerTypography.h5,
//color = colorAccent,
)
}
scope.launch { updateGradient(coverURL) }
}
@Composable
fun DownloadAllButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
ExtendedFloatingActionButton(
text = { Text("Download All") },
onClick = onClick,
icon = { Icon(imageVector = vectorResource(R.drawable.ic_download_arrow),tint = Color.Black) },
backgroundColor = colorAccent,
modifier = modifier
)
}
@Composable
fun TrackCard(track:TrackDetails) {
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) {
GlideImage(
data = track.albumArtURL,
loading = { Image(vectorResource(id = R.drawable.ic_song_placeholder)) },
val imgUri = track.albumArtURL.toUri().buildUpon().scheme("https").build()
CoilImage(
data = imgUri,
//Loading Placeholder Makes Scrolling very stuttery
// loading = { Image(vectorResource(id = R.drawable.ic_song_placeholder)) },
error = { Image(vectorResource(id = R.drawable.ic_musicplaceholder)) },
contentScale = ContentScale.Inside,
// fadeIn = true,
modifier = Modifier.preferredHeight(75.dp).preferredWidth(90.dp)
)
Column(modifier = Modifier.padding(horizontal = 8.dp).fillMaxHeight().weight(1f),verticalArrangement = Arrangement.SpaceBetween) {
Column(modifier = Modifier.padding(horizontal = 8.dp).preferredHeight(60.dp).weight(1f),verticalArrangement = Arrangement.SpaceEvenly) {
Text(track.title,maxLines = 1,overflow = TextOverflow.Ellipsis,style = SpotiFlyerTypography.h6,color = colorAccent)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.padding(horizontal = 12.dp).fillMaxSize()
verticalAlignment = Alignment.Bottom,
modifier = Modifier.padding(horizontal = 8.dp).fillMaxSize()
){
Text("${track.artists.firstOrNull()}...",fontSize = 13.sp)
Text("${track.durationSec/60} minutes, ${track.durationSec%60} sec",fontSize = 13.sp)
@ -67,3 +128,8 @@ fun TrackCard(track:TrackDetails) {
Image(vectorResource(id = R.drawable.ic_arrow))
}
}
suspend fun updateGradient(imageURL:String){
calculateDominantColor(imageURL)?.color
?.let { sharedViewModel.updateGradientColor(it) }
}

View File

@ -0,0 +1,16 @@
package com.shabinder.spotiflyer.ui.utils
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.luminance
import kotlin.math.max
import kotlin.math.min
fun Color.contrastAgainst(background: Color): Float {
val fg = if (alpha < 1f) compositeOver(background) else this
val fgLuminance = fg.luminance() + 0.05f
val bgLuminance = background.luminance() + 0.05f
return max(fgLuminance, bgLuminance) / min(fgLuminance, bgLuminance)
}

View File

@ -0,0 +1,100 @@
/*
* 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.
*/
package com.shabinder.spotiflyer.ui.utils
import android.content.Context
import androidx.collection.LruCache
import androidx.compose.animation.animate
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.AmbientContext
import androidx.core.graphics.drawable.toBitmap
import androidx.palette.graphics.Palette
import coil.Coil
import coil.request.ImageRequest
import coil.request.SuccessResult
import coil.size.Scale
import com.shabinder.spotiflyer.utils.mainActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Immutable
data class DominantColors(val color: Color, val onColor: Color)
suspend fun calculateDominantColor(url: String): DominantColors? {
// we calculate the swatches in the image, and return the first valid color
return calculateSwatchesInImage(mainActivity, url)
// First we want to sort the list by the color's population
.sortedByDescending { swatch -> swatch.population }
// Then we want to find the first valid color
.firstOrNull { swatch -> Color(swatch.rgb).contrastAgainst(Color.Black) >= 3f }
// If we found a valid swatch, wrap it in a [DominantColors]
?.let { swatch ->
DominantColors(
color = Color(swatch.rgb),
onColor = Color(swatch.bodyTextColor).copy(alpha = 1f)
)
}
}
/**
* Fetches the given [imageUrl] with [Coil], then uses [Palette] to calculate the dominant color.
*/
suspend fun calculateSwatchesInImage(
context: Context,
imageUrl: String
): List<Palette.Swatch> {
val r = ImageRequest.Builder(context)
.data(imageUrl)
// We scale the image to cover 128px x 128px (i.e. min dimension == 128px)
.size(128).scale(Scale.FILL)
// Disable hardware bitmaps, since Palette uses Bitmap.getPixels()
.allowHardware(false)
.build()
val bitmap = when (val result = Coil.execute(r)) {
is SuccessResult -> result.drawable.toBitmap()
else -> null
}
return bitmap?.let {
withContext(Dispatchers.Default) {
val palette = Palette.Builder(bitmap)
// Disable any bitmap resizing in Palette. We've already loaded an appropriately
// sized bitmap through Coil
.resizeBitmapArea(0)
// Clear any built-in filters. We want the unfiltered dominant color
.clearFilters()
// We reduce the maximum color count down to 8
.maximumColorCount(8)
.generate()
palette.swatches
}
} ?: emptyList()
}

View File

@ -0,0 +1,81 @@
/*
* 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.
*/
package com.example.jetcaster.util
import androidx.annotation.FloatRange
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import com.shabinder.spotiflyer.utils.log
import kotlin.math.pow
/**
* Draws a vertical gradient scrim in the foreground.
*
* @param color The color of the gradient scrim.
* @param startYPercentage The start y value, in percentage of the layout's height (0f to 1f)
* @param endYPercentage The end y value, in percentage of the layout's height (0f to 1f)
* @param decay The exponential decay to apply to the gradient. Defaults to `1.0f` which is
* a linear gradient.
* @param numStops The number of color stops to draw in the gradient. Higher numbers result in
* the higher visual quality at the cost of draw performance. Defaults to `16`.
*/
fun Modifier.verticalGradientScrim(
color: Color,
@FloatRange(from = 0.0, to = 1.0) startYPercentage: Float = 0f,
@FloatRange(from = 0.0, to = 1.0) endYPercentage: Float = 1f,
decay: Float = 1.0f,
numStops: Int = 16,
fixedHeight: Float? = null
): Modifier = composed {
val colors = remember(color, numStops) {
if (decay != 1f) {
// If we have a non-linear decay, we need to create the color gradient steps
// manually
val baseAlpha = color.alpha
List(numStops) { i ->
val x = i * 1f / (numStops - 1)
val opacity = x.pow(decay)
color.copy(alpha = baseAlpha * opacity)
}
} else {
// If we have a linear decay, we just create a simple list of start + end colors
listOf(color.copy(alpha = 0f), color)
}
}
var height by remember { mutableStateOf(fixedHeight ?: 0f) }
val brush = remember(color, numStops, startYPercentage, endYPercentage, height) {
Brush.verticalGradient(
colors = colors,
startY = height * startYPercentage,
endY = height * endYPercentage
)
}
drawBehind {
height = fixedHeight ?: size.height
// log("Height",size.height.toString())
drawRect(brush = brush)
}
}

View File

@ -17,13 +17,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="300dp"
android:height="300dp" android:viewportWidth="512" android:viewportHeight="512">
<path android:fillColor="#A3FFFFFF" android:pathData="m256,80a48.054,48.054 0,0 1,48 48v32h12a19.991,19.991 0,0 0,3.524 -39.671,63.984 63.984,0 0,0 -127.048,0 19.991,19.991 0,0 0,3.524 39.671h12v-32a48.054,48.054 0,0 1,48 -48z"/>
<path android:fillColor="#A3FFFFFF" android:pathData="m48,152a24.027,24.027 0,0 0,24 -24v-74.234l42.53,-14.176 -5.06,-15.18 -48,16a8,8 0,0 0,-5.47 7.59v57.376a24,24 0,1 0,-8 46.624zM48,120a8,8 0,1 1,-8 8,8.009 8.009,0 0,1 8,-8z"/>
<path android:fillColor="#A3FFFFFF" android:pathData="m485.006,17.76a7.993,7.993 0,0 0,-6.741 -1.569l-72,16a8,8 0,0 0,-6.265 7.809v57.376a24,24 0,1 0,16 22.624v-73.583l56,-12.444v47.4a24,24 0,1 0,16 22.627v-80a8,8 0,0 0,-2.994 -6.24zM392,128a8,8 0,1 1,8 -8,8.009 8.009,0 0,1 -8,8zM464,112a8,8 0,1 1,8 -8,8.009 8.009,0 0,1 -8,8z"/>
<path android:fillColor="#A3FFFFFF" android:pathData="m48,456h416v40h-416z"/>
<path android:fillColor="#A3FFFFFF" android:pathData="m64,376a16,16 0,0 0,-16 16v7h48v-7a16,16 0,0 0,-16 -16z"/>
<path android:fillColor="#A3FFFFFF" android:pathData="m24,416h464v24h-464z"/>
<path android:fillColor="#A3FFFFFF" android:pathData="M256,144m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
<path android:fillColor="#A3FFFFFF" android:pathData="m368,400 l16,-160h-256l16,160zM256,296a24,24 0,1 1,-24 24,24 24,0 0,1 24,-24z"/>
<path android:fillColor="#A3FFFFFF" android:pathData="m168,224h176a32,32 0,0 0,-32 -32h-112a32,32 0,0 0,-32 32z"/>
<path android:fillColor="#A3787878" android:pathData="m256,80a48.054,48.054 0,0 1,48 48v32h12a19.991,19.991 0,0 0,3.524 -39.671,63.984 63.984,0 0,0 -127.048,0 19.991,19.991 0,0 0,3.524 39.671h12v-32a48.054,48.054 0,0 1,48 -48z"/>
<path android:fillColor="#A3787878" android:pathData="m48,152a24.027,24.027 0,0 0,24 -24v-74.234l42.53,-14.176 -5.06,-15.18 -48,16a8,8 0,0 0,-5.47 7.59v57.376a24,24 0,1 0,-8 46.624zM48,120a8,8 0,1 1,-8 8,8.009 8.009,0 0,1 8,-8z"/>
<path android:fillColor="#A3787878" android:pathData="m485.006,17.76a7.993,7.993 0,0 0,-6.741 -1.569l-72,16a8,8 0,0 0,-6.265 7.809v57.376a24,24 0,1 0,16 22.624v-73.583l56,-12.444v47.4a24,24 0,1 0,16 22.627v-80a8,8 0,0 0,-2.994 -6.24zM392,128a8,8 0,1 1,8 -8,8.009 8.009,0 0,1 -8,8zM464,112a8,8 0,1 1,8 -8,8.009 8.009,0 0,1 -8,8z"/>
<path android:fillColor="#A3787878" android:pathData="m48,456h416v40h-416z"/>
<path android:fillColor="#A3787878" android:pathData="m64,376a16,16 0,0 0,-16 16v7h48v-7a16,16 0,0 0,-16 -16z"/>
<path android:fillColor="#A3787878" android:pathData="m24,416h464v24h-464z"/>
<path android:fillColor="#A3787878" android:pathData="M256,144m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
<path android:fillColor="#A3787878" android:pathData="m368,400 l16,-160h-256l16,160zM256,296a24,24 0,1 1,-24 24,24 24,0 0,1 24,-24z"/>
<path android:fillColor="#A3787878" android:pathData="m168,224h176a32,32 0,0 0,-32 -32h-112a32,32 0,0 0,-32 32z"/>
</vector>

View File

@ -15,8 +15,8 @@
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="30dp"
android:height="30dp" android:viewportWidth="512" android:viewportHeight="512">
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="32dp"
android:height="32dp" android:viewportWidth="512" android:viewportHeight="512">
<path android:fillColor="#FF3C64" android:pathData="m304,232a24,24 0,0 1,-16.971 -40.971l160,-160a24,24 0,0 1,33.942 33.942l-160,160a23.926,23.926 0,0 1,-16.971 7.029z"/>
<path android:fillColor="#FF3B63" android:pathData="m464,200a24,24 0,0 1,-24 -24v-104h-104a24,24 0,0 1,0 -48h128a24,24 0,0 1,24 24v128a24,24 0,0 1,-24 24z"/>
<path android:fillColor="#CE1CFF" android:pathData="m464,488h-416a24,24 0,0 1,-24 -24v-416a24,24 0,0 1,24 -24h176a24,24 0,0 1,0 48h-152v368h368v-152a24,24 0,0 1,48 0v176a24,24 0,0 1,-24 24z"/>

View File

@ -17,6 +17,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="42dp"
android:height="42dp" android:viewportWidth="512" android:viewportHeight="512">
<path android:fillColor="#A3FFFFFF" android:pathData="m511.739,103.734 l-257,50.947v233.725c-10.733,-7.199 -23.633,-11.406 -37.5,-11.406 -37.22,0 -67.5,30.28 -67.5,67.5s30.28,67.5 67.5,67.5c34.684,0 63.329,-26.299 67.073,-60h0.427v-182.682l197,-39.053v98.141c-10.733,-7.199 -23.633,-11.406 -37.5,-11.406 -37.22,0 -67.5,30.28 -67.5,67.5s30.28,67.5 67.5,67.5c39.927,0 71.547,-34.762 67.073,-75h0.427zM217.239,482c-20.678,0 -37.5,-16.822 -37.5,-37.5s16.822,-37.5 37.5,-37.5 37.5,16.822 37.5,37.5 -16.822,37.5 -37.5,37.5zM444.239,422c-20.678,0 -37.5,-16.822 -37.5,-37.5s16.822,-37.5 37.5,-37.5 37.5,16.822 37.5,37.5 -16.822,37.5 -37.5,37.5zM481.739,199.682 L284.739,238.735v-59.416l197,-39.053z"/>
<path android:fillColor="#A3FFFFFF" android:pathData="m182.179,159.75h30c0,-31.002 4.415,-66.799 -24.144,-95.356 -8.968,-8.968 -17.455,-16.07 -24.942,-22.336 -19.798,-16.57 -27.832,-24.012 -27.832,-42.058h-30v221.406c-10.734,-7.199 -23.634,-11.406 -37.5,-11.406 -37.22,0 -67.5,30.28 -67.5,67.5s30.28,67.5 67.5,67.5c34.684,0 63.329,-26.299 67.073,-60h0.427v-227.219c9.458,8.262 20.077,16.341 31.562,27.825 19.029,19.031 15.356,44.009 15.356,74.144zM67.761,315c-20.678,0 -37.5,-16.822 -37.5,-37.5s16.822,-37.5 37.5,-37.5 37.5,16.822 37.5,37.5 -16.823,37.5 -37.5,37.5z"/>
<path android:fillColor="#A3787878" android:pathData="m511.739,103.734 l-257,50.947v233.725c-10.733,-7.199 -23.633,-11.406 -37.5,-11.406 -37.22,0 -67.5,30.28 -67.5,67.5s30.28,67.5 67.5,67.5c34.684,0 63.329,-26.299 67.073,-60h0.427v-182.682l197,-39.053v98.141c-10.733,-7.199 -23.633,-11.406 -37.5,-11.406 -37.22,0 -67.5,30.28 -67.5,67.5s30.28,67.5 67.5,67.5c39.927,0 71.547,-34.762 67.073,-75h0.427zM217.239,482c-20.678,0 -37.5,-16.822 -37.5,-37.5s16.822,-37.5 37.5,-37.5 37.5,16.822 37.5,37.5 -16.822,37.5 -37.5,37.5zM444.239,422c-20.678,0 -37.5,-16.822 -37.5,-37.5s16.822,-37.5 37.5,-37.5 37.5,16.822 37.5,37.5 -16.822,37.5 -37.5,37.5zM481.739,199.682 L284.739,238.735v-59.416l197,-39.053z"/>
<path android:fillColor="#A3787878" android:pathData="m182.179,159.75h30c0,-31.002 4.415,-66.799 -24.144,-95.356 -8.968,-8.968 -17.455,-16.07 -24.942,-22.336 -19.798,-16.57 -27.832,-24.012 -27.832,-42.058h-30v221.406c-10.734,-7.199 -23.634,-11.406 -37.5,-11.406 -37.22,0 -67.5,30.28 -67.5,67.5s30.28,67.5 67.5,67.5c34.684,0 63.329,-26.299 67.073,-60h0.427v-227.219c9.458,8.262 20.077,16.341 31.562,27.825 19.029,19.031 15.356,44.009 15.356,74.144zM67.761,315c-20.678,0 -37.5,-16.822 -37.5,-37.5s16.822,-37.5 37.5,-37.5 37.5,16.822 37.5,37.5 -16.823,37.5 -37.5,37.5z"/>
</vector>