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.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.Providers
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.setContent import androidx.compose.ui.platform.setContent
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.example.jetcaster.util.verticalGradientScrim
import com.shabinder.spotiflyer.navigation.ComposeNavigation import com.shabinder.spotiflyer.navigation.ComposeNavigation
import com.shabinder.spotiflyer.navigation.navigateToPlatform import com.shabinder.spotiflyer.navigation.navigateToPlatform
import com.shabinder.spotiflyer.networking.SpotifyService import com.shabinder.spotiflyer.networking.SpotifyService
@ -55,8 +55,7 @@ import com.shabinder.spotiflyer.utils.showDialog as showDialog1
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private var spotifyService : SpotifyService? = null private lateinit var navController: NavHostController
lateinit var navController: NavHostController
@Inject lateinit var moshi: Moshi @Inject lateinit var moshi: Moshi
@Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest @Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest
@ -70,20 +69,28 @@ class MainActivity : AppCompatActivity() {
ComposeLearnTheme { ComposeLearnTheme {
Providers(AmbientContentColor provides colorOffWhite) { Providers(AmbientContentColor provides colorOffWhite) {
ProvideWindowInsets { ProvideWindowInsets {
Column { val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.6f)
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.87f) 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 // Draw a scrim over the status bar which matches the app bar
Spacer( Spacer(
Modifier.background(appBarColor).fillMaxWidth().statusBarsHeight() Modifier.background(appBarColor).fillMaxWidth()
.statusBarsHeight()
) )
AppBar( AppBar(
backgroundColor = appBarColor, backgroundColor = appBarColor,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
navController = rememberNavController()
ComposeNavigation(navController) 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) { private fun implementSpotifyService(token: String) {
val httpClient: OkHttpClient.Builder = OkHttpClient.Builder() val httpClient: OkHttpClient.Builder = OkHttpClient.Builder()
@ -154,17 +161,17 @@ class MainActivity : AppCompatActivity() {
chain.proceed(request) chain.proceed(request)
}).addInterceptor(NetworkInterceptor()) }).addInterceptor(NetworkInterceptor())
val retrofit = Retrofit.Builder() val retrofit = Retrofit.Builder().run{
.baseUrl("https://api.spotify.com/v1/") baseUrl("https://api.spotify.com/v1/")
.client(httpClient.build()) client(httpClient.build())
.addConverterFactory(MoshiConverterFactory.create(moshi)) addConverterFactory(MoshiConverterFactory.create(moshi))
.build() build()
}
spotifyService = retrofit.create(SpotifyService::class.java) sharedViewModel.spotifyService.value = retrofit.create(SpotifyService::class.java)
sharedViewModel.spotifyService.value = spotifyService
} }
fun authenticateSpotify() { fun authenticateSpotify() {
if(sharedViewModel.spotifyService.value == null){
sharedViewModel.viewModelScope.launch { sharedViewModel.viewModelScope.launch {
log("Spotify Authentication","Started") log("Spotify Authentication","Started")
val token = spotifyServiceTokenRequest.getToken() val token = spotifyServiceTokenRequest.getToken()
@ -175,6 +182,7 @@ class MainActivity : AppCompatActivity() {
log("Spotify Token", token.value.toString()) log("Spotify Token", token.value.toString())
} }
} }
}
private fun handleIntentFromExternalActivity(intent: Intent? = getIntent()) { 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 package com.shabinder.spotiflyer
import androidx.compose.material.MaterialTheme
import androidx.compose.ui.graphics.Color
import androidx.hilt.lifecycle.ViewModelInject import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@ -25,8 +27,13 @@ import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.models.PlatformQueryResult import com.shabinder.spotiflyer.models.PlatformQueryResult
import com.shabinder.spotiflyer.networking.GaanaInterface import com.shabinder.spotiflyer.networking.GaanaInterface
import com.shabinder.spotiflyer.networking.SpotifyService 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 dagger.hilt.android.scopes.ActivityRetainedScoped
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ActivityRetainedScoped @ActivityRetainedScoped
class SharedViewModel @ViewModelInject constructor( class SharedViewModel @ViewModelInject constructor(
@ -34,6 +41,17 @@ class SharedViewModel @ViewModelInject constructor(
val gaanaInterface : GaanaInterface, val gaanaInterface : GaanaInterface,
val ytDownloader: YoutubeDownloader val ytDownloader: YoutubeDownloader
) : ViewModel() { ) : ViewModel() {
var intentString = MutableLiveData<String>() var spotifyService = MutableStateFlow<SpotifyService?>(null)
var spotifyService = MutableLiveData<SpotifyService>()
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.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.* import androidx.compose.material.*
@ -21,24 +22,26 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.navigate
import com.shabinder.spotiflyer.MainActivity
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.navigation.navigateToPlatform import com.shabinder.spotiflyer.navigation.navigateToPlatform
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
import com.shabinder.spotiflyer.ui.colorAccent import com.shabinder.spotiflyer.ui.colorAccent
import com.shabinder.spotiflyer.ui.colorPrimary import com.shabinder.spotiflyer.ui.colorPrimary
import com.shabinder.spotiflyer.utils.mainActivity
import com.shabinder.spotiflyer.utils.openPlatform import com.shabinder.spotiflyer.utils.openPlatform
import com.shabinder.spotiflyer.utils.sharedViewModel import com.shabinder.spotiflyer.utils.sharedViewModel
import com.shabinder.spotiflyer.utils.showDialog import dev.chrisbanes.accompanist.glide.GlideImage
import kotlinx.coroutines.flow.StateFlow
@Composable @Composable
fun Home(navController: NavController, modifier: Modifier = Modifier) { fun Home(navController: NavController, modifier: Modifier = Modifier) {
@ -47,7 +50,6 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
Column(modifier = modifier) { Column(modifier = modifier) {
val link by viewModel.link.collectAsState() val link by viewModel.link.collectAsState()
val selectedCategory by viewModel.selectedCategory.collectAsState()
AuthenticationBanner(viewModel,modifier) AuthenticationBanner(viewModel,modifier)
@ -57,7 +59,7 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
navController, navController,
modifier modifier
) )
val selectedCategory by viewModel.selectedCategory.collectAsState()
HomeTabBar( HomeTabBar(
selectedCategory, selectedCategory,
@ -68,10 +70,13 @@ fun Home(navController: NavController, modifier: Modifier = Modifier) {
when(selectedCategory){ when(selectedCategory){
HomeCategory.About -> AboutColumn() 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( Row(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = modifier.fillMaxWidth().padding(8.dp) modifier = modifier.fillMaxSize().padding(8.dp).weight(1f)
) { ) {
Text( Text(
text = stringResource(id = R.string.made_with_love), text = stringResource(id = R.string.made_with_love),
@ -206,8 +211,52 @@ fun AboutColumn(modifier: Modifier = Modifier) {
} }
@Composable @Composable
fun HistoryColumn() { fun HistoryColumn(
//TODO("Not yet implemented") 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 @Composable
@ -236,7 +285,7 @@ fun HomeTabBar(
TabRow( TabRow(
selectedTabIndex = selectedIndex, selectedTabIndex = selectedIndex,
indicator = indicator, indicator = indicator,
modifier = modifier modifier = modifier,
) { ) {
categories.forEachIndexed { index, category -> categories.forEachIndexed { index, category ->
Tab( Tab(
@ -289,14 +338,16 @@ fun SearchPanel(
RoundedCornerShape(30.dp) RoundedCornerShape(30.dp)
), ),
backgroundColor = Color.Black, 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), shape = RoundedCornerShape(size = 30.dp),
activeColor = Color.Transparent, activeColor = Color.Transparent,
inactiveColor = Color.Transparent inactiveColor = Color.Transparent
) )
OutlinedButton( OutlinedButton(
modifier = Modifier.padding(12.dp).wrapContentWidth(), modifier = Modifier.padding(12.dp).wrapContentWidth(),
onClick = {navController.navigateToPlatform(link)}, onClick = {
navController.navigateToPlatform(link)
},
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent))) border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent)))
){ ){
Text(text = "Search",style = SpotiFlyerTypography.h6,modifier = Modifier.padding(4.dp)) Text(text = "Search",style = SpotiFlyerTypography.h6,modifier = Modifier.padding(4.dp))

View File

@ -1,10 +1,17 @@
package com.shabinder.spotiflyer.ui.home package com.shabinder.spotiflyer.ui.home
import androidx.lifecycle.ViewModel 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.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class HomeViewModel: ViewModel() { class HomeViewModel : ViewModel() {
private val _link = MutableStateFlow("") private val _link = MutableStateFlow("")
val link:StateFlow<String> val link:StateFlow<String>
@ -30,6 +37,21 @@ class HomeViewModel: ViewModel() {
_selectedCategory.value = s _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 { enum class HomeCategory {

View File

@ -3,62 +3,123 @@ package com.shabinder.spotiflyer.ui.tracklist
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn 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.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.layout.ContentScale
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.viewModel import androidx.core.net.toUri
import androidx.navigation.NavController
import com.bumptech.glide.RequestManager
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.models.PlatformQueryResult import com.shabinder.spotiflyer.models.PlatformQueryResult
import com.shabinder.spotiflyer.models.TrackDetails import com.shabinder.spotiflyer.models.TrackDetails
import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.models.spotify.Source
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
import com.shabinder.spotiflyer.ui.colorAccent 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. * UI for List of Tracks to be universally used.
* */ **/
@Composable @Composable
fun TrackList( fun TrackList(
result: PlatformQueryResult, result: PlatformQueryResult,
source: Source, source: Source,
modifier: Modifier = Modifier modifier: Modifier = Modifier
){ ){
val coroutineScope = rememberCoroutineScope()
Box(modifier = modifier.fillMaxSize()){
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
content = { content = {
item {
CoverImage(result.title,result.coverUrl,coroutineScope)
}
items(result.trackList) { items(result.trackList) {
TrackCard(track = it) 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 @Composable
fun TrackCard(track:TrackDetails) { fun TrackCard(track:TrackDetails) {
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) { Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) {
GlideImage( val imgUri = track.albumArtURL.toUri().buildUpon().scheme("https").build()
data = track.albumArtURL, CoilImage(
loading = { Image(vectorResource(id = R.drawable.ic_song_placeholder)) }, 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)) }, error = { Image(vectorResource(id = R.drawable.ic_musicplaceholder)) },
contentScale = ContentScale.Inside, contentScale = ContentScale.Inside,
// fadeIn = true,
modifier = Modifier.preferredHeight(75.dp).preferredWidth(90.dp) 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) Text(track.title,maxLines = 1,overflow = TextOverflow.Ellipsis,style = SpotiFlyerTypography.h6,color = colorAccent)
Row( Row(
horizontalArrangement = Arrangement.SpaceBetween, 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.artists.firstOrNull()}...",fontSize = 13.sp)
Text("${track.durationSec/60} minutes, ${track.durationSec%60} sec",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)) 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" <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="300dp"
android:height="300dp" android:viewportWidth="512" android:viewportHeight="512"> 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="#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="#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="#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="#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="#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="#A3FFFFFF" android:pathData="m48,456h416v40h-416z"/> <path android:fillColor="#A3787878" 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="#A3787878" 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="#A3787878" 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="#A3787878" 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="#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="#A3FFFFFF" android:pathData="m168,224h176a32,32 0,0 0,-32 -32h-112a32,32 0,0 0,-32 32z"/> <path android:fillColor="#A3787878" android:pathData="m168,224h176a32,32 0,0 0,-32 -32h-112a32,32 0,0 0,-32 32z"/>
</vector> </vector>

View File

@ -15,8 +15,8 @@
~ along with this program. If not, see <https://www.gnu.org/licenses/>. ~ 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" <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="32dp"
android:height="30dp" android:viewportWidth="512" android:viewportHeight="512"> 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="#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="#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"/> <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" <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="42dp"
android:height="42dp" android:viewportWidth="512" android:viewportHeight="512"> 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="#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="#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="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> </vector>