From abf6a6058a74db83fa44493f67b7b9f2a6b3b8b0 Mon Sep 17 00:00:00 2001 From: shabinder Date: Sun, 9 Aug 2020 22:44:01 +0530 Subject: [PATCH] Download Tab Added, Some Force Closes causing Bugs Fixed, Implementing Dependency Injection(Hilt Android). --- .idea/dictionaries/shabinder.xml | 2 + .idea/misc.xml | 5 + app/build.gradle | 16 +- .../main/java/com/shabinder/spotiflyer/App.kt | 29 +-- .../com/shabinder/spotiflyer/MainActivity.kt | 167 +++--------------- .../shabinder/spotiflyer/SharedViewModel.kt | 11 +- .../spotiflyer/database/DatabaseDAO.kt | 2 +- .../spotiflyer/database/DownloadRecord.kt | 2 +- .../downloadHelper/SpotifyDownloadHelper.kt | 9 +- .../recyclerView/DownloadRecordAdapter.kt | 71 ++++++++ .../recyclerView/SpotifyTrackListAdapter.kt | 15 +- .../recyclerView/YoutubeTrackListAdapter.kt | 10 +- .../spotiflyer/splash/SplashScreen.kt | 2 +- .../downloadrecord/DownloadRecordFragment.kt | 85 +++++++++ .../downloadrecord/DownloadRecordViewModel.kt | 51 ++++++ .../ui/mainfragment/MainFragment.kt | 38 ++-- .../ui/mainfragment/MainViewModel.kt | 7 +- .../spotiflyer/ui/spotify/SpotifyFragment.kt | 15 +- .../spotiflyer/ui/spotify/SpotifyViewModel.kt | 50 ++++-- .../spotiflyer/ui/youtube/YoutubeFragment.kt | 28 +-- .../spotiflyer/ui/youtube/YoutubeViewModel.kt | 24 ++- .../spotiflyer/utils/BindingAdapter.kt | 4 +- .../shabinder/spotiflyer/utils/Provider.kt | 104 +++++++++++ ...{SpotifyService.kt => SpotifyInterface.kt} | 11 +- .../spotiflyer/worker/ForegroundService.kt | 27 ++- app/src/main/res/drawable/ic_arrow_share.xml | 31 ++++ app/src/main/res/drawable/ic_history.xml | 51 ++++++ app/src/main/res/drawable/ic_share_open.xml | 23 +++ app/src/main/res/drawable/ic_tick.xml | 2 +- .../res/layout/download_record_fragment.xml | 85 +++++++++ .../main/res/layout/download_record_item.xml | 105 +++++++++++ app/src/main/res/layout/main_fragment.xml | 16 +- app/src/main/res/navigation/navigation.xml | 21 +++ app/src/main/res/values/colors.xml | 19 +- app/src/main/res/xml/app_update.xml | 4 +- build.gradle | 2 + 36 files changed, 863 insertions(+), 281 deletions(-) create mode 100644 app/src/main/java/com/shabinder/spotiflyer/recyclerView/DownloadRecordAdapter.kt create mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/downloadrecord/DownloadRecordFragment.kt create mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/downloadrecord/DownloadRecordViewModel.kt create mode 100644 app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt rename app/src/main/java/com/shabinder/spotiflyer/utils/{SpotifyService.kt => SpotifyInterface.kt} (90%) create mode 100644 app/src/main/res/drawable/ic_arrow_share.xml create mode 100644 app/src/main/res/drawable/ic_history.xml create mode 100644 app/src/main/res/drawable/ic_share_open.xml create mode 100644 app/src/main/res/layout/download_record_fragment.xml create mode 100644 app/src/main/res/layout/download_record_item.xml diff --git a/.idea/dictionaries/shabinder.xml b/.idea/dictionaries/shabinder.xml index b5d414c9..ac6c8e56 100644 --- a/.idea/dictionaries/shabinder.xml +++ b/.idea/dictionaries/shabinder.xml @@ -1,10 +1,12 @@ + downloadrecord ffmpeg flyer insta instagram + mainfragment maxresdefault moshi musicforeveryone diff --git a/.idea/misc.xml b/.idea/misc.xml index d3c66e7a..6a0d5af2 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,10 @@ + + + + + diff --git a/app/build.gradle b/app/build.gradle index 436d8c12..d8a2a6c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,6 +20,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: "androidx.navigation.safeargs.kotlin" +apply plugin: 'dagger.hilt.android.plugin' //apply plugin: 'kotlinx-serialization' android { @@ -34,8 +35,8 @@ android { applicationId 'com.shabinder.spotiflyer' minSdkVersion 22 targetSdkVersion 29 - versionCode 4 - versionName "1.3" + versionCode 5 + versionName "1.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } packagingOptions { @@ -74,7 +75,7 @@ dependencies { implementation fileTree(dir: 'libs', include:['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 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.webkit:webkit:1.2.0' 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-ui-ktx:2.3.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' - - implementation 'com.google.android.material:material:1.1.0' + implementation 'com.google.android.material:material:1.2.0' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7" implementation "androidx.room:room-runtime:2.2.5" kapt "androidx.room:room-compiler: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 'androidx.legacy:legacy-support-v4:1.0.0' implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") { diff --git a/app/src/main/java/com/shabinder/spotiflyer/App.kt b/app/src/main/java/com/shabinder/spotiflyer/App.kt index 82a589b0..a08e6022 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/App.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/App.kt @@ -18,10 +18,7 @@ package com.shabinder.spotiflyer import android.app.Application -import android.app.NotificationChannel -import android.app.NotificationManager -import android.content.Context -import android.os.Build +import dagger.hilt.android.HiltAndroidApp /* * Copyright (C) 2020 Shabinder Singh @@ -40,24 +37,10 @@ import android.os.Build * along with this program. If not, see . */ -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) - } - +@HiltAndroidApp +class App:Application(){ + companion object{ + const val clientId:String = "694d8bf4f6ec420fa66ea7fb4c68f89d" + const val clientSecret:String = "02ca2d4021a7452dae2328b47a6e8fe8" } } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index c0e88f35..9f72b8dc 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -22,7 +22,6 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.SharedPreferences -import android.net.ConnectivityManager import android.net.Uri import android.os.Build import android.os.Bundle @@ -34,15 +33,13 @@ import androidx.databinding.DataBindingUtil 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.shabinder.spotiflyer.databinding.MainActivityBinding import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper 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.shreyaspatil.EasyUpiPayment.EasyUpiPayment import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -50,41 +47,40 @@ import okhttp3.Request import okhttp3.Response import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory +import javax.inject.Inject @Suppress("DEPRECATION") +@AndroidEntryPoint class MainActivity : AppCompatActivity(){ - private lateinit var binding: MainActivityBinding - private var ytDownloader : YoutubeDownloader? = 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 sharedPref :SharedPreferences? = null - private var easyUpiPayment:EasyUpiPayment? = null private var token :String ="" + private lateinit var binding: MainActivityBinding 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?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this,R.layout.main_activity) sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java) sharedPref = this.getPreferences(Context.MODE_PRIVATE) + //starting Notification and Downloader Service! 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){ authenticateSpotify() }else{ @@ -94,16 +90,11 @@ class MainActivity : AppCompatActivity(){ requestPermission() disableDozeMode() checkIfLatestVersion() - createDir() - setUpi() - isConnected = isOnline() + createDirectories() + isConnected = sharedViewModel.isOnline(this) sharedViewModel.isConnected.value = isConnected 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() } @@ -157,10 +148,6 @@ class MainActivity : AppCompatActivity(){ } }) - val moshi = Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .build() - val retrofit = Retrofit.Builder() .baseUrl("https://api.spotify.com/v1/") .client(httpClient.build()) @@ -171,45 +158,15 @@ class MainActivity : AppCompatActivity(){ 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() { - if (spotifyServiceToken == null) { - getSpotifyToken() - } sharedViewModel.uiScope.launch { if (isConnected) { Log.i("Post Request", "Made") - token = spotifyServiceToken!!.getToken()!!.access_token + token = spotifyServiceTokenRequest.getToken()!!.access_token implementSpotifyService(token) Log.i("Post Request", token) sharedViewModel.accessToken.value = token - saveToken(token) }else{ Log.i("network", "unavailable") // 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() { 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() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions( @@ -267,22 +208,7 @@ class MainActivity : AppCompatActivity(){ } } - 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() { + private fun createDirectories() { createDirectory(SpotifyDownloadHelper.defaultDir) createDirectory(SpotifyDownloadHelper.defaultDir+".Images/") createDirectory(SpotifyDownloadHelper.defaultDir+"Tracks/") @@ -308,55 +234,4 @@ class MainActivity : AppCompatActivity(){ } 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") - } - } - } - } -*/ - - } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt index b7aad664..831493bf 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt @@ -19,13 +19,12 @@ package com.shabinder.spotiflyer import android.content.Context import android.content.res.Resources +import android.net.ConnectivityManager import android.os.Environment import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.github.kiulian.downloader.YoutubeDownloader import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.shabinder.spotiflyer.utils.SpotifyService -import com.shreyaspatil.EasyUpiPayment.EasyUpiPayment import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -34,8 +33,6 @@ import java.io.File class SharedViewModel : ViewModel() { var intentString = "" var spotifyService = MutableLiveData() - var ytDownloader = MutableLiveData() - var easyUpiPayment: EasyUpiPayment? = null var accessToken = MutableLiveData().apply { value = "" } var isConnected = MutableLiveData().apply { value = false } val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator + ".Images" + File.separator @@ -59,4 +56,10 @@ class SharedViewModel : ViewModel() { } .show() } + fun isOnline(context: Context): Boolean { + val cm = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val netInfo = cm.activeNetworkInfo + return netInfo != null && netInfo.isConnectedOrConnecting + } } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/database/DatabaseDAO.kt b/app/src/main/java/com/shabinder/spotiflyer/database/DatabaseDAO.kt index 6cc02c97..f323cf01 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/database/DatabaseDAO.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/database/DatabaseDAO.kt @@ -25,7 +25,7 @@ interface DatabaseDAO { suspend fun insert(record: DownloadRecord) @Update - fun update(record: DownloadRecord) + suspend fun update(record: DownloadRecord) @Query("SELECT * from download_record_table ORDER BY id DESC") suspend fun getRecord():List diff --git a/app/src/main/java/com/shabinder/spotiflyer/database/DownloadRecord.kt b/app/src/main/java/com/shabinder/spotiflyer/database/DownloadRecord.kt index 2f69c9e1..e5df89b5 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/database/DownloadRecord.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/database/DownloadRecord.kt @@ -27,7 +27,7 @@ import kotlinx.android.parcel.Parcelize @Parcelize @Entity( tableName = "download_record_table", - indices = [Index(value = ["id","link"], unique = true)] + indices = [Index(value = ["link"], unique = true)] ) data class DownloadRecord( diff --git a/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt b/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt index 413657d3..5061ac5f 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt @@ -22,6 +22,7 @@ import android.content.Context import android.content.Intent import android.os.Build import android.os.Environment +import android.os.Handler import android.util.Log import android.view.View import android.view.animation.AlphaAnimation @@ -126,13 +127,19 @@ object SpotifyDownloadHelper { isBrowserLoading = false 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() { diff --git a/app/src/main/java/com/shabinder/spotiflyer/recyclerView/DownloadRecordAdapter.kt b/app/src/main/java/com/shabinder/spotiflyer/recyclerView/DownloadRecordAdapter.kt new file mode 100644 index 00000000..696ad40d --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/recyclerView/DownloadRecordAdapter.kt @@ -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 . + */ + +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(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(){ + override fun areItemsTheSame(oldItem: DownloadRecord, newItem: DownloadRecord): Boolean { + return oldItem.coverUrl == newItem.coverUrl + } + + override fun areContentsTheSame(oldItem: DownloadRecord, newItem: DownloadRecord): Boolean { + return oldItem == newItem + } +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/recyclerView/SpotifyTrackListAdapter.kt b/app/src/main/java/com/shabinder/spotiflyer/recyclerView/SpotifyTrackListAdapter.kt index 2e95e9a3..fff27e88 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/recyclerView/SpotifyTrackListAdapter.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/recyclerView/SpotifyTrackListAdapter.kt @@ -24,6 +24,7 @@ import android.widget.Toast import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import com.github.kiulian.downloader.YoutubeDownloader import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.databinding.TrackListItemBinding import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper.context @@ -37,15 +38,14 @@ import kotlinx.coroutines.launch class SpotifyTrackListAdapter: ListAdapter(SpotifyTrackDiffCallback()) { - var spotifyViewModel = SpotifyViewModel() + var spotifyViewModel : SpotifyViewModel? = null var isAlbum:Boolean = false + var ytDownloader: YoutubeDownloader? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) - val binding = TrackListItemBinding.inflate(layoutInflater,parent,false) -// val view = layoutInflater.inflate(R.layout.track_list_item,parent,false) return ViewHolder(binding) } @@ -53,7 +53,7 @@ class SpotifyTrackListAdapter: ListAdapter() 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(){ override fun areContentsTheSame(oldItem: Track, newItem: Track): Boolean { return oldItem == newItem //Downloaded Check } - } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/recyclerView/YoutubeTrackListAdapter.kt b/app/src/main/java/com/shabinder/spotiflyer/recyclerView/YoutubeTrackListAdapter.kt index 4609e0b1..d3e5e224 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/recyclerView/YoutubeTrackListAdapter.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/recyclerView/YoutubeTrackListAdapter.kt @@ -23,17 +23,18 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import com.github.kiulian.downloader.model.formats.Format -import com.shabinder.spotiflyer.SharedViewModel import com.shabinder.spotiflyer.databinding.TrackListItemBinding import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.utils.bindImage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class YoutubeTrackListAdapter: ListAdapter(YouTubeTrackDiffCallback()) { var format:Format? = null - var sharedViewModel = SharedViewModel() + private val adapterScope = CoroutineScope(Dispatchers.Default) override fun onCreateViewHolder( parent: ViewGroup, @@ -49,7 +50,7 @@ class YoutubeTrackListAdapter: ListAdapter(){ override fun areContentsTheSame(oldItem: Track, newItem: Track): Boolean { return oldItem == newItem } - } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/splash/SplashScreen.kt b/app/src/main/java/com/shabinder/spotiflyer/splash/SplashScreen.kt index 50166316..2ba1e77a 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/splash/SplashScreen.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/splash/SplashScreen.kt @@ -31,7 +31,7 @@ class SplashScreen : AppCompatActivity(){ super.onCreate(savedInstanceState) setContentView(R.layout.splash_screen) - val splashTimeout = 500 + val splashTimeout = 400 val homeIntent = Intent(this@SplashScreen, MainActivity::class.java) Handler().postDelayed({ //TODO:Bring Initial Setup here diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/downloadrecord/DownloadRecordFragment.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/downloadrecord/DownloadRecordFragment.kt new file mode 100644 index 00000000..b9271092 --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/downloadrecord/DownloadRecordFragment.kt @@ -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 . + */ + +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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/downloadrecord/DownloadRecordViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/downloadrecord/DownloadRecordViewModel.kt new file mode 100644 index 00000000..0c98d044 --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/downloadrecord/DownloadRecordViewModel.kt @@ -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 . + */ + +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() + var ytList = mutableListOf() + val downloadRecordList = MutableLiveData>().apply { + value = mutableListOf() + } + init { + getDownloadRecordList() + } + private fun getDownloadRecordList() { + uiScope.launch { + downloadRecordList.postValue(databaseDAO.getRecord().toMutableList()) + } + } + override fun onCleared() { + super.onCleared() + viewModelJob.cancel() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/mainfragment/MainFragment.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/mainfragment/MainFragment.kt index 13a34363..c6a48650 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/mainfragment/MainFragment.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/mainfragment/MainFragment.kt @@ -34,12 +34,17 @@ import androidx.navigation.fragment.findNavController import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.SharedViewModel import com.shabinder.spotiflyer.databinding.MainFragmentBinding +import com.shreyaspatil.EasyUpiPayment.EasyUpiPayment +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject +@AndroidEntryPoint class MainFragment : Fragment() { - private lateinit var viewModel: MainViewModel + private lateinit var mainViewModel: MainViewModel private lateinit var sharedViewModel: SharedViewModel private lateinit var binding: MainFragmentBinding + @Inject lateinit var easyUpiPayment: EasyUpiPayment override fun onCreateView( @@ -47,7 +52,6 @@ class MainFragment : Fragment() { savedInstanceState: Bundle? ): View? { binding = DataBindingUtil.inflate(inflater,R.layout.main_fragment,container,false) - viewModel = ViewModelProvider(this).get(MainViewModel::class.java) initializeAll() binding.btnSearch.setOnClickListener { @@ -62,18 +66,26 @@ class MainFragment : Fragment() { return binding.root } + private fun initializeAll() { + mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java) sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java) - setUpUsageText() openYTButton() openSpotifyButton() openGithubButton() openInstaButton() openLinkedInButton() + historyButton() + binding.usage.text = usageText() 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 **/ @@ -161,5 +163,11 @@ class MainFragment : Fragment() { 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") + } } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/mainfragment/MainViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/mainfragment/MainViewModel.kt index 46be4e6b..aaa3f02a 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/mainfragment/MainViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/mainfragment/MainViewModel.kt @@ -17,8 +17,11 @@ package com.shabinder.spotiflyer.ui.mainfragment +import androidx.hilt.lifecycle.ViewModelInject import androidx.lifecycle.ViewModel +import com.shabinder.spotiflyer.database.DatabaseDAO -class MainViewModel : ViewModel() { - // TODO: Implement the ViewModel +class MainViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : + ViewModel(){ +//TODO Refactoring Code Up here } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt index ce530d8d..8e78cdff 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt @@ -41,6 +41,7 @@ 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.github.kiulian.downloader.YoutubeDownloader import com.shabinder.spotiflyer.MainActivity import com.shabinder.spotiflyer.R 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.copyTo import com.shabinder.spotiflyer.utils.rotateAnim +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File import java.io.IOException +import javax.inject.Inject @Suppress("DEPRECATION") + +@AndroidEntryPoint class SpotifyFragment : Fragment() { private lateinit var binding:SpotifyFragmentBinding private lateinit var spotifyViewModel: SpotifyViewModel private lateinit var sharedViewModel: SharedViewModel private lateinit var adapterSpotify:SpotifyTrackListAdapter + @Inject lateinit var ytDownloader:YoutubeDownloader private var webView: WebView? = null private var intentFilter:IntentFilter? = null private var updateUIReceiver: BroadcastReceiver? = null @@ -87,7 +93,7 @@ class SpotifyFragment : Fragment() { Log.i("Fragment", "$type : $link") - if(sharedViewModel.spotifyService.value == null && isNotOnline()){//Authentication pending!! + if(sharedViewModel.spotifyService.value == null){//Authentication pending!! (activity as MainActivity).authenticateSpotify() } if(!isNotOnline()){//Device Offline @@ -122,7 +128,7 @@ class SpotifyFragment : Fragment() { spotifyViewModel.folderType, spotifyViewModel.subFolder, spotifyViewModel.trackList.value!!, - spotifyViewModel.ytDownloader + ytDownloader ) } } @@ -219,9 +225,6 @@ class SpotifyFragment : Fragment() { sharedViewModel.spotifyService.observe(viewLifecycleOwner, Observer { spotifyViewModel.spotifyService = it }) - sharedViewModel.ytDownloader.observe(viewLifecycleOwner, Observer { - spotifyViewModel.ytDownloader = it - }) SpotifyDownloadHelper.webView = binding.webViewSpotify SpotifyDownloadHelper.context = requireContext() SpotifyDownloadHelper.spotifyViewModel = spotifyViewModel @@ -283,7 +286,7 @@ class SpotifyFragment : Fragment() { * Configure Recycler View Adapter **/ private fun adapterConfig(trackList: List){ - adapterSpotify.spotifyFragment = this + adapterSpotify.ytDownloader = ytDownloader adapterSpotify.spotifyViewModel = spotifyViewModel adapterSpotify.submitList(trackList) } diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt index a1815741..7b5343a4 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyViewModel.kt @@ -18,21 +18,21 @@ package com.shabinder.spotiflyer.ui.spotify import android.util.Log +import androidx.hilt.lifecycle.ViewModelInject import androidx.lifecycle.MutableLiveData 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.Playlist import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.utils.SpotifyService import com.shabinder.spotiflyer.utils.finalOutputDir -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import java.io.File -class SpotifyViewModel: ViewModel() { +class SpotifyViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : + ViewModel(){ var folderType:String = "" var subFolder:String = "" @@ -41,7 +41,6 @@ class SpotifyViewModel: ViewModel() { var title = MutableLiveData().apply { value = loading } var coverUrl = MutableLiveData().apply { value = loading } var spotifyService : SpotifyService? = null - var ytDownloader : YoutubeDownloader? = null private var viewModelJob = Job() val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) @@ -61,6 +60,17 @@ class SpotifyViewModel: ViewModel() { trackList.value = tempTrackList title.value = trackObject.name 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 title.value = albumObject.name 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 { it1 -> if(File(finalOutputDir(it1.name!!,folderType,subFolder)).exists()){//Download Already Present!! it1.downloaded = "Downloaded" - Log.i("ViewModel123","${it1.name} Downloaded") } tempTrackList.add(it1) } } - Log.i("ViewModel",tempTrackList.size.toString()) - Log.i("ViewModel",playlistObject.tracks?.items?.size.toString()) trackList.value = tempTrackList title.value = playlistObject.name 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 diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeFragment.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeFragment.kt index 03d9a251..cc3678ad 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeFragment.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeFragment.kt @@ -17,8 +17,6 @@ package com.shabinder.spotiflyer.ui.youtube -import android.content.Context -import android.net.ConnectivityManager import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -28,6 +26,7 @@ import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider +import com.github.kiulian.downloader.YoutubeDownloader import com.shabinder.spotiflyer.R import com.shabinder.spotiflyer.SharedViewModel 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.recyclerView.YoutubeTrackListAdapter import com.shabinder.spotiflyer.utils.bindImage +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject +@AndroidEntryPoint class YoutubeFragment : Fragment() { private lateinit var binding:YoutubeFragmentBinding @@ -44,7 +46,7 @@ class YoutubeFragment : Fragment() { private lateinit var adapter : YoutubeTrackListAdapter private val sampleDomain1 = "youtube.com" private val sampleDomain2 = "youtu.be" - + @Inject lateinit var ytDownloader: YoutubeDownloader override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -57,9 +59,7 @@ class YoutubeFragment : Fragment() { YTDownloadHelper.context = requireContext() YTDownloadHelper.statusBar = binding.StatusBarYoutube binding.trackListYoutube.adapter = adapter - sharedViewModel.ytDownloader.observe(viewLifecycleOwner, Observer { - youtubeViewModel.ytDownloader = it - }) + initializeLiveDataObservers() val args = YoutubeFragmentArgs.fromBundle(requireArguments()) @@ -79,10 +79,10 @@ class YoutubeFragment : Fragment() { searchId = link.substringAfterLast("/","error") } if(searchId != "error") { -// val coverUrl = "https://i.ytimg.com/vi/$searchId/maxresdefault.jpg" - youtubeViewModel.getYTTrack(searchId) + youtubeViewModel.getYTTrack(searchId,ytDownloader) 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!!")) @@ -122,7 +122,6 @@ class YoutubeFragment : Fragment() { * Configure Recycler View Adapter **/ private fun adapterConfig(list:List){ - adapter.sharedViewModel = sharedViewModel adapter.submitList(list) } @@ -133,13 +132,4 @@ class YoutubeFragment : Fragment() { 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 - } } diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeViewModel.kt index 0a565da1..48beedee 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/youtube/YoutubeViewModel.kt @@ -18,34 +18,37 @@ package com.shabinder.spotiflyer.ui.youtube import android.util.Log +import androidx.hilt.lifecycle.ViewModelInject import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.github.kiulian.downloader.YoutubeDownloader import com.github.kiulian.downloader.model.formats.Format import com.github.kiulian.downloader.model.quality.AudioQuality +import com.shabinder.spotiflyer.database.DatabaseDAO +import com.shabinder.spotiflyer.database.DownloadRecord import com.shabinder.spotiflyer.models.Artist import com.shabinder.spotiflyer.models.Track +import com.shabinder.spotiflyer.utils.finalOutputDir import kotlinx.coroutines.* -class YoutubeViewModel : ViewModel() { +class YoutubeViewModel @ViewModelInject constructor(val databaseDAO: DatabaseDAO) : + ViewModel(){ val ytTrack = MutableLiveData() val format = MutableLiveData() private val loading = "Loading" var title = MutableLiveData().apply { value = "\"Loading!\"" } var coverUrl = MutableLiveData().apply { value = loading } - var ytDownloader: YoutubeDownloader? = null - private var viewModelJob = Job() val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) - fun getYTTrack(searchId:String) { + fun getYTTrack(searchId:String,ytDownloader:YoutubeDownloader) { uiScope.launch { withContext(Dispatchers.IO){ Log.i("YT View Model",searchId) - val video = ytDownloader?.getVideo(searchId) + val video = ytDownloader.getVideo(searchId) val detail = video?.details() val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() 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") + )) + } } } } diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/BindingAdapter.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/BindingAdapter.kt index 595057d6..1854f811 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/BindingAdapter.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/BindingAdapter.kt @@ -43,11 +43,11 @@ import java.io.File import java.io.FileInputStream 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 + SpotifyDownloadHelper.defaultDir + SpotifyDownloadHelper.removeIllegalChars(type) + 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){ diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt new file mode 100644 index 00000000..e58f3caa --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt @@ -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 . + */ + +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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/SpotifyService.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/SpotifyInterface.kt similarity index 90% rename from app/src/main/java/com/shabinder/spotiflyer/utils/SpotifyService.kt rename to app/src/main/java/com/shabinder/spotiflyer/utils/SpotifyInterface.kt index 3744eda7..704c1cdc 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/SpotifyService.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/SpotifyInterface.kt @@ -17,7 +17,10 @@ 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.* /* @@ -54,13 +57,9 @@ interface SpotifyService { @GET("albums/{id}") suspend fun getAlbum(@Path("id") albumId: String?): Album - - @GET("me") - suspend fun getMe(): UserPrivate? - } -interface SpotifyServiceToken{ +interface SpotifyServiceTokenRequest{ @POST("api/token") @FormUrlEncoded diff --git a/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt b/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt index 1b9cb4b9..eafeebda 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt @@ -24,10 +24,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.Uri -import android.os.Build -import android.os.Environment -import android.os.IBinder -import android.os.PowerManager +import android.os.* import android.util.Log import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat @@ -198,10 +195,12 @@ class ForegroundService : Service(){ override fun onDestroy() { super.onDestroy() if(downloadMap.isEmpty() && converted == total){ - Log.i(tag,"Service destroyed.") - deleteFile(parentDirectory) - releaseWakeLock() - stopForeground(true) + Handler().postDelayed({ + Log.i(tag,"Service destroyed.") + deleteFile(parentDirectory) + releaseWakeLock() + stopForeground(true) + },2000) } } @@ -224,11 +223,11 @@ class ForegroundService : Service(){ super.onTaskRemoved(rootIntent) if(downloadMap.isEmpty() && converted == total ){ Log.i(tag,"Service Removed.") -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { -// stopForeground(true) -// } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + stopForeground(true) + } else { stopSelf()//System will automatically close it -// } + } } } @@ -243,7 +242,7 @@ class ForegroundService : Service(){ if (file.isDirectory) { deleteFile(file) } 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}") file.delete() } @@ -427,7 +426,7 @@ class ForegroundService : Service(){ val m4aFile = File(filePath) FFmpeg.executeAsync( - "-i $filePath -b:a 160k -vn ${filePath.substringBeforeLast('.') + ".mp3"}" + "-i $filePath -y -b:a 160k -vn ${filePath.substringBeforeLast('.') + ".mp3"}" ) { _, returnCode -> when (returnCode) { RETURN_CODE_SUCCESS -> { diff --git a/app/src/main/res/drawable/ic_arrow_share.xml b/app/src/main/res/drawable/ic_arrow_share.xml new file mode 100644 index 00000000..63951516 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_share.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 00000000..8241717e --- /dev/null +++ b/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_share_open.xml b/app/src/main/res/drawable/ic_share_open.xml new file mode 100644 index 00000000..3f029571 --- /dev/null +++ b/app/src/main/res/drawable/ic_share_open.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_tick.xml b/app/src/main/res/drawable/ic_tick.xml index a857fdcc..7afa2d22 100644 --- a/app/src/main/res/drawable/ic_tick.xml +++ b/app/src/main/res/drawable/ic_tick.xml @@ -16,7 +16,7 @@ --> diff --git a/app/src/main/res/layout/download_record_fragment.xml b/app/src/main/res/layout/download_record_fragment.xml new file mode 100644 index 00000000..3e921168 --- /dev/null +++ b/app/src/main/res/layout/download_record_fragment.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/download_record_item.xml b/app/src/main/res/layout/download_record_item.xml new file mode 100644 index 00000000..d301d54e --- /dev/null +++ b/app/src/main/res/layout/download_record_item.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/main_fragment.xml b/app/src/main/res/layout/main_fragment.xml index 2786c4a6..c89a141a 100644 --- a/app/src/main/res/layout/main_fragment.xml +++ b/app/src/main/res/layout/main_fragment.xml @@ -37,7 +37,6 @@ android:textColorHint="@color/grey" android:textSize="19sp" app:layout_constraintEnd_toStartOf="@+id/btn_search" - app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_chainStyle="spread" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -54,10 +53,10 @@ android:textSize="16sp" app:layout_constraintBottom_toBottomOf="@+id/linkSearch" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/linkSearch" app:layout_constraintTop_toTopOf="@+id/linkSearch" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/navigation.xml b/app/src/main/res/navigation/navigation.xml index db5e89c2..a333ae27 100644 --- a/app/src/main/res/navigation/navigation.xml +++ b/app/src/main/res/navigation/navigation.xml @@ -46,6 +46,11 @@ app:destination="@id/youtubeFragment" app:enterAnim="@android:anim/slide_in_left" app:exitAnim="@android:anim/slide_out_right" /> + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f1f147db..2824d54a 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,9 +1,26 @@ + + #FC5C7D #CE1CFF - #8497FA + #799BFF #FFFFFF #99FFFFFF #000000 diff --git a/app/src/main/res/xml/app_update.xml b/app/src/main/res/xml/app_update.xml index 6703e0d2..f5b822c4 100644 --- a/app/src/main/res/xml/app_update.xml +++ b/app/src/main/res/xml/app_update.xml @@ -18,8 +18,8 @@ - 1.3 - 4 + 1.4 + 5 https://github.com/Shabinder/SpotiFlyer/releases \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9865f31c..71542f46 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,7 @@ buildscript { ext{ kotlin_version = "1.3.72" navigationVersion = '2.3.0' + ext.hilt_version = '2.28-alpha' } repositories { google() @@ -31,6 +32,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" //safe-Args 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" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files