mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-25 02:14:32 +01:00
Latest FFMPEG Async Tasks,Mp3 Art and Album Data,Fetch Downloader Implemented.
This commit is contained in:
parent
ac683279e6
commit
a802751942
@ -1,7 +1,10 @@
|
|||||||
<component name="ProjectDictionaryState">
|
<component name="ProjectDictionaryState">
|
||||||
<dictionary name="shabinder">
|
<dictionary name="shabinder">
|
||||||
<words>
|
<words>
|
||||||
|
<w>ffmpeg</w>
|
||||||
<w>flyer</w>
|
<w>flyer</w>
|
||||||
|
<w>insta</w>
|
||||||
|
<w>instagram</w>
|
||||||
<w>moshi</w>
|
<w>moshi</w>
|
||||||
<w>musicforeveryone</w>
|
<w>musicforeveryone</w>
|
||||||
<w>musicplaceholder</w>
|
<w>musicplaceholder</w>
|
||||||
|
@ -31,5 +31,10 @@
|
|||||||
<option name="name" value="maven" />
|
<option name="name" value="maven" />
|
||||||
<option name="url" value="https://jitpack.io" />
|
<option name="url" value="https://jitpack.io" />
|
||||||
</remote-repository>
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="maven2" />
|
||||||
|
<option name="name" value="maven2" />
|
||||||
|
<option name="url" value="https://dl.bintray.com/hummatli/maven/" />
|
||||||
|
</remote-repository>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -20,7 +20,7 @@ apply plugin: 'kotlin-android'
|
|||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: "androidx.navigation.safeargs.kotlin"
|
apply plugin: "androidx.navigation.safeargs.kotlin"
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
@ -28,16 +28,14 @@ android {
|
|||||||
|
|
||||||
buildFeatures{
|
buildFeatures{
|
||||||
dataBinding = true
|
dataBinding = true
|
||||||
viewBinding = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'com.shabinder.spotiflyer'
|
applicationId 'com.shabinder.spotiflyer'
|
||||||
minSdkVersion 22
|
minSdkVersion 22
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 2
|
||||||
versionName "1.0"
|
versionName "1.1"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,15 +49,20 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
implementation 'androidx.core:core-ktx:1.3.0'
|
implementation 'androidx.core:core-ktx:1.3.1'
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.browser:browser:1.2.0'
|
implementation 'androidx.browser:browser:1.2.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
@ -72,17 +75,21 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
|
||||||
|
|
||||||
// implementation "androidx.room:room-runtime:2.2.5"
|
implementation "androidx.room:room-runtime:2.2.5"
|
||||||
// kapt "androidx.room:room-compiler:2.2.5"
|
kapt "androidx.room:room-compiler:2.2.5"
|
||||||
// implementation "androidx.room:room-ktx:2.2.5"
|
implementation "androidx.room:room-ktx:2.2.5"
|
||||||
implementation "com.github.bumptech.glide:glide:4.11.0"
|
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
|
||||||
kapt "com.github.bumptech.glide:compiler:4.11.0"
|
transitive = true
|
||||||
|
}
|
||||||
|
kapt ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
|
||||||
|
transitive = true
|
||||||
|
}
|
||||||
|
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
|
|
||||||
implementation 'com.google.apis:google-api-services-youtube:v3-rev180-1.22.0'
|
implementation 'com.google.apis:google-api-services-youtube:v3-rev180-1.22.0'
|
||||||
implementation 'com.google.oauth-client:google-oauth-client:1.22.0'
|
implementation 'com.google.oauth-client:google-oauth-client:1.22.0'
|
||||||
implementation 'com.spotify.android:auth:1.1.0'
|
// implementation 'com.spotify.android:auth:1.1.0'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.8.0'
|
implementation 'com.squareup.okhttp3:okhttp:4.8.0'
|
||||||
|
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
@ -90,8 +97,16 @@ dependencies {
|
|||||||
implementation "com.squareup.moshi:moshi-kotlin:1.9.3"
|
implementation "com.squareup.moshi:moshi-kotlin:1.9.3"
|
||||||
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
|
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // or "kotlin-stdlib-jdk8"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0" // JVM dependency
|
||||||
|
|
||||||
|
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||||
|
implementation 'com.arthenica:mobile-ffmpeg-audio:4.4.LTS'
|
||||||
|
|
||||||
implementation 'com.shreyaspatil:EasyUpiPayment:2.2'
|
implementation 'com.shreyaspatil:EasyUpiPayment:2.2'
|
||||||
implementation 'com.github.sealedtx:java-youtube-downloader:2.2.2'
|
implementation 'com.github.sealedtx:java-youtube-downloader:2.2.3'
|
||||||
|
implementation "androidx.tonyodev.fetch2:xfetch2:3.1.4"
|
||||||
|
implementation 'com.github.javiersantos:AppUpdater:2.7'
|
||||||
|
|
||||||
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
||||||
testImplementation 'junit:junit:4.13'
|
testImplementation 'junit:junit:4.13'
|
||||||
|
@ -26,9 +26,12 @@
|
|||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<!-- <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />-->
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
|
<!-- <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />-->
|
||||||
|
|
||||||
<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"
|
||||||
@ -48,20 +51,21 @@
|
|||||||
<activity android:name="com.shabinder.spotiflyer.splash.SplashScreen"
|
<activity android:name="com.shabinder.spotiflyer.splash.SplashScreen"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.Transparent">
|
android:theme="@style/Theme.Transparent">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<!-- <activity
|
||||||
android:name="com.spotify.sdk.android.authentication.LoginActivity"
|
android:name="com.spotify.sdk.android.authentication.LoginActivity"
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
|
-->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="preloaded_fonts"
|
android:name="preloaded_fonts"
|
||||||
android:resource="@array/preloaded_fonts" />
|
android:resource="@array/preloaded_fonts" />
|
||||||
|
|
||||||
|
<service android:name=".worker.ForegroundService"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
63
app/src/main/java/com/shabinder/spotiflyer/App.kt
Normal file
63
app/src/main/java/com/shabinder/spotiflyer/App.kt
Normal file
@ -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
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class App:Application() {
|
||||||
|
private val channelId = "ForegroundServiceChannel"
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
createNotificationChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotificationChannel() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val serviceChannel = NotificationChannel(
|
||||||
|
channelId,
|
||||||
|
"ForeGround Service Channel",
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
)
|
||||||
|
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
manager.createNotificationChannel(serviceChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -18,23 +18,26 @@
|
|||||||
package com.shabinder.spotiflyer
|
package com.shabinder.spotiflyer
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.DownloadManager
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.github.javiersantos.appupdater.AppUpdater
|
||||||
|
import com.github.javiersantos.appupdater.enums.UpdateFrom
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
import com.shabinder.spotiflyer.databinding.MainActivityBinding
|
import com.shabinder.spotiflyer.databinding.MainActivityBinding
|
||||||
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
||||||
import com.shabinder.spotiflyer.utils.SpotifyService
|
import com.shabinder.spotiflyer.utils.SpotifyService
|
||||||
import com.shabinder.spotiflyer.utils.SpotifyServiceToken
|
import com.shabinder.spotiflyer.utils.SpotifyServiceToken
|
||||||
import com.shabinder.spotiflyer.utils.YoutubeInterface
|
import com.shabinder.spotiflyer.utils.YoutubeInterface
|
||||||
|
import com.shabinder.spotiflyer.utils.createDirectory
|
||||||
import com.shreyaspatil.EasyUpiPayment.EasyUpiPayment
|
import com.shreyaspatil.EasyUpiPayment.EasyUpiPayment
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
@ -48,12 +51,11 @@ import retrofit2.converter.moshi.MoshiConverterFactory
|
|||||||
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
class MainActivity : AppCompatActivity() ,DownloadHelper{
|
class MainActivity : AppCompatActivity(){
|
||||||
private lateinit var binding: MainActivityBinding
|
private lateinit var binding: MainActivityBinding
|
||||||
private var ytDownloader : YoutubeDownloader? = null
|
private var ytDownloader : YoutubeDownloader? = null
|
||||||
private var spotifyService : SpotifyService? = null
|
private var spotifyService : SpotifyService? = null
|
||||||
private var spotifyServiceToken : SpotifyServiceToken? = null
|
private var spotifyServiceToken : SpotifyServiceToken? = null
|
||||||
private var downloadManager : DownloadManager? = null
|
|
||||||
// private val redirectUri = "spotiflyer://callback"
|
// private val redirectUri = "spotiflyer://callback"
|
||||||
private val clientId:String = "694d8bf4f6ec420fa66ea7fb4c68f89d"
|
private val clientId:String = "694d8bf4f6ec420fa66ea7fb4c68f89d"
|
||||||
private val clientSecret:String = "02ca2d4021a7452dae2328b47a6e8fe8"
|
private val clientSecret:String = "02ca2d4021a7452dae2328b47a6e8fe8"
|
||||||
@ -63,20 +65,21 @@ class MainActivity : AppCompatActivity() ,DownloadHelper{
|
|||||||
private var token :String =""
|
private var token :String =""
|
||||||
private lateinit var sharedViewModel: SharedViewModel
|
private lateinit var sharedViewModel: SharedViewModel
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = DataBindingUtil.setContentView(this,R.layout.main_activity)
|
binding = DataBindingUtil.setContentView(this,R.layout.main_activity)
|
||||||
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
|
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
|
||||||
sharedPref = this.getPreferences(Context.MODE_PRIVATE)
|
sharedPref = this.getPreferences(Context.MODE_PRIVATE)
|
||||||
|
|
||||||
// if(sharedPref?.contains("token")!! && (sharedPref?.getLong("time",System.currentTimeMillis()/1000/60/60)!! < (System.currentTimeMillis()/1000/60/60)) ){
|
/* if(sharedPref?.contains("token")!! && (sharedPref?.getLong("time",System.currentTimeMillis()/1000/60/60)!! < (System.currentTimeMillis()/1000/60/60)) ){
|
||||||
// val savedToken = sharedPref?.getString("token","error")!!
|
val savedToken = sharedPref?.getString("token","error")!!
|
||||||
// sharedViewModel.accessToken.value = savedToken
|
sharedViewModel.accessToken.value = savedToken
|
||||||
// Log.i("SharedPrefs Token:",savedToken)
|
Log.i("SharedPrefs Token:",savedToken)
|
||||||
// token = savedToken
|
token = savedToken
|
||||||
//
|
|
||||||
// implementSpotifyService(savedToken)
|
implementSpotifyService(savedToken)
|
||||||
// }else{authenticateSpotify()}
|
}else{authenticateSpotify()}*/
|
||||||
|
|
||||||
if(sharedViewModel.spotifyService == null){
|
if(sharedViewModel.spotifyService == null){
|
||||||
authenticateSpotify()
|
authenticateSpotify()
|
||||||
@ -85,6 +88,12 @@ class MainActivity : AppCompatActivity() ,DownloadHelper{
|
|||||||
}
|
}
|
||||||
|
|
||||||
requestPermission()
|
requestPermission()
|
||||||
|
checkIfLatestVersion()
|
||||||
|
createDir()
|
||||||
|
setUpi()
|
||||||
|
isConnected = isOnline()
|
||||||
|
sharedViewModel.isConnected.value = isConnected
|
||||||
|
Log.i("Connection Status",isConnected.toString())
|
||||||
|
|
||||||
//Object to download From Youtube {"https://github.com/sealedtx/java-youtube-downloader"}
|
//Object to download From Youtube {"https://github.com/sealedtx/java-youtube-downloader"}
|
||||||
ytDownloader = YoutubeDownloader()
|
ytDownloader = YoutubeDownloader()
|
||||||
@ -92,32 +101,9 @@ class MainActivity : AppCompatActivity() ,DownloadHelper{
|
|||||||
//Initialing Communication with Youtube
|
//Initialing Communication with Youtube
|
||||||
YoutubeInterface.youtubeConnector()
|
YoutubeInterface.youtubeConnector()
|
||||||
|
|
||||||
//Getting System Download Manager
|
|
||||||
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
|
||||||
sharedViewModel.downloadManager = downloadManager
|
|
||||||
|
|
||||||
isConnected = isOnline()
|
|
||||||
sharedViewModel.isConnected.value = isConnected
|
|
||||||
|
|
||||||
Log.i("Connection Status",isConnected.toString())
|
|
||||||
|
|
||||||
|
|
||||||
easyUpiPayment = EasyUpiPayment.Builder()
|
|
||||||
.with(this)
|
|
||||||
.setPayeeVpa("technoshab@paytm")
|
|
||||||
.setPayeeName("Shabinder Singh")
|
|
||||||
.setTransactionId("UNIQUE_TRANSACTION_ID")
|
|
||||||
.setTransactionRefId("UNIQUE_TRANSACTION_REF_ID")
|
|
||||||
.setDescription("Thanks for donating")
|
|
||||||
.setAmount("39.00")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
sharedViewModel.easyUpiPayment = easyUpiPayment
|
|
||||||
|
|
||||||
handleIntentFromExternalActivity()
|
handleIntentFromExternalActivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adding my own new Spotify Web Api Requests!
|
* Adding my own new Spotify Web Api Requests!
|
||||||
* */
|
* */
|
||||||
@ -246,6 +232,48 @@ class MainActivity : AppCompatActivity() ,DownloadHelper{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setUpi() {
|
||||||
|
easyUpiPayment = EasyUpiPayment.Builder()
|
||||||
|
.with(this)
|
||||||
|
.setPayeeVpa("technoshab@paytm")
|
||||||
|
.setPayeeName("Shabinder Singh")
|
||||||
|
.setTransactionId("UNIQUE_TRANSACTION_ID")
|
||||||
|
.setTransactionRefId("UNIQUE_TRANSACTION_REF_ID")
|
||||||
|
.setDescription("Thanks for donating")
|
||||||
|
.setAmount("39.00")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
sharedViewModel.easyUpiPayment = easyUpiPayment
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDir() {
|
||||||
|
createDirectory(DownloadHelper.defaultDir)
|
||||||
|
createDirectory(DownloadHelper.defaultDir+".Images/")
|
||||||
|
createDirectory(DownloadHelper.defaultDir+"Tracks/")
|
||||||
|
createDirectory(DownloadHelper.defaultDir+"Albums/")
|
||||||
|
createDirectory(DownloadHelper.defaultDir+"Playlists/")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkIfLatestVersion() {
|
||||||
|
val appUpdater = AppUpdater(this)
|
||||||
|
.showAppUpdated(false)//true:Show App is Update Dialog
|
||||||
|
.setUpdateFrom(UpdateFrom.XML)
|
||||||
|
.setUpdateXML("https://raw.githubusercontent.com/Shabinder/SpotiFlyer/master/app/src/main/res/xml/app_update.xml")
|
||||||
|
.setCancelable(false)
|
||||||
|
.setButtonUpdateClickListener { _, _ ->
|
||||||
|
val uri: Uri =
|
||||||
|
Uri.parse("http://github.com/Shabinder/SpotiFlyer/releases")
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
.setButtonDismissClickListener { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
appUpdater.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private fun authenticateSpotify() {
|
private fun authenticateSpotify() {
|
||||||
val builder = AuthenticationRequest.Builder(clientId,AuthenticationResponse.Type.TOKEN,redirectUri)
|
val builder = AuthenticationRequest.Builder(clientId,AuthenticationResponse.Type.TOKEN,redirectUri)
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer
|
package com.shabinder.spotiflyer
|
||||||
|
|
||||||
import android.app.DownloadManager
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.os.Environment
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
@ -32,15 +32,16 @@ import com.shreyaspatil.EasyUpiPayment.EasyUpiPayment
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class SharedViewModel : ViewModel() {
|
class SharedViewModel : ViewModel() {
|
||||||
var intentString = ""
|
var intentString = ""
|
||||||
var accessToken = MutableLiveData<String>().apply { value = "" }
|
var accessToken = MutableLiveData<String>().apply { value = "" }
|
||||||
var spotifyService : SpotifyService? = null
|
var spotifyService : SpotifyService? = null
|
||||||
var ytDownloader : YoutubeDownloader? = null
|
var ytDownloader : YoutubeDownloader? = null
|
||||||
var downloadManager : DownloadManager? = null
|
|
||||||
var isConnected = MutableLiveData<Boolean>().apply { value = false }
|
var isConnected = MutableLiveData<Boolean>().apply { value = false }
|
||||||
var easyUpiPayment: EasyUpiPayment? = null
|
var easyUpiPayment: EasyUpiPayment? = null
|
||||||
|
val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator + ".Images" + File.separator
|
||||||
|
|
||||||
|
|
||||||
private var viewModelJob = Job()
|
private var viewModelJob = Job()
|
||||||
@ -64,13 +65,12 @@ class SharedViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun showAlertDialog(resources:Resources,context: Context){
|
fun showAlertDialog(resources:Resources,context: Context){
|
||||||
val dialog = MaterialAlertDialogBuilder(context,R.style.AlertDialogTheme)
|
MaterialAlertDialogBuilder(context,R.style.AlertDialogTheme)
|
||||||
.setTitle(resources.getString(R.string.title))
|
.setTitle(resources.getString(R.string.title))
|
||||||
.setMessage(resources.getString(R.string.supporting_text))
|
.setMessage(resources.getString(R.string.supporting_text))
|
||||||
.setPositiveButton(resources.getString(R.string.cancel)) { _, _ ->
|
.setPositiveButton(resources.getString(R.string.cancel)) { _, _ ->
|
||||||
// Respond to neutral button press
|
// Respond to neutral button press
|
||||||
}
|
}
|
||||||
.setBackground(resources.getDrawable(R.drawable.gradient))
|
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,22 +17,28 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.downloadHelper
|
package com.shabinder.spotiflyer.downloadHelper
|
||||||
|
|
||||||
import android.app.DownloadManager
|
import android.content.Context
|
||||||
import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.github.kiulian.downloader.YoutubeDownloader
|
import com.github.kiulian.downloader.YoutubeDownloader
|
||||||
import com.github.kiulian.downloader.model.formats.Format
|
import com.github.kiulian.downloader.model.formats.Format
|
||||||
import com.github.kiulian.downloader.model.quality.AudioQuality
|
import com.github.kiulian.downloader.model.quality.AudioQuality
|
||||||
import com.shabinder.spotiflyer.fragments.MainFragment
|
import com.shabinder.spotiflyer.fragments.MainFragment
|
||||||
|
import com.shabinder.spotiflyer.models.DownloadObject
|
||||||
import com.shabinder.spotiflyer.models.Track
|
import com.shabinder.spotiflyer.models.Track
|
||||||
import com.shabinder.spotiflyer.utils.YoutubeInterface
|
import com.shabinder.spotiflyer.utils.YoutubeInterface
|
||||||
|
import com.shabinder.spotiflyer.worker.ForegroundService
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface DownloadHelper {
|
object DownloadHelper {
|
||||||
|
|
||||||
|
var context : Context? = null
|
||||||
|
val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator
|
||||||
|
private var downloadList = arrayListOf<DownloadObject>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function To Download All Tracks Available in a List
|
* Function To Download All Tracks Available in a List
|
||||||
@ -40,111 +46,106 @@ interface DownloadHelper {
|
|||||||
suspend fun downloadAllTracks(
|
suspend fun downloadAllTracks(
|
||||||
type:String,
|
type:String,
|
||||||
subFolder: String?,
|
subFolder: String?,
|
||||||
trackList: List<Track>, ytDownloader: YoutubeDownloader?, downloadManager: DownloadManager?) {
|
trackList: List<Track>, ytDownloader: YoutubeDownloader?) {
|
||||||
trackList.forEach { downloadTrack(null,type,subFolder,ytDownloader,downloadManager,"${it.name} ${it.artists?.get(0)?.name ?:""}") }
|
var size = trackList.size
|
||||||
|
trackList.forEach {
|
||||||
|
size--
|
||||||
|
if(size == 0){
|
||||||
|
downloadTrack(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it ,0 )
|
||||||
|
}else{
|
||||||
|
downloadTrack(null,type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it )
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun downloadTrack(
|
suspend fun downloadTrack(
|
||||||
mainFragment: MainFragment?,
|
mainFragment: MainFragment?,
|
||||||
type:String,
|
type:String,
|
||||||
subFolder:String?,
|
subFolder:String?,
|
||||||
ytDownloader: YoutubeDownloader?,
|
ytDownloader: YoutubeDownloader?,
|
||||||
downloadManager: DownloadManager?,
|
searchQuery: String,
|
||||||
searchQuery: String
|
track: Track,
|
||||||
|
index: Int? = null
|
||||||
) {
|
) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val data = YoutubeInterface.search(searchQuery)?.get(0)
|
val data: YoutubeInterface.VideoItem = YoutubeInterface.search(searchQuery)?.get(0)!!
|
||||||
if (data == null) {
|
|
||||||
Log.i("DownloadHelper", "Youtube Request Failed!")
|
|
||||||
} else {
|
|
||||||
|
|
||||||
val video = ytDownloader?.getVideo(data.id)
|
|
||||||
//Fetching a Video Object.
|
//Fetching a Video Object.
|
||||||
val details = video?.details()
|
try {
|
||||||
try{
|
val audioUrl = getDownloadLink(AudioQuality.medium, ytDownloader, data)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
mainFragment?.showToast("Starting Download")
|
||||||
|
}
|
||||||
|
downloadFile(audioUrl, searchQuery, subFolder, type, track, index,mainFragment)
|
||||||
|
} catch (e: java.lang.IndexOutOfBoundsException) {
|
||||||
|
try {
|
||||||
|
val audioUrl = getDownloadLink(AudioQuality.high, ytDownloader, data)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
mainFragment?.showToast("Starting Download")
|
||||||
|
}
|
||||||
|
downloadFile(audioUrl, searchQuery, subFolder, type, track, index,mainFragment)
|
||||||
|
} catch (e: java.lang.IndexOutOfBoundsException) {
|
||||||
|
try {
|
||||||
|
val audioUrl = getDownloadLink(AudioQuality.low, ytDownloader, data)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
mainFragment?.showToast("Starting Download")
|
||||||
|
}
|
||||||
|
downloadFile(audioUrl, searchQuery, subFolder, type, track, index,mainFragment)
|
||||||
|
} catch (e: java.lang.IndexOutOfBoundsException) {
|
||||||
|
Log.i("Catch", e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getDownloadLink(quality: AudioQuality ,ytDownloader: YoutubeDownloader?,data:YoutubeInterface.VideoItem): String {
|
||||||
|
val video = ytDownloader?.getVideo(data.id)
|
||||||
val format: Format =
|
val format: Format =
|
||||||
video?.findAudioWithQuality(AudioQuality.medium)?.get(0) as Format
|
video?.findAudioWithQuality(quality)?.get(0) as Format
|
||||||
val audioUrl = format.url()
|
Log.i("Format", video.findAudioWithQuality(AudioQuality.medium)?.get(0)!!.mimeType())
|
||||||
|
val audioUrl:String = format.url()
|
||||||
Log.i("DHelper Link Found", audioUrl)
|
Log.i("DHelper Link Found", audioUrl)
|
||||||
if (audioUrl != null) {
|
return audioUrl
|
||||||
downloadFile(audioUrl, downloadManager, details!!.title(),subFolder,type)
|
|
||||||
withContext(Dispatchers.Main){
|
|
||||||
mainFragment?.showToast("Download Started")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.i("YT audio url is null", format.toString())
|
|
||||||
}
|
|
||||||
}catch (e:ArrayIndexOutOfBoundsException){
|
|
||||||
try{
|
|
||||||
val format: Format =
|
|
||||||
video?.findAudioWithQuality(AudioQuality.high)?.get(0) as Format
|
|
||||||
val audioUrl = format.url()
|
|
||||||
Log.i("DHelper Link Found", audioUrl)
|
|
||||||
if (audioUrl != null) {
|
|
||||||
downloadFile(audioUrl, downloadManager, details!!.title(),subFolder,type)
|
|
||||||
withContext(Dispatchers.Main){
|
|
||||||
mainFragment?.showToast("Download Started")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.i("YT audio url is null", format.toString())
|
|
||||||
}
|
|
||||||
}catch (e:ArrayIndexOutOfBoundsException){
|
|
||||||
try{
|
|
||||||
val format: Format =
|
|
||||||
video?.findAudioWithQuality(AudioQuality.high)?.get(0) as Format
|
|
||||||
val audioUrl = format.url()
|
|
||||||
Log.i("DHelper Link Found", audioUrl)
|
|
||||||
if (audioUrl != null) {
|
|
||||||
downloadFile(audioUrl, downloadManager, details!!.title(),subFolder,type)
|
|
||||||
withContext(Dispatchers.Main){
|
|
||||||
mainFragment?.showToast("Download Started")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.i("YT audio url is null", format.toString())
|
|
||||||
}
|
|
||||||
}catch(e:ArrayIndexOutOfBoundsException){
|
|
||||||
Log.i("Catch",e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
private suspend fun downloadFile(url: String, title: String, subFolder: String?, type: String, track:Track, index:Int? = null,mainFragment: MainFragment?) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloading Using Android Download Manager
|
|
||||||
* */
|
|
||||||
suspend fun downloadFile(url: String, downloadManager: DownloadManager?, title: String,subFolder: String?,type: String) {
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val audioUri = Uri.parse(url)
|
val outputFile:String = Environment.getExternalStorageDirectory().toString() + File.separator +
|
||||||
val outputDir:String =
|
DownloadHelper.defaultDir + removeIllegalChars(type) + File.separator + (if(subFolder == null){""}else{ removeIllegalChars(subFolder) + File.separator} + removeIllegalChars(track.name!!)+".m4a")
|
||||||
File.separator + "SpotiFlyer" + File.separator + type + File.separator + (if(subFolder == null){""}else{subFolder + File.separator}) + "${removeIllegalChars(title)}.mp3"
|
|
||||||
|
|
||||||
val request = DownloadManager.Request(audioUri)
|
if(!File(removeIllegalChars(outputFile.substringBeforeLast('.')) +".mp3").exists()){
|
||||||
.setAllowedNetworkTypes(
|
val downloadObject = DownloadObject(
|
||||||
DownloadManager.Request.NETWORK_WIFI or
|
track = track,
|
||||||
DownloadManager.Request.NETWORK_MOBILE
|
url = url,
|
||||||
|
outputDir = outputFile
|
||||||
)
|
)
|
||||||
.setAllowedOverRoaming(false)
|
Log.i("DH",outputFile)
|
||||||
.setTitle(title)
|
if(index==null){
|
||||||
.setDescription("Spotify Downloader Working Up here...")
|
downloadList.add(downloadObject)
|
||||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_MUSIC, outputDir)
|
}else{
|
||||||
.setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
downloadList.add(downloadObject)
|
||||||
downloadManager?.enqueue(request)
|
startService(context!!, downloadList)
|
||||||
Log.i("DownloadManager", "Download Request Sent")
|
downloadList = arrayListOf()
|
||||||
|
}
|
||||||
|
}else{withContext(Dispatchers.Main){mainFragment?.showToast("${track.name} is already Downloaded")}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun startService(context:Context,list: ArrayList<DownloadObject>) {
|
||||||
|
val serviceIntent = Intent(context, ForegroundService::class.java)
|
||||||
|
serviceIntent.putParcelableArrayListExtra("list",list)
|
||||||
|
ContextCompat.startForegroundService(context, serviceIntent)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removing Illegal Chars from File Name
|
* Removing Illegal Chars from File Name
|
||||||
* **/
|
* **/
|
||||||
fun removeIllegalChars(fileName: String): String? {
|
private fun removeIllegalChars(fileName: String): String? {
|
||||||
val illegalCharArray = charArrayOf(
|
val illegalCharArray = charArrayOf(
|
||||||
'/',
|
'/',
|
||||||
'\n',
|
'\n',
|
||||||
@ -161,12 +162,14 @@ interface DownloadHelper {
|
|||||||
'|',
|
'|',
|
||||||
'\"',
|
'\"',
|
||||||
'.',
|
'.',
|
||||||
':'
|
':',
|
||||||
|
'-'
|
||||||
)
|
)
|
||||||
var name = fileName
|
var name = fileName
|
||||||
for (c in illegalCharArray) {
|
for (c in illegalCharArray) {
|
||||||
name = fileName.replace(c, '_')
|
name = fileName.replace(c, '_')
|
||||||
}
|
}
|
||||||
|
name = name.replace("\\s".toRegex(), "_")
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,31 +23,42 @@ import android.content.pm.PackageManager
|
|||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.shabinder.spotiflyer.MainActivity
|
import com.shabinder.spotiflyer.MainActivity
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.SharedViewModel
|
import com.shabinder.spotiflyer.SharedViewModel
|
||||||
import com.shabinder.spotiflyer.databinding.MainFragmentBinding
|
import com.shabinder.spotiflyer.databinding.MainFragmentBinding
|
||||||
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
||||||
|
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper.downloadAllTracks
|
||||||
import com.shabinder.spotiflyer.models.Track
|
import com.shabinder.spotiflyer.models.Track
|
||||||
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
|
import com.shabinder.spotiflyer.recyclerView.TrackListAdapter
|
||||||
import com.shabinder.spotiflyer.utils.SpotifyService
|
import com.shabinder.spotiflyer.utils.SpotifyService
|
||||||
import com.shabinder.spotiflyer.utils.bindImage
|
import com.shabinder.spotiflyer.utils.bindImage
|
||||||
|
import com.shabinder.spotiflyer.utils.copyTo
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
class MainFragment : Fragment(),DownloadHelper {
|
class MainFragment : Fragment() {
|
||||||
private lateinit var binding:MainFragmentBinding
|
private lateinit var binding:MainFragmentBinding
|
||||||
private lateinit var mainViewModel: MainViewModel
|
private lateinit var mainViewModel: MainViewModel
|
||||||
private lateinit var sharedViewModel: SharedViewModel
|
private lateinit var sharedViewModel: SharedViewModel
|
||||||
@ -56,12 +67,13 @@ class MainFragment : Fragment(),DownloadHelper {
|
|||||||
private var type:String = ""
|
private var type:String = ""
|
||||||
private var spotifyLink = ""
|
private var spotifyLink = ""
|
||||||
private var i: Intent? = null
|
private var i: Intent? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
binding = DataBindingUtil.inflate(inflater,R.layout.main_fragment,container,false)
|
binding = DataBindingUtil.inflate(inflater,R.layout.main_fragment,container,false)
|
||||||
|
DownloadHelper.context = requireContext()
|
||||||
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
|
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
|
||||||
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
|
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
|
||||||
spotifyService = sharedViewModel.spotifyService
|
spotifyService = sharedViewModel.spotifyService
|
||||||
@ -73,13 +85,14 @@ class MainFragment : Fragment(),DownloadHelper {
|
|||||||
|
|
||||||
binding.usage.text = spanStringBuilder
|
binding.usage.text = spanStringBuilder
|
||||||
openSpotifyButton()
|
openSpotifyButton()
|
||||||
|
openGithubButton()
|
||||||
|
openInstaButton()
|
||||||
|
|
||||||
binding.btnDonate.setOnClickListener {
|
binding.btnDonate.setOnClickListener {
|
||||||
sharedViewModel.easyUpiPayment?.startPayment()
|
sharedViewModel.easyUpiPayment?.startPayment()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.btnSearch.setOnClickListener {
|
binding.btnSearch.setOnClickListener {
|
||||||
sharedViewModel.isConnected.value = isOnline()
|
|
||||||
spotifyLink = binding.linkSearch.text.toString()
|
spotifyLink = binding.linkSearch.text.toString()
|
||||||
|
|
||||||
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
|
val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?')
|
||||||
@ -87,16 +100,15 @@ class MainFragment : Fragment(),DownloadHelper {
|
|||||||
|
|
||||||
Log.i("Fragment", "$type : $link")
|
Log.i("Fragment", "$type : $link")
|
||||||
|
|
||||||
if(sharedViewModel.spotifyService == null){
|
if(sharedViewModel.spotifyService == null && !isOnline()){
|
||||||
(activity as MainActivity).authenticateSpotify()
|
(activity as MainActivity).authenticateSpotify()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == "Error" || link == "Error") {
|
if (type == "Error" || link == "Error") {
|
||||||
showToast("Please Check Your Link!")
|
showToast("Please Check Your Link!")
|
||||||
} else if(sharedViewModel.isConnected.value == false){
|
} else if(!isOnline()){
|
||||||
sharedViewModel.showAlertDialog(resources,requireContext())
|
sharedViewModel.showAlertDialog(resources,requireContext())
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
adapter = TrackListAdapter()
|
adapter = TrackListAdapter()
|
||||||
binding.trackList.adapter = adapter
|
binding.trackList.adapter = adapter
|
||||||
adapter.sharedViewModel = sharedViewModel
|
adapter.sharedViewModel = sharedViewModel
|
||||||
@ -106,7 +118,9 @@ class MainFragment : Fragment(),DownloadHelper {
|
|||||||
if(mainViewModel.searchLink == spotifyLink){
|
if(mainViewModel.searchLink == spotifyLink){
|
||||||
//it's a Device Configuration Change
|
//it's a Device Configuration Change
|
||||||
adapterConfig(mainViewModel.trackList)
|
adapterConfig(mainViewModel.trackList)
|
||||||
|
sharedViewModel.uiScope.launch {
|
||||||
bindImage(binding.imageView,mainViewModel.coverUrl)
|
bindImage(binding.imageView,mainViewModel.coverUrl)
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
when (type) {
|
when (type) {
|
||||||
"track" -> {
|
"track" -> {
|
||||||
@ -121,15 +135,14 @@ class MainFragment : Fragment(),DownloadHelper {
|
|||||||
adapterConfig(trackList)
|
adapterConfig(trackList)
|
||||||
|
|
||||||
binding.btnDownloadAll.setOnClickListener {
|
binding.btnDownloadAll.setOnClickListener {
|
||||||
|
showToast("Starting Download in Few Seconds")
|
||||||
sharedViewModel.uiScope.launch {
|
sharedViewModel.uiScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
downloadAllTracks(
|
downloadAllTracks(
|
||||||
"Tracks",
|
"Tracks",
|
||||||
null,
|
null,
|
||||||
trackList,
|
trackList,
|
||||||
sharedViewModel.ytDownloader,
|
sharedViewModel.ytDownloader)
|
||||||
sharedViewModel.downloadManager
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,22 +155,21 @@ class MainFragment : Fragment(),DownloadHelper {
|
|||||||
sharedViewModel.uiScope.launch {
|
sharedViewModel.uiScope.launch {
|
||||||
val albumObject = sharedViewModel.getAlbumDetails(link)
|
val albumObject = sharedViewModel.getAlbumDetails(link)
|
||||||
val trackList = mutableListOf<Track>()
|
val trackList = mutableListOf<Track>()
|
||||||
albumObject!!.tracks?.items?.forEach { trackList.add(it!!) }
|
albumObject!!.tracks?.items?.forEach { trackList.add(it) }
|
||||||
mainViewModel.trackList = trackList
|
mainViewModel.trackList = trackList
|
||||||
mainViewModel.coverUrl = albumObject.images?.get(0)!!.url!!
|
mainViewModel.coverUrl = albumObject.images?.get(0)!!.url!!
|
||||||
bindImage(binding.imageView,mainViewModel.coverUrl)
|
bindImage(binding.imageView,mainViewModel.coverUrl)
|
||||||
adapter.isAlbum = true
|
adapter.isAlbum = true
|
||||||
adapterConfig(trackList)
|
adapterConfig(trackList)
|
||||||
binding.btnDownloadAll.setOnClickListener {
|
binding.btnDownloadAll.setOnClickListener {
|
||||||
|
showToast("Starting Download in Few Seconds")
|
||||||
sharedViewModel.uiScope.launch {
|
sharedViewModel.uiScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
downloadAllTracks(
|
downloadAllTracks(
|
||||||
"Albums",
|
"Albums",
|
||||||
albumObject.name,
|
albumObject.name,
|
||||||
trackList,
|
trackList,
|
||||||
sharedViewModel.ytDownloader,
|
sharedViewModel.ytDownloader)
|
||||||
sharedViewModel.downloadManager
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,21 +183,21 @@ class MainFragment : Fragment(),DownloadHelper {
|
|||||||
sharedViewModel.uiScope.launch {
|
sharedViewModel.uiScope.launch {
|
||||||
val playlistObject = sharedViewModel.getPlaylistDetails(link)
|
val playlistObject = sharedViewModel.getPlaylistDetails(link)
|
||||||
val trackList = mutableListOf<Track>()
|
val trackList = mutableListOf<Track>()
|
||||||
playlistObject!!.tracks?.items!!.forEach { trackList.add(it?.track!!) }
|
playlistObject!!.tracks?.items!!.forEach { trackList.add(it.track!!) }
|
||||||
mainViewModel.trackList = trackList
|
mainViewModel.trackList = trackList
|
||||||
mainViewModel.coverUrl = playlistObject.images?.get(0)!!.url!!
|
mainViewModel.coverUrl = playlistObject.images?.get(0)!!.url!!
|
||||||
bindImage(binding.imageView,mainViewModel.coverUrl)
|
bindImage(binding.imageView,mainViewModel.coverUrl)
|
||||||
adapterConfig(trackList)
|
adapterConfig(trackList)
|
||||||
binding.btnDownloadAll.setOnClickListener {
|
binding.btnDownloadAll.setOnClickListener {
|
||||||
|
showToast("Starting Download in Few Seconds")
|
||||||
sharedViewModel.uiScope.launch {
|
sharedViewModel.uiScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
loadAllImages(trackList)
|
||||||
downloadAllTracks(
|
downloadAllTracks(
|
||||||
"Playlists",
|
"Playlists",
|
||||||
playlistObject.name,
|
playlistObject.name,
|
||||||
trackList,
|
trackList,
|
||||||
sharedViewModel.ytDownloader,
|
sharedViewModel.ytDownloader)
|
||||||
sharedViewModel.downloadManager
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,6 +225,59 @@ class MainFragment : Fragment(),DownloadHelper {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to fetch all Images for using in mp3 tag.
|
||||||
|
**/
|
||||||
|
private fun loadAllImages(trackList: List<Track>) {
|
||||||
|
trackList.forEach {
|
||||||
|
val imgUrl = it.album!!.images?.get(0)?.url
|
||||||
|
imgUrl?.let {
|
||||||
|
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
|
||||||
|
Glide
|
||||||
|
.with(requireContext())
|
||||||
|
.asFile()
|
||||||
|
.load(imgUri)
|
||||||
|
.listener(object: RequestListener<File> {
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<File>?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
Log.i("Glide","LoadFailed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: File?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<File>?,
|
||||||
|
dataSource: DataSource?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
sharedViewModel.uiScope.launch {
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
try {
|
||||||
|
val file = File(
|
||||||
|
Environment.getExternalStorageDirectory(),
|
||||||
|
DownloadHelper.defaultDir+".Images/" + imgUrl.substringAfterLast('/') + ".jpeg"
|
||||||
|
)
|
||||||
|
resource?.copyTo(file)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}).submit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementing button to Open Spotify App
|
||||||
|
**/
|
||||||
private fun openSpotifyButton() {
|
private fun openSpotifyButton() {
|
||||||
val manager: PackageManager = requireActivity().packageManager
|
val manager: PackageManager = requireActivity().packageManager
|
||||||
try {
|
try {
|
||||||
@ -232,14 +297,32 @@ class MainFragment : Fragment(),DownloadHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openGithubButton() {
|
||||||
|
val uri: Uri =
|
||||||
|
Uri.parse("http://github.com/Shabinder/SpotiFlyer")
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||||
|
binding.btnGithub.setOnClickListener {
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun openInstaButton() {
|
||||||
|
val uri: Uri =
|
||||||
|
Uri.parse("http://www.instagram.com/mr.shabinder")
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||||
|
binding.developerInsta.setOnClickListener {
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure Recycler View Adapter
|
* Configure Recycler View Adapter
|
||||||
**/
|
**/
|
||||||
private fun adapterConfig(trackList: List<Track>){
|
private fun adapterConfig(trackList: List<Track>){
|
||||||
adapter.trackList = trackList.toList()
|
adapter.trackList = trackList.toList()
|
||||||
adapter.totalItems = trackList.size
|
adapter.totalItems = trackList.size
|
||||||
|
adapter.mainFragment = this
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -274,6 +357,10 @@ class MainFragment : Fragment(),DownloadHelper {
|
|||||||
fun showToast(message:String){
|
fun showToast(message:String){
|
||||||
Toast.makeText(context,message,Toast.LENGTH_SHORT).show()
|
Toast.makeText(context,message,Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util. Function To Check Connection Status
|
||||||
|
**/
|
||||||
private fun isOnline(): Boolean {
|
private fun isOnline(): Boolean {
|
||||||
val cm =
|
val cm =
|
||||||
requireActivity().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
requireActivity().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
@ -21,7 +21,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import com.shabinder.spotiflyer.models.Track
|
import com.shabinder.spotiflyer.models.Track
|
||||||
|
|
||||||
class MainViewModel: ViewModel() {
|
class MainViewModel: ViewModel() {
|
||||||
var searchLink:String = ""
|
var searchLink: String = ""
|
||||||
var trackList = mutableListOf<Track>()
|
var trackList = mutableListOf<Track>()
|
||||||
var coverUrl:String = ""
|
var coverUrl: String = ""
|
||||||
}
|
}
|
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class Album(
|
data class Album(
|
||||||
var album_type: String? = null,
|
var album_type: String? = null,
|
||||||
var artists: List<Artist?>? = null,
|
var artists: List<Artist?>? = null,
|
||||||
@ -33,6 +37,6 @@ data class Album(
|
|||||||
var popularity: Int? = null,
|
var popularity: Int? = null,
|
||||||
var release_date: String? = null,
|
var release_date: String? = null,
|
||||||
var release_date_precision: String? = null,
|
var release_date_precision: String? = null,
|
||||||
var tracks: PagingObject<Track?>? = null,
|
var tracks: PagingObjectTrack? = null,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null):Parcelable
|
@ -16,10 +16,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class Artist(
|
data class Artist(
|
||||||
var external_urls: Map<String?, String?>? = null,
|
var external_urls: Map<String?, String?>? = null,
|
||||||
var href: String? = null,
|
var href: String? = null,
|
||||||
var id: String? = null,
|
var id: String? = null,
|
||||||
var name: String? = null,
|
var name: String? = null,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null):Parcelable
|
@ -16,6 +16,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class Copyright(
|
data class Copyright(
|
||||||
var text: String? = null,
|
var text: String? = null,
|
||||||
var type: String? = null)
|
var type: 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
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class DownloadObject(
|
||||||
|
var track: Track,
|
||||||
|
var url:String,
|
||||||
|
var outputDir:String
|
||||||
|
):Parcelable
|
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class Episodes(
|
data class Episodes(
|
||||||
var audio_preview_url:String?,
|
var audio_preview_url:String?,
|
||||||
var description:String?,
|
var description:String?,
|
||||||
@ -35,4 +39,4 @@ data class Episodes(
|
|||||||
var release_date_precision:String?,
|
var release_date_precision:String?,
|
||||||
var type:String?,
|
var type:String?,
|
||||||
var uri:String
|
var uri:String
|
||||||
)
|
): Parcelable
|
@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Followers(
|
data class Followers(
|
||||||
var href: String? = null,
|
var href: String? = null,
|
||||||
var total: Int? = null)
|
var total: Int? = null):java.io.Serializable
|
@ -16,7 +16,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class Image(
|
data class Image(
|
||||||
var width: Int? = null,
|
var width: Int? = null,
|
||||||
var height: Int? = null,
|
var height: Int? = null,
|
||||||
var url: String? = null)
|
var url: String? = null):Parcelable
|
@ -17,9 +17,13 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class LinkedTrack(
|
data class LinkedTrack(
|
||||||
var external_urls: Map<String?, String?>? = null,
|
var external_urls: Map<String?, String?>? = null,
|
||||||
var href: String? = null,
|
var href: String? = null,
|
||||||
var id: String? = null,
|
var id: String? = null,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: 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
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.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
|
@ -17,11 +17,15 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
data class PagingObject<T>(
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class PagingObjectTrack(
|
||||||
var href: String? = null,
|
var href: String? = null,
|
||||||
var items: List<T>? = null,
|
var items: List<Track>? = null,
|
||||||
var limit: Int = 0,
|
var limit: Int = 0,
|
||||||
var next: String? = null,
|
var next: String? = null,
|
||||||
var offset: Int = 0,
|
var offset: Int = 0,
|
||||||
var previous: String? = null,
|
var previous: String? = null,
|
||||||
var total: Int = 0)
|
var total: Int = 0):Parcelable
|
@ -17,7 +17,11 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class Playlist(
|
data class Playlist(
|
||||||
@Json(name = "collaborative")var is_collaborative: Boolean? = null,
|
@Json(name = "collaborative")var is_collaborative: Boolean? = null,
|
||||||
var description: String? = null,
|
var description: String? = null,
|
||||||
@ -30,6 +34,6 @@ data class Playlist(
|
|||||||
var owner: UserPublic? = null,
|
var owner: UserPublic? = null,
|
||||||
@Json(name = "public")var is_public: Boolean? = null,
|
@Json(name = "public")var is_public: Boolean? = null,
|
||||||
var snapshot_id: String? = null,
|
var snapshot_id: String? = null,
|
||||||
var tracks: PagingObject<PlaylistTrack?>? = null,
|
var tracks: PagingObjectPlaylistTrack? = null,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null): Parcelable
|
@ -17,8 +17,12 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class PlaylistTrack(
|
data class PlaylistTrack(
|
||||||
var added_at: String? = null,
|
var added_at: String? = null,
|
||||||
var added_by: UserPublic? = null,
|
var added_by: UserPublic? = null,
|
||||||
var track: Track? = null,
|
var track: Track? = null,
|
||||||
var is_local: Boolean? = null)
|
var is_local: Boolean? = null): Parcelable
|
@ -17,8 +17,12 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class Token(
|
data class Token(
|
||||||
var access_token:String,
|
var access_token:String,
|
||||||
var token_type:String,
|
var token_type:String,
|
||||||
var expires_in:Int
|
var expires_in:Int
|
||||||
)
|
): Parcelable
|
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class Track(
|
data class Track(
|
||||||
var artists: List<Artist?>? = null,
|
var artists: List<Artist?>? = null,
|
||||||
var available_markets: List<String?>? = null,
|
var available_markets: List<String?>? = null,
|
||||||
@ -35,4 +39,4 @@ data class Track(
|
|||||||
var uri: String? = null,
|
var uri: String? = null,
|
||||||
var album: Album? = null,
|
var album: Album? = null,
|
||||||
var external_ids: Map<String?, String?>? = null,
|
var external_ids: Map<String?, String?>? = null,
|
||||||
var popularity: Int? = null)
|
var popularity: Int? = null):Parcelable
|
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class UserPrivate(
|
data class UserPrivate(
|
||||||
val country:String,
|
val country:String,
|
||||||
var display_name: String,
|
var display_name: String,
|
||||||
@ -28,4 +32,4 @@ data class UserPrivate(
|
|||||||
var images: List<Image?>? = null,
|
var images: List<Image?>? = null,
|
||||||
var product:String,
|
var product:String,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null): Parcelable
|
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.models
|
package com.shabinder.spotiflyer.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class UserPublic(
|
data class UserPublic(
|
||||||
var display_name: String? = null,
|
var display_name: String? = null,
|
||||||
var external_urls: Map<String?, String?>? = null,
|
var external_urls: Map<String?, String?>? = null,
|
||||||
@ -25,4 +29,4 @@ data class UserPublic(
|
|||||||
var id: String? = null,
|
var id: String? = null,
|
||||||
var images: List<Image?>? = null,
|
var images: List<Image?>? = null,
|
||||||
var type: String? = null,
|
var type: String? = null,
|
||||||
var uri: String? = null)
|
var uri: String? = null): Parcelable
|
@ -26,13 +26,14 @@ import android.widget.TextView
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.SharedViewModel
|
import com.shabinder.spotiflyer.SharedViewModel
|
||||||
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper.downloadTrack
|
||||||
import com.shabinder.spotiflyer.fragments.MainFragment
|
import com.shabinder.spotiflyer.fragments.MainFragment
|
||||||
import com.shabinder.spotiflyer.models.Track
|
import com.shabinder.spotiflyer.models.Track
|
||||||
import com.shabinder.spotiflyer.utils.bindImage
|
import com.shabinder.spotiflyer.utils.bindImage
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>(),DownloadHelper {
|
|
||||||
|
class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>() {
|
||||||
|
|
||||||
var trackList = listOf<Track>()
|
var trackList = listOf<Track>()
|
||||||
var totalItems:Int = 0
|
var totalItems:Int = 0
|
||||||
@ -52,15 +53,17 @@ class TrackListAdapter:RecyclerView.Adapter<TrackListAdapter.ViewHolder>(),Downl
|
|||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = trackList[position]
|
val item = trackList[position]
|
||||||
if(totalItems == 1 || isAlbum){holder.coverImage.visibility = View.GONE}else{
|
if(totalItems == 1 || isAlbum){holder.coverImage.visibility = View.GONE}else{
|
||||||
|
sharedViewModel.uiScope.launch {
|
||||||
bindImage(holder.coverImage, item.album!!.images?.get(0)?.url)
|
bindImage(holder.coverImage, item.album!!.images?.get(0)?.url)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
holder.trackName.text = "${if(item.name!!.length > 17){"${item.name!!.subSequence(0,16)}..."}else{item.name}}"
|
holder.trackName.text = "${if(item.name!!.length > 17){"${item.name!!.subSequence(0,16)}..."}else{item.name}}"
|
||||||
holder.artistName.text = "${item.artists?.get(0)?.name?:""}..."
|
holder.artistName.text = "${item.artists?.get(0)?.name?:""}..."
|
||||||
holder.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec"
|
holder.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec"
|
||||||
holder.downloadBtn.setOnClickListener{
|
holder.downloadBtn.setOnClickListener{
|
||||||
sharedViewModel.uiScope.launch {
|
sharedViewModel.uiScope.launch {
|
||||||
downloadTrack(mainFragment,"Tracks",null,sharedViewModel.ytDownloader,sharedViewModel.downloadManager,"${item.name} ${item.artists?.get(0)!!.name?:""}")
|
downloadTrack(mainFragment,"Tracks",null,sharedViewModel.ytDownloader,"${item.name} ${item.artists?.get(0)!!.name?:""}",track = item,index = 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,22 +17,102 @@
|
|||||||
|
|
||||||
package com.shabinder.spotiflyer.utils
|
package com.shabinder.spotiflyer.utils
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.os.Environment
|
||||||
|
import android.util.Log
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.databinding.BindingAdapter
|
import androidx.databinding.BindingAdapter
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
|
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
||||||
@BindingAdapter("imageUrl")
|
@BindingAdapter("imageUrl")
|
||||||
fun bindImage(imgView: ImageView, imgUrl: String?) {
|
fun bindImage(imgView: ImageView, imgUrl: String?) {
|
||||||
imgUrl?.let {
|
imgUrl?.let {
|
||||||
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
|
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
|
||||||
Glide.with(imgView.context)
|
Glide
|
||||||
|
.with(imgView.context)
|
||||||
|
.asFile()
|
||||||
.load(imgUri)
|
.load(imgUri)
|
||||||
.apply(RequestOptions()
|
|
||||||
.placeholder(R.drawable.ic_song_placeholder)
|
.placeholder(R.drawable.ic_song_placeholder)
|
||||||
.error(R.drawable.ic_musicplaceholder))
|
.error(R.drawable.ic_musicplaceholder)
|
||||||
.into(imgView)
|
.listener(object:RequestListener<File>{
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<File>?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
Log.i("Glide","LoadFailed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: File?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<File>?,
|
||||||
|
dataSource: DataSource?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
try {
|
||||||
|
val file = File(
|
||||||
|
Environment.getExternalStorageDirectory(),
|
||||||
|
DownloadHelper.defaultDir+".Images/" + imgUrl.substringAfterLast('/') + ".jpeg"
|
||||||
|
) // the File to save , append increasing numeric counter to prevent files from getting overwritten.
|
||||||
|
val options = BitmapFactory.Options()
|
||||||
|
options.inPreferredConfig = Bitmap.Config.ARGB_8888
|
||||||
|
val bitmap = BitmapFactory.decodeStream(FileInputStream(resource), null, options)
|
||||||
|
resource?.copyTo(file)
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
imgView.setImageBitmap(bitmap)
|
||||||
|
// Log.i("Glide","imageSaved")
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}).submit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*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(Environment.getExternalStorageDirectory(),
|
||||||
|
dir)
|
||||||
|
|
||||||
|
if(!yourAppDir.exists() && !yourAppDir.isDirectory)
|
||||||
|
{ // create empty directory
|
||||||
|
if (yourAppDir.mkdirs())
|
||||||
|
{Log.i("CreateDir","App dir created")}
|
||||||
|
else
|
||||||
|
{Log.w("CreateDir","Unable to create app dir!")}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{Log.i("CreateDir","App dir already exists")}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,343 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
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.mpatric.mp3agic.ID3v1Tag
|
||||||
|
import com.mpatric.mp3agic.ID3v24Tag
|
||||||
|
import com.mpatric.mp3agic.Mp3File
|
||||||
|
import com.shabinder.spotiflyer.MainActivity
|
||||||
|
import com.shabinder.spotiflyer.R
|
||||||
|
import com.shabinder.spotiflyer.downloadHelper.DownloadHelper
|
||||||
|
import com.shabinder.spotiflyer.models.DownloadObject
|
||||||
|
import com.shabinder.spotiflyer.models.Track
|
||||||
|
import com.tonyodev.fetch2.*
|
||||||
|
import com.tonyodev.fetch2core.DownloadBlock
|
||||||
|
import com.tonyodev.fetch2core.Func
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
class ForegroundService : Service(){
|
||||||
|
private val tag = "Foreground Service"
|
||||||
|
private val channelId = "SpotiFlyer: Download Service"
|
||||||
|
private var total = 0 //Total Downloads Requested
|
||||||
|
private var converted = 0//Total Files Converted
|
||||||
|
private var fetch:Fetch? = null
|
||||||
|
private var downloadList = mutableListOf<DownloadObject>()
|
||||||
|
private var serviceJob = Job()
|
||||||
|
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
||||||
|
private val requestMap = mutableMapOf<Request,Track>()
|
||||||
|
private val downloadMap = mutableMapOf<String,Track>()
|
||||||
|
private var speed :Long = 0
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||||
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
|
this,
|
||||||
|
0, notificationIntent, 0
|
||||||
|
)
|
||||||
|
val notification = NotificationCompat.Builder(this, channelId)
|
||||||
|
.setContentTitle("SpotiFlyer: Downloading Your Music")
|
||||||
|
.setSubText("Speed: $speed KB/s ")
|
||||||
|
.setNotificationSilent()
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
|
.setContentText("Total: $total Downloaded: ${total - requestMap.keys.size} Converted:$converted ")
|
||||||
|
.setSmallIcon(R.drawable.down_arrowbw)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val fetchConfiguration =
|
||||||
|
FetchConfiguration.Builder(this)
|
||||||
|
.setDownloadConcurrentLimit(4)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
Fetch.Impl.setDefaultInstanceConfiguration(fetchConfiguration)
|
||||||
|
fetch = Fetch.getDefaultInstance()
|
||||||
|
fetch?.addListener(fetchListener)
|
||||||
|
|
||||||
|
startForeground(1, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
|
// Send a notification that service is started
|
||||||
|
Log.i(tag,"Service Started.")
|
||||||
|
|
||||||
|
//do heavy work on a background thread
|
||||||
|
// val list = intent.getSerializableExtra("list") as List<Any?>
|
||||||
|
val list = intent.getParcelableArrayListExtra<DownloadObject>("list") ?: intent.extras?.getParcelableArrayList<DownloadObject>("list")
|
||||||
|
Log.i(tag,"Intent List Size: ${list!!.size}")
|
||||||
|
total += list.size
|
||||||
|
list.forEach { downloadList.add(it as DownloadObject) }
|
||||||
|
|
||||||
|
serviceScope.launch {
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
for (downloadObject in downloadList) {
|
||||||
|
val request= Request(downloadObject.url, downloadObject.outputDir)
|
||||||
|
request.priority = Priority.NORMAL
|
||||||
|
request.networkType = NetworkType.ALL
|
||||||
|
|
||||||
|
fetch?.enqueue(request,
|
||||||
|
Func {
|
||||||
|
Log.i("DownloadManager", "Download Request Sent")
|
||||||
|
requestMap[it] = downloadObject.track
|
||||||
|
downloadList.remove(downloadObject) },
|
||||||
|
Func {
|
||||||
|
Log.i("DownloadManager", "Download Request Error:${it.throwable.toString()}")}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
if(downloadMap.isEmpty() && converted == total){
|
||||||
|
Log.i(tag,"Service destroyed.")
|
||||||
|
stopForeground(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
|
super.onTaskRemoved(rootIntent)
|
||||||
|
if(downloadMap.isEmpty() && converted == total ){
|
||||||
|
Log.i(tag,"Service destroyed.")
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var fetchListener: FetchListener = object : FetchListener {
|
||||||
|
override fun onQueued(
|
||||||
|
download: Download,
|
||||||
|
waitingOnNetwork: Boolean
|
||||||
|
) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
) {
|
||||||
|
val track = requestMap[download.request]
|
||||||
|
Log.i(tag,"${track?.name} Download Started")
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
val track = requestMap[download.request]
|
||||||
|
speed = 0
|
||||||
|
serviceScope.launch {
|
||||||
|
convertToMp3(download.file, track!!)
|
||||||
|
}
|
||||||
|
Log.i(tag,"${track?.name} Download Completed")
|
||||||
|
requestMap.remove(download.request)
|
||||||
|
updateNotification()
|
||||||
|
}
|
||||||
|
|
||||||
|
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?) {
|
||||||
|
Log.i(tag,download.error.throwable.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPaused(download: Download) {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProgress(
|
||||||
|
download: Download,
|
||||||
|
etaInMilliSeconds: Long,
|
||||||
|
downloadedBytesPerSecond: Long
|
||||||
|
) {
|
||||||
|
val track = requestMap[download.request]
|
||||||
|
Log.i(tag,"${track?.name} ETA: ${etaInMilliSeconds/1000} sec")
|
||||||
|
speed = (downloadedBytesPerSecond/1000)
|
||||||
|
updateNotification()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun convertToMp3(filePath: String,track: Track){
|
||||||
|
val m4aFile = File(filePath)
|
||||||
|
|
||||||
|
val executionId = FFmpeg.executeAsync(
|
||||||
|
"-i $filePath -vn ${filePath.substringBeforeLast('.') + ".mp3"}"
|
||||||
|
) { _, returnCode ->
|
||||||
|
when (returnCode) {
|
||||||
|
RETURN_CODE_SUCCESS -> {
|
||||||
|
Log.i(Config.TAG, "Async command execution completed successfully.")
|
||||||
|
m4aFile.delete()
|
||||||
|
writeMp3Tags(filePath.substringBeforeLast('.')+".mp3",track)
|
||||||
|
//FFMPEG task Completed
|
||||||
|
}
|
||||||
|
RETURN_CODE_CANCEL -> {
|
||||||
|
Log.i(Config.TAG, "Async command execution cancelled by user.")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.i(Config.TAG, String.format("Async command execution failed with rc=%d.", returnCode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeMp3Tags(filePath:String, track: Track){
|
||||||
|
var mp3File = Mp3File(filePath)
|
||||||
|
mp3File = removeAllTags(mp3File)
|
||||||
|
mp3File = setId3v1Tags(mp3File,track)
|
||||||
|
mp3File = setId3v2Tags(mp3File,track)
|
||||||
|
Log.i("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()
|
||||||
|
//All tasks completed (REST IN PEACE)
|
||||||
|
if(converted == total){
|
||||||
|
stopForeground(false)
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This is the method that can be called to update the Notification
|
||||||
|
*/
|
||||||
|
private fun updateNotification() {
|
||||||
|
val mNotificationManager: NotificationManager =
|
||||||
|
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
val notification = NotificationCompat.Builder(this, channelId)
|
||||||
|
.setContentTitle("SpotiFlyer: Downloading Your Music")
|
||||||
|
.setContentText("Total: $total Downloaded: ${total - requestMap.keys.size} Converted:$converted ")
|
||||||
|
.setSubText("Speed: $speed KB/s ")
|
||||||
|
.setNotificationSilent()
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
|
.setSmallIcon(R.drawable.down_arrowbw)
|
||||||
|
.build()
|
||||||
|
mNotificationManager.notify(1, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setId3v1Tags(mp3File: Mp3File, track: Track): Mp3File {
|
||||||
|
val id3v1Tag = ID3v1Tag()
|
||||||
|
id3v1Tag.track = track.disc_number.toString()
|
||||||
|
val artistsList = mutableListOf<String>()
|
||||||
|
track.artists?.forEach { artistsList.add(it!!.name!!) }
|
||||||
|
id3v1Tag.artist = artistsList.joinToString()
|
||||||
|
id3v1Tag.title = track.name
|
||||||
|
id3v1Tag.album = track.album?.name
|
||||||
|
id3v1Tag.year = track.album?.release_date
|
||||||
|
id3v1Tag.comment = "Genres:${track.album?.genres?.joinToString()}"
|
||||||
|
mp3File.id3v1Tag = id3v1Tag
|
||||||
|
return mp3File
|
||||||
|
}
|
||||||
|
private fun setId3v2Tags(mp3file: Mp3File, track: Track): Mp3File {
|
||||||
|
val id3v2Tag = ID3v24Tag()
|
||||||
|
id3v2Tag.track = track.disc_number.toString()
|
||||||
|
val artistsList = mutableListOf<String>()
|
||||||
|
track.artists?.forEach { artistsList.add(it!!.name!!) }
|
||||||
|
id3v2Tag.artist = artistsList.joinToString()
|
||||||
|
id3v2Tag.title = track.name
|
||||||
|
id3v2Tag.album = track.album?.name
|
||||||
|
id3v2Tag.year = track.album?.release_date
|
||||||
|
id3v2Tag.comment = "Genres:${track.album?.genres?.joinToString()}"
|
||||||
|
id3v2Tag.lyrics = "Gonna Implement Soon"
|
||||||
|
val copyrights = mutableListOf<String>()
|
||||||
|
track.album?.copyrights?.forEach { copyrights.add(it!!.type!!) }
|
||||||
|
id3v2Tag.copyright = copyrights.joinToString()
|
||||||
|
id3v2Tag.url = track.href
|
||||||
|
track.let {
|
||||||
|
val file = File(
|
||||||
|
Environment.getExternalStorageDirectory(),
|
||||||
|
DownloadHelper.defaultDir +".Images/" + (it.album!!.images?.get(0)?.url!!).substringAfterLast('/') + ".jpeg")
|
||||||
|
Log.i("Mp3Tags editing Tags",file.path)
|
||||||
|
//init array with file length
|
||||||
|
val bytesArray = ByteArray(file.length().toInt())
|
||||||
|
val fis = FileInputStream(file)
|
||||||
|
fis.read(bytesArray) //read file into bytes[]
|
||||||
|
fis.close()
|
||||||
|
id3v2Tag.setAlbumImage(bytesArray,"image/jpeg")
|
||||||
|
}
|
||||||
|
id3v2Tag.albumImage
|
||||||
|
mp3file.id3v2Tag = id3v2Tag
|
||||||
|
return mp3file
|
||||||
|
}
|
||||||
|
private fun removeAllTags(mp3file: Mp3File): Mp3File {
|
||||||
|
if (mp3file.hasId3v1Tag()) {
|
||||||
|
mp3file.removeId3v1Tag()
|
||||||
|
}
|
||||||
|
if (mp3file.hasId3v2Tag()) {
|
||||||
|
mp3file.removeId3v2Tag()
|
||||||
|
}
|
||||||
|
if (mp3file.hasCustomTag()) {
|
||||||
|
mp3file.removeCustomTag()
|
||||||
|
}
|
||||||
|
return mp3file
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
app/src/main/res/drawable/down_arrowbw.png
Normal file
BIN
app/src/main/res/drawable/down_arrowbw.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
21
app/src/main/res/drawable/ic_github.xml
Normal file
21
app/src/main/res/drawable/ic_github.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="38dp"
|
||||||
|
android:height="38dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||||
|
<path android:fillColor="#5C6BC0" android:pathData="M255.968,5.329C114.624,5.329 0,120.401 0,262.353c0,113.536 73.344,209.856 175.104,243.872c12.8,2.368 17.472,-5.568 17.472,-12.384c0,-6.112 -0.224,-22.272 -0.352,-43.712c-71.2,15.52 -86.24,-34.464 -86.24,-34.464c-11.616,-29.696 -28.416,-37.6 -28.416,-37.6c-23.264,-15.936 1.728,-15.616 1.728,-15.616c25.696,1.824 39.2,26.496 39.2,26.496c22.848,39.264 59.936,27.936 74.528,21.344c2.304,-16.608 8.928,-27.936 16.256,-34.368c-56.832,-6.496 -116.608,-28.544 -116.608,-127.008c0,-28.064 9.984,-51.008 26.368,-68.992c-2.656,-6.496 -11.424,-32.64 2.496,-68c0,0 21.504,-6.912 70.4,26.336c20.416,-5.696 42.304,-8.544 64.096,-8.64c21.728,0.128 43.648,2.944 64.096,8.672c48.864,-33.248 70.336,-26.336 70.336,-26.336c13.952,35.392 5.184,61.504 2.56,68c16.416,17.984 26.304,40.928 26.304,68.992c0,98.72 -59.84,120.448 -116.864,126.816c9.184,7.936 17.376,23.616 17.376,47.584c0,34.368 -0.32,62.08 -0.32,70.496c0,6.88 4.608,14.88 17.6,12.352C438.72,472.145 512,375.857 512,262.353C512,120.401 397.376,5.329 255.968,5.329z"/>
|
||||||
|
</vector>
|
25
app/src/main/res/drawable/ic_heart.xml
Normal file
25
app/src/main/res/drawable/ic_heart.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="25dp"
|
||||||
|
android:height="25dp" android:viewportWidth="512.007" android:viewportHeight="512.007">
|
||||||
|
<path android:fillColor="#fe646f" android:pathData="m380.125,59.036c-59.77,0 -109.664,42.249 -121.469,98.51 -0.608,2.899 -4.703,2.901 -5.312,0 -11.805,-56.261 -61.699,-98.51 -121.469,-98.51 -114.106,0 -167.756,141.01 -82.508,216.858l193.339,172.02c7.58,6.744 19.009,6.744 26.589,0l193.339,-172.02c85.248,-75.848 31.598,-216.858 -82.509,-216.858z"/>
|
||||||
|
<path android:fillColor="#fd4755" android:pathData="m380.125,59.036c-6.912,0 -13.689,0.572 -20.293,1.658 99.376,15.991 141.363,144.168 61.527,215.2l-185.996,165.487 7.343,6.533c7.58,6.744 19.009,6.744 26.589,0l193.339,-172.02c85.248,-75.848 31.598,-216.858 -82.509,-216.858z"/>
|
||||||
|
<path android:fillColor="#fe646f" android:pathData="m380.125,59.036c-59.77,0 -109.664,42.249 -121.469,98.51 -0.608,2.899 -4.703,2.901 -5.312,0 -11.805,-56.261 -61.699,-98.51 -121.469,-98.51 -114.106,0 -167.756,141.01 -82.508,216.858l193.339,172.02c7.58,6.744 19.009,6.744 26.589,0l193.339,-172.02c85.248,-75.848 31.598,-216.858 -82.509,-216.858z"/>
|
||||||
|
<path android:fillColor="#fd4755" android:pathData="m380.125,59.036c-6.912,0 -13.689,0.572 -20.293,1.658 99.376,15.991 141.363,144.168 61.527,215.2l-185.996,165.487 7.343,6.533c7.58,6.744 19.009,6.744 26.589,0l193.339,-172.02c85.248,-75.848 31.598,-216.858 -82.509,-216.858z"/>
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="m237.72,453.517c-204.315,-181.786 -197.402,-175.776 -197.402,-175.776 -25.999,-24.984 -40.318,-58.201 -40.318,-93.533 0,-46.48 24.63,-91.702 65.906,-115.47 3.589,-2.067 8.174,-0.833 10.242,2.757 2.067,3.589 0.833,8.175 -2.757,10.242 -36.017,20.74 -58.391,60.004 -58.391,102.471 0,31.212 12.683,60.588 35.711,82.717 0,0 -6.881,-5.996 196.979,175.386 2.292,2.039 5.242,3.161 8.309,3.161 3.066,0 6.018,-1.123 8.31,-3.162l61.917,-55.089c3.095,-2.753 7.835,-2.477 10.588,0.618s2.477,7.835 -0.618,10.588l-61.917,55.09c-10.431,9.281 -26.148,9.263 -36.559,0zM357.363,377.059c-2.067,0 -4.124,-0.849 -5.606,-2.515 -2.753,-3.095 -2.477,-7.835 0.618,-10.588l105.273,-93.665c21.815,-19.409 35.132,-44.369 38.513,-72.181 0.001,-0.006 0.001,-0.012 0.002,-0.018 7.637,-62.927 -37.915,-131.557 -116.038,-131.557 -54.879,0 -102.877,38.923 -114.129,92.55 -1.005,4.79 -5.116,8.135 -9.997,8.135s-8.991,-3.346 -9.996,-8.136c-11.252,-53.626 -59.25,-92.549 -114.128,-92.549 -9.633,0 -19.082,1.076 -28.084,3.198 -4.033,0.952 -8.07,-1.548 -9.021,-5.579 -0.951,-4.032 1.547,-8.07 5.579,-9.021 10.128,-2.388 20.735,-3.598 31.525,-3.598 55.699,0 105.463,35.109 124.125,87.792 18.71,-52.817 68.567,-87.792 124.125,-87.792 84.905,0 139.884,74.56 130.929,148.362 0,0.007 -0.001,0.015 -0.002,0.022 -3.829,31.494 -18.847,59.703 -43.433,81.578l-105.273,93.665c-1.429,1.272 -3.209,1.897 -4.982,1.897z"/>
|
||||||
|
</vector>
|
51
app/src/main/res/drawable/ic_instagram.xml
Normal file
51
app/src/main/res/drawable/ic_instagram.xml
Normal file
@ -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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="32dp" android:height="32dp"
|
||||||
|
android:viewportWidth="512" android:viewportHeight="512">
|
||||||
|
<path android:pathData="M352,0H160C71.648,0 0,71.648 0,160v192c0,88.352 71.648,160 160,160h192c88.352,0 160,-71.648 160,-160V160C512,71.648 440.352,0 352,0zM464,352c0,61.76 -50.24,112 -112,112H160c-61.76,0 -112,-50.24 -112,-112V160C48,98.24 98.24,48 160,48h192c61.76,0 112,50.24 112,112V352z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient android:endX="465.1312" android:endY="46.8656"
|
||||||
|
android:startX="46.8688" android:startY="465.1344" android:type="linear">
|
||||||
|
<item android:color="#FFFFC107" android:offset="0"/>
|
||||||
|
<item android:color="#FFF44336" android:offset="0.507"/>
|
||||||
|
<item android:color="#FF9C27B0" android:offset="0.99"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path android:pathData="M256,128c-70.688,0 -128,57.312 -128,128s57.312,128 128,128s128,-57.312 128,-128S326.688,128 256,128zM256,336c-44.096,0 -80,-35.904 -80,-80c0,-44.128 35.904,-80 80,-80s80,35.872 80,80C336,300.096 300.096,336 256,336z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient android:endX="346.5072" android:endY="165.4928"
|
||||||
|
android:startX="165.4928" android:startY="346.5072" android:type="linear">
|
||||||
|
<item android:color="#FFFFC107" android:offset="0"/>
|
||||||
|
<item android:color="#FFF44336" android:offset="0.507"/>
|
||||||
|
<item android:color="#FF9C27B0" android:offset="0.99"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path android:pathData="M393.6,118.4m-17.056,0a17.056,17.056 0,1 1,34.112 0a17.056,17.056 0,1 1,-34.112 0">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient android:endX="405.6592" android:endY="106.3408"
|
||||||
|
android:startX="381.5408" android:startY="130.4624" android:type="linear">
|
||||||
|
<item android:color="#FFFFC107" android:offset="0"/>
|
||||||
|
<item android:color="#FFF44336" android:offset="0.507"/>
|
||||||
|
<item android:color="#FF9C27B0" android:offset="0.99"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
</vector>
|
@ -1,5 +1,22 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="42dp"
|
<!--
|
||||||
android:height="42dp" android:viewportWidth="512" android:viewportHeight="512">
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="40dp"
|
||||||
|
android:height="40dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||||
<path android:fillColor="#ff5d7d" android:fillType="evenOdd" android:pathData="m258.229,255.863c-11.191,-11.155 -29.503,-11.155 -40.693,0 -4.486,4.471 -11.053,4.007 -15.072,0 -11.191,-11.155 -29.503,-11.155 -40.693,0 -30.403,30.307 28.128,83.271 48.229,88.64 20.102,-5.369 78.632,-58.333 48.229,-88.64z"/>
|
<path android:fillColor="#ff5d7d" android:fillType="evenOdd" android:pathData="m258.229,255.863c-11.191,-11.155 -29.503,-11.155 -40.693,0 -4.486,4.471 -11.053,4.007 -15.072,0 -11.191,-11.155 -29.503,-11.155 -40.693,0 -30.403,30.307 28.128,83.271 48.229,88.64 20.102,-5.369 78.632,-58.333 48.229,-88.64z"/>
|
||||||
<path android:fillColor="#fff" android:fillType="evenOdd" android:pathData="m258.229,255.863c30.403,30.307 -28.128,83.271 -48.23,88.64 -20.102,-5.369 -78.633,-58.334 -48.229,-88.64 11.191,-11.155 29.502,-11.155 40.693,0 4.02,4.007 10.587,4.471 15.072,0 11.191,-11.155 29.503,-11.155 40.694,0zM10,176c0,94.167 60,173.334 80,260h240c3.112,-13.487 7.193,-26.792 11.866,-40 4.742,-13.403 10.093,-26.707 15.66,-40 16.471,-39.33 34.83,-78.563 44.877,-119.994 3.154,-13.009 5.489,-26.235 6.689,-39.749 0.593,-6.679 0.908,-13.429 0.908,-20.257 0,-11 -9,-20 -20,-20 -120,0 -240,0 -360.001,0 -10.999,0 -19.999,9 -19.999,20z"/>
|
<path android:fillColor="#fff" android:fillType="evenOdd" android:pathData="m258.229,255.863c30.403,30.307 -28.128,83.271 -48.23,88.64 -20.102,-5.369 -78.633,-58.334 -48.229,-88.64 11.191,-11.155 29.502,-11.155 40.693,0 4.02,4.007 10.587,4.471 15.072,0 11.191,-11.155 29.503,-11.155 40.694,0zM10,176c0,94.167 60,173.334 80,260h240c3.112,-13.487 7.193,-26.792 11.866,-40 4.742,-13.403 10.093,-26.707 15.66,-40 16.471,-39.33 34.83,-78.563 44.877,-119.994 3.154,-13.009 5.489,-26.235 6.689,-39.749 0.593,-6.679 0.908,-13.429 0.908,-20.257 0,-11 -9,-20 -20,-20 -120,0 -240,0 -360.001,0 -10.999,0 -19.999,9 -19.999,20z"/>
|
||||||
<path android:fillColor="#ccf5fc" android:fillType="evenOdd" android:pathData="m402,356h-44.474c-5.567,13.293 -10.918,26.597 -15.66,40h60.134c55,0 99.999,-45 99.999,-100 0,-52.616 -41.185,-96.074 -92.908,-99.743 -1.2,13.514 -3.534,26.74 -6.69,39.749 32.818,0.218 59.599,27.129 59.599,59.994 0,33 -27,60 -60,60z"/>
|
<path android:fillColor="#ccf5fc" android:fillType="evenOdd" android:pathData="m402,356h-44.474c-5.567,13.293 -10.918,26.597 -15.66,40h60.134c55,0 99.999,-45 99.999,-100 0,-52.616 -41.185,-96.074 -92.908,-99.743 -1.2,13.514 -3.534,26.74 -6.69,39.749 32.818,0.218 59.599,27.129 59.599,59.994 0,33 -27,60 -60,60z"/>
|
||||||
|
@ -134,7 +134,7 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/constraint_layout"
|
android:id="@+id/constraint_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="match_parent"
|
||||||
android:visibility="visible">
|
android:visibility="visible">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -240,6 +240,80 @@
|
|||||||
app:layout_constraintStart_toStartOf="@+id/btn_donate"
|
app:layout_constraintStart_toStartOf="@+id/btn_donate"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/usage" />
|
app:layout_constraintTop_toBottomOf="@+id/usage" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_github"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_marginTop="130dp"
|
||||||
|
android:background="@drawable/text_background"
|
||||||
|
android:drawableStart="@drawable/ic_github"
|
||||||
|
android:drawablePadding="5dp"
|
||||||
|
android:fontFamily="@font/capriola"
|
||||||
|
android:gravity="end|center_vertical"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:text=" Github "
|
||||||
|
android:textSize="13sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/btn_donate" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/developer_insta"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_marginTop="130dp"
|
||||||
|
android:background="@drawable/text_background"
|
||||||
|
android:drawableEnd="@drawable/ic_instagram"
|
||||||
|
android:drawablePadding="5dp"
|
||||||
|
android:fontFamily="@font/capriola"
|
||||||
|
android:gravity="end|center_vertical"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:text=" Follow Me "
|
||||||
|
android:textSize="13sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/btn_donate" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tagLine"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="80dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="@font/raleway_semibold"
|
||||||
|
android:text="Made with "
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/colorPrimary"
|
||||||
|
android:textSize="22sp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/heart"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/developer_insta" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/heart"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginTop="80dp"
|
||||||
|
android:contentDescription="Made With Love In India"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/developer_insta"
|
||||||
|
app:srcCompat="@drawable/ic_heart" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tagLine2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginTop="80dp"
|
||||||
|
android:fontFamily="@font/raleway_semibold"
|
||||||
|
android:text=" in India"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/colorPrimary"
|
||||||
|
android:textSize="22sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/heart"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/developer_insta" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
@ -252,11 +326,9 @@
|
|||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/open_spotify" />
|
app:layout_constraintTop_toBottomOf="@id/open_spotify" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -1,5 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2020 Shabinder Singh
|
||||||
|
~
|
||||||
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU General Public License as published by
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License
|
||||||
|
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
@ -15,7 +32,6 @@
|
|||||||
android:layout_width="100dp"
|
android:layout_width="100dp"
|
||||||
android:layout_height="80dp"
|
android:layout_height="80dp"
|
||||||
android:contentDescription="Track Image"
|
android:contentDescription="Track Image"
|
||||||
android:visibility="visible"
|
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/artist"
|
app:layout_constraintEnd_toStartOf="@+id/artist"
|
||||||
|
25
app/src/main/res/xml/app_update.xml
Normal file
25
app/src/main/res/xml/app_update.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2020 Shabinder Singh
|
||||||
|
~
|
||||||
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU General Public License as published by
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License
|
||||||
|
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<AppUpdater>
|
||||||
|
<update>
|
||||||
|
<latestVersion>1.1</latestVersion>
|
||||||
|
<latestVersionCode>2</latestVersionCode>
|
||||||
|
<url>https://github.com/Shabinder/SpotiFlyer/releases</url>
|
||||||
|
</update>
|
||||||
|
</AppUpdater>
|
20
build.gradle
20
build.gradle
@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
ext{
|
ext{
|
||||||
@ -7,13 +24,14 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.android.tools.build:gradle:4.0.1"
|
classpath "com.android.tools.build:gradle:4.0.1"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
//safe-Args
|
//safe-Args
|
||||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
|
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
|
||||||
|
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