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 project(path: ':mobile-ffmpeg')
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
kapt "androidx.room:room-compiler:2.2.5"
implementation "androidx.room:room-ktx:2.2.5"
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.kiulian.downloader.YoutubeDownloader
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.SpotifyServiceToken
import com.shabinder.spotiflyer.utils.createDirectory
@ -74,7 +74,7 @@ class MainActivity : AppCompatActivity(){
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
sharedPref = this.getPreferences(Context.MODE_PRIVATE)
//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)) ){
val savedToken = sharedPref?.getString("token","error")!!
@ -85,7 +85,7 @@ class MainActivity : AppCompatActivity(){
implementSpotifyService(savedToken)
}else{authenticateSpotify()}*/
if(sharedViewModel.spotifyService == null){
if(sharedViewModel.spotifyService.value == null){
authenticateSpotify()
}else{
implementSpotifyService(sharedViewModel.accessToken.value!!)
@ -102,7 +102,7 @@ class MainActivity : AppCompatActivity(){
//Object to download From Youtube {"https://github.com/sealedtx/java-youtube-downloader"}
ytDownloader = YoutubeDownloader()
sharedViewModel.ytDownloader = ytDownloader
sharedViewModel.ytDownloader.value = ytDownloader
handleIntentFromExternalActivity()
}
@ -168,7 +168,7 @@ class MainActivity : AppCompatActivity(){
.build()
spotifyService = retrofit.create(SpotifyService::class.java)
sharedViewModel.spotifyService = spotifyService
sharedViewModel.spotifyService.value = spotifyService
}
private fun getSpotifyToken(){
@ -283,12 +283,12 @@ class MainActivity : AppCompatActivity(){
}
private fun createDir() {
createDirectory(DownloadHelper.defaultDir)
createDirectory(DownloadHelper.defaultDir+".Images/")
createDirectory(DownloadHelper.defaultDir+"Tracks/")
createDirectory(DownloadHelper.defaultDir+"Albums/")
createDirectory(DownloadHelper.defaultDir+"Playlists/")
createDirectory(DownloadHelper.defaultDir+"YT_Downloads/")
createDirectory(SpotifyDownloadHelper.defaultDir)
createDirectory(SpotifyDownloadHelper.defaultDir+".Images/")
createDirectory(SpotifyDownloadHelper.defaultDir+"Tracks/")
createDirectory(SpotifyDownloadHelper.defaultDir+"Albums/")
createDirectory(SpotifyDownloadHelper.defaultDir+"Playlists/")
createDirectory(SpotifyDownloadHelper.defaultDir+"YT_Downloads/")
}
private fun checkIfLatestVersion() {

View File

@ -24,9 +24,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.github.kiulian.downloader.YoutubeDownloader
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.shreyaspatil.EasyUpiPayment.EasyUpiPayment
import kotlinx.coroutines.CoroutineScope
@ -36,11 +33,11 @@ import java.io.File
class SharedViewModel : ViewModel() {
var intentString = ""
var accessToken = MutableLiveData<String>().apply { value = "" }
var spotifyService : SpotifyService? = null
var ytDownloader : YoutubeDownloader? = null
var isConnected = MutableLiveData<Boolean>().apply { value = false }
var spotifyService = MutableLiveData<SpotifyService>()
var ytDownloader = MutableLiveData<YoutubeDownloader>()
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
@ -48,17 +45,6 @@ class SharedViewModel : ViewModel() {
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() {
super.onCleared()
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.quality.AudioQuality
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.fragments.MainFragment
import com.shabinder.spotiflyer.models.DownloadObject
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.ui.spotify.SpotifyFragment
import com.shabinder.spotiflyer.worker.ForegroundService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
object DownloadHelper {
object SpotifyDownloadHelper {
var webView:WebView? = null
var context : Context? = null
var statusBar:TextView? = null
val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator
private var downloadList = arrayListOf<DownloadObject>()
var sharedViewModel:SharedViewModel? = null
private var isBrowserLoading = false
private var total = 0
private var Processed = 0
private var listProcessed:Boolean = false
var youtubeList = mutableListOf<YoutubeRequest>()
/**
@ -64,32 +64,27 @@ object DownloadHelper {
subFolder: String?,
trackList: List<Track>, ytDownloader: YoutubeDownloader?) {
withContext(Dispatchers.Main){
var size = trackList.size
total += size
animateStatusBar()
total += trackList.size // Adding New Download List Count to StatusBar
trackList.forEach {
size--
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")
if(File(outputFile).exists()){//Download Already Present!!
Processed++
updateStatusBar()
}else{
if(isBrowserLoading){
if(size == 0){
youtubeList.add(YoutubeRequest(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it ,0 ))
}else{
if(isBrowserLoading){//WebView Busy!!
if (listProcessed){//Previous List request progress check
getYTLink(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it)
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))
}
}else{
if(size == 0){
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!!
@SuppressLint("SetJavaScriptEnabled")
suspend fun getYTLink(mainFragment: MainFragment? = null,
suspend fun getYTLink(spotifyFragment: SpotifyFragment? = null,
type:String,
subFolder:String?,
ytDownloader: YoutubeDownloader?,
searchQuery: String,
track: Track,
index: Int? = null){
track: Track){
val searchText = searchQuery.replace("\\s".toRegex(), "+")
val url = "https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q=$searchText"
Log.i("DH YT LINK ",url)
@ -122,15 +116,16 @@ object DownloadHelper {
val id = value!!.substringAfterLast("=", "error").replace("\"","")
Log.i("YT-id",id)
if(id !="error"){//Link extracting error
mainFragment?.showToast("Starting Download")
spotifyFragment?.showToast("Starting Download")
Processed++
if(Processed == total)listProcessed = true //List Processesd
updateStatusBar()
downloadFile(subFolder, type, track, index,ytDownloader,id)
downloadFile(subFolder, type, track,ytDownloader,id)
}
if(youtubeList.isNotEmpty()){
val request = youtubeList[0]
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)
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() {
statusBar!!.visibility = View.VISIBLE
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 {
withContext(Dispatchers.IO) {
val video = ytDownloader?.getVideo(id)
val detail = video?.details()
val format:Format? =try {
video?.findAudioWithQuality(AudioQuality.high)?.get(0) as Format
}catch (e:java.lang.IndexOutOfBoundsException){
@ -196,9 +167,7 @@ object DownloadHelper {
}
format?.let {
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 +
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)
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
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(
val mainFragment: MainFragment? = null,
val spotifyFragment: SpotifyFragment? = null,
val type:String,
val subFolder:String?,
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
data class DownloadObject(
var track: Track,
var ytVideo: YTTrack?=null,
var track: Track?=null,
var url:String,
var outputDir:String
):Parcelable

View File

@ -26,20 +26,20 @@ import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper.getYTLink
import com.shabinder.spotiflyer.fragments.MainFragment
import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper.getYTLink
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.ui.spotify.SpotifyFragment
import com.shabinder.spotiflyer.utils.bindImage
import kotlinx.coroutines.launch
class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>() {
class SpotifyTrackListAdapter:RecyclerView.Adapter<SpotifyTrackListAdapter.ViewHolder>() {
var trackList = listOf<Track>()
var totalItems:Int = 0
var sharedViewModel = SharedViewModel()
var isAlbum:Boolean = false
var mainFragment:MainFragment? = null
var spotifyFragment: SpotifyFragment? = null
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.downloadBtn.setOnClickListener{
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)
setContentView(R.layout.splash_screen)
val splashTimeout = 600
val splashTimeout = 500
val homeIntent = Intent(this@SplashScreen, MainActivity::class.java)
Handler().postDelayed({
//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.target.Target
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -72,7 +72,7 @@ fun bindImage(imgView: ImageView, imgUrl: String?) {
try {
val file = File(
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.
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.ARGB_8888

View File

@ -41,19 +41,19 @@ import retrofit2.http.*
interface SpotifyService {
@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}")
suspend fun getTrack(@Path("id") var1: String?): Track?
suspend fun getTrack(@Path("id") trackId: String?): Track
@GET("episodes/{id}")
suspend fun getEpisode(@Path("id") var1: String?): Track?
suspend fun getEpisode(@Path("id") episodeId: String?): Track
@GET("shows/{id}")
suspend fun getShow(@Path("id") var1: String?): Track?
suspend fun getShow(@Path("id") showId: String?): Track
@GET("albums/{id}")
suspend fun getAlbum(@Path("id") var1: String?): Album?
suspend fun getAlbum(@Path("id") albumId: String?): Album
@GET("me")
suspend fun getMe(): UserPrivate?

View File

@ -40,7 +40,7 @@ import com.mpatric.mp3agic.ID3v24Tag
import com.mpatric.mp3agic.Mp3File
import com.shabinder.spotiflyer.MainActivity
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.Track
import com.tonyodev.fetch2.*
@ -74,11 +74,9 @@ class ForegroundService : Service(){
)
private var wakeLock: PowerManager.WakeLock? = null
private var isServiceStarted = false
private var messageSnippet1 = ""
private var messageSnippet2 = ""
private var messageSnippet3 = ""
private var messageSnippet4 = ""
var notificationLine = 1
var notificationLine = 0
val messageList = mutableListOf<String>("","","","")
private var pendingIntent:PendingIntent? = null
@ -89,7 +87,7 @@ class ForegroundService : Service(){
override fun onCreate() {
super.onCreate()
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
@ -120,18 +118,16 @@ class ForegroundService : Service(){
}
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)
.setNotificationSilent()
.setSubText("Speed: $speed KB/s")
.setStyle(NotificationCompat.InboxStyle()
.addLine(messageSnippet1)
.addLine(messageSnippet2)
.addLine(messageSnippet3)
.addLine(messageSnippet4))
.setBigContentTitle("Total: $total Completed:$converted")
.addLine(messageList[0])
.addLine(messageList[1])
.addLine(messageList[2])
.addLine(messageList[3]))
.setContentIntent(pendingIntent)
.build()
startForeground(notificationId, notification)
}
@ -149,7 +145,7 @@ class ForegroundService : Service(){
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
// Send a notification that service is started
Log.i(tag,"Service Started.")
startForeground()
//do heavy work on a background thread
//val list = intent.getSerializableExtra("list") as List<Any?>
// val list = intent.getParcelableArrayListExtra<DownloadObject>("list") ?: intent.extras?.getParcelableArrayList<DownloadObject>("list")
@ -166,7 +162,7 @@ class ForegroundService : Service(){
fetch!!.enqueue(request,
Func {
requestMap[it] = obj.track
obj.track?.let { it1 -> requestMap.put(it, it1) }
downloadList.remove(obj)
Log.i(tag, "Enqueuing Download")
},
@ -197,7 +193,6 @@ class ForegroundService : Service(){
super.onDestroy()
if(downloadMap.isEmpty() && converted == total){
Log.i(tag,"Service destroyed.")
fetch?.close()
deleteFile(parentDirectory)
releaseWakeLock()
stopForeground(true)
@ -223,8 +218,11 @@ class ForegroundService : Service(){
super.onTaskRemoved(rootIntent)
if(downloadMap.isEmpty() && converted == total ){
Log.i(tag,"Service Removed.")
fetch?.close()
stopSelf()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true)
} else {
stopSelf()//System will automatically close it
}
}
}
@ -239,7 +237,7 @@ class ForegroundService : Service(){
if (file.isDirectory) {
deleteFile(file)
} 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}")
file.delete()
}
@ -274,21 +272,21 @@ class ForegroundService : Service(){
) {
val track = requestMap[download.request]
when(notificationLine){
0 -> {
messageList[0] = "Downloading ${track?.name}"
notificationLine = 1
}
1 -> {
messageSnippet1 = "Downloading ${track?.name}"
messageList[1] = "Downloading ${track?.name}"
notificationLine = 2
}
2 -> {
messageSnippet2 = "Downloading ${track?.name}"
2-> {
messageList[2] = "Downloading ${track?.name}"
notificationLine = 3
}
3-> {
messageSnippet3 = "Downloading ${track?.name}"
notificationLine = 4
}
4 -> {
messageSnippet4 = "Downloading ${track?.name}"
notificationLine = 1
3 -> {
messageList[3] = "Downloading ${track?.name}"
notificationLine = 0
}
}
Log.i(tag,"${track?.name} Download Started")
@ -309,6 +307,13 @@ class ForegroundService : Service(){
override fun onCompleted(download: Download) {
val track = requestMap[download.request]
for (message in messageList){
if( message == "Downloading ${track?.name}"){
messageList[messageList.indexOf(message)] = ""
}
}
serviceScope.launch {
try{
convertToMp3(download.file, track!!)
@ -370,7 +375,7 @@ class ForegroundService : Service(){
/**
* 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 request = DownloadManager.Request(uri)
.setAllowedNetworkTypes(
@ -407,7 +412,7 @@ class ForegroundService : Service(){
/**
*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)
FFmpeg.executeAsync(
@ -457,17 +462,16 @@ class ForegroundService : Service(){
val mNotificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
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)
.setSubText("Speed: $speed KB/s")
.setNotificationSilent()
.setStyle(NotificationCompat.InboxStyle()
.addLine(messageSnippet1)
.addLine(messageSnippet2)
.addLine(messageSnippet3)
.addLine(messageSnippet4))
.setBigContentTitle("Total: $total Completed:$converted")
.addLine(messageList[0])
.addLine(messageList[1])
.addLine(messageList[2])
.addLine(messageList[3]))
.setContentIntent(pendingIntent)
.build()
mNotificationManager.notify(notificationId, notification)
}
@ -506,7 +510,7 @@ class ForegroundService : Service(){
track.ytCoverUrl?.let {
val file = File(
Environment.getExternalStorageDirectory(),
DownloadHelper.defaultDir +".Images/" + it.substringAfterLast('/',it) + ".jpeg")
SpotifyDownloadHelper.defaultDir +".Images/" + it.substringAfterLast('/',it) + ".jpeg")
Log.i("Mp3Tags editing Tags",file.path)
//init array with file length
val bytesArray = ByteArray(file.length().toInt())
@ -518,7 +522,7 @@ class ForegroundService : Service(){
track.album?.let {
val file = File(
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)
//init array with file length
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"
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>

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">
<fragment
android:id="@+id/mainFragment"
android:name="com.shabinder.spotiflyer.fragments.MainFragment"
android:id="@+id/spotifyFragment"
android:name="com.shabinder.spotiflyer.ui.spotify.SpotifyFragment"
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>

View File

@ -19,7 +19,8 @@
<string name="app_name">SpotiFlyer</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_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_web_link">Open Spotify Web Player</string>
<string name="title"><b>No</b> Internet Connectivity!</string>