Download Tab Added,

Some Force Closes causing Bugs Fixed,
Implementing Dependency Injection(Hilt Android).
This commit is contained in:
shabinder 2020-08-09 22:44:01 +05:30
parent bee88f02c7
commit abf6a6058a
36 changed files with 863 additions and 281 deletions

View File

@ -1,10 +1,12 @@
<component name="ProjectDictionaryState"> <component name="ProjectDictionaryState">
<dictionary name="shabinder"> <dictionary name="shabinder">
<words> <words>
<w>downloadrecord</w>
<w>ffmpeg</w> <w>ffmpeg</w>
<w>flyer</w> <w>flyer</w>
<w>insta</w> <w>insta</w>
<w>instagram</w> <w>instagram</w>
<w>mainfragment</w>
<w>maxresdefault</w> <w>maxresdefault</w>
<w>moshi</w> <w>moshi</w>
<w>musicforeveryone</w> <w>musicforeveryone</w>

View File

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="dagger.hilt.android.HiltAndroidApp" />
</list>
</component>
<component name="ProjectPlainTextFileTypeManager"> <component name="ProjectPlainTextFileTypeManager">
<file url="file://$PROJECT_DIR$/app/src/main/java/com/shabinder/spotiflyer/testing/YoutubeInterface.kt.backup" /> <file url="file://$PROJECT_DIR$/app/src/main/java/com/shabinder/spotiflyer/testing/YoutubeInterface.kt.backup" />
</component> </component>

View File

@ -20,6 +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: 'dagger.hilt.android.plugin'
//apply plugin: 'kotlinx-serialization' //apply plugin: 'kotlinx-serialization'
android { android {
@ -34,8 +35,8 @@ android {
applicationId 'com.shabinder.spotiflyer' applicationId 'com.shabinder.spotiflyer'
minSdkVersion 22 minSdkVersion 22
targetSdkVersion 29 targetSdkVersion 29
versionCode 4 versionCode 5
versionName "1.3" versionName "1.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
packagingOptions { packagingOptions {
@ -74,7 +75,7 @@ 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.1' implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.browser:browser:1.2.0'
implementation 'androidx.webkit:webkit:1.2.0' implementation 'androidx.webkit:webkit:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
@ -83,14 +84,19 @@ dependencies {
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.2.0'
implementation 'com.google.android.material:material:1.1.0'
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.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
implementation project(path: ':mobile-ffmpeg') implementation project(path: ':mobile-ffmpeg')
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") { implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {

View File

@ -18,10 +18,7 @@
package com.shabinder.spotiflyer package com.shabinder.spotiflyer
import android.app.Application import android.app.Application
import android.app.NotificationChannel import dagger.hilt.android.HiltAndroidApp
import android.app.NotificationManager
import android.content.Context
import android.os.Build
/* /*
* Copyright (C) 2020 Shabinder Singh * Copyright (C) 2020 Shabinder Singh
@ -40,24 +37,10 @@ import android.os.Build
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
class App:Application() { @HiltAndroidApp
private val channelId = "ForegroundServiceChannel" class App:Application(){
companion object{
override fun onCreate() { const val clientId:String = "694d8bf4f6ec420fa66ea7fb4c68f89d"
super.onCreate() const val clientSecret:String = "02ca2d4021a7452dae2328b47a6e8fe8"
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)
}
} }
} }

View File

@ -22,7 +22,6 @@ import android.annotation.SuppressLint
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.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -34,15 +33,13 @@ import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.github.javiersantos.appupdater.AppUpdater import com.github.javiersantos.appupdater.AppUpdater
import com.github.javiersantos.appupdater.enums.UpdateFrom import com.github.javiersantos.appupdater.enums.UpdateFrom
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.databinding.MainActivityBinding import com.shabinder.spotiflyer.databinding.MainActivityBinding
import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper
import com.shabinder.spotiflyer.utils.SpotifyService import com.shabinder.spotiflyer.utils.SpotifyService
import com.shabinder.spotiflyer.utils.SpotifyServiceToken import com.shabinder.spotiflyer.utils.SpotifyServiceTokenRequest
import com.shabinder.spotiflyer.utils.createDirectory import com.shabinder.spotiflyer.utils.createDirectory
import com.shreyaspatil.EasyUpiPayment.EasyUpiPayment
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -50,41 +47,40 @@ import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Inject
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@AndroidEntryPoint
class MainActivity : AppCompatActivity(){ class MainActivity : AppCompatActivity(){
private lateinit var binding: MainActivityBinding
private var ytDownloader : YoutubeDownloader? = null
private var spotifyService : SpotifyService? = null private var spotifyService : SpotifyService? = null
private var spotifyServiceToken : SpotifyServiceToken? = null
// private val redirectUri = "spotiflyer://callback"
private val clientId:String = "694d8bf4f6ec420fa66ea7fb4c68f89d"
private val clientSecret:String = "02ca2d4021a7452dae2328b47a6e8fe8"
private var isConnected: Boolean = false private var isConnected: Boolean = false
private var sharedPref :SharedPreferences? = null private var sharedPref :SharedPreferences? = null
private var easyUpiPayment:EasyUpiPayment? = null
private var token :String ="" private var token :String =""
private lateinit var binding: MainActivityBinding
private lateinit var sharedViewModel: SharedViewModel private lateinit var sharedViewModel: SharedViewModel
@Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest
@Inject lateinit var moshi: Moshi
companion object{
private var instance = MainActivity()
fun getInstance():MainActivity{
return instance
}
}
init {
instance = this
}
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)
//starting Notification and Downloader Service! //starting Notification and Downloader Service!
SpotifyDownloadHelper.startService(this) SpotifyDownloadHelper.startService(this)
/* if(sharedPref?.contains("token")!! && (sharedPref?.getLong("time",System.currentTimeMillis()/1000/60/60)!! < (System.currentTimeMillis()/1000/60/60)) ){
val savedToken = sharedPref?.getString("token","error")!!
sharedViewModel.accessToken.value = savedToken
Log.i("SharedPrefs Token:",savedToken)
token = savedToken
implementSpotifyService(savedToken)
}else{authenticateSpotify()}*/
if(sharedViewModel.spotifyService.value == null){ if(sharedViewModel.spotifyService.value == null){
authenticateSpotify() authenticateSpotify()
}else{ }else{
@ -94,16 +90,11 @@ class MainActivity : AppCompatActivity(){
requestPermission() requestPermission()
disableDozeMode() disableDozeMode()
checkIfLatestVersion() checkIfLatestVersion()
createDir() createDirectories()
setUpi() isConnected = sharedViewModel.isOnline(this)
isConnected = isOnline()
sharedViewModel.isConnected.value = isConnected sharedViewModel.isConnected.value = isConnected
Log.i("Connection Status",isConnected.toString()) Log.i("Connection Status",isConnected.toString())
//Object to download From Youtube {"https://github.com/sealedtx/java-youtube-downloader"}
ytDownloader = YoutubeDownloader()
sharedViewModel.ytDownloader.value = ytDownloader
handleIntentFromExternalActivity() handleIntentFromExternalActivity()
} }
@ -157,10 +148,6 @@ class MainActivity : AppCompatActivity(){
} }
}) })
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder() val retrofit = Retrofit.Builder()
.baseUrl("https://api.spotify.com/v1/") .baseUrl("https://api.spotify.com/v1/")
.client(httpClient.build()) .client(httpClient.build())
@ -171,45 +158,15 @@ class MainActivity : AppCompatActivity(){
sharedViewModel.spotifyService.value = spotifyService sharedViewModel.spotifyService.value = spotifyService
} }
private fun getSpotifyToken(){
val httpClient2: OkHttpClient.Builder = OkHttpClient.Builder()
httpClient2.addInterceptor(object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request =
chain.request().newBuilder().addHeader(
"Authorization",
"Basic ${android.util.Base64.encodeToString("$clientId:$clientSecret".toByteArray(),android.util.Base64.NO_WRAP)}"
).build()
return chain.proceed(request)
}
})
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit2 = Retrofit.Builder()
.baseUrl("https://accounts.spotify.com/")
.client(httpClient2.build())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
spotifyServiceToken = retrofit2.create(SpotifyServiceToken::class.java)
}
fun authenticateSpotify() { fun authenticateSpotify() {
if (spotifyServiceToken == null) {
getSpotifyToken()
}
sharedViewModel.uiScope.launch { sharedViewModel.uiScope.launch {
if (isConnected) { if (isConnected) {
Log.i("Post Request", "Made") Log.i("Post Request", "Made")
token = spotifyServiceToken!!.getToken()!!.access_token token = spotifyServiceTokenRequest.getToken()!!.access_token
implementSpotifyService(token) implementSpotifyService(token)
Log.i("Post Request", token) Log.i("Post Request", token)
sharedViewModel.accessToken.value = token sharedViewModel.accessToken.value = token
saveToken(token)
}else{ }else{
Log.i("network", "unavailable") Log.i("network", "unavailable")
// sharedViewModel.showAlertDialog(resources,this@MainActivity) // sharedViewModel.showAlertDialog(resources,this@MainActivity)
@ -217,15 +174,6 @@ class MainActivity : AppCompatActivity(){
} }
} }
private fun saveToken(token:String) {
with (sharedPref?.edit()) {
this?.let {
putString("token", token)
putLong("time",(System.currentTimeMillis()/1000/60/60))
commit()
}
}
}
private fun handleIntentFromExternalActivity() { private fun handleIntentFromExternalActivity() {
if (intent?.action == Intent.ACTION_SEND) { if (intent?.action == Intent.ACTION_SEND) {
@ -238,13 +186,6 @@ class MainActivity : AppCompatActivity(){
} }
} }
private fun isOnline(): Boolean {
val cm =
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = cm.activeNetworkInfo
return netInfo != null && netInfo.isConnectedOrConnecting
}
private fun requestPermission() { private fun requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions( requestPermissions(
@ -267,22 +208,7 @@ class MainActivity : AppCompatActivity(){
} }
} }
private fun setUpi() { private fun createDirectories() {
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(SpotifyDownloadHelper.defaultDir) createDirectory(SpotifyDownloadHelper.defaultDir)
createDirectory(SpotifyDownloadHelper.defaultDir+".Images/") createDirectory(SpotifyDownloadHelper.defaultDir+".Images/")
createDirectory(SpotifyDownloadHelper.defaultDir+"Tracks/") createDirectory(SpotifyDownloadHelper.defaultDir+"Tracks/")
@ -308,55 +234,4 @@ class MainActivity : AppCompatActivity(){
} }
appUpdater.start() appUpdater.start()
} }
/*
private fun authenticateSpotify() {
val builder = AuthenticationRequest.Builder(clientId,AuthenticationResponse.Type.TOKEN,redirectUri)
.setScopes(arrayOf("user-read-private"))
// .setScopes(arrayOf("user-read-private","streaming","user-read-email","user-modify-playback-state","user-top-read","user-library-modify","user-read-currently-playing","user-library-read","user-read-recently-played"))
val request: AuthenticationRequest = builder.build()
AuthenticationClient.openLoginActivity(this, LoginActivity.REQUEST_CODE, request)
}*/
/*override fun onActivityResult(
requestCode: Int,
resultCode: Int,
intent: Intent?
) {
super.onActivityResult(requestCode, resultCode, intent)
// Check if result comes from the correct activity
if (requestCode == LoginActivity.REQUEST_CODE) {
val response = AuthenticationClient.getResponse(resultCode, intent)
when (response.type) {
AuthenticationResponse.Type.TOKEN -> {
Log.i("Network",response.accessToken.toString())
token = response.accessToken
sharedViewModel.accessToken = response.accessToken
//Implementing My Own Spotify Requests
implementSpotifyService(token)
sharedViewModel.uiScope.launch {
val me = spotifyService?.getMe()?.display_name
sharedViewModel.userName.value = "Logged in as: $me"
Log.i("Network","Hello, " + me!!)
}
sharedViewModel.userName.observe(this, Observer {
binding.message.text = it
})
}
AuthenticationResponse.Type.ERROR -> {
Log.i("Network",response.error.toString())
}
else -> {
Log.i("Network","Something Weird Happened While Authenticating")
}
}
}
}
*/
} }

View File

@ -19,13 +19,12 @@ package com.shabinder.spotiflyer
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.net.ConnectivityManager
import android.os.Environment 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.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.shabinder.spotiflyer.utils.SpotifyService import com.shabinder.spotiflyer.utils.SpotifyService
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
@ -34,8 +33,6 @@ import java.io.File
class SharedViewModel : ViewModel() { class SharedViewModel : ViewModel() {
var intentString = "" var intentString = ""
var spotifyService = MutableLiveData<SpotifyService>() var spotifyService = MutableLiveData<SpotifyService>()
var ytDownloader = MutableLiveData<YoutubeDownloader>()
var easyUpiPayment: EasyUpiPayment? = null
var accessToken = MutableLiveData<String>().apply { value = "" } var accessToken = MutableLiveData<String>().apply { value = "" }
var isConnected = MutableLiveData<Boolean>().apply { value = false } var isConnected = MutableLiveData<Boolean>().apply { value = false }
val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator + ".Images" + File.separator val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator + ".Images" + File.separator
@ -59,4 +56,10 @@ class SharedViewModel : ViewModel() {
} }
.show() .show()
} }
fun isOnline(context: Context): Boolean {
val cm =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = cm.activeNetworkInfo
return netInfo != null && netInfo.isConnectedOrConnecting
}
} }

View File

@ -25,7 +25,7 @@ interface DatabaseDAO {
suspend fun insert(record: DownloadRecord) suspend fun insert(record: DownloadRecord)
@Update @Update
fun update(record: DownloadRecord) suspend fun update(record: DownloadRecord)
@Query("SELECT * from download_record_table ORDER BY id DESC") @Query("SELECT * from download_record_table ORDER BY id DESC")
suspend fun getRecord():List<DownloadRecord> suspend fun getRecord():List<DownloadRecord>

View File

@ -27,7 +27,7 @@ import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
@Entity( @Entity(
tableName = "download_record_table", tableName = "download_record_table",
indices = [Index(value = ["id","link"], unique = true)] indices = [Index(value = ["link"], unique = true)]
) )
data class DownloadRecord( data class DownloadRecord(

View File

@ -22,6 +22,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.os.Handler
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.view.animation.AlphaAnimation import android.view.animation.AlphaAnimation
@ -126,13 +127,19 @@ object SpotifyDownloadHelper {
isBrowserLoading = false isBrowserLoading = false
listProcessed = true listProcessed = true
} }
}else{//YT List Empty....Maybe it was one Single Download
Handler().postDelayed({//Delay of 1.5 sec
if(youtubeList.isEmpty()){//Lets Make It sure , There are No more Downloads In Queue.....
isBrowserLoading = false
listProcessed = true
}
},1500)
} }
} }
} ) } )
} }
} }
} }
} }
private fun updateStatusBar() { private fun updateStatusBar() {

View File

@ -0,0 +1,71 @@
/*
* 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.recyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.navigation.findNavController
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.databinding.DownloadRecordItemBinding
import com.shabinder.spotiflyer.ui.downloadrecord.DownloadRecordFragmentDirections
import com.shabinder.spotiflyer.utils.bindImage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class DownloadRecordAdapter: ListAdapter<DownloadRecord,DownloadRecordAdapter.ViewHolder>(DownloadRecordDiffCallback()) {
private val adapterScope = CoroutineScope(Dispatchers.Default)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding =DownloadRecordItemBinding.inflate(layoutInflater)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
adapterScope.launch {
bindImage(holder.binding.coverUrl,item.coverUrl)
}
holder.binding.itemName.text = item.name
holder.binding.totalItems.text = "Tracks: ${item.totalFiles}"
holder.binding.type.text = item.type
holder.binding.btnAction.setOnClickListener {
if (item.link.contains("spotify",true)){
it.findNavController().navigate(DownloadRecordFragmentDirections.actionDownloadRecordToSpotifyFragment((item.link)))
}else if(item.link.contains("youtube.com",true) || item.link.contains("youtu.be",true) ){
it.findNavController().navigate(DownloadRecordFragmentDirections.actionDownloadRecordToYoutubeFragment(item.link))
}
}
}
class ViewHolder(val binding: DownloadRecordItemBinding) : RecyclerView.ViewHolder(binding.root)
}
class DownloadRecordDiffCallback: DiffUtil.ItemCallback<DownloadRecord>(){
override fun areItemsTheSame(oldItem: DownloadRecord, newItem: DownloadRecord): Boolean {
return oldItem.coverUrl == newItem.coverUrl
}
override fun areContentsTheSame(oldItem: DownloadRecord, newItem: DownloadRecord): Boolean {
return oldItem == newItem
}
}

View File

@ -24,6 +24,7 @@ import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.databinding.TrackListItemBinding import com.shabinder.spotiflyer.databinding.TrackListItemBinding
import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper.context import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper.context
@ -37,15 +38,14 @@ import kotlinx.coroutines.launch
class SpotifyTrackListAdapter: ListAdapter<Track,SpotifyTrackListAdapter.ViewHolder>(SpotifyTrackDiffCallback()) { class SpotifyTrackListAdapter: ListAdapter<Track,SpotifyTrackListAdapter.ViewHolder>(SpotifyTrackDiffCallback()) {
var spotifyViewModel = SpotifyViewModel() var spotifyViewModel : SpotifyViewModel? = null
var isAlbum:Boolean = false var isAlbum:Boolean = false
var ytDownloader: YoutubeDownloader? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context) val layoutInflater = LayoutInflater.from(parent.context)
val binding = TrackListItemBinding.inflate(layoutInflater,parent,false) val binding = TrackListItemBinding.inflate(layoutInflater,parent,false)
// val view = layoutInflater.inflate(R.layout.track_list_item,parent,false)
return ViewHolder(binding) return ViewHolder(binding)
} }
@ -53,7 +53,7 @@ class SpotifyTrackListAdapter: ListAdapter<Track,SpotifyTrackListAdapter.ViewHol
val item = getItem(position) val item = getItem(position)
if(itemCount ==1 || isAlbum){ if(itemCount ==1 || isAlbum){
holder.binding.imageUrl.visibility = View.GONE}else{ holder.binding.imageUrl.visibility = View.GONE}else{
spotifyViewModel.uiScope.launch { spotifyViewModel!!.uiScope.launch {
bindImage(holder.binding.imageUrl, item.album!!.images?.get(0)?.url) bindImage(holder.binding.imageUrl, item.album!!.images?.get(0)?.url)
} }
} }
@ -78,12 +78,12 @@ class SpotifyTrackListAdapter: ListAdapter<Track,SpotifyTrackListAdapter.ViewHol
holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh) holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh)
rotateAnim(it) rotateAnim(it)
item.downloaded = "Downloading" item.downloaded = "Downloading"
spotifyViewModel.uiScope.launch { spotifyViewModel!!.uiScope.launch {
val itemList = mutableListOf<Track>() val itemList = mutableListOf<Track>()
itemList.add(item) itemList.add(item)
downloadAllTracks(spotifyViewModel.folderType,spotifyViewModel.subFolder,itemList,spotifyViewModel.ytDownloader) downloadAllTracks(spotifyViewModel!!.folderType,spotifyViewModel!!.subFolder,itemList,ytDownloader)
} }
notifyItemChanged(position) notifyItemChanged(position)//start showing anim!
} }
} }
} }
@ -99,5 +99,4 @@ class SpotifyTrackDiffCallback: DiffUtil.ItemCallback<Track>(){
override fun areContentsTheSame(oldItem: Track, newItem: Track): Boolean { override fun areContentsTheSame(oldItem: Track, newItem: Track): Boolean {
return oldItem == newItem //Downloaded Check return oldItem == newItem //Downloaded Check
} }
} }

View File

@ -23,17 +23,18 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import com.github.kiulian.downloader.model.formats.Format import com.github.kiulian.downloader.model.formats.Format
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.TrackListItemBinding import com.shabinder.spotiflyer.databinding.TrackListItemBinding
import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper
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.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class YoutubeTrackListAdapter: ListAdapter<Track,SpotifyTrackListAdapter.ViewHolder>(YouTubeTrackDiffCallback()) { class YoutubeTrackListAdapter: ListAdapter<Track,SpotifyTrackListAdapter.ViewHolder>(YouTubeTrackDiffCallback()) {
var format:Format? = null var format:Format? = null
var sharedViewModel = SharedViewModel() private val adapterScope = CoroutineScope(Dispatchers.Default)
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
@ -49,7 +50,7 @@ class YoutubeTrackListAdapter: ListAdapter<Track,SpotifyTrackListAdapter.ViewHol
val item = getItem(position) val item = getItem(position)
if(itemCount == 1){ if(itemCount == 1){
holder.binding.imageUrl.visibility = View.GONE}else{ holder.binding.imageUrl.visibility = View.GONE}else{
sharedViewModel.uiScope.launch { adapterScope.launch {
bindImage(holder.binding.imageUrl, item.ytCoverUrl) bindImage(holder.binding.imageUrl, item.ytCoverUrl)
} }
} }
@ -58,7 +59,7 @@ class YoutubeTrackListAdapter: ListAdapter<Track,SpotifyTrackListAdapter.ViewHol
holder.binding.artist.text = "${item.artists?.get(0)?.name?:""}..." holder.binding.artist.text = "${item.artists?.get(0)?.name?:""}..."
holder.binding.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec" holder.binding.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec"
holder.binding.btnDownload.setOnClickListener{ holder.binding.btnDownload.setOnClickListener{
sharedViewModel.uiScope.launch { adapterScope.launch {
YTDownloadHelper.downloadFile(null,"YT_Downloads",item,format) YTDownloadHelper.downloadFile(null,"YT_Downloads",item,format)
} }
} }
@ -72,5 +73,4 @@ class YouTubeTrackDiffCallback: DiffUtil.ItemCallback<Track>(){
override fun areContentsTheSame(oldItem: Track, newItem: Track): Boolean { override fun areContentsTheSame(oldItem: Track, newItem: Track): Boolean {
return oldItem == newItem return oldItem == newItem
} }
} }

View File

@ -31,7 +31,7 @@ class SplashScreen : AppCompatActivity(){
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.splash_screen) setContentView(R.layout.splash_screen)
val splashTimeout = 500 val splashTimeout = 400
val homeIntent = Intent(this@SplashScreen, MainActivity::class.java) val homeIntent = Intent(this@SplashScreen, MainActivity::class.java)
Handler().postDelayed({ Handler().postDelayed({
//TODO:Bring Initial Setup here //TODO:Bring Initial Setup here

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2020 Shabinder Singh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.spotiflyer.ui.downloadrecord
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.tabs.TabLayout
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.databinding.DownloadRecordFragmentBinding
import com.shabinder.spotiflyer.recyclerView.DownloadRecordAdapter
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class DownloadRecordFragment : Fragment() {
private lateinit var downloadRecordViewModel: DownloadRecordViewModel
private lateinit var binding: DownloadRecordFragmentBinding
private lateinit var adapter: DownloadRecordAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater,R.layout.download_record_fragment,container,false)
downloadRecordViewModel = ViewModelProvider(this).get(DownloadRecordViewModel::class.java)
adapter = DownloadRecordAdapter()
binding.downloadRecordList.adapter = adapter
downloadRecordViewModel.downloadRecordList.observe(viewLifecycleOwner, Observer {
if(it.isNotEmpty()){
downloadRecordViewModel.spotifyList = mutableListOf()
downloadRecordViewModel.ytList = mutableListOf()
for (downloadRecord in it) {
if(downloadRecord.link.contains("spotify",true)) downloadRecordViewModel.spotifyList.add(downloadRecord)
else downloadRecordViewModel.ytList.add(downloadRecord)
}
if(binding.tabLayout.selectedTabPosition == 0) adapter.submitList(downloadRecordViewModel.spotifyList)
else adapter.submitList(downloadRecordViewModel.ytList)
// adapter.notifyDataSetChanged()
}
})
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
if(tab?.text == "Spotify"){
adapter.submitList(downloadRecordViewModel.spotifyList)
} else adapter.submitList(downloadRecordViewModel.ytList)
// adapter.notifyDataSetChanged()
}
override fun onTabReselected(tab: TabLayout.Tab?) {
// Handle tab reselect
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
// Handle tab unselect
}
})
return binding.root
}
}

View 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/>.
*/
package com.shabinder.spotiflyer.ui.downloadrecord
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class DownloadRecordViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) :
ViewModel(){
private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
var spotifyList = mutableListOf<DownloadRecord>()
var ytList = mutableListOf<DownloadRecord>()
val downloadRecordList = MutableLiveData<MutableList<DownloadRecord>>().apply {
value = mutableListOf()
}
init {
getDownloadRecordList()
}
private fun getDownloadRecordList() {
uiScope.launch {
downloadRecordList.postValue(databaseDAO.getRecord().toMutableList())
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}

View File

@ -34,12 +34,17 @@ import androidx.navigation.fragment.findNavController
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.shreyaspatil.EasyUpiPayment.EasyUpiPayment
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class MainFragment : Fragment() { class MainFragment : Fragment() {
private lateinit var viewModel: MainViewModel private lateinit var mainViewModel: MainViewModel
private lateinit var sharedViewModel: SharedViewModel private lateinit var sharedViewModel: SharedViewModel
private lateinit var binding: MainFragmentBinding private lateinit var binding: MainFragmentBinding
@Inject lateinit var easyUpiPayment: EasyUpiPayment
override fun onCreateView( override fun onCreateView(
@ -47,7 +52,6 @@ class MainFragment : Fragment() {
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)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
initializeAll() initializeAll()
binding.btnSearch.setOnClickListener { binding.btnSearch.setOnClickListener {
@ -62,18 +66,26 @@ class MainFragment : Fragment() {
return binding.root return binding.root
} }
private fun initializeAll() { private fun initializeAll() {
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java) sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
setUpUsageText()
openYTButton() openYTButton()
openSpotifyButton() openSpotifyButton()
openGithubButton() openGithubButton()
openInstaButton() openInstaButton()
openLinkedInButton() openLinkedInButton()
historyButton()
binding.usage.text = usageText()
binding.btnDonate.setOnClickListener { binding.btnDonate.setOnClickListener {
sharedViewModel.easyUpiPayment?.startPayment() easyUpiPayment.startPayment()
}
} }
private fun historyButton() {
binding.btnHistory.setOnClickListener {
findNavController().navigate(MainFragmentDirections.actionMainFragmentToDownloadRecord())
}
} }
/** /**
@ -92,16 +104,6 @@ class MainFragment : Fragment() {
}) })
} }
private fun setUpUsageText() {
val spanStringBuilder = SpannableStringBuilder()
spanStringBuilder.append(getText(R.string.d_one)).append("\n")
spanStringBuilder.append(getText(R.string.d_two)).append("\n")
spanStringBuilder.append(getText(R.string.d_three)).append("\n")
spanStringBuilder.append(getText(R.string.d_four)).append("\n")
binding.usage.text = spanStringBuilder
}
/** /**
* Implementing buttons * Implementing buttons
**/ **/
@ -161,5 +163,11 @@ class MainFragment : Fragment() {
startActivity(intent) startActivity(intent)
} }
} }
private fun usageText(): SpannableStringBuilder {
return SpannableStringBuilder()
.append(getText(R.string.d_one)).append("\n")
.append(getText(R.string.d_two)).append("\n")
.append(getText(R.string.d_three)).append("\n")
.append(getText(R.string.d_four)).append("\n")
}
} }

View File

@ -17,8 +17,11 @@
package com.shabinder.spotiflyer.ui.mainfragment package com.shabinder.spotiflyer.ui.mainfragment
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.shabinder.spotiflyer.database.DatabaseDAO
class MainViewModel : ViewModel() { class MainViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) :
// TODO: Implement the ViewModel ViewModel(){
//TODO Refactoring Code Up here
} }

View File

@ -41,6 +41,7 @@ import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.github.kiulian.downloader.YoutubeDownloader
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
@ -51,18 +52,23 @@ import com.shabinder.spotiflyer.recyclerView.SpotifyTrackListAdapter
import com.shabinder.spotiflyer.utils.bindImage import com.shabinder.spotiflyer.utils.bindImage
import com.shabinder.spotiflyer.utils.copyTo import com.shabinder.spotiflyer.utils.copyTo
import com.shabinder.spotiflyer.utils.rotateAnim import com.shabinder.spotiflyer.utils.rotateAnim
import dagger.hilt.android.AndroidEntryPoint
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.File
import java.io.IOException import java.io.IOException
import javax.inject.Inject
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@AndroidEntryPoint
class SpotifyFragment : Fragment() { class SpotifyFragment : Fragment() {
private lateinit var binding:SpotifyFragmentBinding private lateinit var binding:SpotifyFragmentBinding
private lateinit var spotifyViewModel: SpotifyViewModel private lateinit var spotifyViewModel: SpotifyViewModel
private lateinit var sharedViewModel: SharedViewModel private lateinit var sharedViewModel: SharedViewModel
private lateinit var adapterSpotify:SpotifyTrackListAdapter private lateinit var adapterSpotify:SpotifyTrackListAdapter
@Inject lateinit var ytDownloader:YoutubeDownloader
private var webView: WebView? = null private var webView: WebView? = null
private var intentFilter:IntentFilter? = null private var intentFilter:IntentFilter? = null
private var updateUIReceiver: BroadcastReceiver? = null private var updateUIReceiver: BroadcastReceiver? = null
@ -87,7 +93,7 @@ class SpotifyFragment : Fragment() {
Log.i("Fragment", "$type : $link") Log.i("Fragment", "$type : $link")
if(sharedViewModel.spotifyService.value == null && isNotOnline()){//Authentication pending!! if(sharedViewModel.spotifyService.value == null){//Authentication pending!!
(activity as MainActivity).authenticateSpotify() (activity as MainActivity).authenticateSpotify()
} }
if(!isNotOnline()){//Device Offline if(!isNotOnline()){//Device Offline
@ -122,7 +128,7 @@ class SpotifyFragment : Fragment() {
spotifyViewModel.folderType, spotifyViewModel.folderType,
spotifyViewModel.subFolder, spotifyViewModel.subFolder,
spotifyViewModel.trackList.value!!, spotifyViewModel.trackList.value!!,
spotifyViewModel.ytDownloader ytDownloader
) )
} }
} }
@ -219,9 +225,6 @@ class SpotifyFragment : Fragment() {
sharedViewModel.spotifyService.observe(viewLifecycleOwner, Observer { sharedViewModel.spotifyService.observe(viewLifecycleOwner, Observer {
spotifyViewModel.spotifyService = it spotifyViewModel.spotifyService = it
}) })
sharedViewModel.ytDownloader.observe(viewLifecycleOwner, Observer {
spotifyViewModel.ytDownloader = it
})
SpotifyDownloadHelper.webView = binding.webViewSpotify SpotifyDownloadHelper.webView = binding.webViewSpotify
SpotifyDownloadHelper.context = requireContext() SpotifyDownloadHelper.context = requireContext()
SpotifyDownloadHelper.spotifyViewModel = spotifyViewModel SpotifyDownloadHelper.spotifyViewModel = spotifyViewModel
@ -283,7 +286,7 @@ class SpotifyFragment : Fragment() {
* Configure Recycler View Adapter * Configure Recycler View Adapter
**/ **/
private fun adapterConfig(trackList: List<Track>){ private fun adapterConfig(trackList: List<Track>){
adapterSpotify.spotifyFragment = this adapterSpotify.ytDownloader = ytDownloader
adapterSpotify.spotifyViewModel = spotifyViewModel adapterSpotify.spotifyViewModel = spotifyViewModel
adapterSpotify.submitList(trackList) adapterSpotify.submitList(trackList)
} }

View File

@ -18,21 +18,21 @@
package com.shabinder.spotiflyer.ui.spotify package com.shabinder.spotiflyer.ui.spotify
import android.util.Log import android.util.Log
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.github.kiulian.downloader.YoutubeDownloader import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.Album import com.shabinder.spotiflyer.models.Album
import com.shabinder.spotiflyer.models.Playlist import com.shabinder.spotiflyer.models.Playlist
import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.utils.SpotifyService import com.shabinder.spotiflyer.utils.SpotifyService
import com.shabinder.spotiflyer.utils.finalOutputDir import com.shabinder.spotiflyer.utils.finalOutputDir
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.io.File import java.io.File
class SpotifyViewModel: ViewModel() { class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) :
ViewModel(){
var folderType:String = "" var folderType:String = ""
var subFolder:String = "" var subFolder:String = ""
@ -41,7 +41,6 @@ class SpotifyViewModel: ViewModel() {
var title = MutableLiveData<String>().apply { value = loading } var title = MutableLiveData<String>().apply { value = loading }
var coverUrl = MutableLiveData<String>().apply { value = loading } var coverUrl = MutableLiveData<String>().apply { value = loading }
var spotifyService : SpotifyService? = null var spotifyService : SpotifyService? = null
var ytDownloader : YoutubeDownloader? = null
private var viewModelJob = Job() private var viewModelJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
@ -61,6 +60,17 @@ class SpotifyViewModel: ViewModel() {
trackList.value = tempTrackList trackList.value = tempTrackList
title.value = trackObject.name title.value = trackObject.name
coverUrl.value = trackObject.album!!.images?.get(0)!!.url!! coverUrl.value = trackObject.album!!.images?.get(0)!!.url!!
withContext(Dispatchers.IO){
databaseDAO.insert(DownloadRecord(
type = "Track",
name = title.value!!,
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value!!,
totalFiles = tempTrackList.size,
downloaded = trackObject.downloaded =="Downloaded",
directory = finalOutputDir(trackObject.name!!,folderType,subFolder)
))
}
} }
} }
@ -79,6 +89,17 @@ class SpotifyViewModel: ViewModel() {
trackList.value = tempTrackList trackList.value = tempTrackList
title.value = albumObject.name title.value = albumObject.name
coverUrl.value = albumObject.images?.get(0)!!.url!! coverUrl.value = albumObject.images?.get(0)!!.url!!
withContext(Dispatchers.IO){
databaseDAO.insert(DownloadRecord(
type = "Album",
name = title.value!!,
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value!!,
totalFiles = tempTrackList.size,
downloaded = File(finalOutputDir(type = folderType,subFolder = subFolder)).listFiles()?.size == tempTrackList.size,
directory = finalOutputDir(type = folderType,subFolder = subFolder)
))
}
} }
} }
@ -92,17 +113,24 @@ class SpotifyViewModel: ViewModel() {
it.track?.let { it.track?.let {
it1 -> if(File(finalOutputDir(it1.name!!,folderType,subFolder)).exists()){//Download Already Present!! it1 -> if(File(finalOutputDir(it1.name!!,folderType,subFolder)).exists()){//Download Already Present!!
it1.downloaded = "Downloaded" it1.downloaded = "Downloaded"
Log.i("ViewModel123","${it1.name} Downloaded")
} }
tempTrackList.add(it1) tempTrackList.add(it1)
} }
} }
Log.i("ViewModel",tempTrackList.size.toString())
Log.i("ViewModel",playlistObject.tracks?.items?.size.toString())
trackList.value = tempTrackList trackList.value = tempTrackList
title.value = playlistObject.name title.value = playlistObject.name
coverUrl.value = playlistObject.images?.get(0)!!.url!! coverUrl.value = playlistObject.images?.get(0)!!.url!!
withContext(Dispatchers.IO){
databaseDAO.insert(DownloadRecord(
type = "Playlist",
name = title.value!!,
link = "https://open.spotify.com/$type/$link",
coverUrl = coverUrl.value!!,
totalFiles = tempTrackList.size,
downloaded = File(finalOutputDir(type = folderType,subFolder = subFolder)).listFiles()?.size == tempTrackList.size,
directory = finalOutputDir(type = folderType,subFolder = subFolder)
))
}
} }
} }
"episode" -> {//TODO "episode" -> {//TODO

View File

@ -17,8 +17,6 @@
package com.shabinder.spotiflyer.ui.youtube package com.shabinder.spotiflyer.ui.youtube
import android.content.Context
import android.net.ConnectivityManager
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -28,6 +26,7 @@ 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.github.kiulian.downloader.YoutubeDownloader
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.YoutubeFragmentBinding import com.shabinder.spotiflyer.databinding.YoutubeFragmentBinding
@ -35,7 +34,10 @@ import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper
import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.recyclerView.YoutubeTrackListAdapter import com.shabinder.spotiflyer.recyclerView.YoutubeTrackListAdapter
import com.shabinder.spotiflyer.utils.bindImage import com.shabinder.spotiflyer.utils.bindImage
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class YoutubeFragment : Fragment() { class YoutubeFragment : Fragment() {
private lateinit var binding:YoutubeFragmentBinding private lateinit var binding:YoutubeFragmentBinding
@ -44,7 +46,7 @@ class YoutubeFragment : Fragment() {
private lateinit var adapter : YoutubeTrackListAdapter private lateinit var adapter : YoutubeTrackListAdapter
private val sampleDomain1 = "youtube.com" private val sampleDomain1 = "youtube.com"
private val sampleDomain2 = "youtu.be" private val sampleDomain2 = "youtu.be"
@Inject lateinit var ytDownloader: YoutubeDownloader
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@ -57,9 +59,7 @@ class YoutubeFragment : Fragment() {
YTDownloadHelper.context = requireContext() YTDownloadHelper.context = requireContext()
YTDownloadHelper.statusBar = binding.StatusBarYoutube YTDownloadHelper.statusBar = binding.StatusBarYoutube
binding.trackListYoutube.adapter = adapter binding.trackListYoutube.adapter = adapter
sharedViewModel.ytDownloader.observe(viewLifecycleOwner, Observer {
youtubeViewModel.ytDownloader = it
})
initializeLiveDataObservers() initializeLiveDataObservers()
val args = YoutubeFragmentArgs.fromBundle(requireArguments()) val args = YoutubeFragmentArgs.fromBundle(requireArguments())
@ -79,10 +79,10 @@ class YoutubeFragment : Fragment() {
searchId = link.substringAfterLast("/","error") searchId = link.substringAfterLast("/","error")
} }
if(searchId != "error") { if(searchId != "error") {
// val coverUrl = "https://i.ytimg.com/vi/$searchId/maxresdefault.jpg" youtubeViewModel.getYTTrack(searchId,ytDownloader)
youtubeViewModel.getYTTrack(searchId)
binding.btnDownloadAllYoutube.setOnClickListener { binding.btnDownloadAllYoutube.setOnClickListener {
//TODO YTDownloadHelper.downloadFile(null,"YT_Downloads",
youtubeViewModel.ytTrack.value!!,youtubeViewModel.format.value)
} }
}else{showToast("Your Youtube Link is not of a Video!!")} }else{showToast("Your Youtube Link is not of a Video!!")}
}else(showToast("Your Youtube Link is not of a Video!!")) }else(showToast("Your Youtube Link is not of a Video!!"))
@ -122,7 +122,6 @@ class YoutubeFragment : Fragment() {
* Configure Recycler View Adapter * Configure Recycler View Adapter
**/ **/
private fun adapterConfig(list:List<Track>){ private fun adapterConfig(list:List<Track>){
adapter.sharedViewModel = sharedViewModel
adapter.submitList(list) adapter.submitList(list)
} }
@ -133,13 +132,4 @@ class YoutubeFragment : Fragment() {
Toast.makeText(context,message, Toast.LENGTH_SHORT).show() Toast.makeText(context,message, Toast.LENGTH_SHORT).show()
} }
/**
* Util. Function To Check Connection Status
**/
private fun isNotOnline(): Boolean {
val cm =
requireActivity().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = cm.activeNetworkInfo
return netInfo != null && netInfo.isConnectedOrConnecting
}
} }

View File

@ -18,34 +18,37 @@
package com.shabinder.spotiflyer.ui.youtube package com.shabinder.spotiflyer.ui.youtube
import android.util.Log import android.util.Log
import androidx.hilt.lifecycle.ViewModelInject
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
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.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecord
import com.shabinder.spotiflyer.models.Artist import com.shabinder.spotiflyer.models.Artist
import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.utils.finalOutputDir
import kotlinx.coroutines.* import kotlinx.coroutines.*
class YoutubeViewModel : ViewModel() { class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) :
ViewModel(){
val ytTrack = MutableLiveData<Track>() val ytTrack = MutableLiveData<Track>()
val format = MutableLiveData<Format>() val format = MutableLiveData<Format>()
private val loading = "Loading" private val loading = "Loading"
var title = MutableLiveData<String>().apply { value = "\"Loading!\"" } var title = MutableLiveData<String>().apply { value = "\"Loading!\"" }
var coverUrl = MutableLiveData<String>().apply { value = loading } var coverUrl = MutableLiveData<String>().apply { value = loading }
var ytDownloader: YoutubeDownloader? = null
private var viewModelJob = Job() private var viewModelJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun getYTTrack(searchId:String) { fun getYTTrack(searchId:String,ytDownloader:YoutubeDownloader) {
uiScope.launch { uiScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
Log.i("YT View Model",searchId) Log.i("YT View Model",searchId)
val video = ytDownloader?.getVideo(searchId) val video = ytDownloader.getVideo(searchId)
val detail = video?.details() val detail = video?.details()
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title()
Log.i("YT View Model",detail.toString()) Log.i("YT View Model",detail.toString())
@ -75,6 +78,17 @@ class YoutubeViewModel : ViewModel() {
} }
} }
}) })
withContext(Dispatchers.IO){
databaseDAO.insert(DownloadRecord(
type = "Track",
name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name},
link = "https://www.youtube.com/watch?v=$searchId",
coverUrl = "https://i.ytimg.com/vi/$searchId/maxresdefault.jpg",
totalFiles = 1,
downloaded = false,
directory = finalOutputDir(type = "YT_Downloads")
))
}
} }
} }
} }

View File

@ -43,11 +43,11 @@ import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.IOException import java.io.IOException
fun finalOutputDir(itemName:String,type:String, subFolder:String?=null): String{ fun finalOutputDir(itemName:String? = null,type:String, subFolder:String?=null,extension:String? = ".mp3"): String{
return Environment.getExternalStorageDirectory().toString() + File.separator + return Environment.getExternalStorageDirectory().toString() + File.separator +
SpotifyDownloadHelper.defaultDir + SpotifyDownloadHelper.removeIllegalChars(type) + File.separator + SpotifyDownloadHelper.defaultDir + SpotifyDownloadHelper.removeIllegalChars(type) + File.separator +
(if(subFolder == null){""}else{ SpotifyDownloadHelper.removeIllegalChars(subFolder) + File.separator} (if(subFolder == null){""}else{ SpotifyDownloadHelper.removeIllegalChars(subFolder) + File.separator}
+ SpotifyDownloadHelper.removeIllegalChars(itemName) +".mp3") + itemName?.let { SpotifyDownloadHelper.removeIllegalChars(it) + extension})
} }
fun rotateAnim(view: View){ fun rotateAnim(view: View){

View File

@ -0,0 +1,104 @@
/*
* Copyright (C) 2020 Shabinder Singh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.spotiflyer.utils
import android.content.Context
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.spotiflyer.App
import com.shabinder.spotiflyer.MainActivity
import com.shabinder.spotiflyer.database.DatabaseDAO
import com.shabinder.spotiflyer.database.DownloadRecordDatabase
import com.shreyaspatil.EasyUpiPayment.EasyUpiPayment
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ApplicationComponent
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Singleton
@InstallIn(ApplicationComponent::class)
@Module
object Provider {
@Provides
fun databaseDAO(@ApplicationContext appContext: Context):DatabaseDAO{
return DownloadRecordDatabase.getInstance(appContext).databaseDAO
}
@Provides
@Singleton
fun provideUpi():EasyUpiPayment {
return EasyUpiPayment.Builder()
.with(MainActivity.getInstance())
.setPayeeVpa("technoshab@paytm")
.setPayeeName("Shabinder Singh")
.setTransactionId("UNIQUE_TRANSACTION_ID")
.setTransactionRefId("UNIQUE_TRANSACTION_REF_ID")
.setDescription("Thanks for donating")
.setAmount("39.00")
.build()
}
@Provides
@Singleton
fun getMoshi():Moshi{
return Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
}
@Provides
@Singleton
fun getYTDownloader():YoutubeDownloader{
return YoutubeDownloader()
}
@Provides
@Singleton
fun getSpotifyTokenInterface():SpotifyServiceTokenRequest{
val httpClient2: OkHttpClient.Builder = OkHttpClient.Builder()
httpClient2.addInterceptor(object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request =
chain.request().newBuilder().addHeader(
"Authorization",
"Basic ${android.util.Base64.encodeToString("${App.clientId}:${App.clientSecret}".toByteArray(),android.util.Base64.NO_WRAP)}"
).build()
return chain.proceed(request)
}
})
val retrofit = Retrofit.Builder()
.baseUrl("https://accounts.spotify.com/")
.client(httpClient2.build())
.addConverterFactory(MoshiConverterFactory.create(getMoshi()))
.build()
return retrofit.create(SpotifyServiceTokenRequest::class.java)
}
}

View File

@ -17,7 +17,10 @@
package com.shabinder.spotiflyer.utils package com.shabinder.spotiflyer.utils
import com.shabinder.spotiflyer.models.* import com.shabinder.spotiflyer.models.Album
import com.shabinder.spotiflyer.models.Playlist
import com.shabinder.spotiflyer.models.Token
import com.shabinder.spotiflyer.models.Track
import retrofit2.http.* import retrofit2.http.*
/* /*
@ -54,13 +57,9 @@ interface SpotifyService {
@GET("albums/{id}") @GET("albums/{id}")
suspend fun getAlbum(@Path("id") albumId: String?): Album suspend fun getAlbum(@Path("id") albumId: String?): Album
@GET("me")
suspend fun getMe(): UserPrivate?
} }
interface SpotifyServiceToken{ interface SpotifyServiceTokenRequest{
@POST("api/token") @POST("api/token")
@FormUrlEncoded @FormUrlEncoded

View File

@ -24,10 +24,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.*
import android.os.Environment
import android.os.IBinder
import android.os.PowerManager
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
@ -198,10 +195,12 @@ class ForegroundService : Service(){
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if(downloadMap.isEmpty() && converted == total){ if(downloadMap.isEmpty() && converted == total){
Handler().postDelayed({
Log.i(tag,"Service destroyed.") Log.i(tag,"Service destroyed.")
deleteFile(parentDirectory) deleteFile(parentDirectory)
releaseWakeLock() releaseWakeLock()
stopForeground(true) stopForeground(true)
},2000)
} }
} }
@ -224,11 +223,11 @@ class ForegroundService : Service(){
super.onTaskRemoved(rootIntent) super.onTaskRemoved(rootIntent)
if(downloadMap.isEmpty() && converted == total ){ if(downloadMap.isEmpty() && converted == total ){
Log.i(tag,"Service Removed.") Log.i(tag,"Service Removed.")
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// stopForeground(true) stopForeground(true)
// } else { } else {
stopSelf()//System will automatically close it stopSelf()//System will automatically close it
// } }
} }
} }
@ -243,7 +242,7 @@ class ForegroundService : Service(){
if (file.isDirectory) { if (file.isDirectory) {
deleteFile(file) deleteFile(file)
} else if(file.isFile) { } else if(file.isFile) {
if(file.path.toString().substringAfterLast(".") != "mp3" && file.path.toString().substringAfterLast(".") != "jpeg"){ if(file.path.toString().substringAfterLast(".") != "mp3"){
// Log.i(tag,"deleting ${file.path}") // Log.i(tag,"deleting ${file.path}")
file.delete() file.delete()
} }
@ -427,7 +426,7 @@ class ForegroundService : Service(){
val m4aFile = File(filePath) val m4aFile = File(filePath)
FFmpeg.executeAsync( FFmpeg.executeAsync(
"-i $filePath -b:a 160k -vn ${filePath.substringBeforeLast('.') + ".mp3"}" "-i $filePath -y -b:a 160k -vn ${filePath.substringBeforeLast('.') + ".mp3"}"
) { _, returnCode -> ) { _, returnCode ->
when (returnCode) { when (returnCode) {
RETURN_CODE_SUCCESS -> { RETURN_CODE_SUCCESS -> {

View File

@ -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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:width="38dp" android:height="38dp"
android:viewportWidth="512" android:viewportHeight="512">
<path android:pathData="m48,496a32.036,32.036 0,0 1,-32 -32,351.731 351.731,0 0,1 352,-351.995l-51.2,-38.4a32,32 0,1 1,38.4 -51.2l128,96a32,32 0,0 1,0 51.2l-128,96a31.736,31.736 0,0 1,-19.18 6.395,32 32,0 0,1 -19.22,-57.6l51.2,-38.4a288,288 0,0 0,-288 288,32.036 32.036,0 0,1 -32,32zM368,128a335.73,335.73 0,0 0,-336 336,16 16,0 0,0 32,0 304,304 0,0 1,304 -304h24a8,8 0,0 1,4.8 14.4l-70.4,52.8a16,16 0,1 0,19.2 25.6l128,-96a16,16 0,0 0,0 -25.6l-128,-96a16,16 0,1 0,-19.2 25.6l70.4,52.8a8,8 0,0 1,-4.8 14.4z">
<aapt:attr name="android:fillColor">
<gradient android:endX="256" android:endY="16"
android:startX="256" android:startY="496" android:type="linear">
<item android:color="#8497FA" android:offset="0"/>
<item android:color="#CE1CFF" android:offset="0.6"/>
<item android:color="#FC5C7D" android:offset="0.9"/>
</gradient>
</aapt:attr>
</path>
</vector>

View 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" android:width="38dp"
android:height="38dp" android:viewportWidth="512" android:viewportHeight="512">
<path android:fillColor="#F7F4FBFF" android:pathData="M256,256m-148.886,0a148.886,148.886 0,1 1,297.772 0a148.886,148.886 0,1 1,-297.772 0"/>
<path android:fillColor="#E9E4F6FF" android:pathData="m343.924,135.849c18.061,24.639 28.734,55.033 28.734,87.924 0,82.227 -66.658,148.886 -148.886,148.886 -32.891,0 -63.285,-10.673 -87.924,-28.734 27.091,36.959 70.815,60.962 120.151,60.962 82.227,0 148.886,-66.658 148.886,-148.886 0.001,-49.337 -24.003,-93.061 -60.961,-120.152z"/>
<path android:fillColor="#2b4d66" android:pathData="m256,412.537c-86.315,0 -156.537,-70.222 -156.537,-156.537s70.222,-156.537 156.537,-156.537 156.537,70.222 156.537,156.537 -70.222,156.537 -156.537,156.537zM256,114.765c-77.877,0 -141.234,63.357 -141.234,141.234 0,77.878 63.357,141.235 141.234,141.235s141.234,-63.357 141.234,-141.234 -63.357,-141.235 -141.234,-141.235z"/>
<path android:fillColor="@color/colorPrimary" android:pathData="m256,512c-78.938,0 -152.265,-35.6 -201.178,-97.671l24.037,-18.942c41.992,53.288 104.411,84.42 171.917,85.95 125.599,2.845 228.78,-96.388 230.596,-222.007 0.324,-22.421 -2.625,-44.569 -8.758,-65.858 -0.764,-2.653 0.749,-5.426 3.384,-6.249l19.479,-6.083c2.737,-0.855 5.634,0.717 6.432,3.471 6.698,23.109 10.091,47.098 10.091,71.389 0,68.38 -26.629,132.667 -74.98,181.019 -48.353,48.352 -112.64,74.981 -181.02,74.981z"/>
<path android:fillColor="@color/colorPrimaryDark" android:pathData="m15.922,328.908c-2.743,0.832 -5.628,-0.761 -6.403,-3.52 -6.318,-22.491 -9.519,-45.807 -9.519,-69.388 0,-68.38 26.629,-132.667 74.98,-181.019 48.353,-48.352 112.64,-74.981 181.02,-74.981 78.92,0 152.236,35.586 201.15,97.634l-24.034,18.946c-43.073,-54.639 -107.63,-85.976 -177.116,-85.976 -124.284,0 -225.396,101.112 -225.396,225.396 0,20.658 2.784,41.073 8.281,60.761 0.743,2.66 -0.791,5.422 -3.434,6.223z"/>
<path android:fillColor="@color/colorPrimaryDark" android:pathData="m501.909,184.611c-0.798,-2.754 -3.695,-4.326 -6.432,-3.471l-10,3.123c6.765,23.216 10.192,47.325 10.192,71.737 0,68.38 -26.629,132.667 -74.98,181.019 -46.438,46.439 -107.578,72.824 -172.922,74.841 2.738,0.086 5.481,0.14 8.233,0.14 68.38,0 132.667,-26.629 181.019,-74.981s74.981,-112.639 74.981,-181.019c0,-24.291 -3.393,-48.28 -10.091,-71.389z"/>
<path android:fillColor="@color/colorPrimary" android:pathData="m256,0c-16.62,0 -32.995,1.586 -48.962,4.662 9.424,-1.04 18.958,-1.586 28.578,-1.586 78.92,0 152.236,35.586 201.15,97.634l-10.161,8.01c2.216,2.569 4.397,5.178 6.512,7.86l24.034,-18.946c-48.915,-62.048 -122.231,-97.634 -201.151,-97.634z"/>
<path android:fillColor="@color/colorPrimary" android:pathData="m463.088,129.101h-81.951c-2.817,0 -5.101,-2.284 -5.101,-5.101v-20.403c0,-2.817 2.284,-5.101 5.101,-5.101h57.467v-57.466c0,-2.817 2.284,-5.101 5.101,-5.101h20.403c2.817,0 5.101,2.284 5.101,5.101v81.951c-0.001,3.38 -2.741,6.12 -6.121,6.12z"/>
<path android:fillColor="@color/colorPrimaryDark" android:pathData="m464.108,35.929h-10.064v71.887c0,3.38 -2.74,6.121 -6.121,6.121h-71.887v10.064c0,2.817 2.284,5.101 5.101,5.101h81.951c3.38,0 6.121,-2.74 6.121,-6.121v-81.951c-0.001,-2.817 -2.284,-5.101 -5.101,-5.101z"/>
<path android:fillColor="@color/colorPrimary" android:pathData="m68.295,476.043h-20.403c-2.817,0 -5.101,-2.284 -5.101,-5.101v-81.951c0,-3.38 2.74,-6.121 6.121,-6.121h81.951c2.817,0 5.101,2.284 5.101,5.101v20.403c0,2.817 -2.284,5.101 -5.101,5.101h-57.467v57.467c0,2.818 -2.284,5.101 -5.101,5.101z"/>
<path android:fillColor="@color/colorPrimaryDark" android:pathData="m130.863,382.871h-9.437v10.966c0,2.817 -2.284,5.101 -5.101,5.101h-51.346c-3.38,0 -6.121,2.74 -6.121,6.121v51.346c0,2.817 -2.284,5.101 -5.101,5.101h-10.965v9.437c0,2.817 2.284,5.101 5.101,5.101h20.403c2.817,0 5.101,-2.284 5.101,-5.101v-57.467h57.467c2.817,0 5.101,-2.284 5.101,-5.101v-20.403c-0.001,-2.817 -2.285,-5.101 -5.102,-5.101z"/>
<path android:fillColor="@color/colorPrimary" android:pathData="m271.302,256h-30.604v-68.167c0,-2.817 2.284,-5.101 5.101,-5.101h20.403c2.817,0 5.101,2.284 5.101,5.101v68.167z"/>
<path android:fillColor="@color/colorAccent" android:pathData="m266.201,182.732h-13.574c2.817,0 5.101,2.284 5.101,5.101v68.167h13.574v-68.167c0,-2.818 -2.284,-5.101 -5.101,-5.101z"/>
<path android:fillColor="@color/colorPrimary" android:pathData="m361.903,271.302h-105.903v-30.604h105.903c2.817,0 5.101,2.284 5.101,5.101v20.403c0,2.816 -2.284,5.1 -5.101,5.1z"/>
<path android:fillColor="@color/colorAccent" android:pathData="m361.903,240.698h-8.694v11.709c0,2.817 -2.284,5.101 -5.101,5.101h-92.108v13.795h105.903c2.817,0 5.101,-2.284 5.101,-5.101v-20.403c0,-2.818 -2.284,-5.101 -5.101,-5.101z"/>
<path android:fillColor="@color/colorPrimary" android:pathData="m276.625,256c0,5.514 10.78,10.71 8.374,15.27 -5.49,10.405 -16.416,17.497 -28.999,17.497 -18.097,0 -32.767,-14.67 -32.767,-32.767s14.67,-32.767 32.767,-32.767c12.441,0 23.263,6.934 28.812,17.148 2.522,4.643 -8.187,9.964 -8.187,15.619z"/>
<path android:fillColor="#2b4d66" android:pathData="m270.644,226.689c2.207,4.408 3.456,9.379 3.456,14.644 0,18.097 -14.67,32.767 -32.767,32.767 -5.265,0 -10.236,-1.249 -14.644,-3.456 5.378,10.743 16.48,18.123 29.311,18.123 18.097,0 32.767,-14.67 32.767,-32.767 0,-12.832 -7.38,-23.933 -18.123,-29.311z"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M146.424,257.964m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M162.087,312.489m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M202.913,351.877m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M257.964,365.575m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M312.489,349.913m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M351.877,309.087m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#E6E4F6FF" android:pathData="m249.765,370.393c1.655,2.81 4.702,4.701 8.199,4.701 5.066,0 9.195,-3.961 9.49,-8.953 -5.756,1.764 -11.659,3.19 -17.689,4.252z"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M349.913,199.511m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M309.087,160.123m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M254.036,146.424m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M199.511,162.087m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#E6E4F6FF" android:pathData="M160.123,202.913m-9.518,0a9.518,9.518 0,1 1,19.036 0a9.518,9.518 0,1 1,-19.036 0"/>
<path android:fillColor="#d3effb" android:pathData="m317.248,358.156c4.552,-2.628 6.112,-8.449 3.484,-13.002 -1.184,-2.05 -3.017,-3.487 -5.088,-4.214 -4.056,3.184 -8.286,6.155 -12.664,8.912 -0.012,1.638 0.388,3.299 1.266,4.82 2.628,4.552 8.449,6.112 13.002,3.484z"/>
<path android:fillColor="#d3effb" android:pathData="m356.636,300.844c-1.485,-0.857 -3.105,-1.262 -4.705,-1.269 -2.708,4.568 -5.644,8.984 -8.809,13.22 0.778,1.842 2.131,3.458 3.996,4.535 4.552,2.628 10.374,1.069 13.002,-3.484s1.069,-10.374 -3.484,-13.002z"/>
</vector>

View File

@ -0,0 +1,23 @@
<!--
~ Copyright (C) 2020 Shabinder Singh
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="30dp"
android:height="30dp" android:viewportWidth="512" android:viewportHeight="512">
<path android:fillColor="#FF3C64" android:pathData="m304,232a24,24 0,0 1,-16.971 -40.971l160,-160a24,24 0,0 1,33.942 33.942l-160,160a23.926,23.926 0,0 1,-16.971 7.029z"/>
<path android:fillColor="#FF3B63" android:pathData="m464,200a24,24 0,0 1,-24 -24v-104h-104a24,24 0,0 1,0 -48h128a24,24 0,0 1,24 24v128a24,24 0,0 1,-24 24z"/>
<path android:fillColor="#CE1CFF" android:pathData="m464,488h-416a24,24 0,0 1,-24 -24v-416a24,24 0,0 1,24 -24h176a24,24 0,0 1,0 48h-152v368h368v-152a24,24 0,0 1,48 0v176a24,24 0,0 1,-24 24z"/>
</vector>

View File

@ -16,7 +16,7 @@
--> -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:width="38dp" android:height="38dp" android:width="36dp" android:height="36dp"
android:viewportWidth="512" android:viewportHeight="512"> android:viewportWidth="512" android:viewportHeight="512">
<path android:pathData="m512,256c0,141.387 -114.613,256 -256,256s-256,-114.613 -256,-256 114.613,-256 256,-256 256,114.613 256,256zM512,256"> <path android:pathData="m512,256c0,141.387 -114.613,256 -256,256s-256,-114.613 -256,-256 114.613,-256 256,-256 256,114.613 256,256zM512,256">
<aapt:attr name="android:fillColor"> <aapt:attr name="android:fillColor">

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 Shabinder Singh
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/text_background_accented"
android:drawablePadding="5dp"
android:fontFamily="@font/raleway_semibold"
android:gravity="center"
android:padding="8dp"
android:text=" Download History "
android:textAlignment="center"
android:textColor="#E1FFFFFF"
android:textSize="21sp"
app:drawableStartCompat="@drawable/ic_history"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/textView"
app:tabGravity="fill"
app:tabIconTint="@null"
app:tabInlineLabel="true"
app:tabMode="fixed"
app:tabSelectedTextColor="@color/colorPrimary"
app:tabTextColor="#B7FFFFFF">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:icon="@drawable/ic_spotify_logo"
android:text="Spotify" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:icon="@drawable/ic_youtube"
android:text="Youtube" />
</com.google.android.material.tabs.TabLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/download_record_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="1dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="1dp"
android:layout_marginBottom="1dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tabLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -0,0 +1,105 @@
<?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">
<data>
<variable
name="downloadRecord"
type="com.shabinder.spotiflyer.database.DownloadRecord" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="92dp"
android:background="#000000"
android:paddingBottom="12dp">
<ImageView
android:id="@+id/coverUrl"
android:layout_width="100dp"
android:layout_height="80dp"
android:layout_alignParentStart="true"
android:contentDescription="Track Image"
android:scaleType="centerInside"
android:src="@drawable/ic_song_placeholder" />
<TextView
android:id="@+id/item_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="12dp"
android:layout_marginTop="14dp"
android:layout_marginEnd="12dp"
android:layout_toStartOf="@+id/btn_action"
android:layout_toEndOf="@+id/coverUrl"
android:fontFamily="@font/raleway_semibold"
android:letterSpacing="0.04"
android:lines="1"
android:text="Weekend Chills"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppTheme.Headline4"
android:textColor="#9AB3FF"
android:textSize="20sp" />
<TextView
android:id="@+id/type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/coverUrl"
android:layout_marginStart="12dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="15dp"
android:layout_toStartOf="@+id/totalItems"
android:layout_toEndOf="@+id/coverUrl"
android:paddingLeft="9dp"
android:text="Playlist"
android:textSize="12sp" />
<TextView
android:id="@+id/totalItems"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/coverUrl"
android:layout_marginTop="7dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="15dp"
android:layout_toStartOf="@+id/btn_action"
android:paddingLeft="9dp"
android:text="50 Tracks"
android:textSize="12sp" />
<ImageButton
android:id="@+id/btn_action"
android:layout_width="60dp"
android:layout_height="80dp"
android:layout_alignBottom="@+id/coverUrl"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginBottom="5dp"
android:backgroundTint="@color/black"
android:scaleType="centerInside"
android:src="@drawable/ic_share_open"
android:tint="@null" />
</RelativeLayout>
</layout>

View File

@ -37,7 +37,6 @@
android:textColorHint="@color/grey" android:textColorHint="@color/grey"
android:textSize="19sp" android:textSize="19sp"
app:layout_constraintEnd_toStartOf="@+id/btn_search" app:layout_constraintEnd_toStartOf="@+id/btn_search"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread" app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -54,10 +53,10 @@
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/linkSearch" app:layout_constraintBottom_toBottomOf="@+id/linkSearch"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/linkSearch" app:layout_constraintStart_toEndOf="@+id/linkSearch"
app:layout_constraintTop_toTopOf="@+id/linkSearch" /> app:layout_constraintTop_toTopOf="@+id/linkSearch" />
<ImageView <ImageView
android:id="@+id/appLogo" android:id="@+id/appLogo"
android:layout_width="0dp" android:layout_width="0dp"
@ -73,6 +72,18 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linkSearch" /> app:layout_constraintTop_toBottomOf="@+id/linkSearch" />
<ImageButton
android:id="@+id/btn_history"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/transparent"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="@+id/appLogo"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_history" />
<TextView <TextView
android:id="@+id/appName" android:id="@+id/appName"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -270,5 +281,6 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/heart" /> app:layout_constraintStart_toEndOf="@+id/heart" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View File

@ -46,6 +46,11 @@
app:destination="@id/youtubeFragment" app:destination="@id/youtubeFragment"
app:enterAnim="@android:anim/slide_in_left" app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right" /> app:exitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_mainFragment_to_downloadRecord"
app:destination="@id/downloadRecord"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/youtubeFragment" android:id="@+id/youtubeFragment"
@ -56,4 +61,20 @@
android:name="link" android:name="link"
app:argType="string" /> app:argType="string" />
</fragment> </fragment>
<fragment
android:id="@+id/downloadRecord"
android:name="com.shabinder.spotiflyer.ui.downloadrecord.DownloadRecordFragment"
android:label="DownloadRecord"
tools:layout="@layout/download_record_fragment">
<action
android:id="@+id/action_downloadRecord_to_spotifyFragment"
app:destination="@id/spotifyFragment"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"/>
<action
android:id="@+id/action_downloadRecord_to_youtubeFragment"
app:destination="@id/youtubeFragment"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"/>
</fragment>
</navigation> </navigation>

View File

@ -1,9 +1,26 @@
<?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/>.
-->
<resources> <resources>
<color name="colorPrimary">#FC5C7D</color> <color name="colorPrimary">#FC5C7D</color>
<color name="colorPrimaryDark">#CE1CFF</color> <color name="colorPrimaryDark">#CE1CFF</color>
<color name="colorAccent">#8497FA</color> <color name="colorAccent">#799BFF</color>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFF</color>
<color name="grey">#99FFFFFF</color> <color name="grey">#99FFFFFF</color>
<color name="black">#000000</color> <color name="black">#000000</color>

View File

@ -18,8 +18,8 @@
<AppUpdater> <AppUpdater>
<update> <update>
<latestVersion>1.3</latestVersion> <latestVersion>1.4</latestVersion>
<latestVersionCode>4</latestVersionCode> <latestVersionCode>5</latestVersionCode>
<url>https://github.com/Shabinder/SpotiFlyer/releases</url> <url>https://github.com/Shabinder/SpotiFlyer/releases</url>
</update> </update>
</AppUpdater> </AppUpdater>

View File

@ -20,6 +20,7 @@ buildscript {
ext{ ext{
kotlin_version = "1.3.72" kotlin_version = "1.3.72"
navigationVersion = '2.3.0' navigationVersion = '2.3.0'
ext.hilt_version = '2.28-alpha'
} }
repositories { repositories {
google() google()
@ -31,6 +32,7 @@ buildscript {
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 "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
// classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" // 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