mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-25 02:14:32 +01:00
changelog same as 1.7 release
This commit is contained in:
parent
2e40db5452
commit
b96008a553
@ -5,10 +5,12 @@
|
|||||||
<w>amita</w>
|
<w>amita</w>
|
||||||
<w>cardview</w>
|
<w>cardview</w>
|
||||||
<w>cherrypick</w>
|
<w>cherrypick</w>
|
||||||
|
<w>crashlytics</w>
|
||||||
<w>downloadrecord</w>
|
<w>downloadrecord</w>
|
||||||
<w>emoji</w>
|
<w>emoji</w>
|
||||||
<w>ffmpeg</w>
|
<w>ffmpeg</w>
|
||||||
<w>flyer</w>
|
<w>flyer</w>
|
||||||
|
<w>fmpeg</w>
|
||||||
<w>gaana</w>
|
<w>gaana</w>
|
||||||
<w>gener</w>
|
<w>gener</w>
|
||||||
<w>hqdefault</w>
|
<w>hqdefault</w>
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveModulePerSourceSet" value="false" />
|
<option name="resolveModulePerSourceSet" value="false" />
|
||||||
|
<option name="useQualifiedModuleNames" value="true" />
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<component name="ProjectPlainTextFileTypeManager">
|
<component name="ProjectPlainTextFileTypeManager">
|
||||||
<file url="file://$PROJECT_DIR$/app/src/main/java/com/shabinder/spotiflyer/testing/YoutubeInterface.kt.backup" />
|
<file url="file://$PROJECT_DIR$/app/src/main/java/com/shabinder/spotiflyer/testing/YoutubeInterface.kt.backup" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
@ -19,7 +19,7 @@ plugins {
|
|||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'kotlin-kapt'
|
id 'kotlin-kapt'
|
||||||
id 'kotlin-android-extensions'
|
id 'kotlin-parcelize'
|
||||||
id 'androidx.navigation.safeargs.kotlin'
|
id 'androidx.navigation.safeargs.kotlin'
|
||||||
id 'dagger.hilt.android.plugin'
|
id 'dagger.hilt.android.plugin'
|
||||||
id 'kotlinx-serialization'
|
id 'kotlinx-serialization'
|
||||||
@ -39,8 +39,8 @@ android {
|
|||||||
applicationId 'com.shabinder.spotiflyer'
|
applicationId 'com.shabinder.spotiflyer'
|
||||||
minSdkVersion 22
|
minSdkVersion 22
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 8
|
versionCode 9
|
||||||
versionName "1.6"
|
versionName "1.7"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +79,7 @@ android {
|
|||||||
exclude 'META-INF/ASL2.0'
|
exclude 'META-INF/ASL2.0'
|
||||||
exclude("META-INF/*.kotlin_module")
|
exclude("META-INF/*.kotlin_module")
|
||||||
}
|
}
|
||||||
|
ndkVersion '21.3.6528147'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -101,7 +102,7 @@ dependencies {
|
|||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.2.1'
|
implementation 'com.google.android.material:material:1.2.1'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2-native-mt'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ import androidx.navigation.findNavController
|
|||||||
import com.github.javiersantos.appupdater.AppUpdater
|
import com.github.javiersantos.appupdater.AppUpdater
|
||||||
import com.github.javiersantos.appupdater.enums.UpdateFrom
|
import com.github.javiersantos.appupdater.enums.UpdateFrom
|
||||||
import com.shabinder.spotiflyer.databinding.MainActivityBinding
|
import com.shabinder.spotiflyer.databinding.MainActivityBinding
|
||||||
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
|
||||||
import com.shabinder.spotiflyer.networking.SpotifyService
|
import com.shabinder.spotiflyer.networking.SpotifyService
|
||||||
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
|
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
|
||||||
import com.shabinder.spotiflyer.utils.NetworkInterceptor
|
import com.shabinder.spotiflyer.utils.NetworkInterceptor
|
||||||
@ -45,6 +44,7 @@ import com.shabinder.spotiflyer.utils.createDirectories
|
|||||||
import com.shabinder.spotiflyer.utils.showMessage
|
import com.shabinder.spotiflyer.utils.showMessage
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -59,6 +59,8 @@ import javax.inject.Inject
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity(){
|
class MainActivity : AppCompatActivity(){
|
||||||
private var spotifyService : SpotifyService? = null
|
private var spotifyService : SpotifyService? = null
|
||||||
|
val viewModelScope : CoroutineScope
|
||||||
|
get() = sharedViewModel.viewModelScope
|
||||||
private lateinit var binding: MainActivityBinding
|
private lateinit var binding: MainActivityBinding
|
||||||
private lateinit var sharedViewModel: SharedViewModel
|
private lateinit var sharedViewModel: SharedViewModel
|
||||||
lateinit var snackBarAnchor: View
|
lateinit var snackBarAnchor: View
|
||||||
@ -75,7 +77,6 @@ class MainActivity : AppCompatActivity(){
|
|||||||
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
|
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
|
||||||
navController = findNavController(R.id.navHostFragment)
|
navController = findNavController(R.id.navHostFragment)
|
||||||
snackBarAnchor = binding.snackBarPosition
|
snackBarAnchor = binding.snackBarPosition
|
||||||
DownloadHelper.youtubeMusicApi = sharedViewModel.youtubeMusicApi
|
|
||||||
authenticateSpotify()
|
authenticateSpotify()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import androidx.room.ColumnInfo
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.Index
|
import androidx.room.Index
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@Entity(
|
@Entity(
|
||||||
@ -48,10 +48,4 @@ data class DownloadRecord(
|
|||||||
|
|
||||||
@ColumnInfo(name = "totalFiles")
|
@ColumnInfo(name = "totalFiles")
|
||||||
var totalFiles:Int = 1,
|
var totalFiles:Int = 1,
|
||||||
|
|
||||||
@ColumnInfo(name = "downloaded")
|
|
||||||
var downloaded:Boolean=false,
|
|
||||||
|
|
||||||
@ColumnInfo(name = "directory")
|
|
||||||
var directory:String?=null
|
|
||||||
):Parcelable
|
):Parcelable
|
@ -1,168 +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.downloadHelper
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
|
||||||
import android.view.animation.AlphaAnimation
|
|
||||||
import android.view.animation.Animation
|
|
||||||
import android.widget.TextView
|
|
||||||
import com.shabinder.spotiflyer.models.DownloadObject
|
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
|
||||||
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
|
|
||||||
import com.shabinder.spotiflyer.networking.makeJsonBody
|
|
||||||
import com.shabinder.spotiflyer.utils.*
|
|
||||||
import com.shabinder.spotiflyer.utils.Provider.defaultDir
|
|
||||||
import com.shabinder.spotiflyer.utils.Provider.mainActivity
|
|
||||||
import com.tonyodev.fetch2.Status
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Callback
|
|
||||||
import retrofit2.Response
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
object DownloadHelper {
|
|
||||||
|
|
||||||
var statusBar:TextView? = null
|
|
||||||
var youtubeMusicApi: YoutubeMusicApi? = null
|
|
||||||
|
|
||||||
private var total = 0
|
|
||||||
private var processed = 0
|
|
||||||
var notFound = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function To Download All Tracks Available in a List
|
|
||||||
**/
|
|
||||||
suspend fun downloadAllTracks(
|
|
||||||
type:String,
|
|
||||||
subFolder: String?,
|
|
||||||
trackList: List<TrackDetails>) {
|
|
||||||
resetStatusBar()// For New Download Request's Status
|
|
||||||
val downloadList = ArrayList<DownloadObject>()
|
|
||||||
withContext(Dispatchers.IO){
|
|
||||||
total += trackList.size // Adding New Download List Count to StatusBar
|
|
||||||
trackList.forEachIndexed { index, it ->
|
|
||||||
if(!isOnline()){
|
|
||||||
showNoConnectionAlert()
|
|
||||||
return@withContext
|
|
||||||
}
|
|
||||||
if(it.downloaded == DownloadStatus.Downloaded){//Download Already Present!!
|
|
||||||
processed++
|
|
||||||
if(index == (trackList.size-1)){//LastElement
|
|
||||||
Handler(Looper.myLooper()!!).postDelayed({
|
|
||||||
//Delay is Added ,if a request is in processing it may finish
|
|
||||||
Log.i("Spotify Helper","Download Request Sent")
|
|
||||||
showMessage("Download Started, Now You can leave the App!")
|
|
||||||
startService(mainActivity,downloadList)
|
|
||||||
},3000)
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
|
||||||
val jsonBody = makeJsonBody(searchQuery.trim()).toJsonString()
|
|
||||||
youtubeMusicApi?.getYoutubeMusicResponse(jsonBody)?.enqueue(
|
|
||||||
object : Callback<String>{
|
|
||||||
override fun onResponse(call: Call<String>, response: Response<String>) {
|
|
||||||
val videoId = sortByBestMatch(
|
|
||||||
getYTTracks(response.body().toString()),
|
|
||||||
trackName = it.title,
|
|
||||||
trackArtists = it.artists,
|
|
||||||
trackDurationSec = it.durationSec
|
|
||||||
).keys.firstOrNull()
|
|
||||||
Log.i("Spotify Helper Video ID",videoId ?: "Not Found")
|
|
||||||
if(videoId.isNullOrBlank()) {
|
|
||||||
//Track Not Found
|
|
||||||
notFound++ ; updateStatusBar()
|
|
||||||
val intent = Intent()
|
|
||||||
.setAction(Status.FAILED.name)
|
|
||||||
.putExtra("track",it)
|
|
||||||
statusBar?.context?.sendBroadcast(intent)
|
|
||||||
}
|
|
||||||
else {//Found Youtube Video ID
|
|
||||||
val outputFile: String =
|
|
||||||
defaultDir +
|
|
||||||
removeIllegalChars(type) + File.separator +
|
|
||||||
(if (subFolder == null) { "" }
|
|
||||||
else { removeIllegalChars(subFolder) + File.separator }
|
|
||||||
+ removeIllegalChars(it.title) + ".m4a")
|
|
||||||
|
|
||||||
val downloadObject = DownloadObject(
|
|
||||||
trackDetails = it,
|
|
||||||
ytVideoId = videoId,
|
|
||||||
outputFile = outputFile
|
|
||||||
)
|
|
||||||
processed++
|
|
||||||
updateStatusBar()
|
|
||||||
downloadList.add(downloadObject)
|
|
||||||
}
|
|
||||||
if(index == (trackList.size-1)){//LastElement
|
|
||||||
statusBar?.clearAnimation()
|
|
||||||
if(downloadList.size > 0) {
|
|
||||||
Handler(Looper.myLooper()!!).postDelayed({
|
|
||||||
//Delay is Added ,if a request is in processing it may finish
|
|
||||||
Log.i("Spotify Helper", "Download Request Sent")
|
|
||||||
showMessage("Download Started, Now You can leave the App!")
|
|
||||||
startService(mainActivity, downloadList)
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override fun onFailure(call: Call<String>, t: Throwable) {
|
|
||||||
if(t.message.toString().contains("Failed to connect")) showMessage("Failed, Check Your Internet Connection!")
|
|
||||||
Log.i("YT API Req. Fail",t.message.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
updateStatusBar()
|
|
||||||
}
|
|
||||||
animateStatusBar()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resetStatusBar() {
|
|
||||||
total = 0
|
|
||||||
processed = 0
|
|
||||||
notFound = 0
|
|
||||||
updateStatusBar()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun animateStatusBar() {
|
|
||||||
val anim: Animation = AlphaAnimation(0.3f, 0.9f)
|
|
||||||
anim.duration = 1500 //You can manage the blinking time with this parameter
|
|
||||||
anim.startOffset = 20
|
|
||||||
anim.repeatMode = Animation.REVERSE
|
|
||||||
anim.repeatCount = Animation.INFINITE
|
|
||||||
statusBar?.animation = anim
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
private fun updateStatusBar() {
|
|
||||||
CoroutineScope(Dispatchers.Main).launch{
|
|
||||||
statusBar!!.visibility = View.VISIBLE
|
|
||||||
statusBar?.text = "Total: $total ${getEmojiByUnicode(0x2705)}: $processed ${getEmojiByUnicode(0x274C)}: $notFound"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +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.downloadHelper
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.shabinder.spotiflyer.models.DownloadObject
|
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
|
||||||
import com.shabinder.spotiflyer.utils.*
|
|
||||||
import com.shabinder.spotiflyer.utils.Provider.defaultDir
|
|
||||||
import com.shabinder.spotiflyer.utils.Provider.mainActivity
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
interface YTDownloadHelper {
|
|
||||||
suspend fun downloadYTTracks(
|
|
||||||
type:String,
|
|
||||||
subFolder: String?,
|
|
||||||
tracks:List<TrackDetails>,
|
|
||||||
){
|
|
||||||
val downloadList = ArrayList<DownloadObject>()
|
|
||||||
tracks.forEach {
|
|
||||||
if(!isOnline()){
|
|
||||||
showNoConnectionAlert()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val outputFile: String = defaultDir +
|
|
||||||
removeIllegalChars(type) + File.separator +
|
|
||||||
(if (subFolder == null) { "" }
|
|
||||||
else { removeIllegalChars(subFolder) + File.separator }
|
|
||||||
+ removeIllegalChars(it.title) + ".m4a")
|
|
||||||
|
|
||||||
val downloadObject = DownloadObject(
|
|
||||||
trackDetails = it,
|
|
||||||
ytVideoId = it.albumArt.absolutePath.substringAfterLast("/")
|
|
||||||
.substringBeforeLast("."),
|
|
||||||
outputFile = outputFile
|
|
||||||
)
|
|
||||||
|
|
||||||
downloadList.add(downloadObject)
|
|
||||||
}
|
|
||||||
Log.i("YT Downloader Helper","Download Request Sent")
|
|
||||||
withContext(Dispatchers.Main){
|
|
||||||
showMessage("Download Started, Now You can leave the App!")
|
|
||||||
startService(mainActivity,downloadList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,16 +19,9 @@ package com.shabinder.spotiflyer.models
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data class DownloadObject(
|
|
||||||
var trackDetails: TrackDetails,
|
|
||||||
var ytVideoId:String,
|
|
||||||
var outputFile:String
|
|
||||||
):Parcelable
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class TrackDetails(
|
data class TrackDetails(
|
||||||
var title:String,
|
var title:String,
|
||||||
@ -43,7 +36,9 @@ data class TrackDetails(
|
|||||||
var albumArtURL: String,
|
var albumArtURL: String,
|
||||||
var source: Source,
|
var source: Source,
|
||||||
var downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
|
var downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
|
||||||
var progress: Int = 0
|
var progress: Int = 0,
|
||||||
|
var outputFile: String,
|
||||||
|
var videoID:String? = null
|
||||||
):Parcelable
|
):Parcelable
|
||||||
|
|
||||||
enum class DownloadStatus{
|
enum class DownloadStatus{
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class YoutubeTrack(
|
data class YoutubeTrack(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Album(
|
data class Album(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Artist(
|
data class Artist(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Copyright(
|
data class Copyright(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Episodes(
|
data class Episodes(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Followers(
|
data class Followers(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Image(
|
data class Image(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class LinkedTrack(
|
data class LinkedTrack(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class PagingObjectPlaylistTrack(
|
data class PagingObjectPlaylistTrack(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class PagingObjectTrack(
|
data class PagingObjectTrack(
|
||||||
|
@ -19,7 +19,7 @@ package com.shabinder.spotiflyer.models.spotify
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Playlist(
|
data class Playlist(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class PlaylistTrack(
|
data class PlaylistTrack(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Token(
|
data class Token(
|
||||||
|
@ -19,7 +19,7 @@ package com.shabinder.spotiflyer.models.spotify
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Track(
|
data class Track(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class UserPrivate(
|
data class UserPrivate(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package com.shabinder.spotiflyer.models.spotify
|
package com.shabinder.spotiflyer.models.spotify
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class UserPublic(
|
data class UserPublic(
|
||||||
|
@ -27,8 +27,6 @@ import androidx.recyclerview.widget.ListAdapter
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.databinding.TrackListItemBinding
|
import com.shabinder.spotiflyer.databinding.TrackListItemBinding
|
||||||
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
|
||||||
import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper
|
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
@ -36,7 +34,7 @@ import com.shabinder.spotiflyer.ui.base.tracklistbase.TrackListViewModel
|
|||||||
import com.shabinder.spotiflyer.utils.*
|
import com.shabinder.spotiflyer.utils.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class TrackListAdapter(private val viewModel : TrackListViewModel): ListAdapter<TrackDetails, TrackListAdapter.ViewHolder>(TrackDiffCallback()),YTDownloadHelper {
|
class TrackListAdapter(private val viewModel : TrackListViewModel): ListAdapter<TrackDetails, TrackListAdapter.ViewHolder>(TrackDiffCallback()){
|
||||||
|
|
||||||
var source:Source =Source.Spotify
|
var source:Source =Source.Spotify
|
||||||
|
|
||||||
@ -115,20 +113,12 @@ class TrackListAdapter(private val viewModel : TrackListViewModel): ListAdapter<
|
|||||||
when(source){
|
when(source){
|
||||||
Source.YouTube -> {
|
Source.YouTube -> {
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
downloadYTTracks(
|
downloadTracks(arrayListOf(item))
|
||||||
viewModel.folderType,
|
|
||||||
viewModel.subFolder,
|
|
||||||
listOf(item)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
DownloadHelper.downloadAllTracks(
|
downloadTracks(arrayListOf(item))
|
||||||
viewModel.folderType,
|
|
||||||
viewModel.subFolder,
|
|
||||||
listOf(item)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,5 +32,6 @@ abstract class BaseFragment<VB:ViewBinding,VM : ViewModel> : Fragment() {
|
|||||||
protected abstract val viewModel: VM
|
protected abstract val viewModel: VM
|
||||||
protected val viewModelScope by lazy{viewModel.viewModelScope}
|
protected val viewModelScope by lazy{viewModel.viewModelScope}
|
||||||
|
|
||||||
open fun applicationContext(): Context = requireActivity().applicationContext
|
protected val applicationContext: Context
|
||||||
|
get() = requireActivity().applicationContext
|
||||||
}
|
}
|
@ -29,7 +29,6 @@ import android.view.ViewGroup
|
|||||||
import androidx.navigation.NavArgs
|
import androidx.navigation.NavArgs
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.databinding.TrackListFragmentBinding
|
import com.shabinder.spotiflyer.databinding.TrackListFragmentBinding
|
||||||
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
@ -63,15 +62,9 @@ abstract class TrackListFragment<VM : TrackListViewModel, args: NavArgs> : BaseF
|
|||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
binding = TrackListFragmentBinding.inflate(inflater,container,false)
|
binding = TrackListFragmentBinding.inflate(inflater,container,false)
|
||||||
initializeAll()
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeAll() {
|
|
||||||
DownloadHelper.youtubeMusicApi = sharedViewModel.youtubeMusicApi
|
|
||||||
DownloadHelper.statusBar = binding.statusBar
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding.trackList.adapter = adapter
|
binding.trackList.adapter = adapter
|
||||||
@ -84,7 +77,7 @@ abstract class TrackListFragment<VM : TrackListViewModel, args: NavArgs> : BaseF
|
|||||||
private fun initializeLiveDataObservers() {
|
private fun initializeLiveDataObservers() {
|
||||||
viewModel.trackList.observe(viewLifecycleOwner, {
|
viewModel.trackList.observe(viewLifecycleOwner, {
|
||||||
if (!it.isNullOrEmpty()){
|
if (!it.isNullOrEmpty()){
|
||||||
Log.i("GaanaFragment","TrackList Updated")
|
Log.i("TrackListFragment","TrackList Updated")
|
||||||
adapter.submitList(it, source)
|
adapter.submitList(it, source)
|
||||||
updateTracksStatus()
|
updateTracksStatus()
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,8 @@ import android.view.ViewGroup
|
|||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
|
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
|
||||||
import com.shabinder.spotiflyer.ui.base.tracklistbase.TrackListFragment
|
import com.shabinder.spotiflyer.ui.base.tracklistbase.TrackListFragment
|
||||||
@ -46,7 +46,7 @@ class GaanaFragment : TrackListFragment<GaanaViewModel, GaanaFragmentArgs>() {
|
|||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
super.onCreateView(inflater, container, savedInstanceState)
|
super.onCreateView(inflater, container, savedInstanceState)
|
||||||
adapter = TrackListAdapter(viewModel)
|
adapter = TrackListAdapter(viewModel)
|
||||||
|
|
||||||
@ -76,24 +76,21 @@ class GaanaFragment : TrackListFragment<GaanaViewModel, GaanaFragmentArgs>() {
|
|||||||
visible()
|
visible()
|
||||||
rotate()
|
rotate()
|
||||||
}
|
}
|
||||||
for (track in viewModel.trackList.value!!){
|
|
||||||
if(track.downloaded != DownloadStatus.Downloaded){
|
|
||||||
track.downloaded = DownloadStatus.Queued
|
|
||||||
adapter.notifyItemChanged(viewModel.trackList.value!!.indexOf(track))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showMessage("Processing!")
|
showMessage("Processing!")
|
||||||
sharedViewModel.viewModelScope.launch(Dispatchers.Default){
|
sharedViewModel.viewModelScope.launch(Dispatchers.Default){
|
||||||
loadAllImages(requireActivity(), viewModel.trackList.value?.map{it.albumArtURL}, Source.Gaana)
|
loadAllImages(requireActivity(), viewModel.trackList.value?.map{it.albumArtURL}, Source.Gaana)
|
||||||
}
|
}
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
val finalList = viewModel.trackList.value
|
val finalList = viewModel.trackList.value?.filter{it.downloaded == DownloadStatus.NotDownloaded}
|
||||||
if(finalList.isNullOrEmpty())showMessage("Not Downloading Any Song")
|
if(finalList.isNullOrEmpty())showMessage("Not Downloading Any Song")
|
||||||
DownloadHelper.downloadAllTracks(
|
finalList?.let { it1 -> downloadTracks(it1 as ArrayList<TrackDetails>) }
|
||||||
viewModel.folderType,
|
for (track in viewModel.trackList.value!!){
|
||||||
viewModel.subFolder,
|
if(track.downloaded == DownloadStatus.NotDownloaded){
|
||||||
finalList ?: listOf(),
|
track.downloaded = DownloadStatus.Queued
|
||||||
)
|
//adapter.notifyItemChanged(viewModel.trackList.value!!.indexOf(track))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ import java.io.File
|
|||||||
|
|
||||||
class GaanaViewModel @ViewModelInject constructor(
|
class GaanaViewModel @ViewModelInject constructor(
|
||||||
val databaseDAO: DatabaseDAO,
|
val databaseDAO: DatabaseDAO,
|
||||||
val gaanaInterface : GaanaInterface
|
private val gaanaInterface : GaanaInterface
|
||||||
) : TrackListViewModel(){
|
) : TrackListViewModel(){
|
||||||
|
|
||||||
override var folderType:String = ""
|
override var folderType:String = ""
|
||||||
@ -51,6 +51,7 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
"song" -> {
|
"song" -> {
|
||||||
gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also {
|
gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also {
|
||||||
folderType = "Tracks"
|
folderType = "Tracks"
|
||||||
|
subFolder = ""
|
||||||
if (File(
|
if (File(
|
||||||
finalOutputDir(
|
finalOutputDir(
|
||||||
it.track_title,
|
it.track_title,
|
||||||
@ -61,7 +62,7 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
) {//Download Already Present!!
|
) {//Download Already Present!!
|
||||||
it.downloaded = DownloadStatus.Downloaded
|
it.downloaded = DownloadStatus.Downloaded
|
||||||
}
|
}
|
||||||
trackList.value = listOf(it).toTrackDetailsList()
|
trackList.value = listOf(it).toTrackDetailsList(folderType, subFolder)
|
||||||
title.value = it.track_title
|
title.value = it.track_title
|
||||||
coverUrl.value = it.artworkLink
|
coverUrl.value = it.artworkLink
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
@ -72,12 +73,6 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
link = "https://gaana.com/$type/$link",
|
link = "https://gaana.com/$type/$link",
|
||||||
coverUrl = coverUrl.value!!,
|
coverUrl = coverUrl.value!!,
|
||||||
totalFiles = 1,
|
totalFiles = 1,
|
||||||
downloaded = it.downloaded == DownloadStatus.Downloaded,
|
|
||||||
directory = finalOutputDir(
|
|
||||||
it.track_title,
|
|
||||||
folderType,
|
|
||||||
subFolder
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -99,7 +94,7 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
track.downloaded = DownloadStatus.Downloaded
|
track.downloaded = DownloadStatus.Downloaded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackList.value = it.tracks.toTrackDetailsList()
|
trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder)
|
||||||
title.value = link
|
title.value = link
|
||||||
coverUrl.value = it.custom_artworks.size_480p
|
coverUrl.value = it.custom_artworks.size_480p
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
@ -110,16 +105,6 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
link = "https://gaana.com/$type/$link",
|
link = "https://gaana.com/$type/$link",
|
||||||
coverUrl = coverUrl.value.toString(),
|
coverUrl = coverUrl.value.toString(),
|
||||||
totalFiles = trackList.value?.size ?: 0,
|
totalFiles = trackList.value?.size ?: 0,
|
||||||
downloaded = File(
|
|
||||||
finalOutputDir(
|
|
||||||
type = folderType,
|
|
||||||
subFolder = subFolder
|
|
||||||
)
|
|
||||||
).listFiles()?.size == trackList.value?.size,
|
|
||||||
directory = finalOutputDir(
|
|
||||||
type = folderType,
|
|
||||||
subFolder = subFolder
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -141,7 +126,7 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
track.downloaded = DownloadStatus.Downloaded
|
track.downloaded = DownloadStatus.Downloaded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackList.value = it.tracks.toTrackDetailsList()
|
trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder)
|
||||||
title.value = link
|
title.value = link
|
||||||
//coverUrl.value = "TODO"
|
//coverUrl.value = "TODO"
|
||||||
coverUrl.value = gaanaPlaceholderImageUrl
|
coverUrl.value = gaanaPlaceholderImageUrl
|
||||||
@ -153,16 +138,6 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
link = "https://gaana.com/$type/$link",
|
link = "https://gaana.com/$type/$link",
|
||||||
coverUrl = coverUrl.value.toString(),
|
coverUrl = coverUrl.value.toString(),
|
||||||
totalFiles = it.tracks.size,
|
totalFiles = it.tracks.size,
|
||||||
downloaded = File(
|
|
||||||
finalOutputDir(
|
|
||||||
type = folderType,
|
|
||||||
subFolder = subFolder
|
|
||||||
)
|
|
||||||
).listFiles()?.size == trackList.value?.size,
|
|
||||||
directory = finalOutputDir(
|
|
||||||
type = folderType,
|
|
||||||
subFolder = subFolder
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -190,7 +165,7 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
track.downloaded = DownloadStatus.Downloaded
|
track.downloaded = DownloadStatus.Downloaded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackList.value = it.tracks.toTrackDetailsList()
|
trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder)
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
databaseDAO.insert(
|
databaseDAO.insert(
|
||||||
DownloadRecord(
|
DownloadRecord(
|
||||||
@ -199,16 +174,6 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
link = "https://gaana.com/$type/$link",
|
link = "https://gaana.com/$type/$link",
|
||||||
coverUrl = coverUrl.value.toString(),
|
coverUrl = coverUrl.value.toString(),
|
||||||
totalFiles = trackList.value?.size ?: 0,
|
totalFiles = trackList.value?.size ?: 0,
|
||||||
downloaded = File(
|
|
||||||
finalOutputDir(
|
|
||||||
type = folderType,
|
|
||||||
subFolder = subFolder
|
|
||||||
)
|
|
||||||
).listFiles()?.size == trackList.value?.size,
|
|
||||||
directory = finalOutputDir(
|
|
||||||
type = folderType,
|
|
||||||
subFolder = subFolder
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -219,7 +184,7 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<GaanaTrack>.toTrackDetailsList() = this.map {
|
private fun List<GaanaTrack>.toTrackDetailsList(type:String , subFolder:String) = this.map {
|
||||||
TrackDetails(
|
TrackDetails(
|
||||||
title = it.track_title,
|
title = it.track_title,
|
||||||
artists = it.artist.map { artist -> artist?.name.toString() },
|
artists = it.artist.map { artist -> artist?.name.toString() },
|
||||||
@ -232,7 +197,8 @@ class GaanaViewModel @ViewModelInject constructor(
|
|||||||
trackUrl = it.lyrics_url,
|
trackUrl = it.lyrics_url,
|
||||||
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
|
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
|
||||||
source = Source.Gaana,
|
source = Source.Gaana,
|
||||||
albumArtURL = it.artworkLink
|
albumArtURL = it.artworkLink,
|
||||||
|
outputFile = finalOutputDir(it.track_title,type, subFolder,".m4a")
|
||||||
)
|
)
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
}
|
}
|
@ -51,7 +51,7 @@ class MainFragment : Fragment() {
|
|||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
binding = MainFragmentBinding.inflate(inflater,container,false)
|
binding = MainFragmentBinding.inflate(inflater,container,false)
|
||||||
initializeAll()
|
initializeAll()
|
||||||
binding.btnSearch.setOnClickListener {
|
binding.btnSearch.setOnClickListener {
|
||||||
@ -86,12 +86,6 @@ class MainFragment : Fragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
//starting Notification and Downloader Service!
|
|
||||||
startService(requireContext())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle Intent If there is any!
|
* Handle Intent If there is any!
|
||||||
**/
|
**/
|
||||||
|
@ -26,8 +26,8 @@ import android.view.ViewGroup
|
|||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
import com.shabinder.spotiflyer.networking.SpotifyService
|
import com.shabinder.spotiflyer.networking.SpotifyService
|
||||||
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
|
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
|
||||||
@ -103,16 +103,6 @@ class SpotifyFragment : TrackListFragment<SpotifyViewModel, SpotifyFragmentArgs>
|
|||||||
visible()
|
visible()
|
||||||
rotate()
|
rotate()
|
||||||
}
|
}
|
||||||
for (track in viewModel.trackList.value ?: listOf()) {
|
|
||||||
if (track.downloaded != DownloadStatus.Downloaded) {
|
|
||||||
track.downloaded = DownloadStatus.Queued
|
|
||||||
adapter.notifyItemChanged(
|
|
||||||
viewModel.trackList.value!!.indexOf(
|
|
||||||
track
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showMessage("Processing!")
|
showMessage("Processing!")
|
||||||
sharedViewModel.viewModelScope.launch(Dispatchers.Default) {
|
sharedViewModel.viewModelScope.launch(Dispatchers.Default) {
|
||||||
loadAllImages(
|
loadAllImages(
|
||||||
@ -122,13 +112,16 @@ class SpotifyFragment : TrackListFragment<SpotifyViewModel, SpotifyFragmentArgs>
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val finalList = viewModel.trackList.value
|
val finalList = viewModel.trackList.value?.filter{it.downloaded == DownloadStatus.NotDownloaded}
|
||||||
if (finalList.isNullOrEmpty()) showMessage("Not Downloading Any Song")
|
if (finalList.isNullOrEmpty()) showMessage("Not Downloading Any Song")
|
||||||
DownloadHelper.downloadAllTracks(
|
else downloadTracks(finalList as ArrayList<TrackDetails>)
|
||||||
viewModel.folderType,
|
for (track in viewModel.trackList.value ?: listOf()) {
|
||||||
viewModel.subFolder,
|
if (track.downloaded == DownloadStatus.NotDownloaded) {
|
||||||
finalList ?: listOf(),
|
track.downloaded = DownloadStatus.Queued
|
||||||
)
|
//adapter.notifyItemChanged(viewModel.trackList.value!!.indexOf(track))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,9 +61,10 @@ class SpotifyViewModel @ViewModelInject constructor(
|
|||||||
"track" -> {
|
"track" -> {
|
||||||
spotifyService?.getTrack(link)?.value?.also {
|
spotifyService?.getTrack(link)?.value?.also {
|
||||||
folderType = "Tracks"
|
folderType = "Tracks"
|
||||||
|
subFolder = ""
|
||||||
if (File(
|
if (File(
|
||||||
finalOutputDir(
|
finalOutputDir(
|
||||||
it.name,
|
it.name.toString(),
|
||||||
folderType,
|
folderType,
|
||||||
subFolder
|
subFolder
|
||||||
)
|
)
|
||||||
@ -71,7 +72,7 @@ class SpotifyViewModel @ViewModelInject constructor(
|
|||||||
) {//Download Already Present!!
|
) {//Download Already Present!!
|
||||||
it.downloaded = DownloadStatus.Downloaded
|
it.downloaded = DownloadStatus.Downloaded
|
||||||
}
|
}
|
||||||
trackList.value = listOf(it).toTrackDetailsList()
|
trackList.value = listOf(it).toTrackDetailsList(folderType, subFolder)
|
||||||
title.value = it.name
|
title.value = it.name
|
||||||
coverUrl.value = it.album!!.images?.elementAtOrNull(1)?.url
|
coverUrl.value = it.album!!.images?.elementAtOrNull(1)?.url
|
||||||
?: it.album!!.images?.elementAtOrNull(0)?.url
|
?: it.album!!.images?.elementAtOrNull(0)?.url
|
||||||
@ -83,8 +84,6 @@ class SpotifyViewModel @ViewModelInject constructor(
|
|||||||
link = "https://open.spotify.com/$type/$link",
|
link = "https://open.spotify.com/$type/$link",
|
||||||
coverUrl = coverUrl.value!!,
|
coverUrl = coverUrl.value!!,
|
||||||
totalFiles = 1,
|
totalFiles = 1,
|
||||||
downloaded = it.downloaded == DownloadStatus.Downloaded,
|
|
||||||
directory = finalOutputDir(it.name, folderType, subFolder)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -115,7 +114,7 @@ class SpotifyViewModel @ViewModelInject constructor(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
trackList.value = albumObject?.tracks?.items?.toTrackDetailsList()
|
trackList.value = albumObject?.tracks?.items?.toTrackDetailsList(folderType, subFolder)
|
||||||
title.value = albumObject?.name
|
title.value = albumObject?.name
|
||||||
coverUrl.value = albumObject?.images?.elementAtOrNull(1)?.url
|
coverUrl.value = albumObject?.images?.elementAtOrNull(1)?.url
|
||||||
?: albumObject?.images?.elementAtOrNull(0)?.url
|
?: albumObject?.images?.elementAtOrNull(0)?.url
|
||||||
@ -127,13 +126,6 @@ class SpotifyViewModel @ViewModelInject constructor(
|
|||||||
link = "https://open.spotify.com/$type/$link",
|
link = "https://open.spotify.com/$type/$link",
|
||||||
coverUrl = coverUrl.value.toString(),
|
coverUrl = coverUrl.value.toString(),
|
||||||
totalFiles = trackList.value?.size ?: 0,
|
totalFiles = trackList.value?.size ?: 0,
|
||||||
downloaded = File(
|
|
||||||
finalOutputDir(
|
|
||||||
type = folderType,
|
|
||||||
subFolder = subFolder
|
|
||||||
)
|
|
||||||
).listFiles()?.size == trackList.value?.size,
|
|
||||||
directory = finalOutputDir(type = folderType, subFolder = subFolder)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -172,7 +164,7 @@ class SpotifyViewModel @ViewModelInject constructor(
|
|||||||
moreTracksAvailable = !moreTracks?.next.isNullOrBlank()
|
moreTracksAvailable = !moreTracks?.next.isNullOrBlank()
|
||||||
}
|
}
|
||||||
Log.i("Total Tracks Fetched", tempTrackList.size.toString())
|
Log.i("Total Tracks Fetched", tempTrackList.size.toString())
|
||||||
trackList.value = tempTrackList.toTrackDetailsList()
|
trackList.value = tempTrackList.toTrackDetailsList(folderType, subFolder)
|
||||||
title.value = playlistObject?.name
|
title.value = playlistObject?.name
|
||||||
coverUrl.value = playlistObject?.images?.elementAtOrNull(1)?.url
|
coverUrl.value = playlistObject?.images?.elementAtOrNull(1)?.url
|
||||||
?: playlistObject?.images?.firstOrNull()?.url.toString()
|
?: playlistObject?.images?.firstOrNull()?.url.toString()
|
||||||
@ -184,13 +176,6 @@ class SpotifyViewModel @ViewModelInject constructor(
|
|||||||
link = "https://open.spotify.com/$type/$link",
|
link = "https://open.spotify.com/$type/$link",
|
||||||
coverUrl = coverUrl.value.toString(),
|
coverUrl = coverUrl.value.toString(),
|
||||||
totalFiles = tempTrackList.size,
|
totalFiles = tempTrackList.size,
|
||||||
downloaded = File(
|
|
||||||
finalOutputDir(
|
|
||||||
type = folderType,
|
|
||||||
subFolder = subFolder
|
|
||||||
)
|
|
||||||
).listFiles()?.size == tempTrackList.size,
|
|
||||||
directory = finalOutputDir(type = folderType, subFolder = subFolder)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -204,8 +189,7 @@ class SpotifyViewModel @ViewModelInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
private fun List<Track>.toTrackDetailsList(type:String , subFolder:String) = this.map {
|
||||||
private fun List<Track>.toTrackDetailsList() = this.map {
|
|
||||||
TrackDetails(
|
TrackDetails(
|
||||||
title = it.name.toString(),
|
title = it.name.toString(),
|
||||||
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
|
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
|
||||||
@ -218,7 +202,8 @@ class SpotifyViewModel @ViewModelInject constructor(
|
|||||||
trackUrl = it.href,
|
trackUrl = it.href,
|
||||||
downloaded = it.downloaded,
|
downloaded = it.downloaded,
|
||||||
source = Source.Spotify,
|
source = Source.Spotify,
|
||||||
albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()
|
albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString(),
|
||||||
|
outputFile = finalOutputDir(it.name.toString(),type, subFolder,".m4a")
|
||||||
)
|
)
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
}
|
}
|
@ -24,8 +24,8 @@ import android.view.ViewGroup
|
|||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper
|
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
|
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
|
||||||
import com.shabinder.spotiflyer.ui.base.tracklistbase.TrackListFragment
|
import com.shabinder.spotiflyer.ui.base.tracklistbase.TrackListFragment
|
||||||
@ -38,7 +38,7 @@ private const val sampleDomain2 = "youtu.be"
|
|||||||
private const val sampleDomain1 = "youtube.com"
|
private const val sampleDomain1 = "youtube.com"
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class YoutubeFragment : TrackListFragment<YoutubeViewModel, YoutubeFragmentArgs>() , YTDownloadHelper {
|
class YoutubeFragment : TrackListFragment<YoutubeViewModel, YoutubeFragmentArgs>(){
|
||||||
|
|
||||||
override val viewModel: YoutubeViewModel by viewModels()
|
override val viewModel: YoutubeViewModel by viewModels()
|
||||||
override lateinit var adapter : TrackListAdapter
|
override lateinit var adapter : TrackListAdapter
|
||||||
@ -48,7 +48,7 @@ class YoutubeFragment : TrackListFragment<YoutubeViewModel, YoutubeFragmentArgs>
|
|||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
super.onCreateView(inflater, container, savedInstanceState)
|
super.onCreateView(inflater, container, savedInstanceState)
|
||||||
adapter = TrackListAdapter(viewModel)
|
adapter = TrackListAdapter(viewModel)
|
||||||
|
|
||||||
@ -90,24 +90,19 @@ class YoutubeFragment : TrackListFragment<YoutubeViewModel, YoutubeFragmentArgs>
|
|||||||
visible()
|
visible()
|
||||||
rotate()
|
rotate()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (track in this.viewModel.trackList.value?: listOf()){
|
|
||||||
if(track.downloaded != DownloadStatus.Downloaded){
|
|
||||||
track.downloaded = DownloadStatus.Queued
|
|
||||||
//adapter.notifyItemChanged(this.viewModel.trackList.value!!.indexOf(track))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
showMessage("Processing!")
|
showMessage("Processing!")
|
||||||
sharedViewModel.viewModelScope.launch(Dispatchers.Default){
|
sharedViewModel.viewModelScope.launch(Dispatchers.Default){
|
||||||
loadAllImages(requireActivity(), viewModel.trackList.value?.map{it.albumArtURL}, Source.YouTube)
|
loadAllImages(requireActivity(), viewModel.trackList.value?.map{it.albumArtURL}, Source.YouTube)
|
||||||
}
|
}
|
||||||
viewModel.viewModelScope.launch {
|
viewModel.viewModelScope.launch {
|
||||||
downloadYTTracks(
|
downloadTracks((viewModel.trackList.value?.filter { it.downloaded == DownloadStatus.NotDownloaded } ?: arrayListOf()) as ArrayList<TrackDetails>)
|
||||||
type = viewModel.folderType,
|
for (track in viewModel.trackList.value?: listOf()){
|
||||||
subFolder = viewModel.subFolder,
|
if(track.downloaded == DownloadStatus.NotDownloaded){
|
||||||
tracks = viewModel.trackList.value ?: listOf()
|
track.downloaded = DownloadStatus.Queued
|
||||||
)
|
//adapter.notifyItemChanged(viewModel.trackList.value!!.indexOf(track))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,9 @@ class YoutubeViewModel @ViewModelInject constructor(
|
|||||||
DownloadStatus.Downloaded
|
DownloadStatus.Downloaded
|
||||||
else {
|
else {
|
||||||
DownloadStatus.NotDownloaded
|
DownloadStatus.NotDownloaded
|
||||||
}
|
},
|
||||||
|
outputFile = finalOutputDir(it.title(),folderType, subFolder,".m4a"),
|
||||||
|
videoID = it.videoId()
|
||||||
)
|
)
|
||||||
}.toMutableList())
|
}.toMutableList())
|
||||||
|
|
||||||
@ -93,8 +95,6 @@ class YoutubeViewModel @ViewModelInject constructor(
|
|||||||
link = "https://www.youtube.com/playlist?list=$searchId",
|
link = "https://www.youtube.com/playlist?list=$searchId",
|
||||||
coverUrl = "https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg",
|
coverUrl = "https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg",
|
||||||
totalFiles = videos.size,
|
totalFiles = videos.size,
|
||||||
directory = finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder),
|
|
||||||
downloaded = File(finalOutputDir(itemName = removeIllegalChars(name),type = folderType,subFolder = subFolder)).exists()
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
queryActiveTracks()
|
queryActiveTracks()
|
||||||
@ -124,7 +124,20 @@ class YoutubeViewModel @ViewModelInject constructor(
|
|||||||
durationSec = detail?.lengthSeconds()?:0,
|
durationSec = detail?.lengthSeconds()?:0,
|
||||||
albumArt = File(imageDir,"$searchId.jpeg"),
|
albumArt = File(imageDir,"$searchId.jpeg"),
|
||||||
source = Source.YouTube,
|
source = Source.YouTube,
|
||||||
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
|
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
|
||||||
|
downloaded = if (File(
|
||||||
|
finalOutputDir(
|
||||||
|
itemName = name,
|
||||||
|
type = folderType,
|
||||||
|
subFolder = subFolder
|
||||||
|
)).exists()
|
||||||
|
)
|
||||||
|
DownloadStatus.Downloaded
|
||||||
|
else {
|
||||||
|
DownloadStatus.NotDownloaded
|
||||||
|
},
|
||||||
|
outputFile = finalOutputDir(name,folderType, subFolder,".m4a"),
|
||||||
|
videoID = searchId
|
||||||
)
|
)
|
||||||
).toMutableList()
|
).toMutableList()
|
||||||
)
|
)
|
||||||
@ -139,8 +152,6 @@ class YoutubeViewModel @ViewModelInject constructor(
|
|||||||
link = "https://www.youtube.com/watch?v=$searchId",
|
link = "https://www.youtube.com/watch?v=$searchId",
|
||||||
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
|
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
|
||||||
totalFiles = 1,
|
totalFiles = 1,
|
||||||
downloaded = false,
|
|
||||||
directory = finalOutputDir(type = "YT_Downloads")
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
queryActiveTracks()
|
queryActiveTracks()
|
||||||
|
@ -34,7 +34,7 @@ import com.bumptech.glide.request.target.Target
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.models.DownloadObject
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
import com.shabinder.spotiflyer.utils.Provider.defaultDir
|
import com.shabinder.spotiflyer.utils.Provider.defaultDir
|
||||||
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
||||||
@ -52,11 +52,17 @@ fun loadAllImages(context: Context?, images:List<String>? = null,source:Source)
|
|||||||
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startService(context:Context? = mainActivity,objects:ArrayList<DownloadObject>? = null ) {
|
fun downloadTracks(
|
||||||
|
trackList: ArrayList<TrackDetails>,
|
||||||
|
context: Context? = mainActivity
|
||||||
|
) {
|
||||||
|
if(!trackList.isNullOrEmpty()){
|
||||||
val serviceIntent = Intent(context, ForegroundService::class.java)
|
val serviceIntent = Intent(context, ForegroundService::class.java)
|
||||||
objects?.let { serviceIntent.putParcelableArrayListExtra("object",it) }
|
serviceIntent.putParcelableArrayListExtra("object",trackList)
|
||||||
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun queryActiveTracks(context:Context? = mainActivity) {
|
fun queryActiveTracks(context:Context? = mainActivity) {
|
||||||
val serviceIntent = Intent(context, ForegroundService::class.java).apply {
|
val serviceIntent = Intent(context, ForegroundService::class.java).apply {
|
||||||
action = "query"
|
action = "query"
|
||||||
@ -64,10 +70,10 @@ fun queryActiveTracks(context:Context? = mainActivity) {
|
|||||||
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finalOutputDir(itemName:String? = null,type:String, subFolder:String?=null,extension:String? = ".mp3"): String{
|
fun finalOutputDir(itemName:String ,type:String, subFolder:String,extension:String = ".mp3"): String{
|
||||||
return defaultDir + removeIllegalChars(type) + File.separator +
|
return defaultDir + removeIllegalChars(type) + File.separator +
|
||||||
(if(subFolder == null){""}else{ removeIllegalChars(subFolder) + File.separator}
|
if(subFolder.isEmpty())"" else { removeIllegalChars(subFolder) + File.separator} +
|
||||||
+ itemName?.let { removeIllegalChars(it) + extension})
|
removeIllegalChars(itemName) + extension
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -220,7 +226,7 @@ fun createDirectory(dir:String){
|
|||||||
/**
|
/**
|
||||||
* Removing Illegal Chars from File Name
|
* Removing Illegal Chars from File Name
|
||||||
* **/
|
* **/
|
||||||
fun removeIllegalChars(fileName: String): String? {
|
fun removeIllegalChars(fileName: String): String {
|
||||||
val illegalCharArray = charArrayOf(
|
val illegalCharArray = charArrayOf(
|
||||||
'/',
|
'/',
|
||||||
'\n',
|
'\n',
|
||||||
@ -266,6 +272,3 @@ fun createDirectories() {
|
|||||||
createDirectory(defaultDir + "Playlists/")
|
createDirectory(defaultDir + "Playlists/")
|
||||||
createDirectory(defaultDir + "YT_Downloads/")
|
createDirectory(defaultDir + "YT_Downloads/")
|
||||||
}
|
}
|
||||||
fun getEmojiByUnicode(unicode: Int): String? {
|
|
||||||
return String(Character.toChars(unicode))
|
|
||||||
}
|
|
@ -24,6 +24,7 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.media.MediaScannerConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@ -46,21 +47,29 @@ import com.mpatric.mp3agic.ID3v1Tag
|
|||||||
import com.mpatric.mp3agic.ID3v24Tag
|
import com.mpatric.mp3agic.ID3v24Tag
|
||||||
import com.mpatric.mp3agic.Mp3File
|
import com.mpatric.mp3agic.Mp3File
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.models.DownloadObject
|
import com.shabinder.spotiflyer.downloadHelper.getYTTracks
|
||||||
|
import com.shabinder.spotiflyer.downloadHelper.sortByBestMatch
|
||||||
import com.shabinder.spotiflyer.models.DownloadStatus
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
import com.shabinder.spotiflyer.models.TrackDetails
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
import com.shabinder.spotiflyer.models.spotify.Source
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
import com.shabinder.spotiflyer.utils.Provider
|
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
|
||||||
|
import com.shabinder.spotiflyer.networking.makeJsonBody
|
||||||
|
import com.shabinder.spotiflyer.utils.*
|
||||||
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
||||||
import com.shabinder.spotiflyer.utils.copyTo
|
|
||||||
import com.tonyodev.fetch2.*
|
import com.tonyodev.fetch2.*
|
||||||
import com.tonyodev.fetch2core.DownloadBlock
|
import com.tonyodev.fetch2core.DownloadBlock
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
@AndroidEntryPoint
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
class ForegroundService : Service(){
|
class ForegroundService : Service(){
|
||||||
private val tag = "Foreground Service"
|
private val tag = "Foreground Service"
|
||||||
private val channelId = "ForegroundDownloaderService"
|
private val channelId = "ForegroundDownloaderService"
|
||||||
@ -68,9 +77,6 @@ class ForegroundService : Service(){
|
|||||||
private var total = 0 //Total Downloads Requested
|
private var total = 0 //Total Downloads Requested
|
||||||
private var converted = 0//Total Files Converted
|
private var converted = 0//Total Files Converted
|
||||||
private var downloaded = 0//Total Files downloaded
|
private var downloaded = 0//Total Files downloaded
|
||||||
private lateinit var fetch:Fetch
|
|
||||||
private lateinit var ytDownloader: YoutubeDownloader
|
|
||||||
private lateinit var downloadManager : DownloadManager
|
|
||||||
private var serviceJob = Job()
|
private var serviceJob = Job()
|
||||||
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
||||||
private val requestMap = mutableMapOf<Request, TrackDetails>()
|
private val requestMap = mutableMapOf<Request, TrackDetails>()
|
||||||
@ -80,12 +86,17 @@ class ForegroundService : Service(){
|
|||||||
private var isServiceStarted = false
|
private var isServiceStarted = false
|
||||||
private var notificationLine = 0
|
private var notificationLine = 0
|
||||||
private var messageList = mutableListOf("", "", "", "")
|
private var messageList = mutableListOf("", "", "", "")
|
||||||
private var cancelIntent:PendingIntent? = null
|
private lateinit var cancelIntent:PendingIntent
|
||||||
|
private lateinit var fetch:Fetch
|
||||||
|
private lateinit var ytDownloader: YoutubeDownloader
|
||||||
|
private lateinit var downloadManager : DownloadManager
|
||||||
|
@Inject lateinit var youtubeMusicApi: YoutubeMusicApi
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? = null
|
override fun onBind(intent: Intent): IBinder? = null
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
createNotificationChannel(channelId,"Downloader Service")
|
||||||
val intent = Intent(
|
val intent = Intent(
|
||||||
this,
|
this,
|
||||||
ForegroundService::class.java
|
ForegroundService::class.java
|
||||||
@ -94,26 +105,26 @@ class ForegroundService : Service(){
|
|||||||
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
ytDownloader = YoutubeDownloader()
|
ytDownloader = YoutubeDownloader()
|
||||||
initialiseFetch()
|
initialiseFetch()
|
||||||
startForeground()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("WakelockTimeout")
|
@SuppressLint("WakelockTimeout")
|
||||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
// Send a notification that service is started
|
// Send a notification that service is started
|
||||||
Log.i(tag, "Service Started.")
|
Log.i(tag, "Service Started.")
|
||||||
startForeground()
|
startForeground(notificationId, getNotification())
|
||||||
|
|
||||||
if(intent.action == "kill") killService()
|
when (intent.action) {
|
||||||
|
"kill" -> killService()
|
||||||
if(intent.action == "query"){
|
"query" -> {
|
||||||
val response = Intent().apply {
|
val response = Intent().apply {
|
||||||
action = "query_result"
|
action = "query_result"
|
||||||
putParcelableArrayListExtra("tracks", allTracksDetails as ArrayList<TrackDetails> )
|
putParcelableArrayListExtra("tracks", allTracksDetails as ArrayList<TrackDetails> )
|
||||||
}
|
}
|
||||||
sendBroadcast(response)
|
sendBroadcast(response)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val downloadObjects: ArrayList<DownloadObject>? = (intent.getParcelableArrayListExtra("object") ?: intent.extras?.getParcelableArrayList(
|
val downloadObjects: ArrayList<TrackDetails>? = (intent.getParcelableArrayListExtra("object") ?: intent.extras?.getParcelableArrayList(
|
||||||
"object"
|
"object"
|
||||||
))
|
))
|
||||||
val imagesList: ArrayList<String>? = (intent.getStringArrayListExtra("imagesList") ?: intent.extras?.getStringArrayList(
|
val imagesList: ArrayList<String>? = (intent.getStringArrayListExtra("imagesList") ?: intent.extras?.getStringArrayList(
|
||||||
@ -130,9 +141,9 @@ class ForegroundService : Service(){
|
|||||||
total += downloadObjects.size
|
total += downloadObjects.size
|
||||||
updateNotification()
|
updateNotification()
|
||||||
it.forEach { it1 ->
|
it.forEach { it1 ->
|
||||||
allTracksDetails.add(it1.trackDetails.apply { downloaded = DownloadStatus.Queued })
|
allTracksDetails.add(it1.apply { downloaded = DownloadStatus.Queued })
|
||||||
}
|
}
|
||||||
downloadAllTracks(downloadObjects)
|
downloadAllTracks(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Wake locks and misc tasks from here :
|
//Wake locks and misc tasks from here :
|
||||||
@ -152,11 +163,59 @@ class ForegroundService : Service(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadAllTracks(downloadObjects: List<DownloadObject>){
|
/**
|
||||||
|
* Function To Download All Tracks Available in a List
|
||||||
|
**/
|
||||||
|
private fun downloadAllTracks(trackList: List<TrackDetails>) {
|
||||||
|
serviceScope.launch(Dispatchers.Default){
|
||||||
|
trackList.forEach {
|
||||||
|
if(it.downloaded == DownloadStatus.Downloaded){//Download Already Present!!
|
||||||
|
}else {
|
||||||
|
if (!it.videoID.isNullOrBlank()) {//Video ID already known!
|
||||||
|
downloadTrack(it.videoID!!, it)
|
||||||
|
} else {
|
||||||
|
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
||||||
|
val jsonBody = makeJsonBody(searchQuery.trim()).toJsonString()
|
||||||
|
youtubeMusicApi.getYoutubeMusicResponse(jsonBody).enqueue(
|
||||||
|
object : Callback<String> {
|
||||||
|
override fun onResponse(
|
||||||
|
call: Call<String>,
|
||||||
|
response: Response<String>
|
||||||
|
) {
|
||||||
|
val videoId = sortByBestMatch(
|
||||||
|
getYTTracks(response.body().toString()),
|
||||||
|
trackName = it.title,
|
||||||
|
trackArtists = it.artists,
|
||||||
|
trackDurationSec = it.durationSec
|
||||||
|
).keys.firstOrNull()
|
||||||
|
Log.i("Service VideoID", videoId ?: "Not Found")
|
||||||
|
if (videoId.isNullOrBlank()) sendTrackBroadcast(
|
||||||
|
Status.FAILED.name,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
else {//Found Youtube Video ID
|
||||||
|
downloadTrack(videoId, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<String>, t: Throwable) {
|
||||||
|
if (t.message.toString()
|
||||||
|
.contains("Failed to connect")
|
||||||
|
) showMessage("Failed, Check Your Internet Connection!")
|
||||||
|
Log.i("YT API Req. Fail", t.message.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadTrack(videoID:String,track: TrackDetails){
|
||||||
serviceScope.launch(Dispatchers.IO) {
|
serviceScope.launch(Dispatchers.IO) {
|
||||||
for(downloadObj in downloadObjects){
|
|
||||||
try {
|
try {
|
||||||
val video = ytDownloader.getVideo(downloadObj.ytVideoId)
|
val video = ytDownloader.getVideo(videoID)
|
||||||
val format: Format? = try {
|
val format: Format? = try {
|
||||||
video?.findAudioWithQuality(AudioQuality.medium)?.get(0) as Format
|
video?.findAudioWithQuality(AudioQuality.medium)?.get(0) as Format
|
||||||
} catch (e: java.lang.IndexOutOfBoundsException) {
|
} catch (e: java.lang.IndexOutOfBoundsException) {
|
||||||
@ -174,13 +233,13 @@ class ForegroundService : Service(){
|
|||||||
format?.let {
|
format?.let {
|
||||||
val url: String = format.url()
|
val url: String = format.url()
|
||||||
Log.i("DHelper Link Found", url)
|
Log.i("DHelper Link Found", url)
|
||||||
val request= Request(url, downloadObj.outputFile).apply{
|
val request= Request(url, track.outputFile).apply{
|
||||||
priority = Priority.NORMAL
|
priority = Priority.NORMAL
|
||||||
networkType = NetworkType.ALL
|
networkType = NetworkType.ALL
|
||||||
}
|
}
|
||||||
fetch.enqueue(request,
|
fetch.enqueue(request,
|
||||||
{
|
{
|
||||||
requestMap[it] = downloadObj.trackDetails
|
requestMap[it] = track
|
||||||
Log.i(tag, "Enqueuing Download")
|
Log.i(tag, "Enqueuing Download")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -193,24 +252,6 @@ class ForegroundService : Service(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
if(converted == total){
|
|
||||||
Handler(Looper.myLooper()!!).postDelayed({
|
|
||||||
killService()
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
||||||
super.onTaskRemoved(rootIntent)
|
|
||||||
if(converted == total ){
|
|
||||||
killService()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch Listener/ Responsible for Fetch Behaviour
|
* Fetch Listener/ Responsible for Fetch Behaviour
|
||||||
@ -220,10 +261,7 @@ class ForegroundService : Service(){
|
|||||||
download: Download,
|
download: Download,
|
||||||
waitingOnNetwork: Boolean
|
waitingOnNetwork: Boolean
|
||||||
) {
|
) {
|
||||||
val intent = Intent()
|
requestMap[download.request]?.let { sendTrackBroadcast(Status.QUEUED.name, it) }
|
||||||
.setAction(Status.QUEUED.name)
|
|
||||||
.putExtra("track", requestMap[download.request])
|
|
||||||
this@ForegroundService.sendBroadcast(intent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRemoved(download: Download) {
|
override fun onRemoved(download: Download) {
|
||||||
@ -262,12 +300,9 @@ class ForegroundService : Service(){
|
|||||||
track?.let{
|
track?.let{
|
||||||
allTracksDetails[allTracksDetails.map{ trackDetails -> trackDetails.title}.indexOf(it.title)] =
|
allTracksDetails[allTracksDetails.map{ trackDetails -> trackDetails.title}.indexOf(it.title)] =
|
||||||
it.apply { downloaded = DownloadStatus.Downloading }
|
it.apply { downloaded = DownloadStatus.Downloading }
|
||||||
}
|
|
||||||
updateNotification()
|
updateNotification()
|
||||||
val intent = Intent()
|
sendTrackBroadcast(Status.DOWNLOADING.name,track)
|
||||||
.setAction(Status.DOWNLOADING.name)
|
}
|
||||||
.putExtra("track", track)
|
|
||||||
this@ForegroundService.sendBroadcast(intent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWaitingNetwork(download: Download) {
|
override fun onWaitingNetwork(download: Download) {
|
||||||
@ -346,12 +381,12 @@ class ForegroundService : Service(){
|
|||||||
) {
|
) {
|
||||||
val track = requestMap[download.request]
|
val track = requestMap[download.request]
|
||||||
Log.i(tag, "${track?.title} ETA: ${etaInMilliSeconds / 1000} sec")
|
Log.i(tag, "${track?.title} ETA: ${etaInMilliSeconds / 1000} sec")
|
||||||
val intent = Intent()
|
val intent = Intent().apply {
|
||||||
.setAction("Progress")
|
action = "Progress"
|
||||||
.putExtra("progress", download.progress)
|
putExtra("progress", download.progress)
|
||||||
.putExtra("track", requestMap[download.request])
|
putExtra("track", requestMap[download.request])
|
||||||
this@ForegroundService.sendBroadcast(intent)
|
}
|
||||||
// updateNotification()
|
sendBroadcast(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,16 +395,18 @@ class ForegroundService : Service(){
|
|||||||
**/
|
**/
|
||||||
fun downloadUsingDM(url: String, outputDir: String, track: TrackDetails){
|
fun downloadUsingDM(url: String, outputDir: String, track: TrackDetails){
|
||||||
val uri = Uri.parse(url)
|
val uri = Uri.parse(url)
|
||||||
val request = DownloadManager.Request(uri)
|
val request = DownloadManager.Request(uri).apply {
|
||||||
.setAllowedNetworkTypes(
|
setAllowedNetworkTypes(
|
||||||
DownloadManager.Request.NETWORK_WIFI or
|
DownloadManager.Request.NETWORK_WIFI or
|
||||||
DownloadManager.Request.NETWORK_MOBILE
|
DownloadManager.Request.NETWORK_MOBILE
|
||||||
)
|
)
|
||||||
.setAllowedOverRoaming(false)
|
setAllowedOverRoaming(false)
|
||||||
.setTitle(track.title)
|
setTitle(track.title)
|
||||||
.setDescription("Spotify Downloader Working Up here...")
|
setDescription("Spotify Downloader Working Up here...")
|
||||||
.setDestinationUri(File(outputDir).toUri())
|
setDestinationUri(File(outputDir).toUri())
|
||||||
.setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||||
|
}
|
||||||
|
|
||||||
//Start Download
|
//Start Download
|
||||||
val downloadID = downloadManager.enqueue(request)
|
val downloadID = downloadManager.enqueue(request)
|
||||||
Log.i("DownloadManager", "Download Request Sent")
|
Log.i("DownloadManager", "Download Request Sent")
|
||||||
@ -396,11 +433,7 @@ class ForegroundService : Service(){
|
|||||||
*Converting Downloaded Audio (m4a) to Mp3.( Also Applying Metadata)
|
*Converting Downloaded Audio (m4a) to Mp3.( Also Applying Metadata)
|
||||||
**/
|
**/
|
||||||
fun convertToMp3(filePath: String, track: TrackDetails){
|
fun convertToMp3(filePath: String, track: TrackDetails){
|
||||||
val intent = Intent()
|
sendTrackBroadcast("Converting",track)
|
||||||
.setAction("Converting")
|
|
||||||
.putExtra("track", track)
|
|
||||||
this@ForegroundService.sendBroadcast(intent)
|
|
||||||
|
|
||||||
val m4aFile = File(filePath)
|
val m4aFile = File(filePath)
|
||||||
|
|
||||||
FFmpeg.executeAsync(
|
FFmpeg.executeAsync(
|
||||||
@ -441,13 +474,10 @@ class ForegroundService : Service(){
|
|||||||
newFile.renameTo(file)
|
newFile.renameTo(file)
|
||||||
converted++
|
converted++
|
||||||
updateNotification()
|
updateNotification()
|
||||||
|
addToLibrary(file.absolutePath)
|
||||||
allTracksDetails.removeAt(allTracksDetails.map{ trackDetails -> trackDetails.title}.indexOf(track.title))
|
allTracksDetails.removeAt(allTracksDetails.map{ trackDetails -> trackDetails.title}.indexOf(track.title))
|
||||||
//Notify Download Completed
|
//Notify Download Completed
|
||||||
val intent = Intent()
|
sendTrackBroadcast("track_download_completed",track)
|
||||||
.setAction("track_download_completed")
|
|
||||||
.putExtra("track", track)
|
|
||||||
this@ForegroundService.sendBroadcast(intent)
|
|
||||||
|
|
||||||
//All tasks completed (REST IN PEACE)
|
//All tasks completed (REST IN PEACE)
|
||||||
if(converted == total){
|
if(converted == total){
|
||||||
onDestroy()
|
onDestroy()
|
||||||
@ -460,21 +490,7 @@ class ForegroundService : Service(){
|
|||||||
private fun updateNotification() {
|
private fun updateNotification() {
|
||||||
val mNotificationManager: NotificationManager =
|
val mNotificationManager: NotificationManager =
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
val notification = NotificationCompat.Builder(this, channelId)
|
mNotificationManager.notify(notificationId, getNotification())
|
||||||
.setSmallIcon(R.drawable.down_arrowbw)
|
|
||||||
.setSubText("Total: $total Completed:$converted")
|
|
||||||
.setNotificationSilent()
|
|
||||||
.setStyle(
|
|
||||||
NotificationCompat.InboxStyle()
|
|
||||||
// .setBigContentTitle("Speed: $speed KB/s")
|
|
||||||
.addLine(messageList[0])
|
|
||||||
.addLine(messageList[1])
|
|
||||||
.addLine(messageList[2])
|
|
||||||
.addLine(messageList[3])
|
|
||||||
)
|
|
||||||
.addAction(R.drawable.ic_baseline_cancel_24,"Exit",cancelIntent)
|
|
||||||
.build()
|
|
||||||
mNotificationManager.notify(notificationId, notification)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -541,47 +557,16 @@ class ForegroundService : Service(){
|
|||||||
isServiceStarted = false
|
isServiceStarted = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*Starting Service with Notification as Foreground!
|
|
||||||
**/
|
|
||||||
private fun startForeground() {
|
|
||||||
val channelId =
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
createNotificationChannel(channelId, "Downloader Service")
|
|
||||||
} else {
|
|
||||||
// If earlier version channel ID is not used
|
|
||||||
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
val notification = NotificationCompat.Builder(this, channelId)
|
|
||||||
.setSmallIcon(R.drawable.down_arrowbw)
|
|
||||||
.setNotificationSilent()
|
|
||||||
.setSubText("Total: $total Completed:$converted")
|
|
||||||
.setStyle(
|
|
||||||
NotificationCompat.InboxStyle()
|
|
||||||
// .setBigContentTitle("Speed: $speed KB/s")
|
|
||||||
.addLine(messageList[0])
|
|
||||||
.addLine(messageList[1])
|
|
||||||
.addLine(messageList[2])
|
|
||||||
.addLine(messageList[3])
|
|
||||||
)
|
|
||||||
.addAction(R.drawable.ic_baseline_cancel_24,"Exit",cancelIntent)
|
|
||||||
.build()
|
|
||||||
startForeground(notificationId, notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("SameParameterValue")
|
@Suppress("SameParameterValue")
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
private fun createNotificationChannel(channelId: String, channelName: String): String{
|
private fun createNotificationChannel(channelId: String, channelName: String){
|
||||||
val chan = NotificationChannel(
|
val channel = NotificationChannel(
|
||||||
channelId,
|
channelId,
|
||||||
channelName, NotificationManager.IMPORTANCE_DEFAULT
|
channelName, NotificationManager.IMPORTANCE_DEFAULT
|
||||||
)
|
)
|
||||||
chan.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||||
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
service.createNotificationChannel(chan)
|
service.createNotificationChannel(channel)
|
||||||
return channelId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -604,6 +589,15 @@ class ForegroundService : Service(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add File to Android's Media Library.
|
||||||
|
* */
|
||||||
|
private fun addToLibrary(path:String) {
|
||||||
|
Log.i(tag,"Scanning File")
|
||||||
|
MediaScannerConnection.scanFile(this,
|
||||||
|
listOf(path).toTypedArray(), null,null)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to fetch all Images for use in mp3 tags.
|
* Function to fetch all Images for use in mp3 tags.
|
||||||
**/
|
**/
|
||||||
@ -681,6 +675,7 @@ class ForegroundService : Service(){
|
|||||||
|
|
||||||
private fun killService() {
|
private fun killService() {
|
||||||
serviceScope.launch{
|
serviceScope.launch{
|
||||||
|
Log.i(tag,"Killing Self")
|
||||||
messageList = mutableListOf("Cleaning And Exiting","","","")
|
messageList = mutableListOf("Cleaning And Exiting","","","")
|
||||||
fetch.cancelAll()
|
fetch.cancelAll()
|
||||||
fetch.removeAll()
|
fetch.removeAll()
|
||||||
@ -696,6 +691,22 @@ class ForegroundService : Service(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
if(converted == total){
|
||||||
|
Handler(Looper.myLooper()!!).postDelayed({
|
||||||
|
killService()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
|
super.onTaskRemoved(rootIntent)
|
||||||
|
if(converted == total ){
|
||||||
|
killService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun initialiseFetch() {
|
private fun initialiseFetch() {
|
||||||
val fetchConfiguration =
|
val fetchConfiguration =
|
||||||
FetchConfiguration.Builder(this)
|
FetchConfiguration.Builder(this)
|
||||||
@ -711,4 +722,28 @@ class ForegroundService : Service(){
|
|||||||
//Starting fresh
|
//Starting fresh
|
||||||
fetch.removeAll()
|
fetch.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getNotification():Notification = NotificationCompat.Builder(this, channelId).run {
|
||||||
|
setSmallIcon(R.drawable.down_arrowbw)
|
||||||
|
setSubText("Total: $total Completed:$converted")
|
||||||
|
setNotificationSilent()
|
||||||
|
setStyle(
|
||||||
|
NotificationCompat.InboxStyle().run {
|
||||||
|
addLine(messageList[0])
|
||||||
|
addLine(messageList[1])
|
||||||
|
addLine(messageList[2])
|
||||||
|
addLine(messageList[3])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
addAction(R.drawable.ic_baseline_cancel_24,"Exit",cancelIntent)
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendTrackBroadcast(action:String,track:TrackDetails){
|
||||||
|
val intent = Intent().apply{
|
||||||
|
setAction(action)
|
||||||
|
putExtra("track", track)
|
||||||
|
}
|
||||||
|
this@ForegroundService.sendBroadcast(intent)
|
||||||
|
}
|
||||||
}
|
}
|
@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
<AppUpdater>
|
<AppUpdater>
|
||||||
<update>
|
<update>
|
||||||
<latestVersion>1.6</latestVersion>
|
<latestVersion>1.7</latestVersion>
|
||||||
<latestVersionCode>8</latestVersionCode>
|
<latestVersionCode>9</latestVersionCode>
|
||||||
<url>https://github.com/Shabinder/SpotiFlyer/releases/</url>
|
<url>https://github.com/Shabinder/SpotiFlyer/releases/</url>
|
||||||
</update>
|
</update>
|
||||||
</AppUpdater>
|
</AppUpdater>
|
@ -18,7 +18,7 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
ext{
|
ext{
|
||||||
kotlin_version = "1.4.10"
|
kotlin_version = "1.4.20"
|
||||||
navigationVersion = '2.3.0'
|
navigationVersion = '2.3.0'
|
||||||
ext.hilt_version = '2.29.1-alpha'
|
ext.hilt_version = '2.29.1-alpha'
|
||||||
}
|
}
|
||||||
@ -28,7 +28,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.2.0-alpha16'
|
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
//Safe-Args
|
//Safe-Args
|
||||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
|
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
|
||||||
|
@ -15,24 +15,20 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
# Project-wide Gradle settings.
|
## For more details on how to configure your build environment visit
|
||||||
# IDE (e.g. Android Studio) users:
|
|
||||||
# Gradle settings configured through the IDE *will override*
|
|
||||||
# any settings specified in this file.
|
|
||||||
# For more details on how to configure your build environment visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
#
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
org.gradle.jvmargs=-Xmx2048m
|
# Default value: -Xmx1024m -XX:MaxPermSize=256m
|
||||||
|
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
|
#
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
#Wed Dec 02 11:11:59 IST 2020
|
||||||
# Android operating system, and which are packaged with your app"s APK
|
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
|
||||||
android.useAndroidX=true
|
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
|
||||||
android.enableJetifier=true
|
|
||||||
# Kotlin code style for this project: "official" or "obsolete":
|
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
org.gradle.jvmargs=-Xmx1536M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
Loading…
Reference in New Issue
Block a user