diff --git a/.idea/misc.xml b/.idea/misc.xml index 37a75096..7bfef59d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 27e9bf91..acc3be5c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,13 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + compileOptions { + targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } } dependencies { @@ -51,6 +58,8 @@ dependencies { 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.github.bumptech.glide:glide:4.11.0" + kapt "com.github.bumptech.glide:compiler:4.11.0" implementation 'androidx.recyclerview:recyclerview:1.1.0' @@ -62,6 +71,8 @@ dependencies { implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.squareup.okhttp3:okhttp:4.8.0' + implementation 'com.github.sealedtx:java-youtube-downloader:2.2.1' + implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test.ext:junit:1.1.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b53b56ca..abd17598 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="com.shabinder.musicForEveryone"> + diff --git a/app/src/main/java/com/shabinder/musicForEveryone/BindingAdapter.kt b/app/src/main/java/com/shabinder/musicForEveryone/BindingAdapter.kt new file mode 100644 index 00000000..e4bfb6d3 --- /dev/null +++ b/app/src/main/java/com/shabinder/musicForEveryone/BindingAdapter.kt @@ -0,0 +1,20 @@ +package com.shabinder.musicForEveryone + +import android.widget.ImageView +import androidx.core.net.toUri +import androidx.databinding.BindingAdapter +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions + + +@BindingAdapter("imageUrl") +fun bindImage(imgView: ImageView, imgUrl: String?) { + imgUrl?.let { + val imgUri = imgUrl.toUri().buildUpon().scheme("https").build() + Glide.with(imgView.context) + .load(imgUri) + .apply(RequestOptions() + .error(R.drawable.ic_musicplaceholder)) + .into(imgView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/musicForEveryone/MainActivity.kt b/app/src/main/java/com/shabinder/musicForEveryone/MainActivity.kt index 55d3aa2f..0601ab96 100644 --- a/app/src/main/java/com/shabinder/musicForEveryone/MainActivity.kt +++ b/app/src/main/java/com/shabinder/musicForEveryone/MainActivity.kt @@ -9,29 +9,33 @@ import android.util.Log import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider +import com.github.kiulian.downloader.YoutubeDownloader import com.shabinder.musicForEveryone.databinding.MainActivityBinding +import com.shabinder.musicForEveryone.utils.YoutubeConnector import com.spotify.sdk.android.authentication.AuthenticationClient import com.spotify.sdk.android.authentication.AuthenticationRequest import com.spotify.sdk.android.authentication.AuthenticationResponse import com.spotify.sdk.android.authentication.LoginActivity import kaaes.spotify.webapi.android.SpotifyApi import kaaes.spotify.webapi.android.SpotifyService +import retrofit.RestAdapter class MainActivity : AppCompatActivity() { private lateinit var binding: MainActivityBinding + var ytDownloader : YoutubeDownloader? = null val REDIRECT_URI = "musicforeveryone://callback" val CLIENT_ID:String = "694d8bf4f6ec420fa66ea7fb4c68f89d" // val musicDirectory = File(this.filesDir?.absolutePath + "/Music/") var message :String ="" var token :String ="" var spotify: SpotifyService? = null - lateinit var mainViewModel: MainViewModel + lateinit var sharedViewModel: SharedViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this,R.layout.main_activity) - mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java) + sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java) val policy = StrictMode.ThreadPolicy.Builder().permitAll().build() StrictMode.setThreadPolicy(policy) @@ -41,15 +45,16 @@ class MainActivity : AppCompatActivity() { } requestPermission() + //Object to download From Youtube {"https://github.com/sealedtx/java-youtube-downloader"} + ytDownloader = YoutubeDownloader() + sharedViewModel.ytDownloader = ytDownloader + //Initialing Communication with Youtube + YoutubeConnector.youtubeConnector() + + + } - private fun authenticateSpotify() { - val builder = AuthenticationRequest.Builder(CLIENT_ID,AuthenticationResponse.Type.TOKEN,REDIRECT_URI) - .setShowDialog(true) - .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, @@ -64,18 +69,18 @@ class MainActivity : AppCompatActivity() { AuthenticationResponse.Type.TOKEN -> { Log.i("Network",response.accessToken.toString()) token = response.accessToken + sharedViewModel.accessToken = response.accessToken - + //Implementing My Own Spotify Requests + implementSpotifyExtra() val api = SpotifyApi() api.setAccessToken(token) spotify = api.service - mainViewModel.spotify = api.service + sharedViewModel.spotify = api.service //Initiate Processes In Main Fragment val me = spotify?.me?.display_name - mainViewModel.userName.value = me + sharedViewModel.userName.value = me Log.i("Network",me!!) - - } AuthenticationResponse.Type.ERROR -> { Log.i("Network",response.error.toString()) @@ -87,6 +92,25 @@ class MainActivity : AppCompatActivity() { } } + /** + * Adding my own new Spotify Web Api Requests! + * */ + private fun implementSpotifyExtra() { + val restAdapter = RestAdapter.Builder() + .setEndpoint(SpotifyApi.SPOTIFY_WEB_API_ENDPOINT) + .setRequestInterceptor { request -> + request.addHeader( + "Authorization", + "Bearer $token" + ) + } + .build() + + val spotifyExtra = restAdapter.create(SpotifyNewService::class.java) + sharedViewModel.spotifyExtra = spotifyExtra + } + + private fun requestPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions( @@ -96,5 +120,13 @@ class MainActivity : AppCompatActivity() { } } + private fun authenticateSpotify() { + val builder = AuthenticationRequest.Builder(CLIENT_ID,AuthenticationResponse.Type.TOKEN,REDIRECT_URI) + .setShowDialog(true) + .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) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/musicForEveryone/MainViewModel.kt b/app/src/main/java/com/shabinder/musicForEveryone/MainViewModel.kt deleted file mode 100644 index 7c8aa62c..00000000 --- a/app/src/main/java/com/shabinder/musicForEveryone/MainViewModel.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.shabinder.musicForEveryone - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import kaaes.spotify.webapi.android.SpotifyService - -class MainViewModel : ViewModel() { - - var apiCame = MutableLiveData().apply { value = 0 } - var userName = MutableLiveData().apply { value = "Placeholder" } - - - var spotify :SpotifyService? = null - -} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/musicForEveryone/SharedViewModel.kt b/app/src/main/java/com/shabinder/musicForEveryone/SharedViewModel.kt new file mode 100644 index 00000000..fa509430 --- /dev/null +++ b/app/src/main/java/com/shabinder/musicForEveryone/SharedViewModel.kt @@ -0,0 +1,28 @@ +package com.shabinder.musicForEveryone + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.github.kiulian.downloader.YoutubeDownloader +import kaaes.spotify.webapi.android.SpotifyService +import kaaes.spotify.webapi.android.models.Album +import kaaes.spotify.webapi.android.models.Playlist +import kaaes.spotify.webapi.android.models.Track + +class SharedViewModel : ViewModel() { + var accessToken:String = "" + var userName = MutableLiveData().apply { value = "Placeholder" } + var spotify :SpotifyService? = null + var spotifyExtra :SpotifyNewService? = null + var ytDownloader : YoutubeDownloader? = null + + fun getTrackDetails(trackLink:String): Track?{ + return spotify?.getTrack(trackLink) + } + fun getAlbumDetails(albumLink:String): Album?{ + return spotify?.getAlbum(albumLink) + } + fun getPlaylistDetails(link:String): Playlist?{ + return spotifyExtra?.getPlaylist(link) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/musicForEveryone/SpotifyNewService.kt b/app/src/main/java/com/shabinder/musicForEveryone/SpotifyNewService.kt new file mode 100644 index 00000000..8438c7d6 --- /dev/null +++ b/app/src/main/java/com/shabinder/musicForEveryone/SpotifyNewService.kt @@ -0,0 +1,15 @@ +package com.shabinder.musicForEveryone + +import kaaes.spotify.webapi.android.models.Playlist +import retrofit.http.GET +import retrofit.http.Path + + +interface SpotifyNewService { + + @GET("/playlists/{playlist_id}") + fun getPlaylist(@Path("playlist_id") playlistId: String?): Playlist? + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/musicForEveryone/fragments/MainFragment.kt b/app/src/main/java/com/shabinder/musicForEveryone/fragments/MainFragment.kt index 78a9f5a4..88f21692 100644 --- a/app/src/main/java/com/shabinder/musicForEveryone/fragments/MainFragment.kt +++ b/app/src/main/java/com/shabinder/musicForEveryone/fragments/MainFragment.kt @@ -1,21 +1,27 @@ package com.shabinder.musicForEveryone.fragments import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider -import com.shabinder.musicForEveryone.MainViewModel +import com.google.android.material.snackbar.Snackbar import com.shabinder.musicForEveryone.R +import com.shabinder.musicForEveryone.SharedViewModel +import com.shabinder.musicForEveryone.bindImage import com.shabinder.musicForEveryone.databinding.MainFragmentBinding +import com.shabinder.musicForEveryone.utils.YoutubeConnector import kaaes.spotify.webapi.android.SpotifyService class MainFragment : Fragment() { lateinit var binding:MainFragmentBinding - private lateinit var mainViewModel: MainViewModel + private lateinit var sharedViewModel: SharedViewModel var spotify : SpotifyService? = null override fun onCreateView( @@ -23,17 +29,89 @@ class MainFragment : Fragment() { savedInstanceState: Bundle? ): View { binding = DataBindingUtil.inflate(inflater,R.layout.main_fragment,container,false) + + sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java) + spotify = sharedViewModel.spotify + sharedViewModel.userName.observe(viewLifecycleOwner, Observer { + binding.message.text = it + if(it!="Placeholder"){Snackbar.make(requireView(),"Hello, $it!", Snackbar.LENGTH_SHORT).show()} + }) + + binding.btnGetDetails.setOnClickListener { + val spotifyLink = binding.spotifyLink.text.toString() + + val link = spotifyLink.substringAfterLast('/' , "Error").substringBefore('?') + val type = spotifyLink.substringBeforeLast('/' , "Error").substringAfterLast('/') + + Log.i("Fragment",link) + + when(type){ + "track" -> { + val trackObject = sharedViewModel.getTrackDetails(link) + Log.i("Fragment",trackObject?.name.toString()) + binding.name.text = trackObject?.name ?: "Error" + var artistNames = "" + trackObject?.artists?.forEach { artistNames = artistNames.plus("${it.name} ,") } + binding.artist.text = artistNames + binding.popularity.text = trackObject?.popularity.toString() + binding.duration.text = ((trackObject?.duration_ms!! /1000/60).toString() + "minutes") + binding.albumName.text = trackObject.album.name + bindImage(binding.imageUrl, trackObject.album.images[0].url) + } + + "album" -> { + val albumObject = sharedViewModel.getAlbumDetails(link) + Log.i("Fragment",albumObject?.name.toString()) + binding.name.text = albumObject?.name ?: "Error" + var artistNames = "" + albumObject?.artists?.forEach { artistNames = artistNames.plus(", ${it.name}") } + binding.artist.text = artistNames + binding.popularity.text = albumObject?.popularity.toString() + binding.duration.visibility = View.GONE + binding.textView5.visibility = View.GONE + binding.albumName.text = albumObject?.name + bindImage(binding.imageUrl, albumObject?.images?.get(0)?.url) + } + + "playlist" -> { + val playlistObject = sharedViewModel.getPlaylistDetails(link) + Log.i("Fragment",playlistObject?.name.toString()) + binding.name.text = playlistObject?.name ?: "Error" + binding.artist.visibility = View.GONE + binding.textView1.visibility = View.GONE + binding.popularity.text = playlistObject?.followers?.total.toString() + binding.textview3.text = "Followers" + binding.duration.visibility = View.GONE + binding.textView5.visibility = View.GONE + binding.textView7.text = "Total Tracks" + binding.albumName.text = playlistObject?.tracks?.items?.size.toString() + bindImage(binding.imageUrl, playlistObject?.images?.get(0)?.url) + } + + "episode" -> {showToast("Implementation Pending")} + "show" -> {showToast("Implementation Pending ")} + } + + binding.spotifyLink.setText(link) + } + + binding.btnDownload.setOnClickListener { + val data = YoutubeConnector.search( + "${binding.name.text} ${if(binding.artist.text != "TextView" ){binding.artist.text}else{""}}") + ?.get(0) + if (data==null){showToast("Youtube Request Failed!")}else{ + val ytDownloader = sharedViewModel.ytDownloader + val video = ytDownloader?.getVideo(data.id) + val details = video?.details() + Log.i("ytDownloader", details?.title()?:"Error") + binding.name.text = details?.title() + } + } + return binding.root } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - mainViewModel = ViewModelProvider(this.requireActivity()).get(MainViewModel::class.java) - spotify = mainViewModel.spotify - mainViewModel.userName.observeForever { - binding.message.text = it - } - + private fun showToast(message:String){ + Toast.makeText(context,message,Toast.LENGTH_SHORT).show() } - } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/musicForEveryone/utils/YoutubeConnector.kt b/app/src/main/java/com/shabinder/musicForEveryone/utils/YoutubeConnector.kt new file mode 100644 index 00000000..ba9b7112 --- /dev/null +++ b/app/src/main/java/com/shabinder/musicForEveryone/utils/YoutubeConnector.kt @@ -0,0 +1,66 @@ +package com.shabinder.musicForEveryone.utils + +import android.util.Log +import com.google.api.client.http.HttpRequestInitializer +import com.google.api.client.http.javanet.NetHttpTransport +import com.google.api.client.json.jackson2.JacksonFactory +import com.google.api.services.youtube.YouTube +import java.io.IOException + +object YoutubeConnector { + private var youtube: YouTube? = null + private var query:YouTube.Search.List? = null + var apiKey:String = "AIzaSyDuRmMA_2mF56BjlhhNpa0SIbjMgjjFaEI" + var clientID : String = "1040727735015-er2mvvljt45cabkuqimsh3iabqvfpvms.apps.googleusercontent.com" + + fun youtubeConnector() { + youtube = + YouTube.Builder(NetHttpTransport(), JacksonFactory(), + HttpRequestInitializer { }) + .setApplicationName("Music For Everyone").build() + try { + query = youtube?.search()?.list("id,snippet") + query?.key = apiKey + query?.maxResults = 1 + query?.type = "video" + query?.fields = + "items(id/kind,id/videoId,snippet/title,snippet/description,snippet/thumbnails/default/url)" + } catch (e: IOException) { + Log.i("YC", "Could not initialize: $e") + } + } + + fun search(keywords: String?): List? { + Log.i("YC searched for",keywords.toString()) + if (youtube == null){youtubeConnector()} + query!!.q= keywords + return try { + val response = query!!.execute() + val results = + response.items + val items = mutableListOf() + for (result in results) { + val item = VideoItem( + id = result.id.videoId, + title = result.snippet.title, + description = result.snippet.description, + thumbnailUrl = result.snippet.thumbnails.default.url + ) + items.add(item) + Log.i("YC links received",item.id) + } + items + } catch (e: IOException) { + Log.d("YC", "Could not search: $e") + null + } + } + + data class VideoItem( + val id:String, + val title:String, + val description: String, + val thumbnailUrl:String + ) + +} \ No newline at end of file diff --git a/app/src/main/res/layout/main_fragment.xml b/app/src/main/res/layout/main_fragment.xml index ca69c1b6..45fc6649 100644 --- a/app/src/main/res/layout/main_fragment.xml +++ b/app/src/main/res/layout/main_fragment.xml @@ -3,6 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> + + + - +