mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-29 19:57:53 +01:00
Models, Business Logic Added.
This commit is contained in:
parent
38618bd2b1
commit
dcfca42c40
@ -1,7 +1,10 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
|
id 'kotlin-parcelize'
|
||||||
id 'kotlin-kapt'
|
id 'kotlin-kapt'
|
||||||
|
id 'dagger.hilt.android.plugin'
|
||||||
|
id 'kotlinx-serialization'
|
||||||
}
|
}
|
||||||
|
|
||||||
kapt {
|
kapt {
|
||||||
@ -98,6 +101,15 @@ dependencies {
|
|||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
implementation "androidx.room:room-ktx:$room_version"
|
implementation "androidx.room:room-ktx:$room_version"
|
||||||
|
|
||||||
|
//Hilt
|
||||||
|
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
|
||||||
|
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||||
|
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
|
||||||
|
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
|
||||||
|
|
||||||
|
//FFmpeg
|
||||||
|
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
|
||||||
|
|
||||||
//Okhttp
|
//Okhttp
|
||||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
|
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
|
||||||
@ -106,11 +118,15 @@ dependencies {
|
|||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
|
|
||||||
//Json
|
//Json
|
||||||
|
implementation 'com.beust:klaxon:5.4'
|
||||||
implementation 'com.squareup.moshi:moshi:1.11.0'
|
implementation 'com.squareup.moshi:moshi:1.11.0'
|
||||||
implementation 'com.squareup.moshi:moshi-kotlin:1.11.0'
|
implementation 'com.squareup.moshi:moshi-kotlin:1.11.0'
|
||||||
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
|
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
|
||||||
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
|
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
|
||||||
implementation 'com.beust:klaxon:5.4'
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
|
||||||
|
|
||||||
|
//Glide-Image Loading
|
||||||
|
implementation "dev.chrisbanes.accompanist:accompanist-glide:0.4.1"
|
||||||
|
|
||||||
//Coil-Image Loading
|
//Coil-Image Loading
|
||||||
implementation "dev.chrisbanes.accompanist:accompanist-coil:$coil_version"
|
implementation "dev.chrisbanes.accompanist:accompanist-coil:$coil_version"
|
||||||
|
BIN
app/libs/mobile-ffmpeg.aar
Normal file
BIN
app/libs/mobile-ffmpeg.aar
Normal file
Binary file not shown.
@ -23,6 +23,7 @@
|
|||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
46
app/src/main/java/com/shabinder/spotiflyer/App.kt
Normal file
46
app/src/main/java/com/shabinder/spotiflyer/App.kt
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@HiltAndroidApp
|
||||||
|
class App:Application(){
|
||||||
|
companion object{
|
||||||
|
const val clientId:String = "694d8bf4f6ec420fa66ea7fb4c68f89d"
|
||||||
|
const val clientSecret:String = "02ca2d4021a7452dae2328b47a6e8fe8"
|
||||||
|
}
|
||||||
|
}
|
@ -16,18 +16,28 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.platform.setContent
|
import androidx.compose.ui.platform.setContent
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import com.shabinder.spotiflyer.home.Home
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.shabinder.spotiflyer.navigation.ComposeNavigation
|
import com.shabinder.spotiflyer.navigation.ComposeNavigation
|
||||||
|
import com.shabinder.spotiflyer.networking.SpotifyService
|
||||||
import com.shabinder.spotiflyer.ui.ComposeLearnTheme
|
import com.shabinder.spotiflyer.ui.ComposeLearnTheme
|
||||||
import com.shabinder.spotiflyer.ui.appNameStyle
|
import com.shabinder.spotiflyer.ui.appNameStyle
|
||||||
import com.shabinder.spotiflyer.utils.requestStoragePermission
|
import com.shabinder.spotiflyer.utils.requestStoragePermission
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dev.chrisbanes.accompanist.insets.ProvideWindowInsets
|
import dev.chrisbanes.accompanist.insets.ProvideWindowInsets
|
||||||
import dev.chrisbanes.accompanist.insets.statusBarsHeight
|
import dev.chrisbanes.accompanist.insets.statusBarsHeight
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is App's God Activity
|
||||||
|
* */
|
||||||
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private var spotifyService : SpotifyService? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
|
||||||
// This app draws behind the system bars, so we want to handle fitting system windows
|
// This app draws behind the system bars, so we want to handle fitting system windows
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
@ -56,7 +66,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
companion object{
|
companion object{
|
||||||
private lateinit var instance: MainActivity
|
private lateinit var instance: MainActivity
|
||||||
|
private lateinit var sharedViewModel: SharedViewModel
|
||||||
fun getInstance():MainActivity = this.instance
|
fun getInstance():MainActivity = this.instance
|
||||||
|
fun getSharedViewModel():SharedViewModel = this.sharedViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -100,6 +112,20 @@ fun AppBar(
|
|||||||
@Composable
|
@Composable
|
||||||
fun DefaultPreview() {
|
fun DefaultPreview() {
|
||||||
ComposeLearnTheme {
|
ComposeLearnTheme {
|
||||||
|
ProvideWindowInsets {
|
||||||
|
Column {
|
||||||
|
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.87f)
|
||||||
|
|
||||||
|
// Draw a scrim over the status bar which matches the app bar
|
||||||
|
Spacer(Modifier.background(appBarColor).fillMaxWidth().statusBarsHeight())
|
||||||
|
|
||||||
|
AppBar(
|
||||||
|
backgroundColor = appBarColor,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
ComposeNavigation()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.shabinder.spotiflyer.networking.SpotifyService
|
||||||
|
|
||||||
|
class SharedViewModel : ViewModel() {
|
||||||
|
var intentString = MutableLiveData<String>()
|
||||||
|
var spotifyService = MutableLiveData<SpotifyService>()
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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.database
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface DatabaseDAO {
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insert(record: DownloadRecord)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(record: DownloadRecord)
|
||||||
|
|
||||||
|
@Query("SELECT * from download_record_table ORDER BY id DESC")
|
||||||
|
suspend fun getRecord():List<DownloadRecord>
|
||||||
|
|
||||||
|
@Query("DELETE FROM download_record_table")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.database
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Index
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@Entity(
|
||||||
|
tableName = "download_record_table",
|
||||||
|
indices = [Index(value = ["link"], unique = true)]
|
||||||
|
)
|
||||||
|
data class DownloadRecord(
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
var id:Int = 0,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "type")
|
||||||
|
var type:String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "name")
|
||||||
|
var name:String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "link")
|
||||||
|
var link:String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "coverUrl")
|
||||||
|
var coverUrl:String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "totalFiles")
|
||||||
|
var totalFiles:Int = 1,
|
||||||
|
):Parcelable
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
|
||||||
|
@Database(entities = [DownloadRecord::class], version = 2, exportSchema = false)
|
||||||
|
abstract class DownloadRecordDatabase:RoomDatabase() {
|
||||||
|
|
||||||
|
abstract val databaseDAO: DatabaseDAO
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: DownloadRecordDatabase? = null
|
||||||
|
|
||||||
|
fun getInstance(context: Context): DownloadRecordDatabase {
|
||||||
|
synchronized(this) {
|
||||||
|
var instance = INSTANCE
|
||||||
|
if (instance == null) {
|
||||||
|
instance = Room.databaseBuilder(
|
||||||
|
context.applicationContext,
|
||||||
|
DownloadRecordDatabase::class.java,
|
||||||
|
"download_record_database")
|
||||||
|
.fallbackToDestructiveMigration()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
INSTANCE = instance
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.beust.klaxon.JsonArray
|
||||||
|
import com.beust.klaxon.JsonObject
|
||||||
|
import com.beust.klaxon.Parser
|
||||||
|
import com.shabinder.spotiflyer.models.YoutubeTrack
|
||||||
|
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Thanks To https://github.com/spotDL/spotify-downloader
|
||||||
|
* */
|
||||||
|
fun getYTTracks(response: String):List<YoutubeTrack>{
|
||||||
|
val youtubeTracks = mutableListOf<YoutubeTrack>()
|
||||||
|
|
||||||
|
val stringBuilder: StringBuilder = StringBuilder(response)
|
||||||
|
val responseObj: JsonObject = Parser.default().parse(stringBuilder) as JsonObject
|
||||||
|
val contentBlocks = responseObj.obj("contents")?.obj("sectionListRenderer")?.array<JsonObject>("contents")
|
||||||
|
val resultBlocks = mutableListOf<JsonArray<JsonObject>>()
|
||||||
|
if (contentBlocks != null) {
|
||||||
|
for (cBlock in contentBlocks){
|
||||||
|
/**
|
||||||
|
*Ignore user-suggestion
|
||||||
|
*The 'itemSectionRenderer' field is for user notices (stuff like - 'showing
|
||||||
|
*results for xyz, search for abc instead') we have no use for them, the for
|
||||||
|
*loop below if throw a keyError if we don't ignore them
|
||||||
|
*/
|
||||||
|
if(cBlock.containsKey("itemSectionRenderer")){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for(contents in cBlock.obj("musicShelfRenderer")?.array<JsonObject>("contents") ?: listOf()){
|
||||||
|
/**
|
||||||
|
* apparently content Blocks without an 'overlay' field don't have linkBlocks
|
||||||
|
* I have no clue what they are and why there even exist
|
||||||
|
*
|
||||||
|
if(!contents.containsKey("overlay")){
|
||||||
|
println(contents)
|
||||||
|
continue
|
||||||
|
TODO check and correct
|
||||||
|
}*/
|
||||||
|
|
||||||
|
val result = contents.obj("musicResponsiveListItemRenderer")
|
||||||
|
?.array<JsonObject>("flexColumns")
|
||||||
|
|
||||||
|
//Add the linkBlock
|
||||||
|
val linkBlock = contents.obj("musicResponsiveListItemRenderer")
|
||||||
|
?.obj("overlay")
|
||||||
|
?.obj("musicItemThumbnailOverlayRenderer")
|
||||||
|
?.obj("content")
|
||||||
|
?.obj("musicPlayButtonRenderer")
|
||||||
|
?.obj("playNavigationEndpoint")
|
||||||
|
|
||||||
|
// detailsBlock is always a list, so we just append the linkBlock to it
|
||||||
|
// instead of carrying along all the other junk from "musicResponsiveListItemRenderer"
|
||||||
|
linkBlock?.let { result?.add(it) }
|
||||||
|
result?.let { resultBlocks.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We only need results that are Songs or Videos, so we filter out the rest, since
|
||||||
|
! Songs and Videos are supplied with different details, extracting all details from
|
||||||
|
! both is just carrying on redundant data, so we also have to selectively extract
|
||||||
|
! relevant details. What you need to know to understand how we do that here:
|
||||||
|
!
|
||||||
|
! Songs details are ALWAYS in the following order:
|
||||||
|
! 0 - Name
|
||||||
|
! 1 - Type (Song)
|
||||||
|
! 2 - com.shabinder.spotiflyer.models.gaana.Artist
|
||||||
|
! 3 - Album
|
||||||
|
! 4 - Duration (mm:ss)
|
||||||
|
!
|
||||||
|
! Video details are ALWAYS in the following order:
|
||||||
|
! 0 - Name
|
||||||
|
! 1 - Type (Video)
|
||||||
|
! 2 - Channel
|
||||||
|
! 3 - Viewers
|
||||||
|
! 4 - Duration (hh:mm:ss)
|
||||||
|
!
|
||||||
|
! We blindly gather all the details we get our hands on, then
|
||||||
|
! cherrypick the details we need based on their index numbers,
|
||||||
|
! we do so only if their Type is 'Song' or 'Video
|
||||||
|
*/
|
||||||
|
|
||||||
|
for(result in resultBlocks){
|
||||||
|
|
||||||
|
// Blindly gather available details
|
||||||
|
val availableDetails = mutableListOf<String>()
|
||||||
|
|
||||||
|
/*
|
||||||
|
Filter Out dummies here itself
|
||||||
|
! 'musicResponsiveListItemFlexColumnRenderer' should have more that one
|
||||||
|
! sub-block, if not its a dummy, why does the YTM response contain dummies?
|
||||||
|
! I have no clue. We skip these.
|
||||||
|
|
||||||
|
! Remember that we appended the linkBlock to result, treating that like the
|
||||||
|
! other constituents of a result block will lead to errors, hence the 'in
|
||||||
|
! result[:-1] ,i.e., skip last element in array '
|
||||||
|
*/
|
||||||
|
for(detail in result.subList(0,result.size-1)){
|
||||||
|
if(detail.obj("musicResponsiveListItemFlexColumnRenderer")?.size!! < 2) continue
|
||||||
|
|
||||||
|
// if not a dummy, collect All Variables
|
||||||
|
detail.obj("musicResponsiveListItemFlexColumnRenderer")
|
||||||
|
?.obj("text")
|
||||||
|
?.array<JsonObject>("runs")?.get(0)?.get("text")?.let {
|
||||||
|
availableDetails.add(
|
||||||
|
it.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//log("Text Api",availableDetails.toString())
|
||||||
|
/*
|
||||||
|
! Filter Out non-Song/Video results and incomplete results here itself
|
||||||
|
! From what we know about detail order, note that [1] - indicate result type
|
||||||
|
*/
|
||||||
|
if ( availableDetails.size == 5 && availableDetails[1] in listOf("Song","Video") ){
|
||||||
|
|
||||||
|
// skip if result is in hours instead of minutes (no song is that long)
|
||||||
|
if(availableDetails[4].split(':').size != 2) continue //Has Been Giving Issues
|
||||||
|
|
||||||
|
/*
|
||||||
|
! grab Video ID
|
||||||
|
! this is nested as [playlistEndpoint/watchEndpoint][videoId/playlistId/...]
|
||||||
|
! so hardcoding the dict keys for data look up is an ardours process, since
|
||||||
|
! the sub-block pattern is fixed even though the key isn't, we just
|
||||||
|
! reference the dict keys by index
|
||||||
|
*/
|
||||||
|
|
||||||
|
val videoId:String = result.last().obj("watchEndpoint")?.get("videoId") as String
|
||||||
|
val ytTrack = YoutubeTrack(
|
||||||
|
name = availableDetails[0],
|
||||||
|
type = availableDetails[1],
|
||||||
|
artist = availableDetails[2],
|
||||||
|
duration = availableDetails[4],
|
||||||
|
videoId = videoId
|
||||||
|
)
|
||||||
|
youtubeTracks.add(ytTrack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return youtubeTracks
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
fun sortByBestMatch(ytTracks:List<YoutubeTrack>,
|
||||||
|
trackName:String,
|
||||||
|
trackArtists:List<String>,
|
||||||
|
trackDurationSec:Int,
|
||||||
|
):Map<String,Int>{
|
||||||
|
/*
|
||||||
|
* "linksWithMatchValue" is map with Youtube VideoID and its rating/match with 100 as Max Value
|
||||||
|
**/
|
||||||
|
val linksWithMatchValue = mutableMapOf<String,Int>()
|
||||||
|
|
||||||
|
for (result in ytTracks){
|
||||||
|
|
||||||
|
// LoweCasing Name to match Properly
|
||||||
|
// most song results on youtube go by $artist - $songName or artist1/artist2
|
||||||
|
var hasCommonWord = false
|
||||||
|
|
||||||
|
val resultName = result.name?.toLowerCase()?.replace("-"," ")?.replace("/"," ") ?: ""
|
||||||
|
val trackNameWords = trackName.toLowerCase().split(" ")
|
||||||
|
|
||||||
|
for (nameWord in trackNameWords){
|
||||||
|
if (nameWord.isNotBlank() && FuzzySearch.partialRatio(nameWord,resultName) > 85) hasCommonWord = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip this Result if No Word is Common in Name
|
||||||
|
if (!hasCommonWord) {
|
||||||
|
//log("YT Api Removing", result.toString())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Find artist match
|
||||||
|
// Will Be Using Fuzzy Search Because YT Spelling might be mucked up
|
||||||
|
// match = (no of artist names in result) / (no. of artist names on spotify) * 100
|
||||||
|
var artistMatchNumber = 0
|
||||||
|
|
||||||
|
if(result.type == "Song"){
|
||||||
|
for (artist in trackArtists){
|
||||||
|
if(FuzzySearch.ratio(artist.toLowerCase(),result.artist?.toLowerCase()) > 85)
|
||||||
|
artistMatchNumber++
|
||||||
|
}
|
||||||
|
}else{//i.e. is a Video
|
||||||
|
for (artist in trackArtists) {
|
||||||
|
if(FuzzySearch.partialRatio(artist.toLowerCase(),result.name?.toLowerCase()) > 85)
|
||||||
|
artistMatchNumber++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(artistMatchNumber == 0) {
|
||||||
|
//log("YT Api Removing", result.toString())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val artistMatch = (artistMatchNumber / trackArtists.size ) * 100
|
||||||
|
|
||||||
|
// Duration Match
|
||||||
|
/*! time match = 100 - (delta(duration)**2 / original duration * 100)
|
||||||
|
! difference in song duration (delta) is usually of the magnitude of a few
|
||||||
|
! seconds, we need to amplify the delta if it is to have any meaningful impact
|
||||||
|
! wen we calculate the avg match value*/
|
||||||
|
val difference = result.duration?.split(":")?.get(0)?.toInt()?.times(60)
|
||||||
|
?.plus(result.duration?.split(":")?.get(1)?.toInt()?:0)
|
||||||
|
?.minus(trackDurationSec)?.absoluteValue ?: 0
|
||||||
|
val nonMatchValue :Float= ((difference*difference).toFloat()/trackDurationSec.toFloat())
|
||||||
|
val durationMatch = 100 - (nonMatchValue*100)
|
||||||
|
|
||||||
|
val avgMatch = (artistMatch + durationMatch)/2
|
||||||
|
linksWithMatchValue[result.videoId.toString()] = avgMatch.toInt()
|
||||||
|
}
|
||||||
|
//log("YT Api Result", "$trackName - $linksWithMatchValue")
|
||||||
|
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap()
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class TrackDetails(
|
||||||
|
var title:String,
|
||||||
|
var artists:List<String>,
|
||||||
|
var durationSec:Int,
|
||||||
|
var albumName:String?=null,
|
||||||
|
var year:String?=null,
|
||||||
|
var comment:String?=null,
|
||||||
|
var lyrics:String?=null,
|
||||||
|
var trackUrl:String?=null,
|
||||||
|
var albumArt: File,
|
||||||
|
var albumArtURL: String,
|
||||||
|
var source: Source,
|
||||||
|
var downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
|
||||||
|
var progress: Int = 0,
|
||||||
|
var outputFile: String,
|
||||||
|
var videoID:String? = null
|
||||||
|
):Parcelable
|
||||||
|
|
||||||
|
enum class DownloadStatus{
|
||||||
|
Downloaded,
|
||||||
|
Downloading,
|
||||||
|
Queued,
|
||||||
|
NotDownloaded,
|
||||||
|
Converting,
|
||||||
|
Failed
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Optional<T>(val value: T?)
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class YoutubeTrack(
|
||||||
|
var name: String? = null,
|
||||||
|
var type: String? = null, // Song / Video
|
||||||
|
var artist: String? = null,
|
||||||
|
var duration:String? = null,
|
||||||
|
var videoId: String? = null
|
||||||
|
):Parcelable
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.gaana
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
|
||||||
|
data class Artist (
|
||||||
|
val popularity : Int,
|
||||||
|
val seokey : String,
|
||||||
|
val name : String,
|
||||||
|
@Json(name = "artwork_175x175")var artworkLink :String?
|
||||||
|
)
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.gaana
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
|
||||||
|
data class CustomArtworks (
|
||||||
|
@Json(name = "40x40") val size_40p : String,
|
||||||
|
@Json(name = "80x80") val size_80p : String,
|
||||||
|
@Json(name = "110x110")val size_110p : String,
|
||||||
|
@Json(name = "175x175")val size_175p : String,
|
||||||
|
@Json(name = "480x480")val size_480p : String,
|
||||||
|
)
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.gaana
|
||||||
|
|
||||||
|
data class GaanaAlbum (
|
||||||
|
val tracks : List<GaanaTrack>,
|
||||||
|
val count : Int,
|
||||||
|
val custom_artworks : CustomArtworks,
|
||||||
|
val release_year : Int,
|
||||||
|
val favorite_count : Int,
|
||||||
|
)
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.gaana
|
||||||
|
|
||||||
|
data class GaanaArtistDetails(
|
||||||
|
val artist : List<Artist>,
|
||||||
|
val count : Int,
|
||||||
|
)
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.gaana
|
||||||
|
|
||||||
|
data class GaanaArtistTracks(
|
||||||
|
val count : Int,
|
||||||
|
val tracks : List<GaanaTrack>
|
||||||
|
)
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.gaana
|
||||||
|
|
||||||
|
data class GaanaPlaylist (
|
||||||
|
val modified_on : String,
|
||||||
|
val count : Int,
|
||||||
|
val created_on : String,
|
||||||
|
val favorite_count : Int,
|
||||||
|
val tracks : List<GaanaTrack>,
|
||||||
|
)
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.gaana
|
||||||
|
|
||||||
|
data class GaanaSong(
|
||||||
|
val tracks : List<GaanaTrack>
|
||||||
|
)
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.gaana
|
||||||
|
|
||||||
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
|
||||||
|
data class GaanaTrack (
|
||||||
|
val tags : List<Tags?>?,
|
||||||
|
val seokey : String,
|
||||||
|
val albumseokey : String?,
|
||||||
|
val track_title : String,
|
||||||
|
val album_title : String?,
|
||||||
|
val language : String?,
|
||||||
|
val duration: Int,
|
||||||
|
@Json(name = "artwork_large") val artworkLink : String,
|
||||||
|
val artist : List<Artist?>,
|
||||||
|
@Json(name = "gener") val genre : List<Genre?>?,
|
||||||
|
val lyrics_url : String?,
|
||||||
|
val youtube_id : String?,
|
||||||
|
val total_favourite_count : Int?,
|
||||||
|
val release_date : String?,
|
||||||
|
val play_ct : String?,
|
||||||
|
val secondary_language : String?,
|
||||||
|
var downloaded: DownloadStatus? = DownloadStatus.NotDownloaded
|
||||||
|
)
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.gaana
|
||||||
|
|
||||||
|
data class Genre (
|
||||||
|
val genre_id : Int,
|
||||||
|
val name : String
|
||||||
|
)
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.gaana
|
||||||
|
|
||||||
|
data class Tags (
|
||||||
|
val tag_id : Int,
|
||||||
|
val tag_name : String
|
||||||
|
)
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Album(
|
||||||
|
var album_type: String? = null,
|
||||||
|
var artists: List<Artist?>? = null,
|
||||||
|
var available_markets: List<String?>? = null,
|
||||||
|
var copyrights: List<Copyright?>? = null,
|
||||||
|
var external_ids: Map<String?, String?>? = null,
|
||||||
|
var external_urls: Map<String?, String?>? = null,
|
||||||
|
var genres: List<String?>? = null,
|
||||||
|
var href: String? = null,
|
||||||
|
var id: String? = null,
|
||||||
|
var images: List<Image?>? = null,
|
||||||
|
var label :String? = null,
|
||||||
|
var name: String? = null,
|
||||||
|
var popularity: Int? = null,
|
||||||
|
var release_date: String? = null,
|
||||||
|
var release_date_precision: String? = null,
|
||||||
|
var tracks: PagingObjectTrack? = null,
|
||||||
|
var type: String? = null,
|
||||||
|
var uri: String? = null):Parcelable
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Artist(
|
||||||
|
var external_urls: Map<String?, String?>? = null,
|
||||||
|
var href: String? = null,
|
||||||
|
var id: String? = null,
|
||||||
|
var name: String? = null,
|
||||||
|
var type: String? = null,
|
||||||
|
var uri: String? = null):Parcelable
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Copyright(
|
||||||
|
var text: String? = null,
|
||||||
|
var type: String? = null):Parcelable
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Episodes(
|
||||||
|
var audio_preview_url:String?,
|
||||||
|
var description:String?,
|
||||||
|
var duration_ms:Int?,
|
||||||
|
var explicit:Boolean?,
|
||||||
|
var external_urls:Map<String,String>?,
|
||||||
|
var href:String?,
|
||||||
|
var id:String?,
|
||||||
|
var images:List<Image?>?,
|
||||||
|
var is_externally_hosted:Boolean?,
|
||||||
|
var is_playable:Boolean?,
|
||||||
|
var language:String?,
|
||||||
|
var languages:List<String?>?,
|
||||||
|
var name:String?,
|
||||||
|
var release_date:String?,
|
||||||
|
var release_date_precision:String?,
|
||||||
|
var type:String?,
|
||||||
|
var uri:String
|
||||||
|
): Parcelable
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Followers(
|
||||||
|
var href: String? = null,
|
||||||
|
var total: Int? = null):Parcelable
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Image(
|
||||||
|
var width: Int? = null,
|
||||||
|
var height: Int? = null,
|
||||||
|
var url: String? = null):Parcelable
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class LinkedTrack(
|
||||||
|
var external_urls: Map<String?, String?>? = null,
|
||||||
|
var href: String? = null,
|
||||||
|
var id: String? = null,
|
||||||
|
var type: String? = null,
|
||||||
|
var uri: String? = null): Parcelable
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class PagingObjectPlaylistTrack(
|
||||||
|
var href: String? = null,
|
||||||
|
var items: List<PlaylistTrack>? = null,
|
||||||
|
var limit: Int = 0,
|
||||||
|
var next: String? = null,
|
||||||
|
var offset: Int = 0,
|
||||||
|
var previous: String? = null,
|
||||||
|
var total: Int = 0): Parcelable
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class PagingObjectTrack(
|
||||||
|
var href: String? = null,
|
||||||
|
var items: List<Track>? = null,
|
||||||
|
var limit: Int = 0,
|
||||||
|
var next: String? = null,
|
||||||
|
var offset: Int = 0,
|
||||||
|
var previous: String? = null,
|
||||||
|
var total: Int = 0):Parcelable
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Playlist(
|
||||||
|
@Json(name = "collaborative")var is_collaborative: Boolean? = null,
|
||||||
|
var description: String? = null,
|
||||||
|
var external_urls: Map<String?, String?>? = null,
|
||||||
|
var followers: Followers? = null,
|
||||||
|
var href: String? = null,
|
||||||
|
var id: String? = null,
|
||||||
|
var images: List<Image?>? = null,
|
||||||
|
var name: String? = null,
|
||||||
|
var owner: UserPublic? = null,
|
||||||
|
@Json(name = "public")var is_public: Boolean? = null,
|
||||||
|
var snapshot_id: String? = null,
|
||||||
|
var tracks: PagingObjectPlaylistTrack? = null,
|
||||||
|
var type: String? = null,
|
||||||
|
var uri: String? = null): Parcelable
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class PlaylistTrack(
|
||||||
|
var added_at: String? = null,
|
||||||
|
var added_by: UserPublic? = null,
|
||||||
|
var track: Track? = null,
|
||||||
|
var is_local: Boolean? = null): Parcelable
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
enum class Source {
|
||||||
|
Spotify,
|
||||||
|
YouTube,
|
||||||
|
Gaana,
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Token(
|
||||||
|
var access_token:String,
|
||||||
|
var token_type:String,
|
||||||
|
var expires_in:Int
|
||||||
|
): Parcelable
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Track(
|
||||||
|
var artists: List<Artist?>? = null,
|
||||||
|
var available_markets: List<String?>? = null,
|
||||||
|
var is_playable: Boolean? = null,
|
||||||
|
var linked_from: LinkedTrack? = null,
|
||||||
|
var disc_number: Int = 0,
|
||||||
|
var duration_ms: Long = 0,
|
||||||
|
var explicit: Boolean? = null,
|
||||||
|
var external_urls: Map<String?, String?>? = null,
|
||||||
|
var href: String? = null,
|
||||||
|
var name: String? = null,
|
||||||
|
var preview_url: String? = null,
|
||||||
|
var track_number: Int = 0,
|
||||||
|
var type: String? = null,
|
||||||
|
var uri: String? = null,
|
||||||
|
var album: Album? = null,
|
||||||
|
var external_ids: Map<String?, String?>? = null,
|
||||||
|
var popularity: Int? = null,
|
||||||
|
var downloaded: DownloadStatus = DownloadStatus.NotDownloaded
|
||||||
|
):Parcelable
|
||||||
|
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class UserPrivate(
|
||||||
|
val country:String,
|
||||||
|
var display_name: String,
|
||||||
|
val email:String,
|
||||||
|
var external_urls: Map<String?, String?>? = null,
|
||||||
|
var followers: Followers? = null,
|
||||||
|
var href: String? = null,
|
||||||
|
var id: String? = null,
|
||||||
|
var images: List<Image?>? = null,
|
||||||
|
var product:String,
|
||||||
|
var type: String? = null,
|
||||||
|
var uri: String? = null): Parcelable
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.spotify
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class UserPublic(
|
||||||
|
var display_name: String? = null,
|
||||||
|
var external_urls: Map<String?, String?>? = null,
|
||||||
|
var followers: Followers? = null,
|
||||||
|
var href: String? = null,
|
||||||
|
var id: String? = null,
|
||||||
|
var images: List<Image?>? = null,
|
||||||
|
var type: String? = null,
|
||||||
|
var uri: String? = null): Parcelable
|
@ -6,8 +6,10 @@ import androidx.navigation.compose.NavHost
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.navArgument
|
import androidx.navigation.compose.navArgument
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.shabinder.spotiflyer.home.Home
|
import com.shabinder.spotiflyer.ui.home.Home
|
||||||
import com.shabinder.spotiflyer.tracklist.TrackList
|
import com.shabinder.spotiflyer.ui.platforms.gaana.Gaana
|
||||||
|
import com.shabinder.spotiflyer.ui.platforms.spotify.Spotify
|
||||||
|
import com.shabinder.spotiflyer.ui.platforms.youtube.Youtube
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ComposeNavigation() {
|
fun ComposeNavigation() {
|
||||||
@ -22,13 +24,37 @@ fun ComposeNavigation() {
|
|||||||
Home(navController = navController)
|
Home(navController = navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Track list Screen
|
//Spotify Screen
|
||||||
//Argument `link` = Link of Track/Album/Playlist
|
//Argument `link` = Link of Track/Album/Playlist
|
||||||
composable(
|
composable(
|
||||||
"track_list/{link}",
|
"spotify/{link}",
|
||||||
arguments = listOf(navArgument("link") { type = NavType.StringType })
|
arguments = listOf(navArgument("link") { type = NavType.StringType })
|
||||||
) {
|
) {
|
||||||
TrackList(
|
Spotify(
|
||||||
|
link = it.arguments?.getString("link") ?: "error",
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Gaana Screen
|
||||||
|
//Argument `link` = Link of Track/Album/Playlist
|
||||||
|
composable(
|
||||||
|
"gaana/{link}",
|
||||||
|
arguments = listOf(navArgument("link") { type = NavType.StringType })
|
||||||
|
) {
|
||||||
|
Gaana(
|
||||||
|
link = it.arguments?.getString("link") ?: "error",
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Youtube Screen
|
||||||
|
//Argument `link` = Link of Track/Album/Playlist
|
||||||
|
composable(
|
||||||
|
"youtube/{link}",
|
||||||
|
arguments = listOf(navArgument("link") { type = NavType.StringType })
|
||||||
|
) {
|
||||||
|
Youtube(
|
||||||
link = it.arguments?.getString("link") ?: "error",
|
link = it.arguments?.getString("link") ?: "error",
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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.networking
|
||||||
|
|
||||||
|
import com.shabinder.spotiflyer.models.Optional
|
||||||
|
import com.shabinder.spotiflyer.models.gaana.*
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Query
|
||||||
|
import retrofit2.http.Url
|
||||||
|
|
||||||
|
const val gaana_token = "b2e6d7fbc136547a940516e9b77e5990"
|
||||||
|
|
||||||
|
interface GaanaInterface {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Api Request: http://api.gaana.com/?type=playlist&subtype=playlist_detail&seokey=gaana-dj-hindi-top-50-1&token=b2e6d7fbc136547a940516e9b77e5990&format=JSON
|
||||||
|
*
|
||||||
|
* subtype : ["most_popular_playlist" , "playlist_home_featured" ,"playlist_detail" ,"user_playlist" ,"topCharts"]
|
||||||
|
**/
|
||||||
|
@GET(".")
|
||||||
|
suspend fun getGaanaPlaylist(
|
||||||
|
@Query("type") type: String = "playlist",
|
||||||
|
@Query("subtype") subtype: String = "playlist_detail",
|
||||||
|
@Query("seokey") seokey: String,
|
||||||
|
@Query("token") token: String = gaana_token,
|
||||||
|
@Query("format") format: String = "JSON",
|
||||||
|
@Query("limit") limit: Int = 2000
|
||||||
|
): Optional<GaanaPlaylist>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Api Request: http://api.gaana.com/?type=album&subtype=album_detail&seokey=kabir-singh&token=b2e6d7fbc136547a940516e9b77e5990&format=JSON
|
||||||
|
*
|
||||||
|
* subtype : ["most_popular" , "new_release" ,"featured_album" ,"similar_album" ,"all_albums", "album" ,"album_detail" ,"album_detail_info"]
|
||||||
|
**/
|
||||||
|
@GET(".")
|
||||||
|
suspend fun getGaanaAlbum(
|
||||||
|
@Query("type") type: String = "album",
|
||||||
|
@Query("subtype") subtype: String = "album_detail",
|
||||||
|
@Query("seokey") seokey: String,
|
||||||
|
@Query("token") token: String = gaana_token,
|
||||||
|
@Query("format") format: String = "JSON",
|
||||||
|
@Query("limit") limit: Int = 2000
|
||||||
|
): Optional<GaanaAlbum>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Api Request: http://api.gaana.com/?type=song&subtype=song_detail&seokey=pachtaoge&token=b2e6d7fbc136547a940516e9b77e5990&format=JSON
|
||||||
|
*
|
||||||
|
* subtype : ["most_popular" , "hot_songs" ,"recommendation" ,"song_detail"]
|
||||||
|
**/
|
||||||
|
@GET(".")
|
||||||
|
suspend fun getGaanaSong(
|
||||||
|
@Query("type") type: String = "song",
|
||||||
|
@Query("subtype") subtype: String = "song_detail",
|
||||||
|
@Query("seokey") seokey: String,
|
||||||
|
@Query("token") token: String = gaana_token,
|
||||||
|
@Query("format") format: String = "JSON",
|
||||||
|
): Optional<GaanaSong>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Api Request: https://api.gaana.com/?type=artist&subtype=artist_details_info&seokey=neha-kakkar&token=b2e6d7fbc136547a940516e9b77e5990&format=JSON
|
||||||
|
*
|
||||||
|
* subtype : ["most_popular" , "artist_list" ,"artist_track_listing" ,"artist_album" ,"similar_artist","artist_details" ,"artist_details_info"]
|
||||||
|
**/
|
||||||
|
@GET(".")
|
||||||
|
suspend fun getGaanaArtistDetails(
|
||||||
|
@Query("type") type: String = "artist",
|
||||||
|
@Query("subtype") subtype: String = "artist_details_info",
|
||||||
|
@Query("seokey") seokey: String,
|
||||||
|
@Query("token") token: String = gaana_token,
|
||||||
|
@Query("format") format: String = "JSON",
|
||||||
|
): Optional<GaanaArtistDetails>
|
||||||
|
/*
|
||||||
|
* Api Request: http://api.gaana.com/?type=artist&subtype=artist_track_listing&seokey=neha-kakkar&limit=50&token=b2e6d7fbc136547a940516e9b77e5990&format=JSON
|
||||||
|
*
|
||||||
|
* subtype : ["most_popular" , "artist_list" ,"artist_track_listing" ,"artist_album" ,"similar_artist","artist_details" ,"artist_details_info"]
|
||||||
|
**/
|
||||||
|
@GET(".")
|
||||||
|
suspend fun getGaanaArtistTracks(
|
||||||
|
@Query("type") type: String = "artist",
|
||||||
|
@Query("subtype") subtype: String = "artist_track_listing",
|
||||||
|
@Query("seokey") seokey: String,
|
||||||
|
@Query("token") token: String = gaana_token,
|
||||||
|
@Query("format") format: String = "JSON",
|
||||||
|
@Query("limit") limit: Int = 50
|
||||||
|
): Optional<GaanaArtistTracks>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dynamic Url Requests
|
||||||
|
* */
|
||||||
|
@GET
|
||||||
|
fun getResponse(@Url url:String): Call<ResponseBody>
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.networking
|
||||||
|
|
||||||
|
import com.shabinder.spotiflyer.models.Optional
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.*
|
||||||
|
import retrofit2.http.*
|
||||||
|
|
||||||
|
interface SpotifyService {
|
||||||
|
|
||||||
|
@GET("playlists/{playlist_id}")
|
||||||
|
suspend fun getPlaylist(@Path("playlist_id") playlistId: String?): Optional<Playlist>
|
||||||
|
|
||||||
|
@GET("playlists/{playlist_id}/tracks")
|
||||||
|
suspend fun getPlaylistTracks(
|
||||||
|
@Path("playlist_id") playlistId: String?,
|
||||||
|
@Query("offset") offset: Int = 0,
|
||||||
|
@Query("limit") limit: Int = 100
|
||||||
|
): Optional<PagingObjectPlaylistTrack>
|
||||||
|
|
||||||
|
@GET("tracks/{id}")
|
||||||
|
suspend fun getTrack(@Path("id") trackId: String?): Optional<Track>
|
||||||
|
|
||||||
|
@GET("episodes/{id}")
|
||||||
|
suspend fun getEpisode(@Path("id") episodeId: String?): Optional<Track>
|
||||||
|
|
||||||
|
@GET("shows/{id}")
|
||||||
|
suspend fun getShow(@Path("id") showId: String?): Optional<Track>
|
||||||
|
|
||||||
|
@GET("albums/{id}")
|
||||||
|
suspend fun getAlbum(@Path("id") albumId: String?): Optional<Album>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpotifyServiceTokenRequest{
|
||||||
|
|
||||||
|
@POST("api/token")
|
||||||
|
@FormUrlEncoded
|
||||||
|
suspend fun getToken(@Field("grant_type") grant_type:String = "client_credentials"): Optional<Token>
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.networking
|
||||||
|
|
||||||
|
import com.beust.klaxon.JsonObject
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.Headers
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
|
||||||
|
const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"
|
||||||
|
|
||||||
|
interface YoutubeMusicApi {
|
||||||
|
|
||||||
|
@Headers("Content-Type: application/json", "Referer: https://music.youtube.com/search")
|
||||||
|
@POST("search?alt=json&key=$apiKey")
|
||||||
|
fun getYoutubeMusicResponse(@Body text: String): Call<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makeJsonBody(query: String):JsonObject{
|
||||||
|
val client = JsonObject()
|
||||||
|
client["clientName"] = "WEB_REMIX"
|
||||||
|
client["clientVersion"] = "0.1"
|
||||||
|
|
||||||
|
val context = JsonObject()
|
||||||
|
context["client"] = client
|
||||||
|
|
||||||
|
val mainObject = JsonObject()
|
||||||
|
mainObject["context"] = context
|
||||||
|
mainObject["query"] = query
|
||||||
|
|
||||||
|
return mainObject
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
package com.shabinder.spotiflyer.tracklist
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TrackList(link: String,navController: NavController, modifier: Modifier = Modifier){
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Shabinder Singh
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.shabinder.spotiflyer.ui.base
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
|
|
||||||
|
abstract class TrackListViewModel:ViewModel() {
|
||||||
|
abstract var folderType:String
|
||||||
|
abstract var subFolder:String
|
||||||
|
open val trackList = MutableLiveData<MutableList<TrackDetails>>()
|
||||||
|
|
||||||
|
private val loading = "Loading!"
|
||||||
|
open var title = MutableLiveData<String>().apply { value = loading }
|
||||||
|
open var coverUrl = MutableLiveData<String>()
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.shabinder.spotiflyer.home
|
package com.shabinder.spotiflyer.ui.home
|
||||||
|
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
@ -1,7 +1,5 @@
|
|||||||
package com.shabinder.spotiflyer.home
|
package com.shabinder.spotiflyer.ui.home
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.shabinder.spotiflyer.ui.platforms.gaana
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Gaana(link: String, navController: NavController,) {
|
||||||
|
}
|
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Shabinder Singh
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.shabinder.spotiflyer.ui.platforms.gaana
|
||||||
|
|
||||||
|
import androidx.hilt.lifecycle.ViewModelInject
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
|
import com.shabinder.spotiflyer.models.gaana.GaanaTrack
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
|
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||||
|
import com.shabinder.spotiflyer.ui.base.TrackListViewModel
|
||||||
|
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
||||||
|
import com.shabinder.spotiflyer.utils.finalOutputDir
|
||||||
|
import com.shabinder.spotiflyer.utils.queryActiveTracks
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class GaanaViewModel @ViewModelInject constructor(
|
||||||
|
private val databaseDAO: DatabaseDAO,
|
||||||
|
private val gaanaInterface : GaanaInterface
|
||||||
|
) : TrackListViewModel(){
|
||||||
|
|
||||||
|
override var folderType:String = ""
|
||||||
|
override var subFolder:String = ""
|
||||||
|
|
||||||
|
private val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg"
|
||||||
|
|
||||||
|
fun gaanaSearch(type:String,link:String){
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (type) {
|
||||||
|
"song" -> {
|
||||||
|
gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also {
|
||||||
|
folderType = "Tracks"
|
||||||
|
subFolder = ""
|
||||||
|
if (File(
|
||||||
|
finalOutputDir(
|
||||||
|
it.track_title,
|
||||||
|
folderType,
|
||||||
|
subFolder
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
) {//Download Already Present!!
|
||||||
|
it.downloaded = DownloadStatus.Downloaded
|
||||||
|
}
|
||||||
|
trackList.value = listOf(it).toTrackDetailsList(folderType, subFolder)
|
||||||
|
title.value = it.track_title
|
||||||
|
coverUrl.value = it.artworkLink
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
databaseDAO.insert(
|
||||||
|
DownloadRecord(
|
||||||
|
type = "Track",
|
||||||
|
name = title.value!!,
|
||||||
|
link = "https://gaana.com/$type/$link",
|
||||||
|
coverUrl = coverUrl.value!!,
|
||||||
|
totalFiles = 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"album" -> {
|
||||||
|
gaanaInterface.getGaanaAlbum(seokey = link).value?.also {
|
||||||
|
folderType = "Albums"
|
||||||
|
subFolder = link
|
||||||
|
it.tracks.forEach { track ->
|
||||||
|
if (File(
|
||||||
|
finalOutputDir(
|
||||||
|
track.track_title,
|
||||||
|
folderType,
|
||||||
|
subFolder
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
) {//Download Already Present!!
|
||||||
|
track.downloaded = DownloadStatus.Downloaded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder)
|
||||||
|
title.value = link
|
||||||
|
coverUrl.value = it.custom_artworks.size_480p
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
databaseDAO.insert(
|
||||||
|
DownloadRecord(
|
||||||
|
type = "Album",
|
||||||
|
name = title.value!!,
|
||||||
|
link = "https://gaana.com/$type/$link",
|
||||||
|
coverUrl = coverUrl.value.toString(),
|
||||||
|
totalFiles = trackList.value?.size ?: 0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"playlist" -> {
|
||||||
|
gaanaInterface.getGaanaPlaylist(seokey = link).value?.also {
|
||||||
|
folderType = "Playlists"
|
||||||
|
subFolder = link
|
||||||
|
it.tracks.forEach { track ->
|
||||||
|
if (File(
|
||||||
|
finalOutputDir(
|
||||||
|
track.track_title,
|
||||||
|
folderType,
|
||||||
|
subFolder
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
) {//Download Already Present!!
|
||||||
|
track.downloaded = DownloadStatus.Downloaded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder)
|
||||||
|
title.value = link
|
||||||
|
//coverUrl.value = "TODO"
|
||||||
|
coverUrl.value = gaanaPlaceholderImageUrl
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
databaseDAO.insert(
|
||||||
|
DownloadRecord(
|
||||||
|
type = "Playlist",
|
||||||
|
name = title.value.toString(),
|
||||||
|
link = "https://gaana.com/$type/$link",
|
||||||
|
coverUrl = coverUrl.value.toString(),
|
||||||
|
totalFiles = it.tracks.size,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"artist" -> {
|
||||||
|
folderType = "Artist"
|
||||||
|
subFolder = link
|
||||||
|
val artistDetails =
|
||||||
|
gaanaInterface.getGaanaArtistDetails(seokey = link).value?.artist?.firstOrNull()
|
||||||
|
?.also {
|
||||||
|
title.value = it.name
|
||||||
|
coverUrl.value = it.artworkLink
|
||||||
|
}
|
||||||
|
gaanaInterface.getGaanaArtistTracks(seokey = link).value?.also {
|
||||||
|
it.tracks.forEach { track ->
|
||||||
|
if (File(
|
||||||
|
finalOutputDir(
|
||||||
|
track.track_title,
|
||||||
|
folderType,
|
||||||
|
subFolder
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
) {//Download Already Present!!
|
||||||
|
track.downloaded = DownloadStatus.Downloaded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder)
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
databaseDAO.insert(
|
||||||
|
DownloadRecord(
|
||||||
|
type = "Artist",
|
||||||
|
name = artistDetails?.name ?: link,
|
||||||
|
link = "https://gaana.com/$type/$link",
|
||||||
|
coverUrl = coverUrl.value.toString(),
|
||||||
|
totalFiles = trackList.value?.size ?: 0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryActiveTracks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<GaanaTrack>.toTrackDetailsList(type:String , subFolder:String) = this.map {
|
||||||
|
TrackDetails(
|
||||||
|
title = it.track_title,
|
||||||
|
artists = it.artist.map { artist -> artist?.name.toString() },
|
||||||
|
durationSec = it.duration,
|
||||||
|
albumArt = File(
|
||||||
|
imageDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg"),
|
||||||
|
albumName = it.album_title,
|
||||||
|
year = it.release_date,
|
||||||
|
comment = "Genres:${it.genre?.map { genre -> genre?.name }?.reduceOrNull { acc, s -> acc + s }}",
|
||||||
|
trackUrl = it.lyrics_url,
|
||||||
|
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
|
||||||
|
source = Source.Gaana,
|
||||||
|
albumArtURL = it.artworkLink,
|
||||||
|
outputFile = finalOutputDir(it.track_title,type, subFolder,".m4a")
|
||||||
|
)
|
||||||
|
}.toMutableList()
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.shabinder.spotiflyer.ui.platforms.spotify
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Spotify(link: String, navController: NavController,) {
|
||||||
|
}
|
@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Shabinder Singh
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.shabinder.spotiflyer.ui.platforms.spotify
|
||||||
|
|
||||||
|
import androidx.hilt.lifecycle.ViewModelInject
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Album
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Image
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Track
|
||||||
|
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||||
|
import com.shabinder.spotiflyer.networking.SpotifyService
|
||||||
|
import com.shabinder.spotiflyer.ui.base.TrackListViewModel
|
||||||
|
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
||||||
|
import com.shabinder.spotiflyer.utils.finalOutputDir
|
||||||
|
import com.shabinder.spotiflyer.utils.log
|
||||||
|
import com.shabinder.spotiflyer.utils.queryActiveTracks
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class SpotifyViewModel @ViewModelInject constructor(
|
||||||
|
private val databaseDAO: DatabaseDAO,
|
||||||
|
private val gaanaInterface : GaanaInterface
|
||||||
|
) : TrackListViewModel(){
|
||||||
|
|
||||||
|
override var folderType:String = ""
|
||||||
|
override var subFolder:String = ""
|
||||||
|
|
||||||
|
var spotifyService : SpotifyService? = null
|
||||||
|
|
||||||
|
fun resolveLink(url:String):String {
|
||||||
|
val response = gaanaInterface.getResponse(url).execute().body()?.string().toString()
|
||||||
|
val regex = """https://open\.spotify\.com.+\w""".toRegex()
|
||||||
|
return regex.find(response)?.value.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun spotifySearch(type:String,link: String){
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (type) {
|
||||||
|
"track" -> {
|
||||||
|
spotifyService?.getTrack(link)?.value?.also {
|
||||||
|
folderType = "Tracks"
|
||||||
|
subFolder = ""
|
||||||
|
if (File(
|
||||||
|
finalOutputDir(
|
||||||
|
it.name.toString(),
|
||||||
|
folderType,
|
||||||
|
subFolder
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
) {//Download Already Present!!
|
||||||
|
it.downloaded = DownloadStatus.Downloaded
|
||||||
|
}
|
||||||
|
trackList.value = listOf(it).toTrackDetailsList(folderType, subFolder)
|
||||||
|
title.value = it.name
|
||||||
|
coverUrl.value = it.album?.images?.elementAtOrNull(1)?.url
|
||||||
|
?: it.album?.images?.elementAtOrNull(0)?.url
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
databaseDAO.insert(
|
||||||
|
DownloadRecord(
|
||||||
|
type = "Track",
|
||||||
|
name = title.value.toString(),
|
||||||
|
link = "https://open.spotify.com/$type/$link",
|
||||||
|
coverUrl = coverUrl.value.toString(),
|
||||||
|
totalFiles = 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"album" -> {
|
||||||
|
val albumObject = spotifyService?.getAlbum(link)?.value
|
||||||
|
folderType = "Albums"
|
||||||
|
subFolder = albumObject?.name.toString()
|
||||||
|
albumObject?.tracks?.items?.forEach {
|
||||||
|
if (File(
|
||||||
|
finalOutputDir(
|
||||||
|
it.name.toString(),
|
||||||
|
folderType,
|
||||||
|
subFolder
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
) {//Download Already Present!!
|
||||||
|
it.downloaded = DownloadStatus.Downloaded
|
||||||
|
}
|
||||||
|
it.album = Album(
|
||||||
|
images = listOf(
|
||||||
|
Image(
|
||||||
|
url = albumObject.images?.elementAtOrNull(1)?.url
|
||||||
|
?: albumObject.images?.elementAtOrNull(0)?.url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
trackList.value = albumObject?.tracks?.items?.toTrackDetailsList(folderType, subFolder)
|
||||||
|
title.value = albumObject?.name
|
||||||
|
coverUrl.value = albumObject?.images?.elementAtOrNull(1)?.url
|
||||||
|
?: albumObject?.images?.elementAtOrNull(0)?.url
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
databaseDAO.insert(
|
||||||
|
DownloadRecord(
|
||||||
|
type = "Album",
|
||||||
|
name = title.value.toString(),
|
||||||
|
link = "https://open.spotify.com/$type/$link",
|
||||||
|
coverUrl = coverUrl.value.toString(),
|
||||||
|
totalFiles = trackList.value?.size ?: 0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"playlist" -> {
|
||||||
|
log("Spotify Service",spotifyService.toString())
|
||||||
|
val playlistObject = spotifyService?.getPlaylist(link)?.value
|
||||||
|
folderType = "Playlists"
|
||||||
|
subFolder = playlistObject?.name.toString()
|
||||||
|
val tempTrackList = mutableListOf<Track>()
|
||||||
|
log("Tracks Fetched", playlistObject?.tracks?.items?.size.toString())
|
||||||
|
playlistObject?.tracks?.items?.forEach {
|
||||||
|
it.track?.let { it1 ->
|
||||||
|
if (File(
|
||||||
|
finalOutputDir(
|
||||||
|
it1.name.toString(),
|
||||||
|
folderType,
|
||||||
|
subFolder
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
) {//Download Already Present!!
|
||||||
|
it1.downloaded = DownloadStatus.Downloaded
|
||||||
|
}
|
||||||
|
tempTrackList.add(it1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var moreTracksAvailable = !playlistObject?.tracks?.next.isNullOrBlank()
|
||||||
|
|
||||||
|
while (moreTracksAvailable) {
|
||||||
|
//Check For More Tracks If available
|
||||||
|
val moreTracks = spotifyService?.getPlaylistTracks(link, offset = tempTrackList.size)?.value
|
||||||
|
moreTracks?.items?.forEach {
|
||||||
|
it.track?.let { it1 -> tempTrackList.add(it1) }
|
||||||
|
}
|
||||||
|
moreTracksAvailable = !moreTracks?.next.isNullOrBlank()
|
||||||
|
}
|
||||||
|
log("Total Tracks Fetched", tempTrackList.size.toString())
|
||||||
|
trackList.value = tempTrackList.toTrackDetailsList(folderType, subFolder)
|
||||||
|
title.value = playlistObject?.name
|
||||||
|
coverUrl.value = playlistObject?.images?.elementAtOrNull(1)?.url
|
||||||
|
?: playlistObject?.images?.firstOrNull()?.url.toString()
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
databaseDAO.insert(
|
||||||
|
DownloadRecord(
|
||||||
|
type = "Playlist",
|
||||||
|
name = title.value.toString(),
|
||||||
|
link = "https://open.spotify.com/$type/$link",
|
||||||
|
coverUrl = coverUrl.value.toString(),
|
||||||
|
totalFiles = tempTrackList.size,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"episode" -> {//TODO
|
||||||
|
}
|
||||||
|
"show" -> {//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryActiveTracks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<Track>.toTrackDetailsList(type:String , subFolder:String) = this.map {
|
||||||
|
TrackDetails(
|
||||||
|
title = it.name.toString(),
|
||||||
|
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
|
||||||
|
durationSec = (it.duration_ms/1000).toInt(),
|
||||||
|
albumArt = File(
|
||||||
|
imageDir() + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg"),
|
||||||
|
albumName = it.album?.name,
|
||||||
|
year = it.album?.release_date,
|
||||||
|
comment = "Genres:${it.album?.genres?.joinToString()}",
|
||||||
|
trackUrl = it.href,
|
||||||
|
downloaded = it.downloaded,
|
||||||
|
source = Source.Spotify,
|
||||||
|
albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString(),
|
||||||
|
outputFile = finalOutputDir(it.name.toString(),type, subFolder,".m4a")
|
||||||
|
)
|
||||||
|
}.toMutableList()
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.shabinder.spotiflyer.ui.platforms.youtube
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Youtube(link: String, navController: NavController,) {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Shabinder Singh
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.shabinder.spotiflyer.ui.platforms.youtube
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.hilt.lifecycle.ViewModelInject
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
|
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||||
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
|
import com.shabinder.spotiflyer.ui.base.TrackListViewModel
|
||||||
|
import com.shabinder.spotiflyer.utils.*
|
||||||
|
import com.shabinder.spotiflyer.utils.Provider.imageDir
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class YoutubeViewModel @ViewModelInject constructor(
|
||||||
|
private val databaseDAO: DatabaseDAO,
|
||||||
|
private val ytDownloader: YoutubeDownloader
|
||||||
|
) : TrackListViewModel(){
|
||||||
|
/*
|
||||||
|
* YT Album Art Schema
|
||||||
|
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
|
||||||
|
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
|
||||||
|
* */
|
||||||
|
|
||||||
|
override var folderType = "YT_Downloads"
|
||||||
|
override var subFolder = ""
|
||||||
|
|
||||||
|
fun getYTPlaylist(searchId:String){
|
||||||
|
if(!isOnline())return
|
||||||
|
try{
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
log("YT Playlist",searchId)
|
||||||
|
val playlist = ytDownloader.getPlaylist(searchId)
|
||||||
|
val playlistDetails = playlist.details()
|
||||||
|
val name = playlistDetails.title()
|
||||||
|
subFolder = removeIllegalChars(name)
|
||||||
|
val videos = playlist.videos()
|
||||||
|
coverUrl.postValue("https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg")
|
||||||
|
title.postValue(
|
||||||
|
if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
|
||||||
|
)
|
||||||
|
this@YoutubeViewModel.trackList.postValue(videos.map {
|
||||||
|
TrackDetails(
|
||||||
|
title = it.title(),
|
||||||
|
artists = listOf(it.author().toString()),
|
||||||
|
durationSec = it.lengthSeconds(),
|
||||||
|
albumArt = File(
|
||||||
|
imageDir() + it.videoId() + ".jpeg"
|
||||||
|
),
|
||||||
|
source = Source.YouTube,
|
||||||
|
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
|
||||||
|
downloaded = if (File(
|
||||||
|
finalOutputDir(
|
||||||
|
itemName = it.title(),
|
||||||
|
type = folderType,
|
||||||
|
subFolder = subFolder
|
||||||
|
)).exists()
|
||||||
|
)
|
||||||
|
DownloadStatus.Downloaded
|
||||||
|
else {
|
||||||
|
DownloadStatus.NotDownloaded
|
||||||
|
},
|
||||||
|
outputFile = finalOutputDir(it.title(),folderType, subFolder,".m4a"),
|
||||||
|
videoID = it.videoId()
|
||||||
|
)
|
||||||
|
}.toMutableList())
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
databaseDAO.insert(DownloadRecord(
|
||||||
|
type = "PlayList",
|
||||||
|
name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
|
||||||
|
link = "https://www.youtube.com/playlist?list=$searchId",
|
||||||
|
coverUrl = "https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg",
|
||||||
|
totalFiles = videos.size,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
queryActiveTracks()
|
||||||
|
}
|
||||||
|
}catch (e:Exception){
|
||||||
|
showDialog("An Error Occurred While Processing!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
fun getYTTrack(searchId:String) {
|
||||||
|
if(!isOnline())return
|
||||||
|
try{
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
log("YT Video",searchId)
|
||||||
|
val video = ytDownloader.getVideo(searchId)
|
||||||
|
coverUrl.postValue("https://i.ytimg.com/vi/$searchId/hqdefault.jpg")
|
||||||
|
val detail = video?.details()
|
||||||
|
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() ?: ""
|
||||||
|
log("YT View Model",detail.toString())
|
||||||
|
this@YoutubeViewModel.trackList.postValue(
|
||||||
|
listOf(
|
||||||
|
TrackDetails(
|
||||||
|
title = name,
|
||||||
|
artists = listOf(detail?.author().toString()),
|
||||||
|
durationSec = detail?.lengthSeconds()?:0,
|
||||||
|
albumArt = File(imageDir(),"$searchId.jpeg"),
|
||||||
|
source = Source.YouTube,
|
||||||
|
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()
|
||||||
|
)
|
||||||
|
title.postValue(
|
||||||
|
if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}
|
||||||
|
)
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
databaseDAO.insert(DownloadRecord(
|
||||||
|
type = "Track",
|
||||||
|
name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
|
||||||
|
link = "https://www.youtube.com/watch?v=$searchId",
|
||||||
|
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
|
||||||
|
totalFiles = 1,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
queryActiveTracks()
|
||||||
|
}
|
||||||
|
} catch (e:Exception){
|
||||||
|
showDialog("An Error Occurred While Processing!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.shabinder.spotiflyer.ui.tracklist
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.viewinterop.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UI for List of Tracks to be universally used.
|
||||||
|
* */
|
||||||
|
@Composable
|
||||||
|
fun TrackList(modifier: Modifier = Modifier){
|
||||||
|
|
||||||
|
}
|
@ -6,6 +6,9 @@ import android.content.pm.PackageManager
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.github.kiulian.downloader.model.YoutubeVideo
|
||||||
|
import com.github.kiulian.downloader.model.formats.Format
|
||||||
|
import com.github.kiulian.downloader.model.quality.AudioQuality
|
||||||
import com.shabinder.spotiflyer.BuildConfig
|
import com.shabinder.spotiflyer.BuildConfig
|
||||||
import com.shabinder.spotiflyer.MainActivity
|
import com.shabinder.spotiflyer.MainActivity
|
||||||
|
|
||||||
@ -26,7 +29,22 @@ fun MainActivity.requestStoragePermission() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun YoutubeVideo.getData(): Format?{
|
||||||
|
return try {
|
||||||
|
findAudioWithQuality(AudioQuality.medium)?.get(0) as Format
|
||||||
|
} catch (e: java.lang.IndexOutOfBoundsException) {
|
||||||
|
try {
|
||||||
|
findAudioWithQuality(AudioQuality.high)?.get(0) as Format
|
||||||
|
} catch (e: java.lang.IndexOutOfBoundsException) {
|
||||||
|
try {
|
||||||
|
findAudioWithQuality(AudioQuality.low)?.get(0) as Format
|
||||||
|
} catch (e: java.lang.IndexOutOfBoundsException) {
|
||||||
|
log("YTDownloader", e.toString())
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fun openPlatform(packageName:String, websiteAddress:String){
|
fun openPlatform(packageName:String, websiteAddress:String){
|
||||||
val manager: PackageManager = mainActivity.packageManager
|
val manager: PackageManager = mainActivity.packageManager
|
||||||
try {
|
try {
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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.utils
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Protocol
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
|
|
||||||
|
const val NoInternetErrorCode = 222
|
||||||
|
|
||||||
|
class NetworkInterceptor: Interceptor {
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
log("Network Requesting",chain.request().url.toString())
|
||||||
|
return if (!isOnline()){
|
||||||
|
//No Internet Connection
|
||||||
|
showDialog()
|
||||||
|
//Lets Stop the Incoming Request and send Dummy Response
|
||||||
|
createEmptyResponse(chain,"No Internet Connection")
|
||||||
|
}else {
|
||||||
|
try{
|
||||||
|
val response = chain.proceed(chain.request())
|
||||||
|
val responseBody = response.body
|
||||||
|
val bodyString = responseBody?.string()
|
||||||
|
Response.Builder().run {
|
||||||
|
code(response.code) // code(200.300) = successful else = unsuccessful
|
||||||
|
body("{\"value\":${bodyString}}".toResponseBody(responseBody?.contentType())) // Whatever body
|
||||||
|
protocol(response.protocol)
|
||||||
|
message(response.message)
|
||||||
|
request(chain.request())
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
}catch (e: java.net.SocketTimeoutException){
|
||||||
|
showDialog("Timeout!","Please Go Back and Try Again")
|
||||||
|
createEmptyResponse(chain,"Timeout!, Slow Internet Connection")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createEmptyResponse(chain: Interceptor.Chain, message:String = "Error") = Response.Builder().run {
|
||||||
|
code(NoInternetErrorCode) // code(200.300) = successful else = unsuccessful
|
||||||
|
body("{}".toResponseBody(null)) // Empty Object
|
||||||
|
protocol(Protocol.HTTP_2)
|
||||||
|
message(message)
|
||||||
|
request(chain.request())
|
||||||
|
build()
|
||||||
|
}
|
125
app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt
Normal file
125
app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package com.shabinder.spotiflyer.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Environment
|
||||||
|
import android.util.Base64
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
|
import com.shabinder.spotiflyer.App
|
||||||
|
import com.shabinder.spotiflyer.SharedViewModel
|
||||||
|
import com.shabinder.spotiflyer.database.DatabaseDAO
|
||||||
|
import com.shabinder.spotiflyer.database.DownloadRecordDatabase
|
||||||
|
import com.shabinder.spotiflyer.networking.GaanaInterface
|
||||||
|
import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest
|
||||||
|
import com.shabinder.spotiflyer.networking.YoutubeMusicApi
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
|
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
@Module
|
||||||
|
object Provider {
|
||||||
|
|
||||||
|
//Default Directory to save Media in their Own Categorized Folders
|
||||||
|
@Suppress("DEPRECATION")// We Do Have Media Access (But Just Media in Media Directory,Not Anything Else)
|
||||||
|
val defaultDir = Environment.getExternalStorageDirectory().toString() + File.separator +
|
||||||
|
Environment.DIRECTORY_MUSIC + File.separator +
|
||||||
|
"SpotiFlyer"+ File.separator
|
||||||
|
|
||||||
|
//Default Cache Directory to save Album Art to use them for writing in Media Later
|
||||||
|
fun imageDir(ctx: Context = mainActivity): String = ctx
|
||||||
|
.externalCacheDir?.absolutePath + File.separator +
|
||||||
|
".Images" + File.separator
|
||||||
|
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun databaseDAO(@ApplicationContext appContext: Context): DatabaseDAO {
|
||||||
|
return DownloadRecordDatabase.getInstance(appContext).databaseDAO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getYTDownloader(): YoutubeDownloader {
|
||||||
|
return YoutubeDownloader()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getMoshi(): Moshi {
|
||||||
|
return Moshi.Builder()
|
||||||
|
.add(KotlinJsonAdapterFactory())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getSpotifyTokenInterface(moshi: Moshi): SpotifyServiceTokenRequest {
|
||||||
|
val httpClient2: OkHttpClient.Builder = OkHttpClient.Builder()
|
||||||
|
.addInterceptor(Interceptor { chain ->
|
||||||
|
val request: Request =
|
||||||
|
chain.request().newBuilder()
|
||||||
|
.addHeader(
|
||||||
|
"Authorization",
|
||||||
|
"Basic ${
|
||||||
|
Base64.encodeToString(
|
||||||
|
"${App.clientId}:${App.clientSecret}".toByteArray(),
|
||||||
|
Base64.NO_WRAP
|
||||||
|
)
|
||||||
|
}"
|
||||||
|
).build()
|
||||||
|
chain.proceed(request)
|
||||||
|
}).addInterceptor(NetworkInterceptor())
|
||||||
|
|
||||||
|
val retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl("https://accounts.spotify.com/")
|
||||||
|
.client(httpClient2.build())
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||||
|
.build()
|
||||||
|
return retrofit.create(SpotifyServiceTokenRequest::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun okHttpClient(): OkHttpClient {
|
||||||
|
return OkHttpClient.Builder()
|
||||||
|
.addInterceptor(NetworkInterceptor())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getGaanaInterface(moshi: Moshi, okHttpClient: OkHttpClient): GaanaInterface {
|
||||||
|
val retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl("https://api.gaana.com/")
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||||
|
.build()
|
||||||
|
return retrofit.create(GaanaInterface::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun getYoutubeMusicApi(moshi: Moshi): YoutubeMusicApi {
|
||||||
|
val retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl("https://music.youtube.com/youtubei/v1/")
|
||||||
|
.addConverterFactory(ScalarsConverterFactory.create())
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||||
|
.build()
|
||||||
|
return retrofit.create(YoutubeMusicApi::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,160 @@
|
|||||||
package com.shabinder.spotiflyer.utils
|
package com.shabinder.spotiflyer.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.shabinder.spotiflyer.MainActivity
|
import com.shabinder.spotiflyer.MainActivity
|
||||||
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
|
import com.shabinder.spotiflyer.worker.ForegroundService
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mainActivity Instance to use whereEver Needed , as Its God Activity.
|
||||||
|
* (i.e, almost Active Throughout App's Lifecycle )
|
||||||
|
*/
|
||||||
val mainActivity
|
val mainActivity
|
||||||
get() = MainActivity.getInstance()
|
get() = MainActivity.getInstance()
|
||||||
|
|
||||||
|
fun loadAllImages(context: Context? = mainActivity, images:List<String>? = null,source: Source) {
|
||||||
|
val serviceIntent = Intent(context, ForegroundService::class.java)
|
||||||
|
images?.let { serviceIntent.putStringArrayListExtra("imagesList",(it + source.name) as ArrayList<String>) }
|
||||||
|
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadTracks(
|
||||||
|
trackList: ArrayList<TrackDetails>,
|
||||||
|
context: Context? = mainActivity
|
||||||
|
) {
|
||||||
|
if(!trackList.isNullOrEmpty()){
|
||||||
|
val serviceIntent = Intent(context, ForegroundService::class.java)
|
||||||
|
serviceIntent.putParcelableArrayListExtra("object",trackList)
|
||||||
|
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryActiveTracks(context:Context? = mainActivity) {
|
||||||
|
val serviceIntent = Intent(context, ForegroundService::class.java).apply {
|
||||||
|
action = "query"
|
||||||
|
}
|
||||||
|
context?.let { ContextCompat.startForegroundService(it, serviceIntent) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finalOutputDir(itemName:String ,type:String, subFolder:String,extension:String = ".mp3"): String{
|
||||||
|
return Provider.defaultDir + removeIllegalChars(type) + File.separator +
|
||||||
|
if(subFolder.isEmpty())"" else { removeIllegalChars(subFolder) + File.separator} +
|
||||||
|
removeIllegalChars(itemName) + extension
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util. Function To Check Connection Status
|
||||||
|
* */
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
fun isOnline(): Boolean {
|
||||||
|
var result = false
|
||||||
|
val connectivityManager =
|
||||||
|
mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
|
||||||
|
connectivityManager?.let {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply {
|
||||||
|
result = when {
|
||||||
|
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
||||||
|
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
||||||
|
hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val netInfo =
|
||||||
|
(mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).activeNetworkInfo
|
||||||
|
result = netInfo != null && netInfo.isConnected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun showDialog(title:String? = null, message: String? = null,response: String = "Ok"){
|
||||||
|
//TODO
|
||||||
|
Toast.makeText(mainActivity,title ?: "No Internet",Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*Extension Function For Copying Files!
|
||||||
|
**/
|
||||||
|
fun File.copyTo(file: File) {
|
||||||
|
inputStream().use { input ->
|
||||||
|
file.outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun createDirectory(dir:String){
|
||||||
|
val yourAppDir = File(dir)
|
||||||
|
|
||||||
|
if(!yourAppDir.exists() && !yourAppDir.isDirectory)
|
||||||
|
{ // create empty directory
|
||||||
|
if (yourAppDir.mkdirs())
|
||||||
|
{log("CreateDir","$dir created")}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.w("CreateDir","Unable to create Dir: $dir!")}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{log("CreateDir","$dir already exists")}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Removing Illegal Chars from File Name
|
||||||
|
* **/
|
||||||
|
fun removeIllegalChars(fileName: String): String {
|
||||||
|
val illegalCharArray = charArrayOf(
|
||||||
|
'/',
|
||||||
|
'\n',
|
||||||
|
'\r',
|
||||||
|
'\t',
|
||||||
|
'\u0000',
|
||||||
|
'\u000C',
|
||||||
|
'`',
|
||||||
|
'?',
|
||||||
|
'*',
|
||||||
|
'\\',
|
||||||
|
'<',
|
||||||
|
'>',
|
||||||
|
'|',
|
||||||
|
'\"',
|
||||||
|
'.',
|
||||||
|
'-',
|
||||||
|
'\''
|
||||||
|
)
|
||||||
|
|
||||||
|
var name = fileName
|
||||||
|
for (c in illegalCharArray) {
|
||||||
|
name = fileName.replace(c, '_')
|
||||||
|
}
|
||||||
|
name = name.replace("\\s".toRegex(), "_")
|
||||||
|
name = name.replace("\\)".toRegex(), "")
|
||||||
|
name = name.replace("\\(".toRegex(), "")
|
||||||
|
name = name.replace("\\[".toRegex(), "")
|
||||||
|
name = name.replace("]".toRegex(), "")
|
||||||
|
name = name.replace("\\.".toRegex(), "")
|
||||||
|
name = name.replace("\"".toRegex(), "")
|
||||||
|
name = name.replace("\'".toRegex(), "")
|
||||||
|
name = name.replace(":".toRegex(), "")
|
||||||
|
name = name.replace("\\|".toRegex(), "")
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createDirectories() {
|
||||||
|
createDirectory(Provider.defaultDir)
|
||||||
|
createDirectory(Provider.imageDir())
|
||||||
|
createDirectory(Provider.defaultDir + "Tracks/")
|
||||||
|
createDirectory(Provider.defaultDir + "Albums/")
|
||||||
|
createDirectory(Provider.defaultDir + "Playlists/")
|
||||||
|
createDirectory(Provider.defaultDir + "YT_Downloads/")
|
||||||
|
}
|
||||||
|
@ -0,0 +1,694 @@
|
|||||||
|
/*
|
||||||
|
* 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.worker
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.*
|
||||||
|
import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.media.MediaScannerConnection
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.*
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Cancel
|
||||||
|
import androidx.compose.material.icons.rounded.CloudDownload
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import com.arthenica.mobileffmpeg.Config
|
||||||
|
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_CANCEL
|
||||||
|
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_SUCCESS
|
||||||
|
import com.arthenica.mobileffmpeg.FFmpeg
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
|
import com.mpatric.mp3agic.Mp3File
|
||||||
|
import com.shabinder.spotiflyer.R
|
||||||
|
import com.shabinder.spotiflyer.downloadHelper.getYTTracks
|
||||||
|
import com.shabinder.spotiflyer.downloadHelper.sortByBestMatch
|
||||||
|
import com.shabinder.spotiflyer.models.DownloadStatus
|
||||||
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
|
import com.shabinder.spotiflyer.models.spotify.Source
|
||||||
|
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.imageDir
|
||||||
|
import com.tonyodev.fetch2.*
|
||||||
|
import com.tonyodev.fetch2core.DownloadBlock
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class ForegroundService : Service(){
|
||||||
|
private val tag = "Foreground Service"
|
||||||
|
private val channelId = "ForegroundDownloaderService"
|
||||||
|
private val notificationId = 101
|
||||||
|
private var total = 0 //Total Downloads Requested
|
||||||
|
private var converted = 0//Total Files Converted
|
||||||
|
private var downloaded = 0//Total Files downloaded
|
||||||
|
private var failed = 0//Total Files failed
|
||||||
|
private val isFinished: Boolean
|
||||||
|
get() = converted + failed == total
|
||||||
|
private var isSingleDownload: Boolean = false
|
||||||
|
private val serviceJob = Job()
|
||||||
|
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
||||||
|
private val requestMap = hashMapOf<Request, TrackDetails>()
|
||||||
|
private val allTracksStatus = hashMapOf<String,DownloadStatus>()
|
||||||
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
|
private var isServiceStarted = false
|
||||||
|
private var messageList = mutableListOf("", "", "", "","")
|
||||||
|
private val imageDir:String
|
||||||
|
get() = imageDir(this)
|
||||||
|
private lateinit var cancelIntent:PendingIntent
|
||||||
|
private lateinit var fetch:Fetch
|
||||||
|
private lateinit var downloadManager : DownloadManager
|
||||||
|
@Inject lateinit var ytDownloader: YoutubeDownloader
|
||||||
|
@Inject lateinit var youtubeMusicApi: YoutubeMusicApi
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder? = null
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
createNotificationChannel(channelId,"Downloader Service")
|
||||||
|
}
|
||||||
|
val intent = Intent(
|
||||||
|
this,
|
||||||
|
ForegroundService::class.java
|
||||||
|
).apply{action = "kill"}
|
||||||
|
cancelIntent = PendingIntent.getService (this, 0 , intent , PendingIntent.FLAG_CANCEL_CURRENT )
|
||||||
|
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
initialiseFetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("WakelockTimeout")
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
// Send a notification that service is started
|
||||||
|
log(tag, "Service Started.")
|
||||||
|
startForeground(notificationId, getNotification())
|
||||||
|
intent?.let{
|
||||||
|
when (it.action) {
|
||||||
|
"kill" -> killService()
|
||||||
|
"query" -> {
|
||||||
|
val response = Intent().apply {
|
||||||
|
action = "query_result"
|
||||||
|
putExtra("tracks", allTracksStatus)
|
||||||
|
}
|
||||||
|
sendBroadcast(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val downloadObjects: ArrayList<TrackDetails>? = (it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList(
|
||||||
|
"object"
|
||||||
|
))
|
||||||
|
val imagesList: ArrayList<String>? = (it.getStringArrayListExtra("imagesList") ?: it.extras?.getStringArrayList(
|
||||||
|
"imagesList"
|
||||||
|
))
|
||||||
|
|
||||||
|
imagesList?.let{ imageList ->
|
||||||
|
serviceScope.launch {
|
||||||
|
downloadAllImages(imageList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadObjects?.let { list ->
|
||||||
|
downloadObjects.size.let { size ->
|
||||||
|
total += size
|
||||||
|
isSingleDownload = (size == 1)
|
||||||
|
}
|
||||||
|
updateNotification()
|
||||||
|
downloadAllTracks(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Wake locks and misc tasks from here :
|
||||||
|
return if (isServiceStarted){
|
||||||
|
//Service Already Started
|
||||||
|
START_STICKY
|
||||||
|
} else{
|
||||||
|
log(tag, "Starting the foreground service task")
|
||||||
|
isServiceStarted = true
|
||||||
|
wakeLock =
|
||||||
|
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||||
|
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EndlessService::lock").apply {
|
||||||
|
acquire()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
START_STICKY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function To Download All Tracks Available in a List
|
||||||
|
**/
|
||||||
|
private fun downloadAllTracks(trackList: List<TrackDetails>) {
|
||||||
|
trackList.forEach {
|
||||||
|
serviceScope.launch {
|
||||||
|
if (it.downloaded == DownloadStatus.Downloaded) {//Download Already Present!!
|
||||||
|
} else {
|
||||||
|
allTracksStatus[it.title] = DownloadStatus.Queued
|
||||||
|
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>
|
||||||
|
) {
|
||||||
|
serviceScope.launch {
|
||||||
|
val videoId = sortByBestMatch(
|
||||||
|
getYTTracks(response.body().toString()),
|
||||||
|
trackName = it.title,
|
||||||
|
trackArtists = it.artists,
|
||||||
|
trackDurationSec = it.durationSec
|
||||||
|
).keys.firstOrNull()
|
||||||
|
log("Service VideoID", videoId ?: "Not Found")
|
||||||
|
if (videoId.isNullOrBlank()) {
|
||||||
|
sendTrackBroadcast(Status.FAILED.name, it)
|
||||||
|
failed++
|
||||||
|
updateNotification()
|
||||||
|
allTracksStatus[it.title] = DownloadStatus.Failed
|
||||||
|
} else {//Found Youtube Video ID
|
||||||
|
downloadTrack(videoId, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<String>, t: Throwable) {
|
||||||
|
if (t.message.toString()
|
||||||
|
.contains("Failed to connect")
|
||||||
|
) showDialog("Failed, Check Your Internet Connection!")
|
||||||
|
log("YT API Req. Fail", t.message.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadTrack(videoID:String,track: TrackDetails){
|
||||||
|
serviceScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val audioData = ytDownloader.getVideo(videoID).getData()
|
||||||
|
|
||||||
|
audioData?.let {
|
||||||
|
val url: String = it.url()
|
||||||
|
log("DHelper Link Found", url)
|
||||||
|
val request= Request(url, track.outputFile).apply{
|
||||||
|
priority = Priority.NORMAL
|
||||||
|
networkType = NetworkType.ALL
|
||||||
|
}
|
||||||
|
fetch.enqueue(request,
|
||||||
|
{ request1 ->
|
||||||
|
requestMap[request1] = track
|
||||||
|
log(tag, "Enqueuing Download")
|
||||||
|
},
|
||||||
|
{ error ->
|
||||||
|
log(tag, "Enqueuing Error:${error.throwable.toString()}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}catch (e: java.lang.Exception){
|
||||||
|
log("Service YT Error", e.message.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Listener/ Responsible for Fetch Behaviour
|
||||||
|
**/
|
||||||
|
private var fetchListener: FetchListener = object : FetchListener {
|
||||||
|
override fun onQueued(
|
||||||
|
download: Download,
|
||||||
|
waitingOnNetwork: Boolean
|
||||||
|
) {
|
||||||
|
requestMap[download.request]?.let { sendTrackBroadcast(Status.QUEUED.name, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRemoved(download: Download) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResumed(download: Download) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStarted(
|
||||||
|
download: Download,
|
||||||
|
downloadBlocks: List<DownloadBlock>,
|
||||||
|
totalBlocks: Int
|
||||||
|
) {
|
||||||
|
serviceScope.launch {
|
||||||
|
val track = requestMap[download.request]
|
||||||
|
addToNotification("Downloading ${track?.title}")
|
||||||
|
log(tag, "${track?.title} Download Started")
|
||||||
|
track?.let{
|
||||||
|
allTracksStatus[it.title] = DownloadStatus.Downloading
|
||||||
|
sendTrackBroadcast(Status.DOWNLOADING.name,track)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWaitingNetwork(download: Download) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAdded(download: Download) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancelled(download: Download) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCompleted(download: Download) {
|
||||||
|
serviceScope.launch {
|
||||||
|
val track = requestMap[download.request]
|
||||||
|
removeFromNotification("Downloading ${track?.title}")
|
||||||
|
try{
|
||||||
|
track?.let {
|
||||||
|
convertToMp3(download.file, it)
|
||||||
|
allTracksStatus[it.title] = DownloadStatus.Converting
|
||||||
|
}
|
||||||
|
log(tag, "${track?.title} Download Completed")
|
||||||
|
}catch (
|
||||||
|
e: KotlinNullPointerException
|
||||||
|
){
|
||||||
|
log(tag, "${track?.title} Download Failed! Error:Fetch!!!!")
|
||||||
|
log(tag, "${track?.title} Requesting Download thru Android DM")
|
||||||
|
downloadUsingDM(download.request.url, download.request.file, track!!)
|
||||||
|
downloaded++
|
||||||
|
requestMap.remove(download.request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleted(download: Download) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDownloadBlockUpdated(
|
||||||
|
download: Download,
|
||||||
|
downloadBlock: DownloadBlock,
|
||||||
|
totalBlocks: Int
|
||||||
|
) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(download: Download, error: Error, throwable: Throwable?) {
|
||||||
|
serviceScope.launch {
|
||||||
|
val track = requestMap[download.request]
|
||||||
|
downloaded++
|
||||||
|
log(tag, download.error.throwable.toString())
|
||||||
|
log(tag, "${track?.title} Requesting Download thru Android DM")
|
||||||
|
downloadUsingDM(download.request.url, download.request.file, track!!)
|
||||||
|
requestMap.remove(download.request)
|
||||||
|
removeFromNotification("Downloading ${track.title}")
|
||||||
|
}
|
||||||
|
updateNotification()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPaused(download: Download) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProgress(
|
||||||
|
download: Download,
|
||||||
|
etaInMilliSeconds: Long,
|
||||||
|
downloadedBytesPerSecond: Long
|
||||||
|
) {
|
||||||
|
serviceScope.launch {
|
||||||
|
val track = requestMap[download.request]
|
||||||
|
log(tag, "${track?.title} ETA: ${etaInMilliSeconds / 1000} sec")
|
||||||
|
val intent = Intent().apply {
|
||||||
|
action = "Progress"
|
||||||
|
putExtra("progress", download.progress)
|
||||||
|
putExtra("track", requestMap[download.request])
|
||||||
|
}
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If fetch Fails , Android Download Manager To RESCUE!!
|
||||||
|
**/
|
||||||
|
fun downloadUsingDM(url: String, outputDir: String, track: TrackDetails){
|
||||||
|
serviceScope.launch {
|
||||||
|
val uri = Uri.parse(url)
|
||||||
|
val request = DownloadManager.Request(uri).apply {
|
||||||
|
setAllowedNetworkTypes(
|
||||||
|
DownloadManager.Request.NETWORK_WIFI or
|
||||||
|
DownloadManager.Request.NETWORK_MOBILE
|
||||||
|
)
|
||||||
|
setAllowedOverRoaming(false)
|
||||||
|
setTitle(track.title)
|
||||||
|
setDescription("Spotify Downloader Working Up here...")
|
||||||
|
setDestinationUri(File(outputDir).toUri())
|
||||||
|
setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Start Download
|
||||||
|
val downloadID = downloadManager.enqueue(request)
|
||||||
|
log("DownloadManager", "Download Request Sent")
|
||||||
|
|
||||||
|
val onDownloadComplete: BroadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
//Fetching the download id received with the broadcast
|
||||||
|
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
|
||||||
|
//Checking if the received broadcast is for our enqueued download by matching download id
|
||||||
|
if (downloadID == id) {
|
||||||
|
allTracksStatus[track.title] = DownloadStatus.Converting
|
||||||
|
convertToMp3(outputDir, track)
|
||||||
|
converted++
|
||||||
|
//Unregister this broadcast Receiver
|
||||||
|
this@ForegroundService.unregisterReceiver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerReceiver(onDownloadComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*Converting Downloaded Audio (m4a) to Mp3.( Also Applying Metadata)
|
||||||
|
**/
|
||||||
|
fun convertToMp3(filePath: String, track: TrackDetails){
|
||||||
|
serviceScope.launch {
|
||||||
|
sendTrackBroadcast("Converting",track)
|
||||||
|
val m4aFile = File(filePath)
|
||||||
|
|
||||||
|
addToNotification("Processing ${track.title}")
|
||||||
|
|
||||||
|
FFmpeg.executeAsync(
|
||||||
|
"-i $filePath -y -b:a 160k -acodec libmp3lame -vn ${filePath.substringBeforeLast('.') + ".mp3"}"
|
||||||
|
) { _, returnCode ->
|
||||||
|
when (returnCode) {
|
||||||
|
RETURN_CODE_SUCCESS -> {
|
||||||
|
log(Config.TAG, "Async command execution completed successfully.")
|
||||||
|
removeFromNotification("Processing ${track.title}")
|
||||||
|
m4aFile.delete()
|
||||||
|
writeMp3Tags(filePath.substringBeforeLast('.') + ".mp3", track)
|
||||||
|
//FFMPEG task Completed
|
||||||
|
}
|
||||||
|
RETURN_CODE_CANCEL -> {
|
||||||
|
log(Config.TAG, "Async command execution cancelled by user.")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
log(
|
||||||
|
Config.TAG, String.format(
|
||||||
|
"Async command execution failed with rc=%d.",
|
||||||
|
returnCode
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
private fun writeMp3Tags(filePath: String, track: TrackDetails){
|
||||||
|
serviceScope.launch {
|
||||||
|
var mp3File = Mp3File(filePath)
|
||||||
|
mp3File = removeAllTags(mp3File)
|
||||||
|
mp3File = setId3v1Tags(mp3File, track)
|
||||||
|
mp3File = setId3v2Tags(mp3File, track,this@ForegroundService)
|
||||||
|
log("Mp3Tags", "saving file")
|
||||||
|
mp3File.save(filePath.substringBeforeLast('.') + ".new.mp3")
|
||||||
|
val file = File(filePath)
|
||||||
|
file.delete()
|
||||||
|
val newFile = File((filePath.substringBeforeLast('.') + ".new.mp3"))
|
||||||
|
newFile.renameTo(file)
|
||||||
|
converted++
|
||||||
|
updateNotification()
|
||||||
|
addToLibrary(file.absolutePath)
|
||||||
|
allTracksStatus.remove(track.title)
|
||||||
|
//Notify Download Completed
|
||||||
|
sendTrackBroadcast("track_download_completed",track)
|
||||||
|
//All tasks completed (REST IN PEACE)
|
||||||
|
if(isFinished && !isSingleDownload){
|
||||||
|
delay(5000)
|
||||||
|
onDestroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the method that can be called to update the Notification
|
||||||
|
*/
|
||||||
|
private fun updateNotification() {
|
||||||
|
val mNotificationManager: NotificationManager =
|
||||||
|
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
mNotificationManager.notify(notificationId, getNotification())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun releaseWakeLock() {
|
||||||
|
log(tag, "Releasing Wake Lock")
|
||||||
|
try {
|
||||||
|
wakeLock?.let {
|
||||||
|
if (it.isHeld) {
|
||||||
|
it.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log(tag, "Service stopped without being started: ${e.message}")
|
||||||
|
}
|
||||||
|
isServiceStarted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("SameParameterValue")
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun createNotificationChannel(channelId: String, channelName: String){
|
||||||
|
val channel = NotificationChannel(
|
||||||
|
channelId,
|
||||||
|
channelName, NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
)
|
||||||
|
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||||
|
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
service.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleaning All Residual Files except Mp3 Files
|
||||||
|
**/
|
||||||
|
private fun cleanFiles(dir: File) {
|
||||||
|
log(tag, "Starting Cleaning in ${dir.path} ")
|
||||||
|
val fList = dir.listFiles()
|
||||||
|
fList?.let {
|
||||||
|
for (file in fList) {
|
||||||
|
if (file.isDirectory) {
|
||||||
|
cleanFiles(file)
|
||||||
|
} else if(file.isFile) {
|
||||||
|
if(file.path.toString().substringAfterLast(".") != "mp3"){
|
||||||
|
log(tag, "Cleaning ${file.path}")
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add File to Android's Media Library.
|
||||||
|
* */
|
||||||
|
private fun addToLibrary(path:String) {
|
||||||
|
log(tag,"Scanning File")
|
||||||
|
MediaScannerConnection.scanFile(this,
|
||||||
|
listOf(path).toTypedArray(), null,null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to fetch all Images for use in mp3 tags.
|
||||||
|
**/
|
||||||
|
fun downloadAllImages(urlList: ArrayList<String>, func: ((resource:File) -> Unit)? = null) {
|
||||||
|
/*
|
||||||
|
* Last Element of this List defines Its Source
|
||||||
|
* */
|
||||||
|
val source = urlList.last()
|
||||||
|
for (url in urlList.subList(0, urlList.size - 2)) {
|
||||||
|
val imgUri = url.toUri().buildUpon().scheme("https").build()
|
||||||
|
Glide
|
||||||
|
.with(this@ForegroundService)
|
||||||
|
.asFile()
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.load(imgUri)
|
||||||
|
.listener(object : RequestListener<File> {
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<File>?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
log("Glide", "LoadFailed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: File?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<File>?,
|
||||||
|
dataSource: DataSource?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
try {
|
||||||
|
serviceScope.launch {
|
||||||
|
val file = when (source) {
|
||||||
|
Source.Spotify.name -> {
|
||||||
|
File(imageDir, url.substringAfterLast('/') + ".jpeg")
|
||||||
|
}
|
||||||
|
Source.YouTube.name -> {
|
||||||
|
File(
|
||||||
|
imageDir,
|
||||||
|
url.substringBeforeLast('/', url)
|
||||||
|
.substringAfterLast(
|
||||||
|
'/',
|
||||||
|
url
|
||||||
|
) + ".jpeg"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Source.Gaana.name -> {
|
||||||
|
File(
|
||||||
|
imageDir,
|
||||||
|
(url.substringBeforeLast('/').substringAfterLast(
|
||||||
|
'/'
|
||||||
|
)) + ".jpeg"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> File(
|
||||||
|
imageDir,
|
||||||
|
url.substringAfterLast('/') + ".jpeg"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
resource?.copyTo(file)
|
||||||
|
func?.let { it(file) }
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}).submit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun killService() {
|
||||||
|
serviceScope.launch{
|
||||||
|
log(tag,"Killing Self")
|
||||||
|
messageList = mutableListOf("Cleaning And Exiting","","","","")
|
||||||
|
fetch.cancelAll()
|
||||||
|
fetch.removeAll()
|
||||||
|
updateNotification()
|
||||||
|
cleanFiles(File(defaultDir))
|
||||||
|
cleanFiles(File(imageDir))
|
||||||
|
messageList = mutableListOf("","","","","")
|
||||||
|
releaseWakeLock()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
stopForeground(true)
|
||||||
|
} else {
|
||||||
|
stopSelf()//System will automatically close it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
if(isFinished){
|
||||||
|
killService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
|
super.onTaskRemoved(rootIntent)
|
||||||
|
if(isFinished){
|
||||||
|
killService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initialiseFetch() {
|
||||||
|
val fetchConfiguration =
|
||||||
|
FetchConfiguration.Builder(this).run {
|
||||||
|
setNamespace(channelId)
|
||||||
|
setDownloadConcurrentLimit(4)
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = Fetch.run {
|
||||||
|
setDefaultInstanceConfiguration(fetchConfiguration)
|
||||||
|
getDefaultInstance()
|
||||||
|
}.apply {
|
||||||
|
addListener(fetchListener)
|
||||||
|
removeAll() //Starting fresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNotification():Notification = NotificationCompat.Builder(this, channelId).run {
|
||||||
|
setSmallIcon(R.drawable.ic_download_arrow)
|
||||||
|
setContentTitle("Total: $total Completed:$converted Failed:$failed")
|
||||||
|
setNotificationSilent()
|
||||||
|
setStyle(
|
||||||
|
NotificationCompat.InboxStyle().run {
|
||||||
|
addLine(messageList[messageList.size - 1])
|
||||||
|
addLine(messageList[messageList.size - 2])
|
||||||
|
addLine(messageList[messageList.size - 3])
|
||||||
|
addLine(messageList[messageList.size - 4])
|
||||||
|
addLine(messageList[messageList.size - 5])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
addAction(R.drawable.ic_round_cancel_24,"Exit",cancelIntent)
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addToNotification(message:String){
|
||||||
|
messageList.add(message)
|
||||||
|
updateNotification()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeFromNotification(message: String){
|
||||||
|
messageList.remove(message)
|
||||||
|
updateNotification()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendTrackBroadcast(action:String,track:TrackDetails){
|
||||||
|
val intent = Intent().apply{
|
||||||
|
setAction(action)
|
||||||
|
putExtra("track", track)
|
||||||
|
}
|
||||||
|
this@ForegroundService.sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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.worker
|
||||||
|
|
||||||
|
import com.mpatric.mp3agic.ID3v1Tag
|
||||||
|
import com.mpatric.mp3agic.ID3v24Tag
|
||||||
|
import com.mpatric.mp3agic.Mp3File
|
||||||
|
import com.shabinder.spotiflyer.models.TrackDetails
|
||||||
|
import com.shabinder.spotiflyer.utils.log
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
*Modifying Mp3 com.shabinder.spotiflyer.models.gaana.Tags with MetaData!
|
||||||
|
**/
|
||||||
|
fun setId3v1Tags(mp3File: Mp3File, track: TrackDetails): Mp3File {
|
||||||
|
val id3v1Tag = ID3v1Tag().apply {
|
||||||
|
artist = track.artists.joinToString(",")
|
||||||
|
title = track.title
|
||||||
|
album = track.albumName
|
||||||
|
year = track.year
|
||||||
|
comment = "Genres:${track.comment}"
|
||||||
|
}
|
||||||
|
mp3File.id3v1Tag = id3v1Tag
|
||||||
|
return mp3File
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setId3v2Tags(mp3file: Mp3File, track: TrackDetails,service: ForegroundService): Mp3File {
|
||||||
|
val id3v2Tag = ID3v24Tag().apply {
|
||||||
|
artist = track.artists.joinToString(",")
|
||||||
|
title = track.title
|
||||||
|
album = track.albumName
|
||||||
|
year = track.year
|
||||||
|
comment = "Genres:${track.comment}"
|
||||||
|
lyrics = "Gonna Implement Soon"
|
||||||
|
url = track.trackUrl
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
val bytesArray = ByteArray(track.albumArt.length().toInt())
|
||||||
|
val fis = FileInputStream(track.albumArt)
|
||||||
|
fis.read(bytesArray) //read file into bytes[]
|
||||||
|
fis.close()
|
||||||
|
id3v2Tag.setAlbumImage(bytesArray, "image/jpeg")
|
||||||
|
}catch (e: java.io.FileNotFoundException){
|
||||||
|
try {
|
||||||
|
//Image Still Not Downloaded!
|
||||||
|
//Lets Download Now and Write it into Album Art
|
||||||
|
service.downloadAllImages(arrayListOf(track.albumArtURL, track.source.name)){
|
||||||
|
val bytesArray = ByteArray(it.length().toInt())
|
||||||
|
val fis = FileInputStream(it)
|
||||||
|
fis.read(bytesArray) //read file into bytes[]
|
||||||
|
fis.close()
|
||||||
|
id3v2Tag.setAlbumImage(bytesArray, "image/jpeg")
|
||||||
|
}
|
||||||
|
}catch (e: Exception){log("Error", "Couldn't Write Mp3 Album Art, error: ${e.stackTrace}")}
|
||||||
|
}
|
||||||
|
mp3file.id3v2Tag = id3v2Tag
|
||||||
|
return mp3file
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAllTags(mp3file: Mp3File): Mp3File {
|
||||||
|
if (mp3file.hasId3v1Tag()) {
|
||||||
|
mp3file.removeId3v1Tag()
|
||||||
|
}
|
||||||
|
if (mp3file.hasId3v2Tag()) {
|
||||||
|
mp3file.removeId3v2Tag()
|
||||||
|
}
|
||||||
|
if (mp3file.hasCustomTag()) {
|
||||||
|
mp3file.removeCustomTag()
|
||||||
|
}
|
||||||
|
return mp3file
|
||||||
|
}
|
10
app/src/main/res/drawable/ic_download_arrow.xml
Normal file
10
app/src/main/res/drawable/ic_download_arrow.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,4c4.41,0 8,3.59 8,8s-3.59,8 -8,8s-8,-3.59 -8,-8S7.59,4 12,4M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10c5.52,0 10,-4.48 10,-10C22,6.48 17.52,2 12,2L12,2zM13,12l0,-4h-2l0,4H8l4,4l4,-4H13z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_round_cancel_24.xml
Normal file
10
app/src/main/res/drawable/ic_round_cancel_24.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM16.3,16.3c-0.39,0.39 -1.02,0.39 -1.41,0L12,13.41 9.11,16.3c-0.39,0.39 -1.02,0.39 -1.41,0 -0.39,-0.39 -0.39,-1.02 0,-1.41L10.59,12 7.7,9.11c-0.39,-0.39 -0.39,-1.02 0,-1.41 0.39,-0.39 1.02,-0.39 1.41,0L12,10.59l2.89,-2.89c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41L13.41,12l2.89,2.89c0.38,0.38 0.38,1.02 0,1.41z"/>
|
||||||
|
</vector>
|
@ -7,6 +7,8 @@ buildscript {
|
|||||||
okhttp_version = "4.9.0"
|
okhttp_version = "4.9.0"
|
||||||
coroutines_version = "1.4.2"
|
coroutines_version = "1.4.2"
|
||||||
coil_version = "0.4.1"
|
coil_version = "0.4.1"
|
||||||
|
kotlin_version = "1.4.21"
|
||||||
|
hilt_version = '2.30.1-alpha'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@ -17,7 +19,10 @@ buildscript {
|
|||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.android.tools.build:gradle:7.0.0-alpha03"
|
classpath "com.android.tools.build:gradle:7.0.0-alpha03"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21"
|
||||||
|
//Hilt
|
||||||
|
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
||||||
|
//Kotlinx-Serialization
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user