Added Gradients in App Ui & Added Download All Tracks Functionality

This commit is contained in:
shabinder 2020-07-22 21:30:42 +05:30
parent 735f7c270e
commit 0ce929df9b
10 changed files with 175 additions and 114 deletions

View File

@ -36,6 +36,10 @@
android:name="com.spotify.sdk.android.authentication.LoginActivity" android:name="com.spotify.sdk.android.authentication.LoginActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" /> android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application> </application>
</manifest> </manifest>

View File

@ -104,8 +104,8 @@ class MainActivity : AppCompatActivity() ,DownloadHelper{
sharedViewModel.uiScope.launch { sharedViewModel.uiScope.launch {
val me = spotifyExtra?.getMe()?.display_name val me = spotifyExtra?.getMe()?.display_name
sharedViewModel.userName.value = me sharedViewModel.userName.value = "Logged in as: $me"
Log.i("Network",me!!) Log.i("Network","Hello, " + me!!)
} }
sharedViewModel.userName.observe(this, Observer { sharedViewModel.userName.observe(this, Observer {

View File

@ -14,43 +14,49 @@ import kotlinx.coroutines.withContext
import java.io.File import java.io.File
interface DownloadHelper { interface DownloadHelper {
suspend fun downloadTrack(ytDownloader: YoutubeDownloader?, downloadManager: DownloadManager?, searchQuery:String){ suspend fun downloadTrack(
ytDownloader: YoutubeDownloader?,
downloadManager: DownloadManager?,
searchQuery: String
) {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO) {
val downloadIdList = mutableListOf<Int>() val data = YoutubeInterface.search(searchQuery)?.get(0)
val data = YoutubeInterface.search(searchQuery)?.get(0) if (data == null) {
if (data==null){Log.i("DownloadHelper","Youtube Request Failed!")}else{ Log.i("DownloadHelper", "Youtube Request Failed!")
} else {
val video = ytDownloader?.getVideo(data.id) val video = ytDownloader?.getVideo(data.id)
//Fetching a Video Object. //Fetching a Video Object.
val details = video?.details() val details = video?.details()
try{
val format: Format =
video?.findAudioWithQuality(AudioQuality.medium)?.get(0) as Format
val audioUrl = format.url()
Log.i("DHelper Link Found", audioUrl)
if (audioUrl != null) {
downloadFile(audioUrl, downloadManager, details!!.title())
} else {
Log.i("YT audio url is null", format.toString())
}
}catch (e:ArrayIndexOutOfBoundsException){
Log.i("Catch",e.toString())
}
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())}
} }
// 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 * Downloading Using Android Download Manager
* */ * */
suspend fun downloadFile(url: String, downloadManager: DownloadManager?,title:String){ suspend fun downloadFile(url: String, downloadManager: DownloadManager?, title: String) {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO) {
val audioUri = Uri.parse(url) val audioUri = Uri.parse(url)
val outputDir = File.separator + "Spotify-Downloads" +File.separator + "${removeIllegalChars(title)}.mp3" val outputDir =
File.separator + "Spotify-Downloads" + File.separator + "${removeIllegalChars(title)}.mp3"
val request = DownloadManager.Request(audioUri) val request = DownloadManager.Request(audioUri)
.setAllowedNetworkTypes( .setAllowedNetworkTypes(
@ -60,10 +66,10 @@ interface DownloadHelper {
.setAllowedOverRoaming(false) .setAllowedOverRoaming(false)
.setTitle(title) .setTitle(title)
.setDescription("Spotify Downloader Working Up here...") .setDescription("Spotify Downloader Working Up here...")
.setDestinationInExternalPublicDir(Environment.DIRECTORY_MUSIC,outputDir) .setDestinationInExternalPublicDir(Environment.DIRECTORY_MUSIC, outputDir)
.setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED) .setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
downloadManager?.enqueue(request) downloadManager?.enqueue(request)
Log.i("DownloadManager","Download Request Sent") Log.i("DownloadManager", "Download Request Sent")
} }
} }

View File

@ -61,51 +61,56 @@ class MainFragment : Fragment(),DownloadHelper {
adapter.trackList = trackList adapter.trackList = trackList
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
Log.i("Adapter",trackList.size.toString()) Log.i("Adapter",trackList.size.toString())
binding.btnDownloadAll.setOnClickListener { downloadAllTracks(trackList) }
} }
} }
"album" -> { "album" -> {
sharedViewModel.uiScope.launch{ sharedViewModel.uiScope.launch{
val albumObject = sharedViewModel.getAlbumDetails(link) val albumObject = sharedViewModel.getAlbumDetails(link)
// binding.titleView.text = albumObject!!.name
// binding.titleView.visibility =View.VISIBLE
binding.imageView.visibility =View.VISIBLE
binding.btnDownloadAll.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()
binding.titleView.text = albumObject!!.name Log.i("Adapter",trackList.size.toString())
binding.titleView.visibility =View.VISIBLE
binding.imageView.visibility =View.VISIBLE
val trackList = mutableListOf<Track>() bindImage(binding.imageView, albumObject.images[0].url)
albumObject.tracks?.items?.forEach { trackList.add(it as Track) } binding.btnDownloadAll.setOnClickListener { downloadAllTracks(trackList) }
adapter.totalItems = trackList.size
adapter.trackList = trackList
adapter.notifyDataSetChanged()
Log.i("Adapter",trackList.size.toString()) }
bindImage(binding.imageView, albumObject.images[0].url)
}
} }
"playlist" -> { "playlist" -> {
sharedViewModel.uiScope.launch{ sharedViewModel.uiScope.launch{
val playlistObject = sharedViewModel.getPlaylistDetails(link) val playlistObject = sharedViewModel.getPlaylistDetails(link)
binding.btnDownloadAll.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) 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.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)
binding.btnDownloadAll.setOnClickListener { downloadAllTracks(trackList) }
} }
@ -127,6 +132,12 @@ class MainFragment : Fragment(),DownloadHelper {
return binding.root return binding.root
} }
private fun downloadAllTracks(trackList : List<Track>) {
sharedViewModel.uiScope.launch {
trackList.forEach { downloadTrack(sharedViewModel.ytDownloader,sharedViewModel.downloadManager,"${it.name} ${it.artists[0].name?:""}") }
}
}
private fun showToast(message:String){ private fun showToast(message:String){
Toast.makeText(context,message,Toast.LENGTH_SHORT).show() Toast.makeText(context,message,Toast.LENGTH_SHORT).show()
} }

View File

@ -11,6 +11,8 @@ object YoutubeInterface {
private var youtube: YouTube? = null private var youtube: YouTube? = null
private var query:YouTube.Search.List? = null private var query:YouTube.Search.List? = null
var apiKey:String = "AIzaSyDuRmMA_2mF56BjlhhNpa0SIbjMgjjFaEI" var apiKey:String = "AIzaSyDuRmMA_2mF56BjlhhNpa0SIbjMgjjFaEI"
var apiKey2:String = "AIzaSyCotyqgqmz5qw4-IH0tiezIrIIDHLI2yNs"
var clientID : String = "1040727735015-er2mvvljt45cabkuqimsh3iabqvfpvms.apps.googleusercontent.com" var clientID : String = "1040727735015-er2mvvljt45cabkuqimsh3iabqvfpvms.apps.googleusercontent.com"
fun youtubeConnector() { fun youtubeConnector() {
@ -23,14 +25,14 @@ object YoutubeInterface {
query?.maxResults = 1 query?.maxResults = 1
query?.type = "video" query?.type = "video"
query?.fields = query?.fields =
"items(id/kind,id/videoId,snippet/title,snippet/description,snippet/thumbnails/default/url)" "items(id/videoId,snippet/title,snippet/thumbnails/default/url)"
} catch (e: IOException) { } catch (e: IOException) {
Log.i("YC", "Could not initialize: $e") Log.i("YI", "Could not initialize: $e")
} }
} }
fun search(keywords: String?): List<VideoItem>? { fun search(keywords: String?): List<VideoItem>? {
Log.i("YC searched for",keywords.toString()) Log.i("YI searched for",keywords.toString())
if (youtube == null){youtubeConnector()} if (youtube == null){youtubeConnector()}
query!!.q= keywords query!!.q= keywords
return try { return try {
@ -42,23 +44,25 @@ object YoutubeInterface {
val item = VideoItem( val item = VideoItem(
id = result.id.videoId, id = result.id.videoId,
title = result.snippet.title, title = result.snippet.title,
description = result.snippet.description, // description = result.snippet.description,
thumbnailUrl = result.snippet.thumbnails.default.url thumbnailUrl = result.snippet.thumbnails.default.url
) )
items.add(item) items.add(item)
Log.i("YC links received",item.id) Log.i("YI links received",item.id)
} }
items items
} catch (e: IOException) { } catch (e: IOException) {
Log.d("YC", "Could not search: $e") Log.d("YI", "Could not search: $e")
null if(query?.key == apiKey2){return null}
query?.key = apiKey2
search(keywords)
} }
} }
data class VideoItem( data class VideoItem(
val id:String, val id:String,
val title:String, val title:String,
val description: String, // val description: String,
val thumbnailUrl:String val thumbnailUrl:String
) )

View File

@ -12,12 +12,12 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="2dp" android:layout_marginBottom="2dp"
android:background="@drawable/text_background" android:background="@drawable/text_background_accented"
android:padding="5dp" android:padding="5dp"
android:paddingTop="6dp" android:paddingTop="6dp"
android:text="MainFragment" android:text="Authentication Needed"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
android:textSize="12dp" android:textSize="10dp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -10,64 +10,96 @@
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context=".fragments.MainFragment"> tools:context=".fragments.MainFragment">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_download_all"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:background="@drawable/btn_design"
android:drawableEnd="@drawable/ic_arrow_slim"
android:drawablePadding="4dp"
android:drawableTint="@color/black"
android:padding="12dp"
android:text="Download All |"
android:textColor="@color/black"
android:textSize="16sp"
android:visibility="visible"
app:layout_anchor="@+id/constraint_layout"
app:layout_anchorGravity="top|center" />
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar" android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="230dp"> android:layout_height="280dp">
<com.google.android.material.appbar.CollapsingToolbarLayout <com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:contentScrim="?attr/colorPrimary" app:contentScrim="#CE6A40FF"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed" app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:layout_scrollInterpolator="@android:anim/decelerate_interpolator" app:layout_scrollInterpolator="@android:anim/decelerate_interpolator"
app:toolbarId="@+id/toolbar"> app:toolbarId="@+id/toolbar">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <EditText
android:id="@+id/spotifyLink" android:id="@+id/spotifyLink"
android:layout_width="257dp" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="48dp"
android:layout_marginStart="8dp" android:layout_marginStart="16dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="20dp" android:layout_marginBottom="8dp"
android:background="@drawable/text_background" android:background="@drawable/text_background_accented"
android:ems="10" android:ems="10"
android:hint="Link From Spotify" android:hint="Link From Spotify"
android:inputType="text" android:inputType="text"
android:padding="5dp" android:padding="8dp"
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/white" android:textColor="@color/white"
android:textColorHint="@color/grey" android:textColorHint="@color/grey"
android:textSize="19sp" /> android:textSize="19sp"
app:layout_constraintBottom_toTopOf="@+id/image_view"
app:layout_constraintEnd_toStartOf="@+id/btn_search"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_search" android:id="@+id/btn_search"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="44dp"
android:layout_marginTop="8dp" android:layout_marginEnd="16dp"
android:backgroundTint="@color/colorPrimary" android:background="@drawable/btn_design"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:text="Search" android:text="Search"
android:textSize="18sp" /> android:textColor="@color/black"
</LinearLayout> android:textSize="16sp"
<ImageView app:layout_constraintBottom_toBottomOf="@id/spotifyLink"
android:id="@+id/image_view" app:layout_constraintEnd_toEndOf="parent"
android:layout_width="match_parent" app:layout_constraintStart_toEndOf="@+id/spotifyLink"
android:layout_height="match_parent" app:layout_constraintTop_toTopOf="@id/spotifyLink" />
android:layout_marginTop="8dp"
android:contentDescription="Album Cover" <ImageView
android:scaleType="centerInside" android:id="@+id/image_view"
android:src="@drawable/ic_launcher_foreground" android:layout_width="0dp"
app:layout_collapseMode="parallax"/> android:layout_height="0dp"
</LinearLayout> android:layout_marginTop="6dp"
android:contentDescription="Album Cover"
android:foreground="@drawable/gradient"
android:padding="20dp"
android:paddingBottom="35dp"
android:src="@drawable/ic_launcher_foreground"
android:visibility="gone"
app:layout_collapseMode="parallax"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_search" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
@ -113,7 +145,7 @@
android:id="@+id/track_list" android:id="@+id/track_list"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="12dp" android:layout_marginTop="22dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -121,7 +153,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_view" /> app:layout_constraintTop_toBottomOf="@+id/title_view" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -28,17 +28,16 @@
android:id="@+id/track_name" android:id="@+id/track_name"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="14dp"
android:fontFamily="monospace" android:fontFamily="@font/raleway_semibold"
android:letterSpacing="-0.03" android:letterSpacing="0.04"
android:lines="1" android:lines="1"
android:text="The Spectre" android:text="The Spectre"
android:textAllCaps="false" android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppTheme.Headline4" android:textAppearance="@style/TextAppearance.AppTheme.Headline4"
android:textColor="@color/colorPrimary" android:textColor="#9AB3FF"
android:textSize="18sp" android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/btn_download" app:layout_constraintEnd_toStartOf="@+id/btn_download"
app:layout_constraintStart_toStartOf="@+id/artist" app:layout_constraintStart_toStartOf="@+id/artist"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -49,9 +48,11 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginTop="12dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:paddingLeft="9dp" android:paddingLeft="9dp"
android:text="Alan Walker" android:text="Alan Walker"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/duration" app:layout_constraintEnd_toStartOf="@+id/duration"
app:layout_constraintStart_toEndOf="@+id/imageUrl" app:layout_constraintStart_toEndOf="@+id/imageUrl"
@ -63,8 +64,10 @@
style="@style/TextAppearance.AppCompat.Body2" style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="3dp"
android:paddingLeft="9dp" android:paddingLeft="9dp"
android:text="4 minutes, 20 sec" android:text="4 minutes, 20 sec"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/artist" app:layout_constraintBottom_toBottomOf="@+id/artist"
app:layout_constraintEnd_toStartOf="@+id/btn_download" app:layout_constraintEnd_toStartOf="@+id/btn_download"
app:layout_constraintStart_toEndOf="@+id/artist" app:layout_constraintStart_toEndOf="@+id/artist"

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="colorPrimary">#0AFF02</color> <color name="colorPrimary">#FC5C7D</color>
<color name="colorPrimaryDark">#43FF56</color> <color name="colorPrimaryDark">#CE1CFF</color>
<color name="colorAccent">#43FF56</color> <color name="colorAccent">#8497FA</color>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFF</color>
<color name="grey">#99FFFFFF</color> <color name="grey">#99FFFFFF</color>
<color name="black">#000000</color> <color name="black">#000000</color>

View File

@ -2,12 +2,12 @@
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimaryDark">#00730C</item> <item name="colorPrimaryDark">#000000</item>
<item name="colorPrimary">#05A300</item> <item name="colorPrimary">#FC5C7D</item>
<item name="android:background">#000000</item> <item name="android:background">#000000</item>
<item name="android:textColor">#FFFFFF</item> <item name="android:textColor">#FFFFFF</item>
<item name="colorAccent">#43FF56</item> <item name="colorAccent">#6A82FB</item>
<item name="android:outlineAmbientShadowColor" tools:targetApi="p">#6DFF7C</item> <item name="android:outlineAmbientShadowColor" tools:targetApi="p">#A9B200FF</item>
<item name="android:radius">11dp</item> <item name="android:radius">11dp</item>
<!-- Text Appearances !--> <!-- Text Appearances !-->
<!-- use our brand's custom TextAppearance4 !--> <!-- use our brand's custom TextAppearance4 !-->