diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 037487a5..4b129179 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -56,6 +56,6 @@ android:value="rzp_live_3ZQeoFYOxjmXye" /> + - \ 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 9c16eb8b..886af2ef 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -24,14 +24,11 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.core.view.WindowCompat import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import com.example.jetcaster.util.verticalGradientScrim -import com.shabinder.spotiflyer.models.spotify.Token import com.shabinder.spotiflyer.navigation.ComposeNavigation import com.shabinder.spotiflyer.navigation.navigateToTrackList -import com.shabinder.spotiflyer.networking.SpotifyService import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest import com.shabinder.spotiflyer.ui.ComposeLearnTheme import com.shabinder.spotiflyer.ui.appNameStyle @@ -41,17 +38,7 @@ import com.squareup.moshi.Moshi import dagger.hilt.android.AndroidEntryPoint import dev.chrisbanes.accompanist.insets.ProvideWindowInsets import dev.chrisbanes.accompanist.insets.statusBarsHeight -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Request -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory import javax.inject.Inject -import com.shabinder.spotiflyer.utils.showDialog as showDialog1 /* * This is App's God Activity diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/tracklist/TrackList.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/tracklist/TrackList.kt index 74f289a0..35cd38e3 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/tracklist/TrackList.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/tracklist/TrackList.kt @@ -1,6 +1,7 @@ package com.shabinder.spotiflyer.ui.tracklist import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.ExtendedFloatingActionButton @@ -25,14 +26,18 @@ import androidx.compose.ui.unit.sp import androidx.core.net.toUri import androidx.navigation.NavController import com.shabinder.spotiflyer.R +import com.shabinder.spotiflyer.models.DownloadStatus import com.shabinder.spotiflyer.models.PlatformQueryResult import com.shabinder.spotiflyer.models.TrackDetails +import com.shabinder.spotiflyer.models.spotify.Source import com.shabinder.spotiflyer.ui.SpotiFlyerTypography import com.shabinder.spotiflyer.ui.colorAccent import com.shabinder.spotiflyer.providers.queryGaana import com.shabinder.spotiflyer.providers.querySpotify import com.shabinder.spotiflyer.providers.queryYoutube import com.shabinder.spotiflyer.ui.utils.calculateDominantColor +import com.shabinder.spotiflyer.utils.downloadTracks +import com.shabinder.spotiflyer.utils.loadAllImages import com.shabinder.spotiflyer.utils.sharedViewModel import com.shabinder.spotiflyer.utils.showDialog import dev.chrisbanes.accompanist.coil.CoilImage @@ -83,14 +88,35 @@ fun TrackList( item { CoverImage(it.title,it.coverUrl,coroutineScope) } - items(it.trackList) { - TrackCard(track = it) + items(it.trackList) { item -> + TrackCard( + track = item, + onDownload = { + showDialog("Downloading ${it.title}") + downloadTracks(arrayListOf(it)) + } + ) } }, modifier = Modifier.fillMaxSize(), ) DownloadAllButton( - onClick = {}, + onClick = { + loadAllImages( + it.trackList.map { it.albumArtURL }, + Source.Spotify + ) + val finalList = it.trackList.filter{it.downloaded == DownloadStatus.NotDownloaded} + if (finalList.isNullOrEmpty()) showDialog("Not Downloading Any Song") + else downloadTracks(finalList as ArrayList) + for (track in it.trackList) { + if (track.downloaded == DownloadStatus.NotDownloaded) { + track.downloaded = DownloadStatus.Queued + //adapter.notifyItemChanged(viewModel.trackList.value!!.indexOf(track)) + } + } + showDialog("Downloading All Tracks") + }, modifier = Modifier.padding(bottom = 24.dp).align(Alignment.BottomCenter) ) } @@ -140,7 +166,10 @@ fun DownloadAllButton(onClick: () -> Unit, modifier: Modifier = Modifier) { } @Composable -fun TrackCard(track:TrackDetails) { +fun TrackCard( + track:TrackDetails, + onDownload:(TrackDetails)->Unit +) { Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) { val imgUri = track.albumArtURL.toUri().buildUpon().scheme("https").build() CoilImage( @@ -163,7 +192,7 @@ fun TrackCard(track:TrackDetails) { Text("${track.durationSec/60} minutes, ${track.durationSec%60} sec",fontSize = 13.sp) } } - Image(vectorResource(id = R.drawable.ic_arrow)) + Image(vectorResource(id = R.drawable.ic_arrow), Modifier.clickable(onClick = { onDownload(track) })) } } diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/Extensions.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/Extensions.kt index d7e2bb33..be8ef6ea 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/Extensions.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/Extensions.kt @@ -3,6 +3,7 @@ package com.shabinder.spotiflyer.utils import android.Manifest import android.content.Intent import android.content.pm.PackageManager +import android.graphics.Bitmap import android.net.Uri import android.os.Build import android.util.Log @@ -11,6 +12,7 @@ import com.github.kiulian.downloader.model.formats.Format import com.github.kiulian.downloader.model.quality.AudioQuality import com.shabinder.spotiflyer.BuildConfig import com.shabinder.spotiflyer.MainActivity +import java.io.File /* @@ -29,6 +31,12 @@ fun MainActivity.requestStoragePermission() { ) } } +fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG , quality: Int = 90) { + outputStream().use { out -> + bitmap.compress(format, quality, out) + out.flush() + } +} fun YoutubeVideo.getData(): Format?{ return try { findAudioWithQuality(AudioQuality.medium)?.get(0) as Format diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt index ddcfef8f..ed79cda5 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt @@ -45,7 +45,7 @@ val mainActivity val sharedViewModel get() = MainActivity.getSharedViewModel() -fun loadAllImages(context: Context? = mainActivity, images:List? = null,source: Source) { +fun loadAllImages( images:List? = null,source: Source, context: Context? = mainActivity) { val serviceIntent = Intent(context, ForegroundService::class.java) images?.let { serviceIntent.putStringArrayListExtra("imagesList",(it + source.name) as ArrayList) } context?.let { ContextCompat.startForegroundService(it, serviceIntent) } @@ -105,7 +105,9 @@ fun isOnline(): Boolean { fun showDialog(title:String? = null, message: String? = null,response: String = "Ok"){ //TODO - Toast.makeText(mainActivity,title ?: "No Internet",Toast.LENGTH_SHORT).show() + CoroutineScope(Dispatchers.Main).launch { + Toast.makeText(mainActivity,title ?: "No Internet",Toast.LENGTH_SHORT).show() + } } /** 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 06d65101..398028f0 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt @@ -20,6 +20,7 @@ package com.shabinder.spotiflyer.worker import android.annotation.SuppressLint import android.app.* import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED +import android.app.PendingIntent.FLAG_CANCEL_CURRENT import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -28,21 +29,15 @@ import android.media.MediaScannerConnection import android.net.Uri import android.os.* import androidx.annotation.RequiresApi -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Cancel -import androidx.compose.material.icons.rounded.CloudDownload import androidx.core.app.NotificationCompat +import androidx.core.graphics.drawable.toBitmap import androidx.core.net.toUri +import coil.Coil +import coil.request.ImageRequest import com.arthenica.mobileffmpeg.Config import com.arthenica.mobileffmpeg.Config.RETURN_CODE_CANCEL import com.arthenica.mobileffmpeg.Config.RETURN_CODE_SUCCESS import com.arthenica.mobileffmpeg.FFmpeg -import com.bumptech.glide.Glide -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.target.Target import com.github.kiulian.downloader.YoutubeDownloader import com.mpatric.mp3agic.Mp3File import com.shabinder.spotiflyer.R @@ -64,7 +59,6 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.io.File -import java.io.IOException import java.util.* import javax.inject.Inject @@ -97,6 +91,7 @@ class ForegroundService : Service(){ override fun onBind(intent: Intent): IBinder? = null + @SuppressLint("UnspecifiedImmutableFlag") override fun onCreate() { super.onCreate() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -106,7 +101,7 @@ class ForegroundService : Service(){ this, ForegroundService::class.java ).apply{action = "kill"} - cancelIntent = PendingIntent.getService (this, 0 , intent , PendingIntent.FLAG_CANCEL_CURRENT ) + cancelIntent = PendingIntent.getService (this, 0 , intent , FLAG_CANCEL_CURRENT ) downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager initialiseFetch() } @@ -535,75 +530,50 @@ class ForegroundService : Service(){ /** * Function to fetch all Images for use in mp3 tags. **/ - fun downloadAllImages(urlList: ArrayList, func: ((resource:File) -> Unit)? = null) { + suspend fun downloadAllImages(urlList: ArrayList, func: ((resource:File) -> Unit)? = null) { /* * Last Element of this List defines Its Source * */ val source = urlList.last() + for (url in urlList.subList(0, urlList.size - 2)) { - val imgUri = url.toUri().buildUpon().scheme("https").build() - Glide - .with(this@ForegroundService) - .asFile() - .diskCacheStrategy(DiskCacheStrategy.NONE) - .load(imgUri) - .listener(object : RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - log("Glide", "LoadFailed") - return false - } + withContext(Dispatchers.IO) { + val imgUri = url.toUri().buildUpon().scheme("https").build() - override fun onResourceReady( - resource: File?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean { - try { - serviceScope.launch { - val file = when (source) { - Source.Spotify.name -> { - File(imageDir, url.substringAfterLast('/') + ".jpeg") - } - Source.YouTube.name -> { - File( - imageDir, - url.substringBeforeLast('/', url) - .substringAfterLast( - '/', - url - ) + ".jpeg" - ) - } - Source.Gaana.name -> { - File( - imageDir, - (url.substringBeforeLast('/').substringAfterLast( - '/' - )) + ".jpeg" - ) - } + val r = ImageRequest.Builder(this@ForegroundService) + .data(imgUri) + .build() - else -> File( - imageDir, - url.substringAfterLast('/') + ".jpeg" - ) - } - resource?.copyTo(file) - func?.let { it(file) } - } - } catch (e: IOException) { - e.printStackTrace() - } - return false + val bitmap = Coil.execute(r).drawable?.toBitmap() + val file = when (source) { + Source.Spotify.name -> { + File(imageDir, url.substringAfterLast('/') + ".jpeg") } - }).submit() + Source.YouTube.name -> { + File( + imageDir, + url.substringBeforeLast('/', url) + .substringAfterLast( + '/', + url + ) + ".jpeg" + ) + } + Source.Gaana.name -> { + File( + imageDir, + (url.substringBeforeLast('/').substringAfterLast( + '/' + )) + ".jpeg" + ) + } + else -> File(imageDir, url.substringAfterLast('/') + ".jpeg") + } + if (bitmap != null) { + file.writeBitmap(bitmap) + func?.let { it(file) } + } else log("Foreground Service", "Album Art Could Not be Fetched") + } } } @@ -660,7 +630,7 @@ class ForegroundService : Service(){ private fun getNotification():Notification = NotificationCompat.Builder(this, channelId).run { setSmallIcon(R.drawable.ic_download_arrow) setContentTitle("Total: $total Completed:$converted Failed:$failed") - setNotificationSilent() + setSilent(true) setStyle( NotificationCompat.InboxStyle().run { addLine(messageList[messageList.size - 1]) diff --git a/app/src/main/java/com/shabinder/spotiflyer/worker/ID3Tagging.kt b/app/src/main/java/com/shabinder/spotiflyer/worker/ID3Tagging.kt index 040dc13f..90f18eb1 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/worker/ID3Tagging.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/worker/ID3Tagging.kt @@ -22,6 +22,8 @@ import com.mpatric.mp3agic.ID3v24Tag import com.mpatric.mp3agic.Mp3File import com.shabinder.spotiflyer.models.TrackDetails import com.shabinder.spotiflyer.utils.log +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import java.io.FileInputStream /** @@ -59,12 +61,14 @@ fun setId3v2Tags(mp3file: Mp3File, track: TrackDetails,service: ForegroundServic try { //Image Still Not Downloaded! //Lets Download Now and Write it into Album Art - service.downloadAllImages(arrayListOf(track.albumArtURL, track.source.name)){ - val bytesArray = ByteArray(it.length().toInt()) - val fis = FileInputStream(it) - fis.read(bytesArray) //read file into bytes[] - fis.close() - id3v2Tag.setAlbumImage(bytesArray, "image/jpeg") + GlobalScope.launch { + service.downloadAllImages(arrayListOf(track.albumArtURL, track.source.name)) { + val bytesArray = ByteArray(it.length().toInt()) + val fis = FileInputStream(it) + fis.read(bytesArray) //read file into bytes[] + fis.close() + id3v2Tag.setAlbumImage(bytesArray, "image/jpeg") + } } }catch (e: Exception){log("Error", "Couldn't Write Mp3 Album Art, error: ${e.stackTrace}")} }