From 86431dbc1ca8c713a7b3622b181e4b96ab060baf Mon Sep 17 00:00:00 2001 From: shabinder Date: Wed, 30 Dec 2020 15:16:21 +0530 Subject: [PATCH] Added Providers. --- .../com/shabinder/spotiflyer/MainActivity.kt | 118 +++++++++- .../shabinder/spotiflyer/SharedViewModel.kt | 12 +- .../spotiflyer/models/PlatformQueryResult.kt | 9 + .../navigation/ComposeNavigation.kt | 49 +++- .../spotiflyer/ui/base/TrackListViewModel.kt | 15 +- .../com/shabinder/spotiflyer/ui/home/Home.kt | 7 +- .../spotiflyer/ui/platforms/gaana/Gaana.kt | 39 +++- .../ui/platforms/gaana/GaanaProvider.kt | 208 +++++++++++++++++ .../ui/platforms/gaana/GaanaViewModel.kt | 204 ---------------- .../ui/platforms/spotify/Spotify.kt | 63 ++++- .../ui/platforms/spotify/SpotifyProvider.kt | 221 ++++++++++++++++++ .../ui/platforms/spotify/SpotifyViewModel.kt | 209 ----------------- .../ui/platforms/youtube/Youtube.kt | 45 +++- .../ui/platforms/youtube/YoutubeProvider.kt | 187 +++++++++++++++ .../ui/platforms/youtube/YoutubeViewModel.kt | 163 ------------- .../com/shabinder/spotiflyer/utils/Utils.kt | 3 + 16 files changed, 958 insertions(+), 594 deletions(-) create mode 100644 app/src/main/java/com/shabinder/spotiflyer/models/PlatformQueryResult.kt create mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaProvider.kt delete mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaViewModel.kt create mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyProvider.kt delete mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyViewModel.kt create mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeProvider.kt delete mode 100644 app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeViewModel.kt diff --git a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index 5bd2575d..f9392e8c 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -1,6 +1,13 @@ package com.shabinder.spotiflyer +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build import android.os.Bundle +import android.os.PowerManager +import android.provider.Settings import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -17,15 +24,25 @@ import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.vectorResource import androidx.core.view.WindowCompat import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope import com.shabinder.spotiflyer.navigation.ComposeNavigation import com.shabinder.spotiflyer.networking.SpotifyService +import com.shabinder.spotiflyer.networking.SpotifyServiceTokenRequest import com.shabinder.spotiflyer.ui.ComposeLearnTheme import com.shabinder.spotiflyer.ui.appNameStyle -import com.shabinder.spotiflyer.utils.requestStoragePermission +import com.shabinder.spotiflyer.utils.* +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.launch +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 @@ -34,13 +51,15 @@ import javax.inject.Inject class MainActivity : AppCompatActivity() { private var spotifyService : SpotifyService? = null + @Inject lateinit var moshi: Moshi + @Inject lateinit var spotifyServiceTokenRequest: SpotifyServiceTokenRequest override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java) + // This app draws behind the system bars, so we want to handle fitting system windows WindowCompat.setDecorFitsSystemWindows(window, false) - setContent { ComposeLearnTheme { ProvideWindowInsets { @@ -60,8 +79,103 @@ class MainActivity : AppCompatActivity() { } } } + initialize() + } + private fun initialize() { + authenticateSpotify() requestStoragePermission() + disableDozeMode() + //checkIfLatestVersion() + createDirectories() + handleIntentFromExternalActivity() + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + //Return to MainFragment For Further Processing of this Intent + handleIntentFromExternalActivity(intent) + } + + @SuppressLint("BatteryLife") + fun disableDozeMode() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val pm = + this.getSystemService(Context.POWER_SERVICE) as PowerManager + val isIgnoringBatteryOptimizations = pm.isIgnoringBatteryOptimizations(packageName) + if (!isIgnoringBatteryOptimizations) { + val intent = Intent().apply{ + action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + data = Uri.parse("package:$packageName") + } + startActivityForResult(intent, 1233) + } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == 1233) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val pm = + getSystemService(Context.POWER_SERVICE) as PowerManager + val isIgnoringBatteryOptimizations = + pm.isIgnoringBatteryOptimizations(packageName) + if (isIgnoringBatteryOptimizations) { + // Ignoring battery optimization + } else { + disableDozeMode()//Again Ask For Permission!! + } + } + } + } + + /** + * Adding my own new Spotify Web Api Requests! + * */ + private fun implementSpotifyService(token: String) { + val httpClient: OkHttpClient.Builder = OkHttpClient.Builder() + httpClient.addInterceptor(Interceptor { chain -> + val request: Request = + chain.request().newBuilder().addHeader( + "Authorization", + "Bearer $token" + ).build() + chain.proceed(request) + }).addInterceptor(NetworkInterceptor()) + + val retrofit = Retrofit.Builder() + .baseUrl("https://api.spotify.com/v1/") + .client(httpClient.build()) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() + + spotifyService = retrofit.create(SpotifyService::class.java) + sharedViewModel.spotifyService.value = spotifyService + } + + fun authenticateSpotify() { + sharedViewModel.viewModelScope.launch { + log("Spotify Authentication","Started") + val token = spotifyServiceTokenRequest.getToken() + token.value?.let { + showDialog1("Success: Spotify Token Acquired") + implementSpotifyService(it.access_token) + } + log("Spotify Token", token.value.toString()) + } + } + + + private fun handleIntentFromExternalActivity(intent: Intent? = getIntent()) { + if (intent?.action == Intent.ACTION_SEND) { + if ("text/plain" == intent.type) { + intent.getStringExtra(Intent.EXTRA_TEXT)?.let { + log("Intent Received", it) + sharedViewModel.intentString.value = it + } + } + } } companion object{ diff --git a/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt index 9c5711e7..839bbadc 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/SharedViewModel.kt @@ -17,11 +17,21 @@ package com.shabinder.spotiflyer +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.networking.GaanaInterface import com.shabinder.spotiflyer.networking.SpotifyService +import dagger.hilt.android.scopes.ActivityRetainedScoped -class SharedViewModel : ViewModel() { +@ActivityRetainedScoped +class SharedViewModel @ViewModelInject constructor( + val databaseDAO: DatabaseDAO, + val gaanaInterface : GaanaInterface, + val ytDownloader: YoutubeDownloader +) : ViewModel() { var intentString = MutableLiveData() var spotifyService = MutableLiveData() } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/models/PlatformQueryResult.kt b/app/src/main/java/com/shabinder/spotiflyer/models/PlatformQueryResult.kt new file mode 100644 index 00000000..40de91f1 --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/models/PlatformQueryResult.kt @@ -0,0 +1,9 @@ +package com.shabinder.spotiflyer.models + +data class PlatformQueryResult( + var folderType: String, + var subFolder: String, + var title: String, + var coverUrl: String, + var trackList: List +) \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/navigation/ComposeNavigation.kt b/app/src/main/java/com/shabinder/spotiflyer/navigation/ComposeNavigation.kt index 281b4bd0..14f7439b 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/navigation/ComposeNavigation.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/navigation/ComposeNavigation.kt @@ -1,15 +1,17 @@ package com.shabinder.spotiflyer.navigation import androidx.compose.runtime.Composable +import androidx.navigation.NavController import androidx.navigation.NavType -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.navArgument -import androidx.navigation.compose.rememberNavController +import androidx.navigation.compose.* +import androidx.navigation.compose.popUpTo import com.shabinder.spotiflyer.ui.home.Home import com.shabinder.spotiflyer.ui.platforms.gaana.Gaana import com.shabinder.spotiflyer.ui.platforms.spotify.Spotify import com.shabinder.spotiflyer.ui.platforms.youtube.Youtube +import com.shabinder.spotiflyer.utils.mainActivity +import com.shabinder.spotiflyer.utils.sharedViewModel +import com.shabinder.spotiflyer.utils.showDialog @Composable fun ComposeNavigation() { @@ -31,7 +33,7 @@ fun ComposeNavigation() { arguments = listOf(navArgument("link") { type = NavType.StringType }) ) { Spotify( - link = it.arguments?.getString("link") ?: "error", + fullLink = it.arguments?.getString("link") ?: "error", navController = navController ) } @@ -43,7 +45,7 @@ fun ComposeNavigation() { arguments = listOf(navArgument("link") { type = NavType.StringType }) ) { Gaana( - link = it.arguments?.getString("link") ?: "error", + fullLink = it.arguments?.getString("link") ?: "error", navController = navController ) } @@ -55,9 +57,42 @@ fun ComposeNavigation() { arguments = listOf(navArgument("link") { type = NavType.StringType }) ) { Youtube( - link = it.arguments?.getString("link") ?: "error", + fullLink = it.arguments?.getString("link") ?: "error", navController = navController ) } } +} + +fun NavController.navigateToPlatform(link:String){ + when{ + //SPOTIFY + link.contains("spotify",true) -> { + if(sharedViewModel.spotifyService.value == null){//Authentication pending!! + mainActivity.authenticateSpotify() + } + this.navigateAndPopUpToHome("spotify/$link") + } + + //YOUTUBE + link.contains("youtube.com",true) || link.contains("youtu.be",true) -> { + this.navigateAndPopUpToHome("youtube/$link") + } + + //GAANA + link.contains("gaana",true) -> { + this.navigateAndPopUpToHome("gaana/$link") + } + + else -> showDialog("Link is Not Valid") + } +} + +fun NavController.navigateAndPopUpToHome(route:String, inclusive:Boolean = false,singleInstance:Boolean = true){ + this.navigate(route){ + launchSingleTop = singleInstance + popUpTo(route = "home"){ + this.inclusive = inclusive + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/base/TrackListViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/base/TrackListViewModel.kt index 533b7bfe..55d6457a 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/base/TrackListViewModel.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/base/TrackListViewModel.kt @@ -17,17 +17,24 @@ package com.shabinder.spotiflyer.ui.base -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.shabinder.spotiflyer.models.TrackDetails +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow abstract class TrackListViewModel:ViewModel() { abstract var folderType:String abstract var subFolder:String - open val trackList = MutableLiveData>() + private val _trackList = MutableStateFlow>(mutableListOf()) + open val trackList:StateFlow> + get() = _trackList + + fun updateTrackList(list:List){ + _trackList.value = list + } private val loading = "Loading!" - open var title = MutableLiveData().apply { value = loading } - open var coverUrl = MutableLiveData() + open var title = MutableStateFlow(loading) + open var coverUrl = MutableStateFlow(loading) } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/home/Home.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/home/Home.kt index ef6ff397..4f63ab49 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/home/Home.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/home/Home.kt @@ -29,11 +29,16 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import androidx.navigation.compose.navigate +import com.shabinder.spotiflyer.MainActivity import com.shabinder.spotiflyer.R +import com.shabinder.spotiflyer.navigation.navigateToPlatform import com.shabinder.spotiflyer.ui.SpotiFlyerTypography import com.shabinder.spotiflyer.ui.colorAccent import com.shabinder.spotiflyer.ui.colorPrimary +import com.shabinder.spotiflyer.utils.mainActivity import com.shabinder.spotiflyer.utils.openPlatform +import com.shabinder.spotiflyer.utils.sharedViewModel +import com.shabinder.spotiflyer.utils.showDialog @Composable fun Home(navController: NavController, modifier: Modifier = Modifier) { @@ -289,7 +294,7 @@ fun SearchPanel( ) OutlinedButton( modifier = Modifier.padding(12.dp).wrapContentWidth(), - onClick = {navController.navigate("track_list/$link") }, + onClick = {navController.navigateToPlatform(link)}, border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent))) ){ Text(text = "Search",style = SpotiFlyerTypography.h6,modifier = Modifier.padding(4.dp)) diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/Gaana.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/Gaana.kt index ed1f3a6e..877542d3 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/Gaana.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/Gaana.kt @@ -1,8 +1,45 @@ package com.shabinder.spotiflyer.ui.platforms.gaana import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavController +import com.shabinder.spotiflyer.utils.* +import kotlinx.coroutines.launch @Composable -fun Gaana(link: String, navController: NavController,) { +fun Gaana( + fullLink: String, + navController: NavController +) { + //Coroutine Scope Active till this Composable is Active + val coroutineScope = rememberCoroutineScope() + + //Link Schema: https://gaana.com/type/link + val gaanaLink = fullLink.substringAfter("gaana.com/") + + val link = gaanaLink.substringAfterLast('/', "error") + val type = gaanaLink.substringBeforeLast('/', "error").substringAfterLast('/') + + log("Gaana Fragment", "$type : $link") + + //Error + if (type == "Error" || link == "Error"){ + showDialog("Please Check Your Link!") + navController.popBackStack() + } + + coroutineScope.launch { + val result = gaanaSearch( + type, + link, + sharedViewModel.gaanaInterface, + sharedViewModel.databaseDAO, + ) + + log("Gaana",result.toString()) + log("Gaana Tracks",result.trackList.size.toString()) + + + + } } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaProvider.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaProvider.kt new file mode 100644 index 00000000..f942ee9c --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaProvider.kt @@ -0,0 +1,208 @@ +/* + * 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.platforms.gaana + +import com.shabinder.spotiflyer.database.DatabaseDAO +import com.shabinder.spotiflyer.database.DownloadRecord +import com.shabinder.spotiflyer.models.DownloadStatus +import com.shabinder.spotiflyer.models.PlatformQueryResult +import com.shabinder.spotiflyer.models.TrackDetails +import com.shabinder.spotiflyer.models.gaana.GaanaTrack +import com.shabinder.spotiflyer.models.spotify.Source +import com.shabinder.spotiflyer.networking.GaanaInterface +import com.shabinder.spotiflyer.utils.Provider.imageDir +import com.shabinder.spotiflyer.utils.finalOutputDir +import com.shabinder.spotiflyer.utils.queryActiveTracks +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File + +private const val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg" + +suspend fun gaanaSearch( + type:String, + link:String, + gaanaInterface: GaanaInterface, + databaseDAO: DatabaseDAO, +): PlatformQueryResult { + val result = PlatformQueryResult( + folderType = "", + subFolder = link, + title = link, + coverUrl = gaanaPlaceholderImageUrl, + trackList = listOf(), + ) + with(result) { + when (type) { + "song" -> { + gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also { + folderType = "Tracks" + subFolder = "" + if (File( + finalOutputDir( + it.track_title, + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! + it.downloaded = DownloadStatus.Downloaded + } + trackList = listOf(it).toTrackDetailsList(folderType, subFolder) + title = it.track_title + coverUrl = it.artworkLink + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Track", + name = title, + link = "https://gaana.com/$type/$link", + coverUrl = coverUrl, + totalFiles = 1, + ) + ) + } + } + } + "album" -> { + gaanaInterface.getGaanaAlbum(seokey = link).value?.also { + folderType = "Albums" + subFolder = link + it.tracks.forEach { track -> + if (File( + finalOutputDir( + track.track_title, + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! + track.downloaded = DownloadStatus.Downloaded + } + } + trackList = it.tracks.toTrackDetailsList(folderType, subFolder) + title = link + coverUrl = it.custom_artworks.size_480p + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Album", + name = title, + link = "https://gaana.com/$type/$link", + coverUrl = coverUrl, + totalFiles = trackList.size, + ) + ) + } + } + } + "playlist" -> { + gaanaInterface.getGaanaPlaylist(seokey = link).value?.also { + folderType = "Playlists" + subFolder = link + it.tracks.forEach { track -> + if (File( + finalOutputDir( + track.track_title, + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! + track.downloaded = DownloadStatus.Downloaded + } + } + trackList = it.tracks.toTrackDetailsList(folderType, subFolder) + title = link + //coverUrl.value = "TODO" + coverUrl = gaanaPlaceholderImageUrl + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Playlist", + name = title, + link = "https://gaana.com/$type/$link", + coverUrl = coverUrl, + totalFiles = it.tracks.size, + ) + ) + } + } + } + "artist" -> { + folderType = "Artist" + subFolder = link + coverUrl = gaanaPlaceholderImageUrl + val artistDetails = + gaanaInterface.getGaanaArtistDetails(seokey = link).value?.artist?.firstOrNull() + ?.also { + title = it.name + coverUrl = it.artworkLink ?: gaanaPlaceholderImageUrl + } + gaanaInterface.getGaanaArtistTracks(seokey = link).value?.also { + it.tracks.forEach { track -> + if (File( + finalOutputDir( + track.track_title, + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! + track.downloaded = DownloadStatus.Downloaded + } + } + trackList = it.tracks.toTrackDetailsList(folderType, subFolder) + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Artist", + name = artistDetails?.name ?: link, + link = "https://gaana.com/$type/$link", + coverUrl = coverUrl, + totalFiles = trackList.size, + ) + ) + } + } + } + else -> {//TODO Handle Error} + } + } + queryActiveTracks() + return result + } +} + +private fun List.toTrackDetailsList(type:String , subFolder:String) = this.map { + TrackDetails( + title = it.track_title, + artists = it.artist.map { artist -> artist?.name.toString() }, + durationSec = it.duration, + albumArt = File( + imageDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg"), + albumName = it.album_title, + year = it.release_date, + comment = "Genres:${it.genre?.map { genre -> genre?.name }?.reduceOrNull { acc, s -> acc + s }}", + trackUrl = it.lyrics_url, + downloaded = it.downloaded ?: DownloadStatus.NotDownloaded, + source = Source.Gaana, + albumArtURL = it.artworkLink, + outputFile = finalOutputDir(it.track_title,type, subFolder,".m4a") + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaViewModel.kt deleted file mode 100644 index bf8aeee8..00000000 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/gaana/GaanaViewModel.kt +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 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.platforms.gaana - -import androidx.hilt.lifecycle.ViewModelInject -import androidx.lifecycle.viewModelScope -import com.shabinder.spotiflyer.database.DatabaseDAO -import com.shabinder.spotiflyer.database.DownloadRecord -import com.shabinder.spotiflyer.models.DownloadStatus -import com.shabinder.spotiflyer.models.TrackDetails -import com.shabinder.spotiflyer.models.gaana.GaanaTrack -import com.shabinder.spotiflyer.models.spotify.Source -import com.shabinder.spotiflyer.networking.GaanaInterface -import com.shabinder.spotiflyer.ui.base.TrackListViewModel -import com.shabinder.spotiflyer.utils.Provider.imageDir -import com.shabinder.spotiflyer.utils.finalOutputDir -import com.shabinder.spotiflyer.utils.queryActiveTracks -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.File - -class GaanaViewModel @ViewModelInject constructor( - private val databaseDAO: DatabaseDAO, - private val gaanaInterface : GaanaInterface -) : TrackListViewModel(){ - - override var folderType:String = "" - override var subFolder:String = "" - - private val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg" - - fun gaanaSearch(type:String,link:String){ - viewModelScope.launch { - when (type) { - "song" -> { - gaanaInterface.getGaanaSong(seokey = link).value?.tracks?.firstOrNull()?.also { - folderType = "Tracks" - subFolder = "" - if (File( - finalOutputDir( - it.track_title, - folderType, - subFolder - ) - ).exists() - ) {//Download Already Present!! - it.downloaded = DownloadStatus.Downloaded - } - trackList.value = listOf(it).toTrackDetailsList(folderType, subFolder) - title.value = it.track_title - coverUrl.value = it.artworkLink - withContext(Dispatchers.IO) { - databaseDAO.insert( - DownloadRecord( - type = "Track", - name = title.value!!, - link = "https://gaana.com/$type/$link", - coverUrl = coverUrl.value!!, - totalFiles = 1, - ) - ) - } - } - } - "album" -> { - gaanaInterface.getGaanaAlbum(seokey = link).value?.also { - folderType = "Albums" - subFolder = link - it.tracks.forEach { track -> - if (File( - finalOutputDir( - track.track_title, - folderType, - subFolder - ) - ).exists() - ) {//Download Already Present!! - track.downloaded = DownloadStatus.Downloaded - } - } - trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder) - title.value = link - coverUrl.value = it.custom_artworks.size_480p - withContext(Dispatchers.IO) { - databaseDAO.insert( - DownloadRecord( - type = "Album", - name = title.value!!, - link = "https://gaana.com/$type/$link", - coverUrl = coverUrl.value.toString(), - totalFiles = trackList.value?.size ?: 0, - ) - ) - } - } - } - "playlist" -> { - gaanaInterface.getGaanaPlaylist(seokey = link).value?.also { - folderType = "Playlists" - subFolder = link - it.tracks.forEach { track -> - if (File( - finalOutputDir( - track.track_title, - folderType, - subFolder - ) - ).exists() - ) {//Download Already Present!! - track.downloaded = DownloadStatus.Downloaded - } - } - trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder) - title.value = link - //coverUrl.value = "TODO" - coverUrl.value = gaanaPlaceholderImageUrl - withContext(Dispatchers.IO) { - databaseDAO.insert( - DownloadRecord( - type = "Playlist", - name = title.value.toString(), - link = "https://gaana.com/$type/$link", - coverUrl = coverUrl.value.toString(), - totalFiles = it.tracks.size, - ) - ) - } - } - } - "artist" -> { - folderType = "Artist" - subFolder = link - val artistDetails = - gaanaInterface.getGaanaArtistDetails(seokey = link).value?.artist?.firstOrNull() - ?.also { - title.value = it.name - coverUrl.value = it.artworkLink - } - gaanaInterface.getGaanaArtistTracks(seokey = link).value?.also { - it.tracks.forEach { track -> - if (File( - finalOutputDir( - track.track_title, - folderType, - subFolder - ) - ).exists() - ) {//Download Already Present!! - track.downloaded = DownloadStatus.Downloaded - } - } - trackList.value = it.tracks.toTrackDetailsList(folderType, subFolder) - withContext(Dispatchers.IO) { - databaseDAO.insert( - DownloadRecord( - type = "Artist", - name = artistDetails?.name ?: link, - link = "https://gaana.com/$type/$link", - coverUrl = coverUrl.value.toString(), - totalFiles = trackList.value?.size ?: 0, - ) - ) - } - } - } - } - queryActiveTracks() - } - } - - private fun List.toTrackDetailsList(type:String , subFolder:String) = this.map { - TrackDetails( - title = it.track_title, - artists = it.artist.map { artist -> artist?.name.toString() }, - durationSec = it.duration, - albumArt = File( - imageDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg"), - albumName = it.album_title, - year = it.release_date, - comment = "Genres:${it.genre?.map { genre -> genre?.name }?.reduceOrNull { acc, s -> acc + s }}", - trackUrl = it.lyrics_url, - downloaded = it.downloaded ?: DownloadStatus.NotDownloaded, - source = Source.Gaana, - albumArtURL = it.artworkLink, - outputFile = finalOutputDir(it.track_title,type, subFolder,".m4a") - ) - }.toMutableList() -} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/Spotify.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/Spotify.kt index 74205f9a..f9c8fc09 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/Spotify.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/Spotify.kt @@ -1,8 +1,69 @@ package com.shabinder.spotiflyer.ui.platforms.spotify import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.viewinterop.viewModel +import androidx.lifecycle.viewModelScope import androidx.navigation.NavController +import com.shabinder.spotiflyer.models.spotify.Source +import com.shabinder.spotiflyer.networking.SpotifyService +import com.shabinder.spotiflyer.utils.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch @Composable -fun Spotify(link: String, navController: NavController,) { +fun Spotify(fullLink: String, navController: NavController,) { + val source: Source = Source.Spotify + + val coroutineScope = rememberCoroutineScope() + + var spotifyLink = + "https://" + fullLink.substringAfterLast("https://").substringBefore(" ").trim() + log("Spotify Fragment Link", spotifyLink) + + coroutineScope.launch(Dispatchers.Default) { + + /* + * New Link Schema: https://link.tospotify.com/kqTBblrjQbb, + * Fetching Standard Link: https://open.spotify.com/playlist/37i9dQZF1DX9RwfGbeGQwP?si=iWz7B1tETiunDntnDo3lSQ&_branch_match_id=862039436205270630 + * */ + if (!spotifyLink.contains("open.spotify")) { + val resolvedLink = resolveLink(spotifyLink, sharedViewModel.gaanaInterface) + log("Spotify Resolved Link", resolvedLink) + spotifyLink = resolvedLink + } + + val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?') + val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/') + + log("Spotify Fragment", "$type : $link") + + if (sharedViewModel.spotifyService.value == null) {//Authentication pending!! + if (isOnline()) mainActivity.authenticateSpotify() + } + + if (type == "Error" || link == "Error") { + showDialog("Please Check Your Link!") + navController.popBackStack() + } + + if (type == "episode" || type == "show") { + //TODO Implementation + showDialog("Implementing Soon, Stay Tuned!") + } else { + if (sharedViewModel.spotifyService.value == null){ + //Authentication Still Pending + // TODO Better Implementation + showDialog("Authentication Failed") + navController.popBackStack() + }else{ + val result = spotifySearch( + type, + link, + sharedViewModel.spotifyService.value!!, + sharedViewModel.databaseDAO + ) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyProvider.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyProvider.kt new file mode 100644 index 00000000..d500feef --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyProvider.kt @@ -0,0 +1,221 @@ +/* + * 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.platforms.spotify + +import androidx.annotation.WorkerThread +import com.shabinder.spotiflyer.database.DatabaseDAO +import com.shabinder.spotiflyer.database.DownloadRecord +import com.shabinder.spotiflyer.models.DownloadStatus +import com.shabinder.spotiflyer.models.PlatformQueryResult +import com.shabinder.spotiflyer.models.TrackDetails +import com.shabinder.spotiflyer.models.spotify.Album +import com.shabinder.spotiflyer.models.spotify.Image +import com.shabinder.spotiflyer.models.spotify.Source +import com.shabinder.spotiflyer.models.spotify.Track +import com.shabinder.spotiflyer.networking.GaanaInterface +import com.shabinder.spotiflyer.networking.SpotifyService +import com.shabinder.spotiflyer.utils.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File + +suspend fun spotifySearch( + type:String, + link: String, + spotifyService: SpotifyService, + databaseDAO: DatabaseDAO +): PlatformQueryResult { + val result = PlatformQueryResult( + folderType = "", + subFolder = "", + title = "", + coverUrl = "", + trackList = listOf(), + ) + with(result) { + when (type) { + "track" -> { + spotifyService.getTrack(link).value?.also { + folderType = "Tracks" + subFolder = "" + if (File( + finalOutputDir( + it.name.toString(), + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! + it.downloaded = DownloadStatus.Downloaded + } + trackList = listOf(it).toTrackDetailsList(folderType, subFolder) + title = it.name.toString() + coverUrl = (it.album?.images?.elementAtOrNull(1)?.url + ?: it.album?.images?.elementAtOrNull(0)?.url).toString() + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Track", + name = title, + link = "https://open.spotify.com/$type/$link", + coverUrl = coverUrl, + totalFiles = 1, + ) + ) + } + } + } + + "album" -> { + val albumObject = spotifyService.getAlbum(link).value + folderType = "Albums" + subFolder = albumObject?.name.toString() + albumObject?.tracks?.items?.forEach { + if (File( + finalOutputDir( + it.name.toString(), + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! + it.downloaded = DownloadStatus.Downloaded + } + it.album = Album( + images = listOf( + Image( + url = albumObject.images?.elementAtOrNull(1)?.url + ?: albumObject.images?.elementAtOrNull(0)?.url + ) + ) + ) + } + albumObject?.tracks?.items?.toTrackDetailsList(folderType, subFolder).let { + if (it.isNullOrEmpty()) { + //TODO Handle Error + showDialog("Error Fetching Album") + } else { + trackList = it + title = albumObject?.name.toString() + coverUrl = (albumObject?.images?.elementAtOrNull(1)?.url + ?: albumObject?.images?.elementAtOrNull(0)?.url).toString() + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Album", + name = title, + link = "https://open.spotify.com/$type/$link", + coverUrl = coverUrl, + totalFiles = trackList.size, + ) + ) + } + } + } + } + + "playlist" -> { + log("Spotify Service", spotifyService.toString()) + val playlistObject = spotifyService.getPlaylist(link).value + folderType = "Playlists" + subFolder = playlistObject?.name.toString() + val tempTrackList = mutableListOf() + log("Tracks Fetched", playlistObject?.tracks?.items?.size.toString()) + playlistObject?.tracks?.items?.forEach { + it.track?.let { it1 -> + if (File( + finalOutputDir( + it1.name.toString(), + folderType, + subFolder + ) + ).exists() + ) {//Download Already Present!! + it1.downloaded = DownloadStatus.Downloaded + } + tempTrackList.add(it1) + } + } + var moreTracksAvailable = !playlistObject?.tracks?.next.isNullOrBlank() + + while (moreTracksAvailable) { + //Check For More Tracks If available + val moreTracks = + spotifyService.getPlaylistTracks(link, offset = tempTrackList.size).value + moreTracks?.items?.forEach { + it.track?.let { it1 -> tempTrackList.add(it1) } + } + moreTracksAvailable = !moreTracks?.next.isNullOrBlank() + } + log("Total Tracks Fetched", tempTrackList.size.toString()) + trackList = tempTrackList.toTrackDetailsList(folderType, subFolder) + title = playlistObject?.name.toString() + coverUrl = playlistObject?.images?.elementAtOrNull(1)?.url + ?: playlistObject?.images?.firstOrNull()?.url.toString() + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Playlist", + name = title, + link = "https://open.spotify.com/$type/$link", + coverUrl = coverUrl, + totalFiles = tempTrackList.size, + ) + ) + } + } + "episode" -> {//TODO + } + "show" -> {//TODO + } + else -> { + //TODO Handle Error + } + } + } + queryActiveTracks() + return result +} + +@WorkerThread +fun resolveLink( + url:String, + gaanaInterface: GaanaInterface +):String { + val response = gaanaInterface.getResponse(url).execute().body()?.string().toString() + val regex = """https://open\.spotify\.com.+\w""".toRegex() + return regex.find(response)?.value.toString() +} + +private fun List.toTrackDetailsList(type:String, subFolder:String) = this.map { + TrackDetails( + title = it.name.toString(), + artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(), + durationSec = (it.duration_ms/1000).toInt(), + albumArt = File( + Provider.imageDir() + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg"), + albumName = it.album?.name, + year = it.album?.release_date, + comment = "Genres:${it.album?.genres?.joinToString()}", + trackUrl = it.href, + downloaded = it.downloaded, + source = Source.Spotify, + albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString(), + outputFile = finalOutputDir(it.name.toString(),type, subFolder,".m4a") + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyViewModel.kt deleted file mode 100644 index 0cdeb694..00000000 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/spotify/SpotifyViewModel.kt +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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.platforms.spotify - -import androidx.hilt.lifecycle.ViewModelInject -import androidx.lifecycle.viewModelScope -import com.shabinder.spotiflyer.database.DatabaseDAO -import com.shabinder.spotiflyer.database.DownloadRecord -import com.shabinder.spotiflyer.models.DownloadStatus -import com.shabinder.spotiflyer.models.TrackDetails -import com.shabinder.spotiflyer.models.spotify.Album -import com.shabinder.spotiflyer.models.spotify.Image -import com.shabinder.spotiflyer.models.spotify.Source -import com.shabinder.spotiflyer.models.spotify.Track -import com.shabinder.spotiflyer.networking.GaanaInterface -import com.shabinder.spotiflyer.networking.SpotifyService -import com.shabinder.spotiflyer.ui.base.TrackListViewModel -import com.shabinder.spotiflyer.utils.Provider.imageDir -import com.shabinder.spotiflyer.utils.finalOutputDir -import com.shabinder.spotiflyer.utils.log -import com.shabinder.spotiflyer.utils.queryActiveTracks -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.File - -class SpotifyViewModel @ViewModelInject constructor( - private val databaseDAO: DatabaseDAO, - private val gaanaInterface : GaanaInterface -) : TrackListViewModel(){ - - override var folderType:String = "" - override var subFolder:String = "" - - var spotifyService : SpotifyService? = null - - fun resolveLink(url:String):String { - val response = gaanaInterface.getResponse(url).execute().body()?.string().toString() - val regex = """https://open\.spotify\.com.+\w""".toRegex() - return regex.find(response)?.value.toString() - } - - fun spotifySearch(type:String,link: String){ - viewModelScope.launch { - when (type) { - "track" -> { - spotifyService?.getTrack(link)?.value?.also { - folderType = "Tracks" - subFolder = "" - if (File( - finalOutputDir( - it.name.toString(), - folderType, - subFolder - ) - ).exists() - ) {//Download Already Present!! - it.downloaded = DownloadStatus.Downloaded - } - trackList.value = listOf(it).toTrackDetailsList(folderType, subFolder) - title.value = it.name - coverUrl.value = it.album?.images?.elementAtOrNull(1)?.url - ?: it.album?.images?.elementAtOrNull(0)?.url - withContext(Dispatchers.IO) { - databaseDAO.insert( - DownloadRecord( - type = "Track", - name = title.value.toString(), - link = "https://open.spotify.com/$type/$link", - coverUrl = coverUrl.value.toString(), - totalFiles = 1, - ) - ) - } - } - } - - "album" -> { - val albumObject = spotifyService?.getAlbum(link)?.value - folderType = "Albums" - subFolder = albumObject?.name.toString() - albumObject?.tracks?.items?.forEach { - if (File( - finalOutputDir( - it.name.toString(), - folderType, - subFolder - ) - ).exists() - ) {//Download Already Present!! - it.downloaded = DownloadStatus.Downloaded - } - it.album = Album( - images = listOf( - Image( - url = albumObject.images?.elementAtOrNull(1)?.url - ?: albumObject.images?.elementAtOrNull(0)?.url - ) - ) - ) - } - trackList.value = albumObject?.tracks?.items?.toTrackDetailsList(folderType, subFolder) - title.value = albumObject?.name - coverUrl.value = albumObject?.images?.elementAtOrNull(1)?.url - ?: albumObject?.images?.elementAtOrNull(0)?.url - withContext(Dispatchers.IO) { - databaseDAO.insert( - DownloadRecord( - type = "Album", - name = title.value.toString(), - link = "https://open.spotify.com/$type/$link", - coverUrl = coverUrl.value.toString(), - totalFiles = trackList.value?.size ?: 0, - ) - ) - } - } - - "playlist" -> { - log("Spotify Service",spotifyService.toString()) - val playlistObject = spotifyService?.getPlaylist(link)?.value - folderType = "Playlists" - subFolder = playlistObject?.name.toString() - val tempTrackList = mutableListOf() - log("Tracks Fetched", playlistObject?.tracks?.items?.size.toString()) - playlistObject?.tracks?.items?.forEach { - it.track?.let { it1 -> - if (File( - finalOutputDir( - it1.name.toString(), - folderType, - subFolder - ) - ).exists() - ) {//Download Already Present!! - it1.downloaded = DownloadStatus.Downloaded - } - tempTrackList.add(it1) - } - } - var moreTracksAvailable = !playlistObject?.tracks?.next.isNullOrBlank() - - while (moreTracksAvailable) { - //Check For More Tracks If available - val moreTracks = spotifyService?.getPlaylistTracks(link, offset = tempTrackList.size)?.value - moreTracks?.items?.forEach { - it.track?.let { it1 -> tempTrackList.add(it1) } - } - moreTracksAvailable = !moreTracks?.next.isNullOrBlank() - } - log("Total Tracks Fetched", tempTrackList.size.toString()) - trackList.value = tempTrackList.toTrackDetailsList(folderType, subFolder) - title.value = playlistObject?.name - coverUrl.value = playlistObject?.images?.elementAtOrNull(1)?.url - ?: playlistObject?.images?.firstOrNull()?.url.toString() - withContext(Dispatchers.IO) { - databaseDAO.insert( - DownloadRecord( - type = "Playlist", - name = title.value.toString(), - link = "https://open.spotify.com/$type/$link", - coverUrl = coverUrl.value.toString(), - totalFiles = tempTrackList.size, - ) - ) - } - } - "episode" -> {//TODO - } - "show" -> {//TODO - } - } - queryActiveTracks() - } - } - - private fun List.toTrackDetailsList(type:String , subFolder:String) = this.map { - TrackDetails( - title = it.name.toString(), - artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(), - durationSec = (it.duration_ms/1000).toInt(), - albumArt = File( - imageDir() + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg"), - albumName = it.album?.name, - year = it.album?.release_date, - comment = "Genres:${it.album?.genres?.joinToString()}", - trackUrl = it.href, - downloaded = it.downloaded, - source = Source.Spotify, - albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString(), - outputFile = finalOutputDir(it.name.toString(),type, subFolder,".m4a") - ) - }.toMutableList() -} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/Youtube.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/Youtube.kt index 0ddaba00..7ea3c31f 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/Youtube.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/Youtube.kt @@ -1,9 +1,52 @@ package com.shabinder.spotiflyer.ui.platforms.youtube import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavController +import com.shabinder.spotiflyer.models.spotify.Source +import com.shabinder.spotiflyer.utils.sharedViewModel +import com.shabinder.spotiflyer.utils.showDialog +import kotlinx.coroutines.launch + + +private const val sampleDomain2 = "youtu.be" +private const val sampleDomain1 = "youtube.com" @Composable -fun Youtube(link: String, navController: NavController,) { +fun Youtube(fullLink: String, navController: NavController,) { + val source = Source.YouTube + //Coroutine Scope Active till this Composable is Active + val coroutineScope = rememberCoroutineScope() + + coroutineScope.launch { + val link = fullLink.removePrefix("https://").removePrefix("http://") + if(link.contains("playlist",true) || link.contains("list",true)){ + // Given Link is of a Playlist + val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&") + getYTPlaylist( + playlistId, + sharedViewModel.ytDownloader, + sharedViewModel.databaseDAO + ) + }else{//Given Link is of a Video + var searchId = "error" + if(link.contains(sampleDomain1,true) ){ + searchId = link.substringAfterLast("=","error") + } + if(link.contains(sampleDomain2,true) ){ + searchId = link.substringAfterLast("/","error") + } + if(searchId != "error") { + getYTTrack( + searchId, + sharedViewModel.ytDownloader, + sharedViewModel.databaseDAO + ) + }else{ + showDialog("Your Youtube Link is not of a Video!!") + navController.popBackStack() + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeProvider.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeProvider.kt new file mode 100644 index 00000000..aae0283e --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeProvider.kt @@ -0,0 +1,187 @@ +/* + * 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.platforms.youtube + +import android.annotation.SuppressLint +import com.github.kiulian.downloader.YoutubeDownloader +import com.shabinder.spotiflyer.database.DatabaseDAO +import com.shabinder.spotiflyer.database.DownloadRecord +import com.shabinder.spotiflyer.models.DownloadStatus +import com.shabinder.spotiflyer.models.PlatformQueryResult +import com.shabinder.spotiflyer.models.TrackDetails +import com.shabinder.spotiflyer.models.spotify.Source +import com.shabinder.spotiflyer.utils.* +import com.shabinder.spotiflyer.utils.Provider.imageDir +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File + +/* +* YT Album Art Schema +* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg" +* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg" +* */ + +suspend fun getYTPlaylist( + searchId: String, + ytDownloader: YoutubeDownloader, + databaseDAO: DatabaseDAO, +):PlatformQueryResult{ + val result = PlatformQueryResult( + folderType = "", + subFolder = "", + title = "", + coverUrl = "", + trackList = listOf(), + ) + with(result) { + try { + log("YT Playlist", searchId) + val playlist = ytDownloader.getPlaylist(searchId) + val playlistDetails = playlist.details() + val name = playlistDetails.title() + subFolder = removeIllegalChars(name) + val videos = playlist.videos() + + coverUrl = "https://i.ytimg.com/vi/${ + videos.firstOrNull()?.videoId() + }/hqdefault.jpg" + title = name + + trackList = videos.map { + TrackDetails( + title = it.title(), + artists = listOf(it.author().toString()), + durationSec = it.lengthSeconds(), + albumArt = File( + imageDir() + it.videoId() + ".jpeg" + ), + source = Source.YouTube, + albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg", + downloaded = if (File( + finalOutputDir( + itemName = it.title(), + type = folderType, + subFolder = subFolder + ) + ).exists() + ) + DownloadStatus.Downloaded + else { + DownloadStatus.NotDownloaded + }, + outputFile = finalOutputDir(it.title(), folderType, subFolder, ".m4a"), + videoID = it.videoId() + ) + } + + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "PlayList", + name = if (name.length > 17) { + "${name.subSequence(0, 16)}..." + } else { + name + }, + link = "https://www.youtube.com/playlist?list=$searchId", + coverUrl = "https://i.ytimg.com/vi/${ + videos.firstOrNull()?.videoId() + }/hqdefault.jpg", + totalFiles = videos.size, + ) + ) + } + queryActiveTracks() + } catch (e: Exception) { + showDialog("An Error Occurred While Processing!") + } + } + return result +} + +@SuppressLint("DefaultLocale") +suspend fun getYTTrack( + searchId:String, + ytDownloader: YoutubeDownloader, + databaseDAO: DatabaseDAO +):PlatformQueryResult { + val result = PlatformQueryResult( + folderType = "", + subFolder = "", + title = "", + coverUrl = "", + trackList = listOf(), + ) + with(result) { + try { + log("YT Video", searchId) + val video = ytDownloader.getVideo(searchId) + coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg" + val detail = video?.details() + val name = detail?.title()?.replace(detail.author()!!.toUpperCase(), "", true) + ?: detail?.title() ?: "" + log("YT View Model", detail.toString()) + trackList = listOf( + TrackDetails( + title = name, + artists = listOf(detail?.author().toString()), + durationSec = detail?.lengthSeconds() ?: 0, + albumArt = File(imageDir(), "$searchId.jpeg"), + source = Source.YouTube, + albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg", + downloaded = if (File( + finalOutputDir( + itemName = name, + type = folderType, + subFolder = subFolder + ) + ).exists() + ) + DownloadStatus.Downloaded + else { + DownloadStatus.NotDownloaded + }, + outputFile = finalOutputDir(name, folderType, subFolder, ".m4a"), + videoID = searchId + ) + ) + title = name + + withContext(Dispatchers.IO) { + databaseDAO.insert( + DownloadRecord( + type = "Track", + name = if (name.length > 17) { + "${name.subSequence(0, 16)}..." + } else { + name + }, + link = "https://www.youtube.com/watch?v=$searchId", + coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg", + totalFiles = 1, + ) + ) + } + queryActiveTracks() + } catch (e: Exception) { + showDialog("An Error Occurred While Processing!") + } + } + return result +} diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeViewModel.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeViewModel.kt deleted file mode 100644 index 92c41952..00000000 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/platforms/youtube/YoutubeViewModel.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * 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.platforms.youtube - -import android.annotation.SuppressLint -import androidx.hilt.lifecycle.ViewModelInject -import androidx.lifecycle.viewModelScope -import com.github.kiulian.downloader.YoutubeDownloader -import com.shabinder.spotiflyer.database.DatabaseDAO -import com.shabinder.spotiflyer.database.DownloadRecord -import com.shabinder.spotiflyer.models.DownloadStatus -import com.shabinder.spotiflyer.models.TrackDetails -import com.shabinder.spotiflyer.models.spotify.Source -import com.shabinder.spotiflyer.ui.base.TrackListViewModel -import com.shabinder.spotiflyer.utils.* -import com.shabinder.spotiflyer.utils.Provider.imageDir -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.File - -class YoutubeViewModel @ViewModelInject constructor( - private val databaseDAO: DatabaseDAO, - private val ytDownloader: YoutubeDownloader -) : TrackListViewModel(){ - /* - * YT Album Art Schema - * HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg" - * Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg" - * */ - - override var folderType = "YT_Downloads" - override var subFolder = "" - - fun getYTPlaylist(searchId:String){ - if(!isOnline())return - try{ - viewModelScope.launch(Dispatchers.IO) { - log("YT Playlist",searchId) - val playlist = ytDownloader.getPlaylist(searchId) - val playlistDetails = playlist.details() - val name = playlistDetails.title() - subFolder = removeIllegalChars(name) - val videos = playlist.videos() - coverUrl.postValue("https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg") - title.postValue( - if(name.length > 17){"${name.subSequence(0,16)}..."}else{name} - ) - this@YoutubeViewModel.trackList.postValue(videos.map { - TrackDetails( - title = it.title(), - artists = listOf(it.author().toString()), - durationSec = it.lengthSeconds(), - albumArt = File( - imageDir() + it.videoId() + ".jpeg" - ), - source = Source.YouTube, - albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg", - downloaded = if (File( - finalOutputDir( - itemName = it.title(), - type = folderType, - subFolder = subFolder - )).exists() - ) - DownloadStatus.Downloaded - else { - DownloadStatus.NotDownloaded - }, - outputFile = finalOutputDir(it.title(),folderType, subFolder,".m4a"), - videoID = it.videoId() - ) - }.toMutableList()) - - withContext(Dispatchers.IO){ - databaseDAO.insert(DownloadRecord( - type = "PlayList", - name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}, - link = "https://www.youtube.com/playlist?list=$searchId", - coverUrl = "https://i.ytimg.com/vi/${videos.firstOrNull()?.videoId()}/hqdefault.jpg", - totalFiles = videos.size, - )) - } - queryActiveTracks() - } - }catch (e:Exception){ - showDialog("An Error Occurred While Processing!") - } - - } - - @SuppressLint("DefaultLocale") - fun getYTTrack(searchId:String) { - if(!isOnline())return - try{ - viewModelScope.launch(Dispatchers.IO) { - log("YT Video",searchId) - val video = ytDownloader.getVideo(searchId) - coverUrl.postValue("https://i.ytimg.com/vi/$searchId/hqdefault.jpg") - val detail = video?.details() - val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title() ?: "" - log("YT View Model",detail.toString()) - this@YoutubeViewModel.trackList.postValue( - listOf( - TrackDetails( - title = name, - artists = listOf(detail?.author().toString()), - durationSec = detail?.lengthSeconds()?:0, - albumArt = File(imageDir(),"$searchId.jpeg"), - source = Source.YouTube, - albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg", - downloaded = if (File( - finalOutputDir( - itemName = name, - type = folderType, - subFolder = subFolder - )).exists() - ) - DownloadStatus.Downloaded - else { - DownloadStatus.NotDownloaded - }, - outputFile = finalOutputDir(name,folderType, subFolder,".m4a"), - videoID = searchId - ) - ).toMutableList() - ) - title.postValue( - if(name.length > 17){"${name.subSequence(0,16)}..."}else{name} - ) - - withContext(Dispatchers.IO){ - databaseDAO.insert(DownloadRecord( - type = "Track", - name = if(name.length > 17){"${name.subSequence(0,16)}..."}else{name}, - link = "https://www.youtube.com/watch?v=$searchId", - coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg", - totalFiles = 1, - )) - } - queryActiveTracks() - } - } catch (e:Exception){ - showDialog("An Error Occurred While Processing!") - } - } -} - 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 8f206bf1..71c07519 100644 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/Utils.kt @@ -21,6 +21,9 @@ import java.io.File val mainActivity get() = MainActivity.getInstance() +val sharedViewModel + get() = MainActivity.getSharedViewModel() + fun loadAllImages(context: Context? = mainActivity, images:List? = null,source: Source) { val serviceIntent = Intent(context, ForegroundService::class.java) images?.let { serviceIntent.putStringArrayListExtra("imagesList",(it + source.name) as ArrayList) }