mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-12-22 20:57:54 +01:00
Dynamic Gradient.
This commit is contained in:
parent
66064c631b
commit
df5f7dd2c5
@ -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,25 +161,26 @@ 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() {
|
||||
sharedViewModel.viewModelScope.launch {
|
||||
log("Spotify Authentication","Started")
|
||||
val token = spotifyServiceTokenRequest.getToken()
|
||||
token.value?.let {
|
||||
showDialog1("Success: Spotify Token Acquired")
|
||||
implementSpotifyService(it.access_token)
|
||||
if(sharedViewModel.spotifyService.value == null){
|
||||
sharedViewModel.viewModelScope.launch {
|
||||
log("Spotify Authentication","Started")
|
||||
val token = spotifyServiceTokenRequest.getToken()
|
||||
token.value?.let {
|
||||
showDialog1("Success: Spotify Token Acquired")
|
||||
implementSpotifyService(it.access_token)
|
||||
}
|
||||
log("Spotify Token", token.value.toString())
|
||||
}
|
||||
log("Spotify Token", token.value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +235,8 @@ fun AppBar(
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = modifier
|
||||
modifier = modifier,
|
||||
elevation = 0.dp
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
){
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
content = {
|
||||
items(result.trackList) {
|
||||
TrackCard(track = it)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
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(),
|
||||
)
|
||||
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)
|
||||
@ -66,4 +127,9 @@ fun TrackCard(track:TrackDetails) {
|
||||
}
|
||||
Image(vectorResource(id = R.drawable.ic_arrow))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateGradient(imageURL:String){
|
||||
calculateDominantColor(imageURL)?.color
|
||||
?.let { sharedViewModel.updateGradientColor(it) }
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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"/>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user