Made All Long Running Calls Asynchronous(Coroutines deployed!)

This commit is contained in:
shabinder 2020-07-22 11:34:08 +05:30
parent d4823f0e10
commit 735f7c270e
15 changed files with 407 additions and 293 deletions

View File

@ -1,6 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="shabinder">
<words>
<w>moshi</w>
<w>musicforeveryone</w>
<w>musicplaceholder</w>
<w>shabinder</w>

View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectPlainTextFileTypeManager">
<file url="file://$PROJECT_DIR$/app/src/main/java/com/shabinder/musicForEveryone/downloadHelper/DownloadHelperLibrary.kt" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>

View File

@ -68,9 +68,14 @@ dependencies {
implementation 'com.google.apis:google-api-services-youtube:v3-rev180-1.22.0'
implementation 'com.google.oauth-client:google-oauth-client:1.22.0'
implementation 'com.spotify.android:auth:1.1.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.okhttp3:okhttp:4.8.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.squareup.moshi:moshi:1.9.3"
implementation "com.squareup.moshi:moshi-kotlin:1.9.3"
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
implementation 'com.github.sealedtx:java-youtube-downloader:2.2.2'
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'

View File

@ -21,6 +21,11 @@
android:theme="@style/AppTheme">
<activity android:name="com.shabinder.musicForEveryone.MainActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -6,43 +6,52 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.StrictMode
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.musicForEveryone.databinding.MainActivityBinding
import com.shabinder.musicForEveryone.downloadHelper.DownloadHelper
import com.shabinder.musicForEveryone.utils.SpotifyNewService
import com.shabinder.musicForEveryone.utils.YoutubeInterface
import com.spotify.sdk.android.authentication.AuthenticationClient
import com.spotify.sdk.android.authentication.AuthenticationRequest
import com.spotify.sdk.android.authentication.AuthenticationResponse
import com.spotify.sdk.android.authentication.LoginActivity
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import kaaes.spotify.webapi.android.SpotifyApi
import kaaes.spotify.webapi.android.SpotifyService
import retrofit.RestAdapter
import kotlinx.coroutines.launch
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
class MainActivity : AppCompatActivity() {
class MainActivity : AppCompatActivity() ,DownloadHelper{
private lateinit var binding: MainActivityBinding
var ytDownloader : YoutubeDownloader? = null
var spotifyExtra : SpotifyNewService? = null
var downloadManager : DownloadManager? = null
val REDIRECT_URI = "musicforeveryone://callback"
val CLIENT_ID:String = "694d8bf4f6ec420fa66ea7fb4c68f89d"
// val musicDirectory = File(this.filesDir?.absolutePath + "/Music/")
var message :String =""
var token :String =""
var spotify: SpotifyService? = null
lateinit var sharedViewModel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this,R.layout.main_activity)
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
val policy =
StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// val policy =
// StrictMode.ThreadPolicy.Builder().permitAll().build()
// StrictMode.setThreadPolicy(policy)
//TODO Use Coroutines
if(spotify==null){
authenticateSpotify()
@ -59,7 +68,14 @@ class MainActivity : AppCompatActivity() {
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
sharedViewModel.downloadManager = downloadManager
if (intent?.action == Intent.ACTION_SEND) {
if ("text/plain" == intent.type) {
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
Log.i("Intent Received",it)
sharedViewModel.intentString = it
}
}
}
}
@ -85,13 +101,21 @@ class MainActivity : AppCompatActivity() {
spotify = api.service
sharedViewModel.spotify = api.service
//Initiate Processes In Main Fragment
val me = spotify?.me?.display_name
sharedViewModel.userName.value = me
Log.i("Network",me!!)
sharedViewModel.uiScope.launch {
val me = spotifyExtra?.getMe()?.display_name
sharedViewModel.userName.value = me
Log.i("Network",me!!)
}
sharedViewModel.userName.observe(this, Observer {
binding.message.text = it
})
}
AuthenticationResponse.Type.ERROR -> {
Log.i("Network",response.error.toString())
}
else -> {
}
@ -103,17 +127,33 @@ class MainActivity : AppCompatActivity() {
* Adding my own new Spotify Web Api Requests!
* */
private fun implementSpotifyExtra() {
val restAdapter = RestAdapter.Builder()
.setEndpoint(SpotifyApi.SPOTIFY_WEB_API_ENDPOINT)
.setRequestInterceptor { request ->
request.addHeader(
"Authorization",
"Bearer $token"
)
val httpClient: OkHttpClient.Builder = OkHttpClient.Builder()
httpClient.addInterceptor(object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request =
chain.request().newBuilder().addHeader(
"Authorization",
"Bearer $token"
).build()
return chain.proceed(request)
}
})
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val spotifyExtra = restAdapter.create(SpotifyNewService::class.java)
val retrofit: Retrofit =
Retrofit.Builder()
.baseUrl("https://api.spotify.com/v1/")
.client(httpClient.build())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
spotifyExtra = retrofit.create(SpotifyNewService::class.java)
sharedViewModel.spotifyExtra = spotifyExtra
}
@ -129,7 +169,7 @@ class MainActivity : AppCompatActivity() {
private fun authenticateSpotify() {
val builder = AuthenticationRequest.Builder(CLIENT_ID,AuthenticationResponse.Type.TOKEN,REDIRECT_URI)
.setShowDialog(true)
.setShowDialog(false)
.setScopes(arrayOf("user-read-private","streaming","user-read-email","user-modify-playback-state","user-top-read","user-library-modify","user-read-currently-playing","user-library-read","user-read-recently-played"))
val request: AuthenticationRequest = builder.build()
AuthenticationClient.openLoginActivity(this, LoginActivity.REQUEST_CODE, request)

View File

@ -9,8 +9,12 @@ import kaaes.spotify.webapi.android.SpotifyService
import kaaes.spotify.webapi.android.models.Album
import kaaes.spotify.webapi.android.models.Playlist
import kaaes.spotify.webapi.android.models.Track
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
class SharedViewModel : ViewModel() {
var intentString = ""
var accessToken:String = ""
var userName = MutableLiveData<String>().apply { value = "Placeholder" }
var spotify :SpotifyService? = null
@ -18,15 +22,23 @@ class SharedViewModel : ViewModel() {
var ytDownloader : YoutubeDownloader? = null
var downloadManager : DownloadManager? = null
var viewModelJob = Job()
fun getTrackDetails(trackLink:String): Track?{
return spotify?.getTrack(trackLink)
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
suspend fun getTrackDetails(trackLink:String): Track?{
return spotifyExtra?.getTrack(trackLink)
}
fun getAlbumDetails(albumLink:String): Album?{
return spotify?.getAlbum(albumLink)
suspend fun getAlbumDetails(albumLink:String): Album?{
return spotifyExtra?.getAlbum(albumLink)
}
fun getPlaylistDetails(link:String): Playlist?{
suspend fun getPlaylistDetails(link:String): Playlist?{
return spotifyExtra?.getPlaylist(link)
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}

View File

@ -9,51 +9,62 @@ import com.github.kiulian.downloader.YoutubeDownloader
import com.github.kiulian.downloader.model.formats.Format
import com.github.kiulian.downloader.model.quality.AudioQuality
import com.shabinder.musicForEveryone.utils.YoutubeInterface
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
interface DownloadHelper {
fun downloadTrack(ytDownloader: YoutubeDownloader?, downloadManager: DownloadManager?, searchQuery:String){
//@data = 1st object from YT query.
val data = YoutubeInterface.search(searchQuery)?.get(0)
if (data==null){Log.i("DownloadHelper","Youtube Request Failed!")}else{
suspend fun downloadTrack(ytDownloader: YoutubeDownloader?, downloadManager: DownloadManager?, searchQuery:String){
//Fetching a Video Object.
val video = ytDownloader?.getVideo(data.id)
val details = video?.details()
withContext(Dispatchers.IO){
val downloadIdList = mutableListOf<Int>()
val data = YoutubeInterface.search(searchQuery)?.get(0)
if (data==null){Log.i("DownloadHelper","Youtube Request Failed!")}else{
val format:Format = video?.findAudioWithQuality(AudioQuality.low)?.get(0) as Format
val video = ytDownloader?.getVideo(data.id)
//Fetching a Video Object.
val details = video?.details()
val audioUrl = format.url()
val format:Format = video?.findAudioWithQuality(AudioQuality.low)?.get(0) as Format
val audioUrl = format.url()
if (audioUrl != null) {
downloadFile(audioUrl,downloadManager,details!!.title())
Log.i("DHelper Start Download", audioUrl)
}else{Log.i("YT audio url is null", format.toString())}
}
if (audioUrl != null) {
downloadFile(audioUrl,downloadManager,details?.title()?:"Error")
Log.i("DHelper Start Download", audioUrl)
}else{Log.i("YT audio url is null", format.toString())}
// Library Inbuilt function to Save File (Need Scoped Storage Implementation)
// val file: File = video.download( format , outputDir)
}
}
//@data = 1st object from YT query.
}
/**
* Downloading Using Android Download Manager
* */
fun downloadFile(url: String, downloadManager: DownloadManager?,title:String){
val audioUri = Uri.parse(url)
val outputDir = File.separator + "Spotify-Downloads" +File.separator + "${removeIllegalChars(title)}.mp3"
suspend fun downloadFile(url: String, downloadManager: DownloadManager?,title:String){
withContext(Dispatchers.IO){
val audioUri = Uri.parse(url)
val outputDir = File.separator + "Spotify-Downloads" +File.separator + "${removeIllegalChars(title)}.mp3"
val request = DownloadManager.Request(audioUri)
.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or
DownloadManager.Request.NETWORK_MOBILE
)
.setAllowedOverRoaming(false)
.setTitle(title)
.setDescription("Spotify Downloader Working Up here...")
.setDestinationInExternalPublicDir(Environment.DIRECTORY_MUSIC,outputDir)
.setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
downloadManager?.enqueue(request)
Log.i("DownloadManager","Download Request Sent")
val request = DownloadManager.Request(audioUri)
.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or
DownloadManager.Request.NETWORK_MOBILE
)
.setAllowedOverRoaming(false)
.setTitle(title)
.setDescription("Spotify Downloader Working Up here...")
.setDestinationInExternalPublicDir(Environment.DIRECTORY_MUSIC,outputDir)
.setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
downloadManager?.enqueue(request)
Log.i("DownloadManager","Download Request Sent")
}
}

View File

@ -18,6 +18,7 @@ import com.shabinder.musicForEveryone.recyclerView.TrackListAdapter
import com.shabinder.musicForEveryone.utils.bindImage
import kaaes.spotify.webapi.android.SpotifyService
import kaaes.spotify.webapi.android.models.Track
import kotlinx.coroutines.launch
class MainFragment : Fragment(),DownloadHelper {
@ -25,6 +26,7 @@ class MainFragment : Fragment(),DownloadHelper {
private lateinit var sharedViewModel: SharedViewModel
var spotify : SpotifyService? = null
var type:String = ""
var spotifyLink = ""
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@ -33,13 +35,9 @@ class MainFragment : Fragment(),DownloadHelper {
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
spotify = sharedViewModel.spotify
sharedViewModel.userName.observe(viewLifecycleOwner, Observer {
binding.message.text = it
// if(it!="Placeholder"){Snackbar.make(requireView(),"Hello, $it!", Snackbar.LENGTH_SHORT).show()}
})
binding.btnSearch.setOnClickListener {
val spotifyLink = binding.spotifyLink.text.toString()
spotifyLink = binding.spotifyLink.text.toString()
val link = spotifyLink.substringAfterLast('/' , "Error").substringBefore('?')
type = spotifyLink.substringBeforeLast('/' , "Error").substringAfterLast('/')
@ -49,60 +47,83 @@ class MainFragment : Fragment(),DownloadHelper {
val adapter = TrackListAdapter()
binding.trackList.adapter = adapter
adapter.sharedViewModel = sharedViewModel
when(type){
"track" -> {
val trackObject = sharedViewModel.getTrackDetails(link)
sharedViewModel.uiScope.launch{
val trackObject = sharedViewModel.getTrackDetails(link)
val trackList = mutableListOf<Track>()
trackList.add(trackObject!!)
adapter.totalItems = 1
adapter.trackList = trackList
adapter.notifyDataSetChanged()
binding.imageView.visibility =View.VISIBLE
Log.i("Adapter",trackList.size.toString())
val trackList = mutableListOf<Track>()
trackList.add(trackObject!!)
bindImage(binding.imageView, trackObject.album.images[0].url)
adapter.totalItems = 1
adapter.trackList = trackList
adapter.notifyDataSetChanged()
Log.i("Adapter",trackList.size.toString())
}
}
"album" -> {
val albumObject = sharedViewModel.getAlbumDetails(link)
sharedViewModel.uiScope.launch{
bindImage(binding.imageView,albumObject!!.images[1].url)
binding.titleView.text = albumObject.name
binding.titleView.visibility =View.VISIBLE
binding.imageView.visibility =View.VISIBLE
val albumObject = sharedViewModel.getAlbumDetails(link)
val trackList = mutableListOf<Track>()
albumObject.tracks?.items?.forEach { trackList.add(it as Track) }
adapter.totalItems = trackList.size
adapter.trackList = trackList
adapter.notifyDataSetChanged()
binding.titleView.text = albumObject!!.name
binding.titleView.visibility =View.VISIBLE
binding.imageView.visibility =View.VISIBLE
val trackList = mutableListOf<Track>()
albumObject.tracks?.items?.forEach { trackList.add(it as Track) }
adapter.totalItems = trackList.size
adapter.trackList = trackList
adapter.notifyDataSetChanged()
Log.i("Adapter",trackList.size.toString())
bindImage(binding.imageView, albumObject.images[0].url)
}
Log.i("Adapter",trackList.size.toString())
}
"playlist" -> {
val playlistObject = sharedViewModel.getPlaylistDetails(link)
sharedViewModel.uiScope.launch{
val playlistObject = sharedViewModel.getPlaylistDetails(link)
bindImage(binding.imageView,playlistObject!!.images[0].url)
binding.titleView.text = playlistObject.name
binding.titleView.visibility =View.VISIBLE
binding.imageView.visibility =View.VISIBLE
binding.titleView.text = "${if(playlistObject!!.name.length > 18){"${playlistObject.name.subSequence(0,17)}..."}else{playlistObject.name}}"
binding.titleView.visibility =View.VISIBLE
binding.imageView.visibility =View.VISIBLE
binding.playlistOwner.visibility =View.VISIBLE
binding.playlistOwner.text = "by: ${playlistObject.owner.display_name}"
val trackList = mutableListOf<Track>()
playlistObject.tracks?.items!!.forEach { trackList.add(it.track) }
adapter.trackList = trackList.toList()
adapter.totalItems = trackList.size
adapter.notifyDataSetChanged()
Log.i("Adapter",trackList.size.toString())
bindImage(binding.imageView, playlistObject.images[0].url)
}
}
val trackList = mutableListOf<Track>()
playlistObject.tracks?.items!!.forEach { trackList.add(it.track) }
adapter.trackList = trackList.toList()
adapter.totalItems = trackList.size
adapter.notifyDataSetChanged()
Log.i("Adapter",trackList.size.toString())
}
"episode" -> {showToast("Implementation Pending")}
"show" -> {showToast("Implementation Pending ")}
}
binding.spotifyLink.setText(link)
}
binding.spotifyLink.setText(sharedViewModel.intentString)
sharedViewModel.userName.observe(viewLifecycleOwner, Observer {
//Waiting for Authentication to Finish with Spotify
if (it != "Placeholder"){
if(sharedViewModel.intentString != ""){binding.btnSearch.performClick()}
}
})
return binding.root
}

View File

@ -3,7 +3,7 @@ package com.shabinder.musicForEveryone.recyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
@ -12,6 +12,7 @@ import com.shabinder.musicForEveryone.SharedViewModel
import com.shabinder.musicForEveryone.downloadHelper.DownloadHelper
import com.shabinder.musicForEveryone.utils.bindImage
import kaaes.spotify.webapi.android.models.Track
import kotlinx.coroutines.launch
class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>(),DownloadHelper {
@ -30,25 +31,25 @@ class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>(),Downl
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = trackList[position]
if(totalItems == 1){holder.coverImage.visibility = View.GONE}else{
bindImage(holder.coverImage,item.album.images[0].url)
}
bindImage(holder.coverImage,item.album.images[0].url)
holder.trackName.text = "${if(item.name.length > 22){"${item.name.subSequence(0,19)}..."}else{item.name}}"
holder.trackName.text = "${if(item.name.length > 17){"${item.name.subSequence(0,16)}..."}else{item.name}}"
holder.artistName.text = "${item.artists[0]?.name?:""}..."
holder.popularity.text = item.popularity.toString()
holder.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} seconds"
holder.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec"
holder.downloadBtn.setOnClickListener{
downloadTrack(sharedViewModel.ytDownloader,sharedViewModel.downloadManager,"${item.name} ${item.artists[0].name?:""}")
sharedViewModel.uiScope.launch {
downloadTrack(sharedViewModel.ytDownloader,sharedViewModel.downloadManager,"${item.name} ${item.artists[0].name?:""}")
}
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val trackName:TextView = itemView.findViewById(R.id.track_name)
val artistName:TextView = itemView.findViewById(R.id.artist)
val popularity:TextView = itemView.findViewById(R.id.popularity)
val duration:TextView = itemView.findViewById(R.id.duration)
val downloadBtn:Button = itemView.findViewById(R.id.btn_download)
val downloadBtn:ImageButton = itemView.findViewById(R.id.btn_download)
val coverImage:ImageView = itemView.findViewById(R.id.imageUrl)
}
}
}

View File

@ -10,12 +10,14 @@ import com.shabinder.musicForEveryone.R
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions()
.error(R.drawable.ic_musicplaceholder))
.into(imgView)
}
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions()
.error(R.drawable.ic_musicplaceholder))
.into(imgView)
}
}

View File

@ -1,15 +1,36 @@
package com.shabinder.musicForEveryone.utils
import kaaes.spotify.webapi.android.models.Playlist
import retrofit.http.GET
import retrofit.http.Path
import kaaes.spotify.webapi.android.models.*
import retrofit2.http.GET
import retrofit2.http.Path
interface SpotifyNewService {
@GET("/playlists/{playlist_id}")
fun getPlaylist(@Path("playlist_id") playlistId: String?): Playlist?
@GET("playlists/{playlist_id}")
suspend fun getPlaylist(@Path("playlist_id") playlistId: String?): Playlist?
@GET("tracks/{id}")
suspend fun getTrack(@Path("id") var1: String?): Track?
@GET("albums/{id}")
suspend fun getAlbum(@Path("id") var1: String?): Album?
@GET("me")
suspend fun getMe(): com.shabinder.musicForEveryone.utils.UserPrivate?
}
}
data class UserPrivate(
val country:String,
var display_name: String,
val email:String,
var external_urls: Map<String?, String?>? = null,
var followers: Followers? = null,
var href: String? = null,
var id: String? = null,
var images: List<Image?>? = null,
var product:String,
var type: String? = null,
var uri: String? = null)

View File

@ -7,13 +7,32 @@
android:id="@+id/mainActivity"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:background="@drawable/text_background"
android:padding="5dp"
android:paddingTop="6dp"
android:text="MainFragment"
android:textColor="@color/colorPrimary"
android:textSize="12dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<fragment
android:id="@+id/NavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/navigation"
tools:ignore="FragmentTagUsage" />

View File

@ -1,105 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="25dp"
android:fitsSystemWindows="true"
tools:context=".fragments.MainFragment">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="230dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:layout_scrollInterpolator="@android:anim/decelerate_interpolator"
app:toolbarId="@+id/toolbar">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/spotifyLink"
android:layout_width="257dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="20dp"
android:background="@drawable/text_background"
android:ems="10"
android:hint="Link From Spotify"
android:inputType="text"
android:padding="5dp"
android:textAlignment="center"
android:textColor="@color/white"
android:textColorHint="@color/grey"
android:textSize="19sp" />
<Button
android:id="@+id/btn_search"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:backgroundTint="@color/colorPrimary"
android:text="Search"
android:textSize="18sp" />
</LinearLayout>
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:contentDescription="Album Cover"
android:scaleType="centerInside"
android:src="@drawable/ic_launcher_foreground"
app:layout_collapseMode="parallax"/>
</LinearLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:id="@+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.MainFragment">
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="MainFragment"
android:background="@drawable/text_backdround"
android:padding="5dp"
android:textColor="@color/colorPrimary"
android:textSize="12dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/spotifyLink"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:background="@drawable/text_backdround"
android:hint="Link From Spotify"
android:ems="10"
android:padding="5dp"
android:inputType="text"
android:textAlignment="center"
android:textColor="@color/white"
android:textColorHint="@color/grey"
android:textSize="20dp"
app:layout_constraintEnd_toStartOf="@+id/btn_search"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_search"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:backgroundTint="@color/colorPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Search"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="@+id/spotifyLink"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/spotifyLink"
app:layout_constraintTop_toTopOf="@+id/spotifyLink" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/track_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="3dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_view"
app:layout_constraintVertical_bias="0.0" />
<ImageView
android:id="@+id/image_view"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/spotifyLink"
android:visibility="gone"/>
<TextView
android:id="@+id/title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="5dp"
android:text="TextView"
android:background="@drawable/text_backdround"
android:drawableTint="@color/colorPrimary"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textAppearance="@style/TextAppearance.AppTheme.Headline4"
android:textAllCaps="false"
android:layout_marginTop="10dp"
android:text="Shabinder's Playlist"
android:textAlignment="center"
android:textColor="@color/colorPrimary"
android:textSize="24sp"
android:textSize="26sp"
android:visibility="gone"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/track_list"
app:layout_constraintEnd_toStartOf="@+id/playlist_owner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/playlist_owner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/text_background_accented"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:text="by : Shabinder"
android:textColor="@color/white"
android:textSize="15sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/title_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/title_view" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/track_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="12dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_view" />
app:layout_constraintTop_toBottomOf="@+id/title_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -6,130 +6,80 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="80dp"
android:background="#000000"
android:layout_marginBottom="12dp"
>
<ImageView
android:id="@+id/imageUrl"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_height="80dp"
android:contentDescription="Track Image"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/artist"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_musicplaceholder"
tools:tint="#A3FFFFFF"/>
tools:tint="#A3FFFFFF" />
<TextView
android:id="@+id/track_name"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:fontFamily="monospace"
android:text="TextView"
android:letterSpacing="-0.03"
android:lines="1"
android:text="The Spectre"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppTheme.Headline4"
android:textColor="@color/colorPrimary"
android:textSize="20sp"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageUrl"
app:layout_constraintEnd_toStartOf="@+id/btn_download"
app:layout_constraintStart_toStartOf="@+id/artist"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:paddingLeft="9dp"
android:text="Artist"
android:textSize="12dp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/textview3"
app:layout_constraintEnd_toStartOf="@+id/artist"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/imageUrl"
app:layout_constraintTop_toBottomOf="@+id/track_name" />
<TextView
android:id="@+id/artist"
style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:paddingLeft="9dp"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="@+id/textView1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView1"
app:layout_constraintTop_toTopOf="@+id/textView1" />
<TextView
android:id="@+id/textview3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="9dp"
android:text="Popularity"
android:textSize="12dp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/textView5"
app:layout_constraintEnd_toStartOf="@+id/popularity"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/imageUrl"
app:layout_constraintTop_toBottomOf="@+id/textView1" />
<TextView
android:id="@+id/popularity"
style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="9dp"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="@+id/textview3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textview3"
app:layout_constraintTop_toTopOf="@+id/textview3" />
<TextView
android:id="@+id/textView5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:paddingLeft="9dp"
android:text="Duration"
android:textSize="12dp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/btn_download"
android:text="Alan Walker"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/duration"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/imageUrl"
app:layout_constraintTop_toBottomOf="@+id/textview3" />
app:layout_constraintTop_toBottomOf="@+id/track_name" />
<TextView
android:id="@+id/duration"
style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="9dp"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="@+id/textView5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView5"
app:layout_constraintTop_toTopOf="@+id/textView5" />
android:text="4 minutes, 20 sec"
app:layout_constraintBottom_toBottomOf="@+id/artist"
app:layout_constraintEnd_toStartOf="@+id/btn_download"
app:layout_constraintStart_toEndOf="@+id/artist"
app:layout_constraintTop_toTopOf="@+id/artist" />
<Button
<ImageButton
android:id="@+id/btn_download"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:backgroundTint="#27FF3D"
android:text="Download"
android:textSize="14sp"
android:layout_width="60dp"
android:layout_height="0dp"
android:backgroundTint="@color/black"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageUrl" />
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_arrow" />
</androidx.constraintlayout.widget.ConstraintLayout>