From 5f1176ead5443be5283675048eb17bc924fe24ba Mon Sep 17 00:00:00 2001 From: shabinder Date: Wed, 5 Aug 2020 13:16:29 +0530 Subject: [PATCH] Apk size reduced,MVVM Approach,UI Changes --- app/build.gradle | 1 + .../com/shabinder/spotiflyer/MainActivity.kt | 22 +- .../shabinder/spotiflyer/SharedViewModel.kt | 22 +- ...loadHelper.kt => SpotifyDownloadHelper.kt} | 117 ++--- .../spotiflyer/fragments/MainFragment.kt | 445 ------------------ .../spotiflyer/fragments/MainViewModel.kt | 27 -- .../spotiflyer/models/DownloadObject.kt | 3 +- ...tAdapter.kt => SpotifyTrackListAdapter.kt} | 10 +- .../spotiflyer/splash/SplashScreen.kt | 2 +- .../spotiflyer/ui/spotify/SpotifyFragment.kt | 241 ++++++++++ .../spotiflyer/ui/spotify/SpotifyViewModel.kt | 116 +++++ .../spotiflyer/utils/BindingAdapter.kt | 4 +- .../spotiflyer/utils/SpotifyService.kt | 10 +- .../spotiflyer/worker/ForegroundService.kt | 122 ++--- app/src/main/res/drawable/ic_github.xml | 3 +- app/src/main/res/layout/main_fragment.xml | 362 -------------- app/src/main/res/layout/spotify_fragment.xml | 148 ++++++ app/src/main/res/navigation/navigation.xml | 35 +- app/src/main/res/values/strings.xml | 3 +- 19 files changed, 684 insertions(+), 1009 deletions(-) rename app/src/main/java/com/shabinder/spotiflyer/downloadHelper/{DownloadHelper.kt => SpotifyDownloadHelper.kt} (83%) delete mode 100644 app/src/main/java/com/shabinder/spotiflyer/fragments/MainFragment.kt delete mode 100644 app/src/main/java/com/shabinder/spotiflyer/fragments/MainViewModel.kt rename app/src/main/java/com/shabinder/spotiflyer/recyclerView/{TrackListAdapter.kt => SpotifyTrackListAdapter.kt} (86%) create mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt create mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt delete mode 100644 app/src/main/res/layout/main_fragment.xml create mode 100644 app/src/main/res/layout/spotify_fragment.xml diff --git a/app/build.gradle b/app/build.gradle index f286018e..1523d234 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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") { diff --git a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index 788dc826..c0e88f35 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -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() { diff --git a/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt index 86a3d69d..b7aad664 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt @@ -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().apply { value = "" } - var spotifyService : SpotifyService? = null - var ytDownloader : YoutubeDownloader? = null - var isConnected = MutableLiveData().apply { value = false } + var spotifyService = MutableLiveData() + var ytDownloader = MutableLiveData() var easyUpiPayment: EasyUpiPayment? = null + var accessToken = MutableLiveData().apply { value = "" } + var isConnected = MutableLiveData().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() diff --git a/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/DownloadHelper.kt b/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt similarity index 83% rename from app/src/main/java/com/shabinder/spotiflyer/downloadHelper/DownloadHelper.kt rename to app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt index 6cc76497..90c9cc1e 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/DownloadHelper.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt @@ -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() var sharedViewModel:SharedViewModel? = null private var isBrowserLoading = false private var total = 0 private var Processed = 0 + private var listProcessed:Boolean = false var youtubeList = mutableListOf() /** @@ -64,32 +64,27 @@ object DownloadHelper { subFolder: String?, trackList: List, 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) - } + 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, - type:String, - subFolder:String?, - ytDownloader: YoutubeDownloader?, - searchQuery: String, - track: Track, - index: Int? = null){ + suspend fun getYTLink(spotifyFragment: SpotifyFragment? = null, + type:String, + subFolder:String?, + ytDownloader: YoutubeDownloader?, + searchQuery: String, + 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?, diff --git a/app/src/main/java/com/shabinder/spotiflyer/fragments/MainFragment.kt b/app/src/main/java/com/shabinder/spotiflyer/fragments/MainFragment.kt deleted file mode 100644 index 044ee832..00000000 --- a/app/src/main/java/com/shabinder/spotiflyer/fragments/MainFragment.kt +++ /dev/null @@ -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 . - */ - -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 { - 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() - 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() - 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() - 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) { - 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 { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - Log.i("Glide","LoadFailed") - return false - } - - override fun onResourceReady( - resource: File?, - model: Any?, - target: Target?, - 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){ - 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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/fragments/MainViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/fragments/MainViewModel.kt deleted file mode 100644 index 53a91ec4..00000000 --- a/app/src/main/java/com/shabinder/spotiflyer/fragments/MainViewModel.kt +++ /dev/null @@ -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 . - */ - -package com.shabinder.spotiflyer.fragments - -import androidx.lifecycle.ViewModel -import com.shabinder.spotiflyer.models.Track - -class MainViewModel: ViewModel() { - var searchLink: String = "" - var trackList = mutableListOf() - var coverUrl: String = "" -} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/models/DownloadObject.kt b/app/src/main/java/com/shabinder/spotiflyer/models/DownloadObject.kt index adcef155..a630a217 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/models/DownloadObject.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/models/DownloadObject.kt @@ -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 \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/recyclerView/TrackListAdapter.kt b/app/src/main/java/com/shabinder/spotiflyer/recyclerView/SpotifyTrackListAdapter.kt similarity index 86% rename from app/src/main/java/com/shabinder/spotiflyer/recyclerView/TrackListAdapter.kt rename to app/src/main/java/com/shabinder/spotiflyer/recyclerView/SpotifyTrackListAdapter.kt index c687dcf3..09cc1167 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/recyclerView/TrackListAdapter.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/recyclerView/SpotifyTrackListAdapter.kt @@ -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() { +class SpotifyTrackListAdapter:RecyclerView.Adapter() { var trackList = listOf() 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() { 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) } } diff --git a/app/src/main/java/com/shabinder/spotiflyer/splash/SplashScreen.kt b/app/src/main/java/com/shabinder/spotiflyer/splash/SplashScreen.kt index 66ed62b4..50166316 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/splash/SplashScreen.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/splash/SplashScreen.kt @@ -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 diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt new file mode 100644 index 00000000..4b5f620f --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt @@ -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 . + */ + +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) { + 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 { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + Log.i("Glide","LoadFailed") + return false + } + + override fun onResourceReady( + resource: File?, + model: Any?, + target: Target?, + 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){ + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt new file mode 100644 index 00000000..c3a98e34 --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt @@ -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 . + */ + +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>() + private val loading = "Loading" + var title = MutableLiveData().apply { value = "\"SpotiFlyer\"" } + var coverUrl = MutableLiveData().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() + 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() + 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() + 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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/BindingAdapter.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/BindingAdapter.kt index d754bded..2f29889b 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/BindingAdapter.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/BindingAdapter.kt @@ -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 diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/SpotifyService.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/SpotifyService.kt index 05ff0655..3744eda7 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/SpotifyService.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/SpotifyService.kt @@ -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? diff --git a/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt b/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt index 5a213608..1e251411 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt @@ -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("","","","") + 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 // val list = intent.getParcelableArrayListExtra("list") ?: intent.extras?.getParcelableArrayList("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") @@ -308,20 +306,27 @@ class ForegroundService : Service(){ } override fun onCompleted(download: Download) { - val track = requestMap[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) - } + 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!!) + 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 updateNotification() } @@ -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()) diff --git a/app/src/main/res/drawable/ic_github.xml b/app/src/main/res/drawable/ic_github.xml index 050eff17..d8771012 100644 --- a/app/src/main/res/drawable/ic_github.xml +++ b/app/src/main/res/drawable/ic_github.xml @@ -17,5 +17,6 @@ - + + diff --git a/app/src/main/res/layout/main_fragment.xml b/app/src/main/res/layout/main_fragment.xml deleted file mode 100644 index 18405696..00000000 --- a/app/src/main/res/layout/main_fragment.xml +++ /dev/null @@ -1,362 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/spotify_fragment.xml b/app/src/main/res/layout/spotify_fragment.xml new file mode 100644 index 00000000..580a1194 --- /dev/null +++ b/app/src/main/res/layout/spotify_fragment.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/navigation/navigation.xml b/app/src/main/res/navigation/navigation.xml index 3ee38008..db5e89c2 100644 --- a/app/src/main/res/navigation/navigation.xml +++ b/app/src/main/res/navigation/navigation.xml @@ -23,8 +23,37 @@ app:startDestination="@id/mainFragment"> + tools:layout="@layout/spotify_fragment" > + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fb5d58e6..0e95e1d0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,7 +19,8 @@ SpotiFlyer Usage Instructions: 1. Paste, Your Copied Link in Search Box at Top. - 2. Share directly from Spotify App to this App. + or Open platform using Logo Button Above. + 2. Share directly to this App. It Seems you don\'t have Spotify Installed Open Spotify Web Player No Internet Connectivity!