Apk size reduced,MVVM Approach,UI Changes

This commit is contained in:
shabinder 2020-08-05 13:16:29 +05:30
parent eecb7c3b43
commit 5f1176ead5
19 changed files with 684 additions and 1009 deletions

View File

@ -90,6 +90,7 @@ dependencies {
implementation "androidx.room:room-runtime:2.2.5" implementation "androidx.room:room-runtime:2.2.5"
implementation project(path: ':mobile-ffmpeg') implementation project(path: ':mobile-ffmpeg')
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
kapt "androidx.room:room-compiler:2.2.5" kapt "androidx.room:room-compiler:2.2.5"
implementation "androidx.room:room-ktx:2.2.5" implementation "androidx.room:room-ktx:2.2.5"
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") { implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {

View File

@ -36,7 +36,7 @@ import com.github.javiersantos.appupdater.AppUpdater
import com.github.javiersantos.appupdater.enums.UpdateFrom import com.github.javiersantos.appupdater.enums.UpdateFrom
import com.github.kiulian.downloader.YoutubeDownloader import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.databinding.MainActivityBinding import com.shabinder.spotiflyer.databinding.MainActivityBinding
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper
import com.shabinder.spotiflyer.utils.SpotifyService import com.shabinder.spotiflyer.utils.SpotifyService
import com.shabinder.spotiflyer.utils.SpotifyServiceToken import com.shabinder.spotiflyer.utils.SpotifyServiceToken
import com.shabinder.spotiflyer.utils.createDirectory import com.shabinder.spotiflyer.utils.createDirectory
@ -74,7 +74,7 @@ class MainActivity : AppCompatActivity(){
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java) sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
sharedPref = this.getPreferences(Context.MODE_PRIVATE) sharedPref = this.getPreferences(Context.MODE_PRIVATE)
//starting Notification and Downloader Service! //starting Notification and Downloader Service!
DownloadHelper.startService(this) SpotifyDownloadHelper.startService(this)
/* if(sharedPref?.contains("token")!! && (sharedPref?.getLong("time",System.currentTimeMillis()/1000/60/60)!! < (System.currentTimeMillis()/1000/60/60)) ){ /* if(sharedPref?.contains("token")!! && (sharedPref?.getLong("time",System.currentTimeMillis()/1000/60/60)!! < (System.currentTimeMillis()/1000/60/60)) ){
val savedToken = sharedPref?.getString("token","error")!! val savedToken = sharedPref?.getString("token","error")!!
@ -85,7 +85,7 @@ class MainActivity : AppCompatActivity(){
implementSpotifyService(savedToken) implementSpotifyService(savedToken)
}else{authenticateSpotify()}*/ }else{authenticateSpotify()}*/
if(sharedViewModel.spotifyService == null){ if(sharedViewModel.spotifyService.value == null){
authenticateSpotify() authenticateSpotify()
}else{ }else{
implementSpotifyService(sharedViewModel.accessToken.value!!) implementSpotifyService(sharedViewModel.accessToken.value!!)
@ -102,7 +102,7 @@ class MainActivity : AppCompatActivity(){
//Object to download From Youtube {"https://github.com/sealedtx/java-youtube-downloader"} //Object to download From Youtube {"https://github.com/sealedtx/java-youtube-downloader"}
ytDownloader = YoutubeDownloader() ytDownloader = YoutubeDownloader()
sharedViewModel.ytDownloader = ytDownloader sharedViewModel.ytDownloader.value = ytDownloader
handleIntentFromExternalActivity() handleIntentFromExternalActivity()
} }
@ -168,7 +168,7 @@ class MainActivity : AppCompatActivity(){
.build() .build()
spotifyService = retrofit.create(SpotifyService::class.java) spotifyService = retrofit.create(SpotifyService::class.java)
sharedViewModel.spotifyService = spotifyService sharedViewModel.spotifyService.value = spotifyService
} }
private fun getSpotifyToken(){ private fun getSpotifyToken(){
@ -283,12 +283,12 @@ class MainActivity : AppCompatActivity(){
} }
private fun createDir() { private fun createDir() {
createDirectory(DownloadHelper.defaultDir) createDirectory(SpotifyDownloadHelper.defaultDir)
createDirectory(DownloadHelper.defaultDir+".Images/") createDirectory(SpotifyDownloadHelper.defaultDir+".Images/")
createDirectory(DownloadHelper.defaultDir+"Tracks/") createDirectory(SpotifyDownloadHelper.defaultDir+"Tracks/")
createDirectory(DownloadHelper.defaultDir+"Albums/") createDirectory(SpotifyDownloadHelper.defaultDir+"Albums/")
createDirectory(DownloadHelper.defaultDir+"Playlists/") createDirectory(SpotifyDownloadHelper.defaultDir+"Playlists/")
createDirectory(DownloadHelper.defaultDir+"YT_Downloads/") createDirectory(SpotifyDownloadHelper.defaultDir+"YT_Downloads/")
} }
private fun checkIfLatestVersion() { private fun checkIfLatestVersion() {

View File

@ -24,9 +24,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.github.kiulian.downloader.YoutubeDownloader import com.github.kiulian.downloader.YoutubeDownloader
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.shabinder.spotiflyer.models.Album
import com.shabinder.spotiflyer.models.Playlist
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.utils.SpotifyService import com.shabinder.spotiflyer.utils.SpotifyService
import com.shreyaspatil.EasyUpiPayment.EasyUpiPayment import com.shreyaspatil.EasyUpiPayment.EasyUpiPayment
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -36,11 +33,11 @@ import java.io.File
class SharedViewModel : ViewModel() { class SharedViewModel : ViewModel() {
var intentString = "" var intentString = ""
var accessToken = MutableLiveData<String>().apply { value = "" } var spotifyService = MutableLiveData<SpotifyService>()
var spotifyService : SpotifyService? = null var ytDownloader = MutableLiveData<YoutubeDownloader>()
var ytDownloader : YoutubeDownloader? = null
var isConnected = MutableLiveData<Boolean>().apply { value = false }
var easyUpiPayment: EasyUpiPayment? = null var easyUpiPayment: EasyUpiPayment? = null
var accessToken = MutableLiveData<String>().apply { value = "" }
var isConnected = MutableLiveData<Boolean>().apply { value = false }
val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator + ".Images" + File.separator val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator + ".Images" + File.separator
@ -48,17 +45,6 @@ class SharedViewModel : ViewModel() {
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
suspend fun getTrackDetails(trackLink:String): Track?{
return spotifyService?.getTrack(trackLink)
}
suspend fun getAlbumDetails(albumLink:String): Album?{
return spotifyService?.getAlbum(albumLink)
}
suspend fun getPlaylistDetails(link:String): Playlist?{
return spotifyService?.getPlaylist(link)
}
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
viewModelJob.cancel() viewModelJob.cancel()

View File

@ -35,25 +35,25 @@ import com.github.kiulian.downloader.YoutubeDownloader
import com.github.kiulian.downloader.model.formats.Format import com.github.kiulian.downloader.model.formats.Format
import com.github.kiulian.downloader.model.quality.AudioQuality import com.github.kiulian.downloader.model.quality.AudioQuality
import com.shabinder.spotiflyer.SharedViewModel import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.fragments.MainFragment
import com.shabinder.spotiflyer.models.DownloadObject import com.shabinder.spotiflyer.models.DownloadObject
import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.ui.spotify.SpotifyFragment
import com.shabinder.spotiflyer.worker.ForegroundService import com.shabinder.spotiflyer.worker.ForegroundService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
object DownloadHelper { object SpotifyDownloadHelper {
var webView:WebView? = null var webView:WebView? = null
var context : Context? = null var context : Context? = null
var statusBar:TextView? = null var statusBar:TextView? = null
val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator
private var downloadList = arrayListOf<DownloadObject>()
var sharedViewModel:SharedViewModel? = null var sharedViewModel:SharedViewModel? = null
private var isBrowserLoading = false private var isBrowserLoading = false
private var total = 0 private var total = 0
private var Processed = 0 private var Processed = 0
private var listProcessed:Boolean = false
var youtubeList = mutableListOf<YoutubeRequest>() var youtubeList = mutableListOf<YoutubeRequest>()
/** /**
@ -64,32 +64,27 @@ object DownloadHelper {
subFolder: String?, subFolder: String?,
trackList: List<Track>, ytDownloader: YoutubeDownloader?) { trackList: List<Track>, ytDownloader: YoutubeDownloader?) {
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
var size = trackList.size total += trackList.size // Adding New Download List Count to StatusBar
total += size
animateStatusBar()
trackList.forEach { trackList.forEach {
size--
val outputFile:String = Environment.getExternalStorageDirectory().toString() + File.separator + val outputFile:String = Environment.getExternalStorageDirectory().toString() + File.separator +
defaultDir + removeIllegalChars(type) + File.separator + (if(subFolder == null){""}else{ removeIllegalChars(subFolder) + File.separator} + removeIllegalChars(it.name!!)+".mp3") defaultDir + removeIllegalChars(type) + File.separator + (if(subFolder == null){""}else{ removeIllegalChars(subFolder) + File.separator} + removeIllegalChars(it.name!!)+".mp3")
if(File(outputFile).exists()){//Download Already Present!! if(File(outputFile).exists()){//Download Already Present!!
Processed++ Processed++
updateStatusBar()
}else{ }else{
if(isBrowserLoading){ if(isBrowserLoading){//WebView Busy!!
if(size == 0){ if (listProcessed){//Previous List request progress check
youtubeList.add(YoutubeRequest(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it ,0 )) getYTLink(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it)
}else{ listProcessed = false//Notifying A list Processing Started
}else{//Adding Requests to a Queue
youtubeList.add(YoutubeRequest(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it)) youtubeList.add(YoutubeRequest(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it))
} }
}else{ }else{
if(size == 0){ getYTLink(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it)
getYTLink(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it ,0 )
}else{
getYTLink(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it)
}
} }
} }
updateStatusBar()
} }
animateStatusBar()
} }
} }
@ -97,13 +92,12 @@ object DownloadHelper {
//TODO CleanUp here and there!! //TODO CleanUp here and there!!
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
suspend fun getYTLink(mainFragment: MainFragment? = null, suspend fun getYTLink(spotifyFragment: SpotifyFragment? = null,
type:String, type:String,
subFolder:String?, subFolder:String?,
ytDownloader: YoutubeDownloader?, ytDownloader: YoutubeDownloader?,
searchQuery: String, searchQuery: String,
track: Track, track: Track){
index: Int? = null){
val searchText = searchQuery.replace("\\s".toRegex(), "+") val searchText = searchQuery.replace("\\s".toRegex(), "+")
val url = "https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q=$searchText" val url = "https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q=$searchText"
Log.i("DH YT LINK ",url) Log.i("DH YT LINK ",url)
@ -122,15 +116,16 @@ object DownloadHelper {
val id = value!!.substringAfterLast("=", "error").replace("\"","") val id = value!!.substringAfterLast("=", "error").replace("\"","")
Log.i("YT-id",id) Log.i("YT-id",id)
if(id !="error"){//Link extracting error if(id !="error"){//Link extracting error
mainFragment?.showToast("Starting Download") spotifyFragment?.showToast("Starting Download")
Processed++ Processed++
if(Processed == total)listProcessed = true //List Processesd
updateStatusBar() updateStatusBar()
downloadFile(subFolder, type, track, index,ytDownloader,id) downloadFile(subFolder, type, track,ytDownloader,id)
} }
if(youtubeList.isNotEmpty()){ if(youtubeList.isNotEmpty()){
val request = youtubeList[0] val request = youtubeList[0]
sharedViewModel!!.uiScope.launch { sharedViewModel!!.uiScope.launch {
getYTLink(request.mainFragment,request.type,request.subFolder,request.ytDownloader,request.searchQuery,request.track,request.index) getYTLink(request.spotifyFragment,request.type,request.subFolder,request.ytDownloader,request.searchQuery,request.track)
} }
youtubeList.remove(request) youtubeList.remove(request)
if(youtubeList.size == 0){//list processing completed , webView is free again! if(youtubeList.size == 0){//list processing completed , webView is free again!
@ -145,41 +140,17 @@ object DownloadHelper {
} }
@SuppressLint("SetJavaScriptEnabled")
fun applyWebViewSettings(webView: WebView) {
val desktopUserAgent =
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.4) Gecko/20100101 Firefox/4.0"
val mobileUserAgent =
"Mozilla/5.0 (Linux; U; Android 4.4; en-us; Nexus 4 Build/JOP24G) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30"
//Choose Mobile/Desktop client.
webView.settings.userAgentString = desktopUserAgent
webView.settings.loadWithOverviewMode = true
webView.settings.loadWithOverviewMode = true
webView.settings.builtInZoomControls = true
webView.settings.setSupportZoom(true)
webView.isScrollbarFadingEnabled = false
webView.scrollBarStyle = WebView.SCROLLBARS_OUTSIDE_OVERLAY
webView.settings.displayZoomControls = false
webView.settings.useWideViewPort = true
webView.settings.javaScriptEnabled = true
webView.settings.loadsImagesAutomatically = false
webView.settings.blockNetworkImage = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
webView.settings.safeBrowsingEnabled = true
}
}
private fun updateStatusBar() { private fun updateStatusBar() {
statusBar!!.visibility = View.VISIBLE statusBar!!.visibility = View.VISIBLE
statusBar?.text = "Total: $total Processed: $Processed" statusBar?.text = "Total: $total Processed: $Processed"
} }
fun downloadFile(subFolder: String?, type: String, track:Track, index:Int? = null,ytDownloader: YoutubeDownloader?,id: String) { fun downloadFile(subFolder: String?, type: String, track:Track, ytDownloader: YoutubeDownloader?, id: String) {
sharedViewModel!!.uiScope.launch { sharedViewModel!!.uiScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val video = ytDownloader?.getVideo(id) val video = ytDownloader?.getVideo(id)
val detail = video?.details()
val format:Format? =try { val format:Format? =try {
video?.findAudioWithQuality(AudioQuality.high)?.get(0) as Format video?.findAudioWithQuality(AudioQuality.high)?.get(0) as Format
}catch (e:java.lang.IndexOutOfBoundsException){ }catch (e:java.lang.IndexOutOfBoundsException){
@ -196,9 +167,7 @@ object DownloadHelper {
} }
format?.let { format?.let {
val url:String = format.url() val url:String = format.url()
// Log.i("DHelper Link Found", url)
Log.i("DHelper Link Found", url)
val outputFile:String = Environment.getExternalStorageDirectory().toString() + File.separator + val outputFile:String = Environment.getExternalStorageDirectory().toString() + File.separator +
defaultDir + removeIllegalChars(type) + File.separator + (if(subFolder == null){""}else{ removeIllegalChars(subFolder) + File.separator} + removeIllegalChars(track.name!!)+".m4a") defaultDir + removeIllegalChars(type) + File.separator + (if(subFolder == null){""}else{ removeIllegalChars(subFolder) + File.separator} + removeIllegalChars(track.name!!)+".m4a")
@ -209,17 +178,6 @@ object DownloadHelper {
) )
Log.i("DH",outputFile) Log.i("DH",outputFile)
startService(context!!, downloadObject) startService(context!!, downloadObject)
/*if(index==null){
downloadList.add(downloadObject)
}else{
downloadList.add(downloadObject)
startService(context!!, downloadList)
Log.i("DH No of Songs", downloadList.size.toString())
downloadList = arrayListOf()
}*/
// downloadList.add(downloadObject)
// downloadList = arrayListOf()
} }
} }
} }
@ -281,10 +239,33 @@ object DownloadHelper {
anim.repeatCount = Animation.INFINITE anim.repeatCount = Animation.INFINITE
statusBar?.animation = anim statusBar?.animation = anim
} }
@SuppressLint("SetJavaScriptEnabled")
fun applyWebViewSettings(webView: WebView) {
val desktopUserAgent =
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.4) Gecko/20100101 Firefox/4.0"
val mobileUserAgent =
"Mozilla/5.0 (Linux; U; Android 4.4; en-us; Nexus 4 Build/JOP24G) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30"
//Choose Mobile/Desktop client.
webView.settings.userAgentString = desktopUserAgent
webView.settings.loadWithOverviewMode = true
webView.settings.loadWithOverviewMode = true
webView.settings.builtInZoomControls = true
webView.settings.setSupportZoom(true)
webView.isScrollbarFadingEnabled = false
webView.scrollBarStyle = WebView.SCROLLBARS_OUTSIDE_OVERLAY
webView.settings.displayZoomControls = false
webView.settings.useWideViewPort = true
webView.settings.javaScriptEnabled = true
webView.settings.loadsImagesAutomatically = false
webView.settings.blockNetworkImage = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
webView.settings.safeBrowsingEnabled = true
}
}
} }
data class YoutubeRequest( data class YoutubeRequest(
val mainFragment: MainFragment? = null, val spotifyFragment: SpotifyFragment? = null,
val type:String, val type:String,
val subFolder:String?, val subFolder:String?,
val ytDownloader: YoutubeDownloader?, val ytDownloader: YoutubeDownloader?,

View File

@ -1,445 +0,0 @@
/*
* Copyright (C) 2020 Shabinder Singh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.spotiflyer.fragments
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.text.SpannableStringBuilder
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.ValueCallback
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.core.net.toUri
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.shabinder.spotiflyer.MainActivity
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.MainFragmentBinding
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper.applyWebViewSettings
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper.downloadAllTracks
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
import com.shabinder.spotiflyer.utils.SpotifyService
import com.shabinder.spotiflyer.utils.bindImage
import com.shabinder.spotiflyer.utils.copyTo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.IOException
@Suppress("DEPRECATION")
class MainFragment : Fragment() {
private lateinit var binding:MainFragmentBinding
private lateinit var mainViewModel: MainViewModel
private lateinit var sharedViewModel: SharedViewModel
private lateinit var adapter:TrackListAdapter
private var spotifyService : SpotifyService? = null
private var type:String = ""
private var spotifyLink = ""
private var i: Intent? = null
private var webView: WebView? = null
@SuppressLint("SetJavaScriptEnabled")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater,R.layout.main_fragment,container,false)
webView = binding.webView
DownloadHelper.webView = binding.webView
DownloadHelper.context = requireContext()
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
spotifyService = sharedViewModel.spotifyService
DownloadHelper.sharedViewModel = sharedViewModel
DownloadHelper.statusBar = binding.StatusBar
setUpUsageText()
openSpotifyButton()
openGithubButton()
openInstaButton()
binding.btnDonate.setOnClickListener {
sharedViewModel.easyUpiPayment?.startPayment()
}
binding.btnSearch.setOnClickListener {
val link = binding.linkSearch.text.toString()
if(link.contains("open.spotify",true)){
spotifySearch()
}
if(link.contains("youtube.com",true) || link.contains("youtu.be",true) ){
youtubeSearch()
}
}
handleIntent()
//Handling Device Configuration Change
if(savedInstanceState != null && savedInstanceState["searchLink"].toString() != ""){
binding.linkSearch.setText(savedInstanceState["searchLink"].toString())
binding.btnSearch.performClick()
setUiVisibility()
}
return binding.root
}
private fun youtubeSearch() {
val youtubeLink = binding.linkSearch.text.toString()
var title = ""
val link = youtubeLink.removePrefix("https://").removePrefix("http://")
val sampleDomain1 = "youtube.com"
val sampleDomain2 = "youtu.be"
if(!link.contains("playlist",true)){
var searchId = "error"
if(link.contains(sampleDomain1,true) ){
searchId = link.substringAfterLast("=","error")
}
if(link.contains(sampleDomain2,true) && !link.contains("playlist",true) ){
searchId = link.substringAfterLast("/","error")
}
if(searchId != "error"){
val coverLink = "https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
applyWebViewSettings(webView!!)
sharedViewModel.uiScope.launch {
webView!!.loadUrl(youtubeLink)
webView!!.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
view?.evaluateJavascript(
"document.getElementsByTagName(\"h1\")[0].textContent"
,object : ValueCallback<String> {
override fun onReceiveValue(value: String?) {
title = DownloadHelper.removeIllegalChars(value.toString()).toString()
Log.i("YT-id", title)
Log.i("YT-id", value)
Log.i("YT-id", coverLink)
setUiVisibility()
bindImage(binding.imageView,coverLink)
binding.btnDownloadAll.setOnClickListener {
showToast("Starting Download in Few Seconds")
//TODO Clean This Code!
DownloadHelper.downloadFile(null,"YT_Downloads",Track(name = value,ytCoverUrl = coverLink),0,sharedViewModel.ytDownloader,searchId)
}
}
})
}
}
}
}
}else(showToast("Your Youtube Link is not of a Video!!"))
}
private fun spotifySearch(){
spotifyLink = binding.linkSearch.text.toString()
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
Log.i("Fragment", "$type : $link")
if(sharedViewModel.spotifyService == null && !isOnline()){
(activity as MainActivity).authenticateSpotify()
}
if (type == "Error" || link == "Error") {
showToast("Please Check Your Link!")
} else if(!isOnline()){
sharedViewModel.showAlertDialog(resources,requireContext())
} else {
adapter = TrackListAdapter()
binding.trackList.adapter = adapter
adapter.sharedViewModel = sharedViewModel
adapter.mainFragment = this
setUiVisibility()
if(mainViewModel.searchLink == spotifyLink){
//it's a Device Configuration Change
adapterConfig(mainViewModel.trackList)
sharedViewModel.uiScope.launch {
bindImage(binding.imageView,mainViewModel.coverUrl)
}
}else{
when (type) {
"track" -> {
mainViewModel.searchLink = spotifyLink
sharedViewModel.uiScope.launch {
val trackObject = sharedViewModel.getTrackDetails(link)
val trackList = mutableListOf<Track>()
trackList.add(trackObject!!)
mainViewModel.trackList = trackList
mainViewModel.coverUrl = trackObject.album!!.images?.get(0)!!.url!!
bindImage(binding.imageView,mainViewModel.coverUrl)
adapterConfig(trackList)
binding.btnDownloadAll.setOnClickListener {
showToast("Starting Download in Few Seconds")
sharedViewModel.uiScope.launch {
downloadAllTracks(
"Tracks",
null,
trackList,
sharedViewModel.ytDownloader
)
}
}
}
}
"album" -> {
mainViewModel.searchLink = spotifyLink
sharedViewModel.uiScope.launch {
val albumObject = sharedViewModel.getAlbumDetails(link)
val trackList = mutableListOf<Track>()
albumObject!!.tracks?.items?.forEach { trackList.add(it) }
mainViewModel.trackList = trackList
mainViewModel.coverUrl = albumObject.images?.get(0)!!.url!!
bindImage(binding.imageView,mainViewModel.coverUrl)
adapter.isAlbum = true
adapterConfig(trackList)
binding.btnDownloadAll.setOnClickListener {
showToast("Starting Download in Few Seconds")
sharedViewModel.uiScope.launch {
loadAllImages(trackList)
downloadAllTracks(
"Albums",
albumObject.name,
trackList,
sharedViewModel.ytDownloader
)
}
}
}
}
"playlist" -> {
mainViewModel.searchLink = spotifyLink
sharedViewModel.uiScope.launch {
val playlistObject = sharedViewModel.getPlaylistDetails(link)
val trackList = mutableListOf<Track>()
playlistObject!!.tracks?.items!!.forEach { trackList.add(it.track!!) }
mainViewModel.trackList = trackList
mainViewModel.coverUrl = playlistObject.images?.get(0)!!.url!!
bindImage(binding.imageView,mainViewModel.coverUrl)
adapterConfig(trackList)
binding.btnDownloadAll.setOnClickListener {
showToast("Starting Download in Few Seconds")
sharedViewModel.uiScope.launch {
loadAllImages(trackList)
downloadAllTracks(
"Playlists",
playlistObject.name,
trackList,
sharedViewModel.ytDownloader
)
}
}
}
}
"episode" -> {
showToast("Implementation Pending")
}
"show" -> {
showToast("Implementation Pending ")
}
}
}
}
}
/**
* Function to fetch all Images for using in mp3 tag.
**/
private fun loadAllImages(trackList: List<Track>) {
trackList.forEach {
val imgUrl = it.album!!.images?.get(0)?.url
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
Glide
.with(requireContext())
.asFile()
.load(imgUri)
.listener(object: RequestListener<File> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<File>?,
isFirstResource: Boolean
): Boolean {
Log.i("Glide","LoadFailed")
return false
}
override fun onResourceReady(
resource: File?,
model: Any?,
target: Target<File>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
sharedViewModel.uiScope.launch {
withContext(Dispatchers.IO){
try {
val file = File(
Environment.getExternalStorageDirectory(),
DownloadHelper.defaultDir+".Images/" + imgUrl.substringAfterLast('/') + ".jpeg"
)
resource?.copyTo(file)
} catch (e: IOException) {
e.printStackTrace()
}
}
}
return false
}
}).submit()
}
}
}
/**
* Implementing button to Open Spotify App
**/
private fun openSpotifyButton() {
val manager: PackageManager = requireActivity().packageManager
try {
i = manager.getLaunchIntentForPackage("com.spotify.music")
if (i == null) throw PackageManager.NameNotFoundException()
i?.addCategory(Intent.CATEGORY_LAUNCHER)
binding.btnOpenSpotify.setOnClickListener { startActivity(i) }
} catch (e: PackageManager.NameNotFoundException) {
binding.textView.text = getString(R.string.spotify_not_installed)
binding.btnOpenSpotify.text = getString(R.string.spotify_web_link)
val uri: Uri =
Uri.parse("http://open.spotify.com")
val intent = Intent(Intent.ACTION_VIEW, uri)
binding.btnOpenSpotify.setOnClickListener {
startActivity(intent)
}
}
}
private fun openGithubButton() {
val uri: Uri =
Uri.parse("http://github.com/Shabinder/SpotiFlyer")
val intent = Intent(Intent.ACTION_VIEW, uri)
binding.btnGithub.setOnClickListener {
startActivity(intent)
}
}
private fun openInstaButton() {
val uri: Uri =
Uri.parse("http://www.instagram.com/mr.shabinder")
val intent = Intent(Intent.ACTION_VIEW, uri)
binding.developerInsta.setOnClickListener {
startActivity(intent)
}
}
/**
* Configure Recycler View Adapter
**/
private fun adapterConfig(trackList: List<Track>){
adapter.trackList = trackList.toList()
adapter.totalItems = trackList.size
adapter.mainFragment = this
adapter.notifyDataSetChanged()
}
/**
* Make Ui elements Visible
**/
private fun setUiVisibility() {
binding.btnDownloadAll.visibility =View.VISIBLE
binding.titleView.visibility = View.GONE
binding.openSpotify.visibility = View.GONE
binding.trackList.visibility = View.VISIBLE
}
/**
* Handle Intent If there is any!
**/
private fun handleIntent() {
binding.linkSearch.setText(sharedViewModel.intentString)
sharedViewModel.accessToken.observe(viewLifecycleOwner, Observer {
//Waiting for Authentication to Finish with Spotify
if (it != ""){
if(sharedViewModel.intentString != ""){
binding.btnSearch.performClick()
setUiVisibility()
}
}
})
}
private fun setUpUsageText() {
val spanStringBuilder = SpannableStringBuilder()
spanStringBuilder.append(getText(R.string.d_one)).append("\n")
spanStringBuilder.append(getText(R.string.d_two)).append("\n")
spanStringBuilder.append(getText(R.string.d_three)).append("\n")
binding.usage.text = spanStringBuilder
}
/**
* Util. Function to create toasts!
**/
fun showToast(message:String){
Toast.makeText(context,message,Toast.LENGTH_SHORT).show()
}
/**
* Util. Function To Check Connection Status
**/
private fun isOnline(): Boolean {
val cm =
requireActivity().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = cm.activeNetworkInfo
return netInfo != null && netInfo.isConnectedOrConnecting
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putCharSequence("searchLink",mainViewModel.searchLink)
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright (C) 2020 Shabinder Singh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.spotiflyer.fragments
import androidx.lifecycle.ViewModel
import com.shabinder.spotiflyer.models.Track
class MainViewModel: ViewModel() {
var searchLink: String = ""
var trackList = mutableListOf<Track>()
var coverUrl: String = ""
}

View File

@ -22,7 +22,8 @@ import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
data class DownloadObject( data class DownloadObject(
var track: Track, var ytVideo: YTTrack?=null,
var track: Track?=null,
var url:String, var url:String,
var outputDir:String var outputDir:String
):Parcelable ):Parcelable

View File

@ -26,20 +26,20 @@ import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper.getYTLink import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper.getYTLink
import com.shabinder.spotiflyer.fragments.MainFragment
import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.ui.spotify.SpotifyFragment
import com.shabinder.spotiflyer.utils.bindImage import com.shabinder.spotiflyer.utils.bindImage
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>() { class SpotifyTrackListAdapter:RecyclerView.Adapter<SpotifyTrackListAdapter.ViewHolder>() {
var trackList = listOf<Track>() var trackList = listOf<Track>()
var totalItems:Int = 0 var totalItems:Int = 0
var sharedViewModel = SharedViewModel() var sharedViewModel = SharedViewModel()
var isAlbum:Boolean = false var isAlbum:Boolean = false
var mainFragment:MainFragment? = null var spotifyFragment: SpotifyFragment? = null
override fun getItemCount():Int = totalItems override fun getItemCount():Int = totalItems
@ -63,7 +63,7 @@ class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>() {
holder.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec" holder.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec"
holder.downloadBtn.setOnClickListener{ holder.downloadBtn.setOnClickListener{
sharedViewModel.uiScope.launch { sharedViewModel.uiScope.launch {
getYTLink(mainFragment,"Tracks",null,sharedViewModel.ytDownloader,"${item.name} ${item.artists?.get(0)!!.name?:""}",track = item,index = 0) getYTLink(spotifyFragment,"Tracks",null,sharedViewModel.ytDownloader.value,"${item.name} ${item.artists?.get(0)!!.name?:""}",track = item)
} }
} }

View File

@ -31,7 +31,7 @@ class SplashScreen : AppCompatActivity(){
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.splash_screen) setContentView(R.layout.splash_screen)
val splashTimeout = 600 val splashTimeout = 500
val homeIntent = Intent(this@SplashScreen, MainActivity::class.java) val homeIntent = Intent(this@SplashScreen, MainActivity::class.java)
Handler().postDelayed({ Handler().postDelayed({
//TODO:Bring Initial Setup here //TODO:Bring Initial Setup here

View File

@ -0,0 +1,241 @@
/*
* Copyright (C) 2020 Shabinder Singh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.spotiflyer.ui.spotify
import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebView
import android.widget.Toast
import androidx.core.net.toUri
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.shabinder.spotiflyer.MainActivity
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.SpotifyFragmentBinding
import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.recyclerView.SpotifyTrackListAdapter
import com.shabinder.spotiflyer.utils.bindImage
import com.shabinder.spotiflyer.utils.copyTo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.IOException
@Suppress("DEPRECATION")
class SpotifyFragment : Fragment() {
private lateinit var binding:SpotifyFragmentBinding
private lateinit var spotifyViewModel: SpotifyViewModel
private lateinit var sharedViewModel: SharedViewModel
private lateinit var adapterSpotify:SpotifyTrackListAdapter
private var webView: WebView? = null
@SuppressLint("SetJavaScriptEnabled")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater,R.layout.spotify_fragment,container,false)
adapterSpotify = SpotifyTrackListAdapter()
initializeAll()
initializeLiveDataObservers()
val args = SpotifyFragmentArgs.fromBundle(requireArguments())
val spotifyLink = args.link
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/')
Log.i("Fragment", "$type : $link")
if(sharedViewModel.spotifyService.value == null && isNotOnline()){//Authentication pending!!
(activity as MainActivity).authenticateSpotify()
}
if(!isNotOnline()){//Device Offline
sharedViewModel.showAlertDialog(resources,requireContext())
}else if (type == "Error" || link == "Error") {//Incorrect Link
showToast("Please Check Your Link!")
}else if(spotifyLink.contains("open.spotify",true)){//Link Validation!!
if(type == "episode" || type == "show"){//TODO Implementation
showToast("Implementing Soon, Stay Tuned!")
}
else{
spotifyViewModel.spotifySearch(type,link)
if(type=="album")adapterSpotify.isAlbum = true
binding.btnDownloadAllSpotify.setOnClickListener {
showToast("Starting Download in Few Seconds")
loadAllImages(spotifyViewModel.trackList.value!!)
spotifyViewModel.uiScope.launch {
SpotifyDownloadHelper.downloadAllTracks(
spotifyViewModel.folderType,
spotifyViewModel.subFolder,
spotifyViewModel.trackList.value!!,
spotifyViewModel.ytDownloader
)
}
}
}
}
return binding.root
}
/**
*Live Data Observers
**/
private fun initializeLiveDataObservers() {
/**
* CoverUrl Binding Observer!
**/
spotifyViewModel.coverUrl.observe(viewLifecycleOwner, Observer {
if(it!="Loading") bindImage(binding.spotifyCoverImage,it)
})
/**
* TrackList Binding Observer!
**/
spotifyViewModel.trackList.observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty()){
Log.i("SpotifyFragment","TrackList Fetched!")
adapterConfig(it)
}
})
/**
* Title Binding Observer!
**/
spotifyViewModel.title.observe(viewLifecycleOwner, Observer {
binding.titleViewSpotify.text = it
})
}
/**
* Basic Initialization
**/
private fun initializeAll() {
webView = binding.webViewSpotify
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
spotifyViewModel = ViewModelProvider(this).get(SpotifyViewModel::class.java)
sharedViewModel.spotifyService.observe(viewLifecycleOwner, Observer {
spotifyViewModel.spotifyService = it
})
sharedViewModel.ytDownloader.observe(viewLifecycleOwner, Observer {
spotifyViewModel.ytDownloader = it
})
SpotifyDownloadHelper.webView = binding.webViewSpotify
SpotifyDownloadHelper.context = requireContext()
SpotifyDownloadHelper.sharedViewModel = sharedViewModel
SpotifyDownloadHelper.statusBar = binding.StatusBarSpotify
binding.trackListSpotify.adapter = adapterSpotify
}
/**
* Function to fetch all Images for using in mp3 tag.
**/
private fun loadAllImages(trackList: List<Track>) {
trackList.forEach {
val imgUrl = it.album!!.images?.get(0)?.url
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
Glide
.with(requireContext())
.asFile()
.load(imgUri)
.listener(object: RequestListener<File> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<File>?,
isFirstResource: Boolean
): Boolean {
Log.i("Glide","LoadFailed")
return false
}
override fun onResourceReady(
resource: File?,
model: Any?,
target: Target<File>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
sharedViewModel.uiScope.launch {
withContext(Dispatchers.IO){
try {
val file = File(
Environment.getExternalStorageDirectory(),
SpotifyDownloadHelper.defaultDir+".Images/" + imgUrl.substringAfterLast('/') + ".jpeg"
)
resource?.copyTo(file)
} catch (e: IOException) {
e.printStackTrace()
}
}
}
return false
}
}).submit()
}
}
}
/**
* Configure Recycler View Adapter
**/
private fun adapterConfig(trackList: List<Track>){
adapterSpotify.trackList = trackList
adapterSpotify.totalItems = trackList.size
adapterSpotify.spotifyFragment = this
adapterSpotify.sharedViewModel = sharedViewModel
adapterSpotify.notifyDataSetChanged()
}
/**
* Util. Function to create toasts!
**/
fun showToast(message:String){
Toast.makeText(context,message,Toast.LENGTH_SHORT).show()
}
/**
* Util. Function To Check Connection Status
**/
private fun isNotOnline(): Boolean {
val cm =
requireActivity().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = cm.activeNetworkInfo
return netInfo != null && netInfo.isConnectedOrConnecting
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (C) 2020 Shabinder Singh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.spotiflyer.ui.spotify
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.models.Album
import com.shabinder.spotiflyer.models.Playlist
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.utils.SpotifyService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class SpotifyViewModel: ViewModel() {
var folderType:String = ""
var subFolder:String = ""
var trackList = MutableLiveData<List<Track>>()
private val loading = "Loading"
var title = MutableLiveData<String>().apply { value = "\"SpotiFlyer\"" }
var coverUrl = MutableLiveData<String>().apply { value = loading }
var spotifyService : SpotifyService? = null
var ytDownloader : YoutubeDownloader? = null
private var viewModelJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun spotifySearch(type:String,link: String){
when (type) {
"track" -> {
uiScope.launch {
val trackObject = getTrackDetails(link)
val tempTrackList = mutableListOf<Track>()
tempTrackList.add(trackObject!!)
trackList.value = tempTrackList
title.value = trackObject.name
coverUrl.value = trackObject.album!!.images?.get(0)!!.url!!
folderType = "Tracks"
}
}
"album" -> {
uiScope.launch {
val albumObject = getAlbumDetails(link)
val tempTrackList = mutableListOf<Track>()
albumObject!!.tracks?.items?.forEach { tempTrackList.add(it) }
trackList.value = tempTrackList
title.value = albumObject.name
coverUrl.value = albumObject.images?.get(0)!!.url!!
folderType = "Albums"
subFolder = albumObject.name!!
}
}
"playlist" -> {
uiScope.launch {
val playlistObject = getPlaylistDetails(link)
val tempTrackList = mutableListOf<Track>()
playlistObject!!.tracks?.items?.forEach {
it.track?.let { it1 -> tempTrackList.add(it1) }
}
trackList.value = tempTrackList
Log.i("VIEW MODEL",playlistObject.tracks?.items!!.toString())
Log.i("VIEW MODEL",trackList.value?.size.toString())
title.value = playlistObject.name
coverUrl.value = playlistObject.images?.get(0)!!.url!!
folderType = "Playlists"
subFolder = playlistObject.name!!
}
}
"episode" -> {//TODO
}
"show" -> {//TODO
}
}
}
private suspend fun getTrackDetails(trackLink:String): Track?{
Log.i("Requesting","https://api.spotify.com/v1/tracks/$trackLink")
return spotifyService?.getTrack(trackLink)
}
private suspend fun getAlbumDetails(albumLink:String): Album?{
Log.i("Requesting","https://api.spotify.com/v1/albums/$albumLink")
return spotifyService?.getAlbum(albumLink)
}
private suspend fun getPlaylistDetails(link:String): Playlist?{
Log.i("Requesting","https://api.spotify.com/v1/playlists/$link")
return spotifyService?.getPlaylist(link)
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}

View File

@ -30,7 +30,7 @@ import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -72,7 +72,7 @@ fun bindImage(imgView: ImageView, imgUrl: String?) {
try { try {
val file = File( val file = File(
Environment.getExternalStorageDirectory(), Environment.getExternalStorageDirectory(),
DownloadHelper.defaultDir+".Images/" + imgUrl.substringAfterLast('/',imgUrl) + ".jpeg" SpotifyDownloadHelper.defaultDir+".Images/" + imgUrl.substringAfterLast('/',imgUrl) + ".jpeg"
) // the File to save , append increasing numeric counter to prevent files from getting overwritten. ) // the File to save , append increasing numeric counter to prevent files from getting overwritten.
val options = BitmapFactory.Options() val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.ARGB_8888 options.inPreferredConfig = Bitmap.Config.ARGB_8888

View File

@ -41,19 +41,19 @@ import retrofit2.http.*
interface SpotifyService { interface SpotifyService {
@GET("playlists/{playlist_id}") @GET("playlists/{playlist_id}")
suspend fun getPlaylist(@Path("playlist_id") playlistId: String?): Playlist? suspend fun getPlaylist(@Path("playlist_id") playlistId: String?): Playlist
@GET("tracks/{id}") @GET("tracks/{id}")
suspend fun getTrack(@Path("id") var1: String?): Track? suspend fun getTrack(@Path("id") trackId: String?): Track
@GET("episodes/{id}") @GET("episodes/{id}")
suspend fun getEpisode(@Path("id") var1: String?): Track? suspend fun getEpisode(@Path("id") episodeId: String?): Track
@GET("shows/{id}") @GET("shows/{id}")
suspend fun getShow(@Path("id") var1: String?): Track? suspend fun getShow(@Path("id") showId: String?): Track
@GET("albums/{id}") @GET("albums/{id}")
suspend fun getAlbum(@Path("id") var1: String?): Album? suspend fun getAlbum(@Path("id") albumId: String?): Album
@GET("me") @GET("me")
suspend fun getMe(): UserPrivate? suspend fun getMe(): UserPrivate?

View File

@ -40,7 +40,7 @@ import com.mpatric.mp3agic.ID3v24Tag
import com.mpatric.mp3agic.Mp3File import com.mpatric.mp3agic.Mp3File
import com.shabinder.spotiflyer.MainActivity import com.shabinder.spotiflyer.MainActivity
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper
import com.shabinder.spotiflyer.models.DownloadObject import com.shabinder.spotiflyer.models.DownloadObject
import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.models.Track
import com.tonyodev.fetch2.* import com.tonyodev.fetch2.*
@ -74,11 +74,9 @@ class ForegroundService : Service(){
) )
private var wakeLock: PowerManager.WakeLock? = null private var wakeLock: PowerManager.WakeLock? = null
private var isServiceStarted = false private var isServiceStarted = false
private var messageSnippet1 = "" var notificationLine = 0
private var messageSnippet2 = "" val messageList = mutableListOf<String>("","","","")
private var messageSnippet3 = "" private var pendingIntent:PendingIntent? = null
private var messageSnippet4 = ""
var notificationLine = 1
@ -89,7 +87,7 @@ class ForegroundService : Service(){
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
val notificationIntent = Intent(this, MainActivity::class.java) val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity( pendingIntent = PendingIntent.getActivity(
this, this,
0, notificationIntent, 0 0, notificationIntent, 0
) )
@ -120,18 +118,16 @@ class ForegroundService : Service(){
} }
val notification = NotificationCompat.Builder(this, channelId) val notification = NotificationCompat.Builder(this, channelId)
.setOngoing(true)
.setContentTitle("SpotiFlyer: Downloading Your Music")
.setSubText("Speed: $speed KB/s ")
.setNotificationSilent()
.setOnlyAlertOnce(true)
.setContentText("Total: $total Downloaded: $downloaded Completed:$converted ")
.setSmallIcon(R.drawable.down_arrowbw) .setSmallIcon(R.drawable.down_arrowbw)
.setNotificationSilent()
.setSubText("Speed: $speed KB/s")
.setStyle(NotificationCompat.InboxStyle() .setStyle(NotificationCompat.InboxStyle()
.addLine(messageSnippet1) .setBigContentTitle("Total: $total Completed:$converted")
.addLine(messageSnippet2) .addLine(messageList[0])
.addLine(messageSnippet3) .addLine(messageList[1])
.addLine(messageSnippet4)) .addLine(messageList[2])
.addLine(messageList[3]))
.setContentIntent(pendingIntent)
.build() .build()
startForeground(notificationId, notification) startForeground(notificationId, notification)
} }
@ -149,7 +145,7 @@ class ForegroundService : Service(){
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
// Send a notification that service is started // Send a notification that service is started
Log.i(tag,"Service Started.") Log.i(tag,"Service Started.")
startForeground()
//do heavy work on a background thread //do heavy work on a background thread
//val list = intent.getSerializableExtra("list") as List<Any?> //val list = intent.getSerializableExtra("list") as List<Any?>
// val list = intent.getParcelableArrayListExtra<DownloadObject>("list") ?: intent.extras?.getParcelableArrayList<DownloadObject>("list") // val list = intent.getParcelableArrayListExtra<DownloadObject>("list") ?: intent.extras?.getParcelableArrayList<DownloadObject>("list")
@ -166,7 +162,7 @@ class ForegroundService : Service(){
fetch!!.enqueue(request, fetch!!.enqueue(request,
Func { Func {
requestMap[it] = obj.track obj.track?.let { it1 -> requestMap.put(it, it1) }
downloadList.remove(obj) downloadList.remove(obj)
Log.i(tag, "Enqueuing Download") Log.i(tag, "Enqueuing Download")
}, },
@ -197,7 +193,6 @@ class ForegroundService : Service(){
super.onDestroy() super.onDestroy()
if(downloadMap.isEmpty() && converted == total){ if(downloadMap.isEmpty() && converted == total){
Log.i(tag,"Service destroyed.") Log.i(tag,"Service destroyed.")
fetch?.close()
deleteFile(parentDirectory) deleteFile(parentDirectory)
releaseWakeLock() releaseWakeLock()
stopForeground(true) stopForeground(true)
@ -223,8 +218,11 @@ class ForegroundService : Service(){
super.onTaskRemoved(rootIntent) super.onTaskRemoved(rootIntent)
if(downloadMap.isEmpty() && converted == total ){ if(downloadMap.isEmpty() && converted == total ){
Log.i(tag,"Service Removed.") Log.i(tag,"Service Removed.")
fetch?.close() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopSelf() stopForeground(true)
} else {
stopSelf()//System will automatically close it
}
} }
} }
@ -239,7 +237,7 @@ class ForegroundService : Service(){
if (file.isDirectory) { if (file.isDirectory) {
deleteFile(file) deleteFile(file)
} else if(file.isFile) { } else if(file.isFile) {
if(file.path.toString().substringAfterLast(".") != "mp3"){ if(file.path.toString().substringAfterLast(".") != "mp3" && file.path.toString().substringAfterLast(".") != "jpeg"){
// Log.i(tag,"deleting ${file.path}") // Log.i(tag,"deleting ${file.path}")
file.delete() file.delete()
} }
@ -274,21 +272,21 @@ class ForegroundService : Service(){
) { ) {
val track = requestMap[download.request] val track = requestMap[download.request]
when(notificationLine){ when(notificationLine){
0 -> {
messageList[0] = "Downloading ${track?.name}"
notificationLine = 1
}
1 -> { 1 -> {
messageSnippet1 = "Downloading ${track?.name}" messageList[1] = "Downloading ${track?.name}"
notificationLine = 2 notificationLine = 2
} }
2 -> { 2-> {
messageSnippet2 = "Downloading ${track?.name}" messageList[2] = "Downloading ${track?.name}"
notificationLine = 3 notificationLine = 3
} }
3-> { 3 -> {
messageSnippet3 = "Downloading ${track?.name}" messageList[3] = "Downloading ${track?.name}"
notificationLine = 4 notificationLine = 0
}
4 -> {
messageSnippet4 = "Downloading ${track?.name}"
notificationLine = 1
} }
} }
Log.i(tag,"${track?.name} Download Started") Log.i(tag,"${track?.name} Download Started")
@ -308,20 +306,27 @@ class ForegroundService : Service(){
} }
override fun onCompleted(download: Download) { override fun onCompleted(download: Download) {
val track = requestMap[download.request] val track = requestMap[download.request]
serviceScope.launch {
try{ for (message in messageList){
convertToMp3(download.file, track!!) if( message == "Downloading ${track?.name}"){
Log.i(tag,"${track.name} Download Completed") messageList[messageList.indexOf(message)] = ""
}catch (e:KotlinNullPointerException
){
Log.i(tag,"${track?.name} Download Failed! Error:Fetch!!!!")
Log.i(tag,"${track?.name} Requesting Download thru Android DM")
downloadUsingDM(download.request.url,download.request.file, track!!)
downloaded++
requestMap.remove(download.request)
}
} }
}
serviceScope.launch {
try{
convertToMp3(download.file, track!!)
Log.i(tag,"${track.name} Download Completed")
}catch (e:KotlinNullPointerException
){
Log.i(tag,"${track?.name} Download Failed! Error:Fetch!!!!")
Log.i(tag,"${track?.name} Requesting Download thru Android DM")
downloadUsingDM(download.request.url,download.request.file, track!!)
downloaded++
requestMap.remove(download.request)
}
}
if(requestMap.keys.toList().isEmpty()) speed = 0 if(requestMap.keys.toList().isEmpty()) speed = 0
updateNotification() updateNotification()
} }
@ -370,7 +375,7 @@ class ForegroundService : Service(){
/** /**
* If fetch Fails , Android Download Manager To RESCUE!! * If fetch Fails , Android Download Manager To RESCUE!!
**/ **/
fun downloadUsingDM(url:String,outputDir:String,track: Track){ fun downloadUsingDM(url:String, outputDir:String, track: Track){
val uri = Uri.parse(url) val uri = Uri.parse(url)
val request = DownloadManager.Request(uri) val request = DownloadManager.Request(uri)
.setAllowedNetworkTypes( .setAllowedNetworkTypes(
@ -407,7 +412,7 @@ class ForegroundService : Service(){
/** /**
*Converting Downloaded Audio (m4a) to Mp3.( Also Applying Metadata) *Converting Downloaded Audio (m4a) to Mp3.( Also Applying Metadata)
**/ **/
fun convertToMp3(filePath: String,track: Track){ fun convertToMp3(filePath: String, track: Track){
val m4aFile = File(filePath) val m4aFile = File(filePath)
FFmpeg.executeAsync( FFmpeg.executeAsync(
@ -457,17 +462,16 @@ class ForegroundService : Service(){
val mNotificationManager: NotificationManager = val mNotificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = NotificationCompat.Builder(this, channelId) val notification = NotificationCompat.Builder(this, channelId)
.setContentTitle("SpotiFlyer: Downloading Your Music")
.setContentText("Total: $total Completed:$converted ")
.setSubText("Speed: $speed KB/s ")
.setNotificationSilent()
.setOnlyAlertOnce(true)
.setSmallIcon(R.drawable.down_arrowbw) .setSmallIcon(R.drawable.down_arrowbw)
.setSubText("Speed: $speed KB/s")
.setNotificationSilent()
.setStyle(NotificationCompat.InboxStyle() .setStyle(NotificationCompat.InboxStyle()
.addLine(messageSnippet1) .setBigContentTitle("Total: $total Completed:$converted")
.addLine(messageSnippet2) .addLine(messageList[0])
.addLine(messageSnippet3) .addLine(messageList[1])
.addLine(messageSnippet4)) .addLine(messageList[2])
.addLine(messageList[3]))
.setContentIntent(pendingIntent)
.build() .build()
mNotificationManager.notify(notificationId, notification) mNotificationManager.notify(notificationId, notification)
} }
@ -506,7 +510,7 @@ class ForegroundService : Service(){
track.ytCoverUrl?.let { track.ytCoverUrl?.let {
val file = File( val file = File(
Environment.getExternalStorageDirectory(), Environment.getExternalStorageDirectory(),
DownloadHelper.defaultDir +".Images/" + it.substringAfterLast('/',it) + ".jpeg") SpotifyDownloadHelper.defaultDir +".Images/" + it.substringAfterLast('/',it) + ".jpeg")
Log.i("Mp3Tags editing Tags",file.path) Log.i("Mp3Tags editing Tags",file.path)
//init array with file length //init array with file length
val bytesArray = ByteArray(file.length().toInt()) val bytesArray = ByteArray(file.length().toInt())
@ -518,7 +522,7 @@ class ForegroundService : Service(){
track.album?.let { track.album?.let {
val file = File( val file = File(
Environment.getExternalStorageDirectory(), Environment.getExternalStorageDirectory(),
DownloadHelper.defaultDir +".Images/" + (it.images?.get(0)?.url!!).substringAfterLast('/') + ".jpeg") SpotifyDownloadHelper.defaultDir +".Images/" + (it.images?.get(0)?.url!!).substringAfterLast('/') + ".jpeg")
Log.i("Mp3Tags editing Tags",file.path) Log.i("Mp3Tags editing Tags",file.path)
//init array with file length //init array with file length
val bytesArray = ByteArray(file.length().toInt()) val bytesArray = ByteArray(file.length().toInt())

View File

@ -17,5 +17,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="38dp" <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="38dp"
android:height="38dp" android:viewportWidth="512" android:viewportHeight="512"> android:height="38dp" android:viewportWidth="512" android:viewportHeight="512">
<path android:fillColor="#5C6BC0" android:pathData="M255.968,5.329C114.624,5.329 0,120.401 0,262.353c0,113.536 73.344,209.856 175.104,243.872c12.8,2.368 17.472,-5.568 17.472,-12.384c0,-6.112 -0.224,-22.272 -0.352,-43.712c-71.2,15.52 -86.24,-34.464 -86.24,-34.464c-11.616,-29.696 -28.416,-37.6 -28.416,-37.6c-23.264,-15.936 1.728,-15.616 1.728,-15.616c25.696,1.824 39.2,26.496 39.2,26.496c22.848,39.264 59.936,27.936 74.528,21.344c2.304,-16.608 8.928,-27.936 16.256,-34.368c-56.832,-6.496 -116.608,-28.544 -116.608,-127.008c0,-28.064 9.984,-51.008 26.368,-68.992c-2.656,-6.496 -11.424,-32.64 2.496,-68c0,0 21.504,-6.912 70.4,26.336c20.416,-5.696 42.304,-8.544 64.096,-8.64c21.728,0.128 43.648,2.944 64.096,8.672c48.864,-33.248 70.336,-26.336 70.336,-26.336c13.952,35.392 5.184,61.504 2.56,68c16.416,17.984 26.304,40.928 26.304,68.992c0,98.72 -59.84,120.448 -116.864,126.816c9.184,7.936 17.376,23.616 17.376,47.584c0,34.368 -0.32,62.08 -0.32,70.496c0,6.88 4.608,14.88 17.6,12.352C438.72,472.145 512,375.857 512,262.353C512,120.401 397.376,5.329 255.968,5.329z"/> <path android:fillColor="#4EA4FF" android:pathData="M255.968,5.329C114.624,5.329 0,120.401 0,262.353c0,113.536 73.344,209.856 175.104,243.872c12.8,2.368 17.472,-5.568 17.472,-12.384c0,-6.112 -0.224,-22.272 -0.352,-43.712c-71.2,15.52 -86.24,-34.464 -86.24,-34.464c-11.616,-29.696 -28.416,-37.6 -28.416,-37.6c-23.264,-15.936 1.728,-15.616 1.728,-15.616c25.696,1.824 39.2,26.496 39.2,26.496c22.848,39.264 59.936,27.936 74.528,21.344c2.304,-16.608 8.928,-27.936 16.256,-34.368c-56.832,-6.496 -116.608,-28.544 -116.608,-127.008c0,-28.064 9.984,-51.008 26.368,-68.992c-2.656,-6.496 -11.424,-32.64 2.496,-68c0,0 21.504,-6.912 70.4,26.336c20.416,-5.696 42.304,-8.544 64.096,-8.64c21.728,0.128 43.648,2.944 64.096,8.672c48.864,-33.248 70.336,-26.336 70.336,-26.336c13.952,35.392 5.184,61.504 2.56,68c16.416,17.984 26.304,40.928 26.304,68.992c0,98.72 -59.84,120.448 -116.864,126.816c9.184,7.936 17.376,23.616 17.376,47.584c0,34.368 -0.32,62.08 -0.32,70.496c0,6.88 4.608,14.88 17.6,12.352C438.72,472.145 512,375.857 512,262.353C512,120.401 397.376,5.329 255.968,5.329z"/>
</vector> </vector>

View File

@ -1,362 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 Shabinder Singh
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_download_all"
android:layout_width="wrap_content"
android:layout_height="44dp"
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="gone"
app:layout_anchor="@+id/appbar"
app:layout_anchorGravity="bottom|center" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="280dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="#F2C102B7"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:layout_scrollInterpolator="@android:anim/decelerate_interpolator"
app:toolbarId="@+id/toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/TopLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/linkSearch"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:background="@drawable/text_background_accented"
android:ems="10"
android:hint="Link From Spotify"
android:inputType="text"
android:padding="8dp"
android:textAlignment="center"
android:textColor="@color/white"
android:textColorHint="@color/grey"
android:textSize="19sp"
app:layout_constraintEnd_toStartOf="@+id/btn_search"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_search"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginEnd="16dp"
android:background="@drawable/btn_design"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:text="Search"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/linkSearch"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/linkSearch"
app:layout_constraintTop_toTopOf="@id/linkSearch" />
<ImageView
android:id="@+id/image_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="3dp"
android:contentDescription="Album Cover"
android:foreground="@drawable/gradient"
android:padding="20dp"
android:paddingBottom="10dp"
android:src="@drawable/spotify_download"
android:visibility="visible"
app:layout_collapseMode="parallax"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linkSearch" />
<TextView
android:id="@+id/StatusBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/text_background_accented"
android:fontFamily="@font/raleway_semibold"
android:foreground="@drawable/rounded_gradient"
android:gravity="center"
android:paddingLeft="12dp"
android:paddingTop="1dp"
android:paddingRight="12dp"
android:paddingBottom="1dp"
android:text="Total: 100 Processed: 50"
android:textAlignment="center"
android:textColor="@color/grey"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@+id/btn_search"
app:layout_constraintStart_toStartOf="@+id/linkSearch"
app:layout_constraintTop_toBottomOf="@+id/linkSearch" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/open_spotify"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="12dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintTop_toBottomOf="@id/appbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<TextView
android:id="@+id/title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:fontFamily="@font/raleway_semibold"
android:gravity="end"
android:text='"SpotiFlyer"'
android:textAlignment="viewEnd"
android:textColor="#9AB3FF"
android:textSize="34sp"
android:textStyle="bold"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:fontFamily="@font/raleway_semibold"
android:text=" -Spotify Downloader"
android:textAlignment="center"
android:textColor="@color/colorPrimary"
app:layout_constraintEnd_toEndOf="@+id/title_view"
app:layout_constraintStart_toStartOf="@+id/title_view"
app:layout_constraintTop_toBottomOf="@+id/title_view" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:fontFamily="@font/raleway"
android:text="Forgot To Copy Link ?"
android:textColor="@color/grey"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/btn_openSpotify"
app:layout_constraintEnd_toEndOf="@+id/btn_openSpotify"
app:layout_constraintStart_toStartOf="@+id/btn_openSpotify" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_openSpotify"
android:layout_width="wrap_content"
android:layout_height="42dp"
android:layout_marginTop="80dp"
android:background="@drawable/text_background"
android:fontFamily="@font/raleway_semibold"
android:padding="12dp"
android:text="Open Spotify App"
android:textColor="@color/grey"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_donate"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:background="@drawable/text_background_accented"
android:drawableEnd="@drawable/ic_mug"
android:drawablePadding="5dp"
android:fontFamily="@font/capriola"
android:foreground="@drawable/rounded_gradient"
android:gravity="end|center_vertical"
android:padding="5dp"
android:text="Buy Me a Coffee"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView4" />
<TextView
android:id="@+id/usage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="90dp"
android:text="Usage Instructions!"
android:textColor="#D0838383"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/btn_donate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_openSpotify" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:padding="4dp"
android:text="Like This App?"
android:textColor="@color/colorPrimary"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@+id/btn_donate"
app:layout_constraintEnd_toEndOf="@+id/btn_donate"
app:layout_constraintStart_toStartOf="@+id/btn_donate"
app:layout_constraintTop_toBottomOf="@+id/usage" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_github"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginTop="130dp"
android:background="@drawable/text_background"
android:drawableStart="@drawable/ic_github"
android:drawablePadding="5dp"
android:fontFamily="@font/capriola"
android:gravity="end|center_vertical"
android:padding="5dp"
android:text=" Github "
android:textSize="13sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_donate" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/developer_insta"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginTop="130dp"
android:background="@drawable/text_background"
android:drawableEnd="@drawable/ic_instagram"
android:drawablePadding="5dp"
android:fontFamily="@font/capriola"
android:gravity="end|center_vertical"
android:padding="5dp"
android:text=" Follow Me "
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_donate" />
<TextView
android:id="@+id/tagLine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:layout_marginEnd="8dp"
android:fontFamily="@font/raleway_semibold"
android:text="Made with "
android:textAlignment="center"
android:textColor="@color/colorPrimary"
android:textSize="22sp"
app:layout_constraintEnd_toStartOf="@id/heart"
app:layout_constraintTop_toBottomOf="@+id/developer_insta" />
<ImageView
android:id="@+id/heart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="80dp"
android:contentDescription="Made With Love In India"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/developer_insta"
app:srcCompat="@drawable/ic_heart" />
<TextView
android:id="@+id/tagLine2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="80dp"
android:fontFamily="@font/raleway_semibold"
android:text=" in India"
android:textAlignment="center"
android:textColor="@color/colorPrimary"
android:textSize="22sp"
app:layout_constraintStart_toEndOf="@id/heart"
app:layout_constraintTop_toBottomOf="@+id/developer_insta" />
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="300dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/track_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="26dp"
android:visibility="gone"
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/open_spotify" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 Shabinder Singh
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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_youtube"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="25dp"
android:fitsSystemWindows="true"
tools:context=".ui.spotify.SpotifyFragment">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_download_all_spotify"
android:layout_width="wrap_content"
android:layout_height="44dp"
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/appbar_spotify"
app:layout_anchorGravity="bottom|center" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_spotify"
android:layout_width="match_parent"
android:layout_height="280dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="#F2C102B7"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:layout_scrollInterpolator="@android:anim/decelerate_interpolator"
app:toolbarId="@+id/toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/TopLayout_spotify"
android:layout_width="match_parent"
android:foreground="@drawable/gradient"
android:layout_height="match_parent">
<ImageView
android:id="@+id/spotify_cover_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="23dp"
android:layout_marginBottom="3dp"
android:contentDescription="Album Cover"
android:padding="15dp"
android:src="@drawable/spotify_download"
android:visibility="visible"
app:layout_collapseMode="parallax"
app:layout_constraintBottom_toTopOf="@id/title_view_spotify"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/StatusBar_spotify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:background="@drawable/text_background_accented"
android:fontFamily="@font/raleway_semibold"
android:foreground="@drawable/rounded_gradient"
android:gravity="center"
android:paddingLeft="12dp"
android:paddingTop="1dp"
android:paddingRight="12dp"
android:paddingBottom="1dp"
android:text="Total: 100 Processed: 50"
android:textAlignment="center"
android:textColor="@color/grey"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/spotify_cover_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/title_view_spotify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="20dp"
android:background="#00000000"
android:fontFamily="@font/raleway_semibold"
android:gravity="end"
android:text='"Loading..."'
android:textAlignment="viewEnd"
android:textColor="#9AB3FF"
android:textSize="28sp"
android:textStyle="bold"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/track_list_spotify"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="26dp"
android:visibility="visible"
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/appbar_spotify" />
<WebView
android:id="@+id/webView_spotify"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -23,8 +23,37 @@
app:startDestination="@id/mainFragment"> app:startDestination="@id/mainFragment">
<fragment <fragment
android:id="@+id/mainFragment" android:id="@+id/spotifyFragment"
android:name="com.shabinder.spotiflyer.fragments.MainFragment" android:name="com.shabinder.spotiflyer.ui.spotify.SpotifyFragment"
android:label="main_fragment" android:label="main_fragment"
tools:layout="@layout/main_fragment" /> tools:layout="@layout/spotify_fragment" >
<argument
android:name="link"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/mainFragment"
android:name="com.shabinder.spotiflyer.ui.mainfragment.MainFragment"
android:label="MainFragment"
tools:layout="@layout/main_fragment">
<action
android:id="@+id/action_mainFragment_to_spotifyFragment"
app:destination="@id/spotifyFragment"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_mainFragment_to_youtubeFragment"
app:destination="@id/youtubeFragment"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right" />
</fragment>
<fragment
android:id="@+id/youtubeFragment"
android:name="com.shabinder.spotiflyer.ui.youtube.YoutubeFragment"
android:label="YoutubeFragment"
tools:layout="@layout/youtube_fragment">
<argument
android:name="link"
app:argType="string" />
</fragment>
</navigation> </navigation>

View File

@ -19,7 +19,8 @@
<string name="app_name">SpotiFlyer</string> <string name="app_name">SpotiFlyer</string>
<string name="d_one"><b><i>Usage Instructions:</i></b></string> <string name="d_one"><b><i>Usage Instructions:</i></b></string>
<string name="d_two"><b>1. Paste</b>, Your Copied Link in Search Box at Top.</string> <string name="d_two"><b>1. Paste</b>, Your Copied Link in Search Box at Top.</string>
<string name="d_three"><b>2. Share directly</b> from <b>Spotify</b> App to this App.</string> <string name="d_three">or Open platform using <b>Logo Button Above.</b></string>
<string name="d_four"><b>2. Share directly</b> to this App.</string>
<string name="spotify_not_installed">It Seems you don\'t have Spotify Installed</string> <string name="spotify_not_installed">It Seems you don\'t have Spotify Installed</string>
<string name="spotify_web_link">Open Spotify Web Player</string> <string name="spotify_web_link">Open Spotify Web Player</string>
<string name="title"><b>No</b> Internet Connectivity!</string> <string name="title"><b>No</b> Internet Connectivity!</string>