From 1a142cfc451f564d38518caa75a7e83e40cdbb6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20=C4=90=E1=BB=A9c=20Tu=E1=BA=A5n=20Minh?= Date: Sat, 10 Feb 2024 19:42:46 +0700 Subject: [PATCH] Fixed #167 Spotify Canvas Supported --- .../data/repository/MainRepository.kt | 93 +- .../ui/fragment/player/NowPlayingFragment.kt | 228 ++- .../simpmusic/viewModel/SharedViewModel.kt | 22 + app/src/main/res/drawable/bottom_overlay.xml | 8 + app/src/main/res/drawable/canvas_overlay.xml | 8 + .../main/res/layout/fragment_now_playing.xml | 1269 +++++++++-------- kotlinYtmusicScraper/build.gradle.kts | 6 - .../maxrave/kotlinytmusicscraper/test/main.kt | 4 +- 8 files changed, 1044 insertions(+), 594 deletions(-) create mode 100644 app/src/main/res/drawable/bottom_overlay.xml create mode 100644 app/src/main/res/drawable/canvas_overlay.xml diff --git a/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt b/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt index a5ab1d96..91022634 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/repository/MainRepository.kt @@ -13,6 +13,7 @@ import com.maxrave.kotlinytmusicscraper.models.musixmatch.MusixmatchCredential import com.maxrave.kotlinytmusicscraper.models.musixmatch.MusixmatchTranslationLyricsResponse import com.maxrave.kotlinytmusicscraper.models.musixmatch.SearchMusixmatchResponse import com.maxrave.kotlinytmusicscraper.models.response.SearchResponse +import com.maxrave.kotlinytmusicscraper.models.response.spotify.CanvasResponse import com.maxrave.kotlinytmusicscraper.models.simpmusic.GithubResponse import com.maxrave.kotlinytmusicscraper.models.sponsorblock.SkipSegments import com.maxrave.kotlinytmusicscraper.models.youtube.YouTubeInitialPage @@ -1159,6 +1160,92 @@ class MainRepository @Inject constructor(private val localDataSource: LocalDataS } } + suspend fun getCanvas(videoId: String, duration: Int): Flow = flow { + runCatching { + getSongById(videoId).first().let { song -> + val q = "${song?.title} ${song?.artistName?.firstOrNull() ?: ""}".replace( + Regex("\\((feat\\.|ft.|cùng với|con|mukana|com|avec|合作音乐人: ) "), + " " + ).replace( + Regex("( và | & | и | e | und |, |和| dan)"), " " + ).replace(" ", " ").replace(Regex("([()])"), "").replace(".", " ") + .replace(" ", " ") + var spotifyPersonalToken = "" + if (dataStoreManager.spotifyPersonalToken.first() + .isNotEmpty() && dataStoreManager.spotifyPersonalTokenExpires.first() > System.currentTimeMillis() && dataStoreManager.spotifyPersonalTokenExpires.first() != 0L + ) { + spotifyPersonalToken = dataStoreManager.spotifyPersonalToken.first() + Log.d("Lyrics", "spotifyPersonalToken: $spotifyPersonalToken") + } else if (dataStoreManager.spdc.first().isNotEmpty()) { + YouTube.getPersonalToken(dataStoreManager.spdc.first()).onSuccess { + spotifyPersonalToken = it.accessToken + dataStoreManager.setSpotifyPersonalToken(spotifyPersonalToken) + dataStoreManager.setSpotifyPersonalTokenExpires(it.accessTokenExpirationTimestampMs) + Log.d("Lyrics", "spotifyPersonalToken: $spotifyPersonalToken") + }.onFailure { + it.printStackTrace() + emit(null) + } + } + if (spotifyPersonalToken.isNotEmpty()) { + var clientToken = dataStoreManager.spotifyClientToken.first() + Log.d("Lyrics", "clientToken: $clientToken") + YouTube.searchSpotifyTrack(q, clientToken).onSuccess { searchResponse -> + val track = if (duration != 0) { + searchResponse.tracks.items.find { abs(((it.duration_ms / 1000) - duration)) < 1 } + ?: searchResponse.tracks.items.firstOrNull() + } else { + searchResponse.tracks.items.firstOrNull() + } + Log.d("Lyrics", "track: $track") + if (track != null) { + YouTube.getSpotifyCanvas(track.id, spotifyPersonalToken).onSuccess { + Log.w("Spotify Canvas", "canvas: $it") + emit(it) + }.onFailure { + it.printStackTrace() + emit(null) + } + } else { + emit(null) + } + }.onFailure { throwable -> + throwable.printStackTrace() + YouTube.getClientToken().onSuccess { tokenResponse -> + clientToken = tokenResponse.accessToken + Log.w("Lyrics", "clientToken: $clientToken") + dataStoreManager.setSpotifyClientToken(clientToken) + YouTube.searchSpotifyTrack(q, clientToken).onSuccess { searchResponse -> + val track = if (duration != 0) { + searchResponse.tracks.items.find { abs(((it.duration_ms / 1000) - duration)) < 1 } + ?: searchResponse.tracks.items.firstOrNull() + } else { + searchResponse.tracks.items.firstOrNull() + } + Log.d("Lyrics", "track: $track") + if (track != null) { + YouTube.getSpotifyCanvas(track.id, spotifyPersonalToken) + .onSuccess { + Log.w("Spotify Canvas", "canvas: $it") + emit(it) + }.onFailure { + it.printStackTrace() + emit(null) + } + } else { + emit(null) + } + } + }.onFailure { + it.printStackTrace() + emit(null) + } + } + } + } + } + }.flowOn(Dispatchers.IO) + suspend fun getSpotifyLyrics(query: String, duration: Int?): Flow> = flow { runCatching { val q = @@ -1168,7 +1255,7 @@ class MainRepository @Inject constructor(private val localDataSource: LocalDataS ).replace( Regex("( và | & | и | e | und |, |和| dan)"), " " ).replace(" ", " ").replace(Regex("([()])"), "").replace(".", " ") - .replace(" ", " ").replace(" ", "%2520") + .replace(" ", " ") Log.d("Lyrics", "query: $q") var spotifyPersonalToken = "" if (dataStoreManager.spotifyPersonalToken.first() @@ -1192,7 +1279,7 @@ class MainRepository @Inject constructor(private val localDataSource: LocalDataS Log.d("Lyrics", "clientToken: $clientToken") YouTube.searchSpotifyTrack(q, clientToken).onSuccess { searchResponse -> val track = if (duration != null && duration != 0) { - searchResponse.tracks.items.find { abs(((it.duration_ms / 1000) - duration)) < 3 } + searchResponse.tracks.items.find { abs(((it.duration_ms / 1000) - duration)) < 1 } ?: searchResponse.tracks.items.firstOrNull() } else { searchResponse.tracks.items.firstOrNull() @@ -1216,7 +1303,7 @@ class MainRepository @Inject constructor(private val localDataSource: LocalDataS dataStoreManager.setSpotifyClientToken(clientToken) YouTube.searchSpotifyTrack(q, clientToken).onSuccess { searchResponse -> val track = if (duration != null && duration != 0) { - searchResponse.tracks.items.find { abs(((it.duration_ms / 1000) - duration)) < 3 } + searchResponse.tracks.items.find { abs(((it.duration_ms / 1000) - duration)) < 1 } ?: searchResponse.tracks.items.firstOrNull() } else { searchResponse.tracks.items.firstOrNull() diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt index bca4ad8d..6eb033dd 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/player/NowPlayingFragment.kt @@ -1,18 +1,23 @@ package com.maxrave.simpmusic.ui.fragment.player import android.animation.ObjectAnimator +import android.app.Activity import android.content.Intent import android.graphics.Bitmap import android.graphics.Color +import android.graphics.Insets import android.graphics.drawable.ColorDrawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.TransitionDrawable import android.net.Uri +import android.os.Build import android.os.Bundle +import android.util.DisplayMetrics import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.WindowInsets import android.view.animation.AnimationUtils import android.widget.Toast import androidx.core.graphics.ColorUtils @@ -23,10 +28,13 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.media3.common.MediaItem +import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.offline.Download import androidx.media3.exoplayer.offline.DownloadRequest import androidx.media3.exoplayer.offline.DownloadService +import androidx.media3.ui.AspectRatioFrameLayout import androidx.navigation.fragment.findNavController import androidx.palette.graphics.Palette import androidx.recyclerview.widget.LinearLayoutManager @@ -117,6 +125,10 @@ class NowPlayingFragment : Fragment() { private lateinit var disableScrolling: DisableTouchEventRecyclerView private var overlayJob: Job? = null + private var canvasOverlayJob: Job? = null + + private var player: ExoPlayer? = null + private var isFullScreen = false // private lateinit var songChangeListener: OnNowPlayingSongChangeListener @@ -129,11 +141,49 @@ class NowPlayingFragment : Fragment() { // } // } -// override fun onResume() { -// Log.d("NowPlayingFragment", "onResume") -// updateUIfromCurrentMediaItem(viewModel.getCurrentMediaItem()) -// super.onResume() -// } + override fun onResume() { + Log.d("NowPlayingFragment", "onResume") + super.onResume() + val track = viewModel.canvas.value?.canvases?.firstOrNull() + if (track != null && track.canvas_url.contains(".mp4")) { + player?.stop() + player?.release() + player = ExoPlayer.Builder(requireContext()).build() + binding.playerCanvas.player = player + binding.playerCanvas.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM + player?.repeatMode = Player.REPEAT_MODE_ONE + player?.setMediaItem( + MediaItem.fromUri(track.canvas_url) + ) + player?.prepare() + player?.play() + } + } + + fun getScreenHeight(activity: Activity): Int { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val windowMetrics = activity.windowManager.currentWindowMetrics + val insets: Insets = windowMetrics.windowInsets + .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()) + (windowMetrics.bounds.height()) + } else { + val displayMetrics = DisplayMetrics() + activity.windowManager.defaultDisplay.getMetrics(displayMetrics) + (displayMetrics.heightPixels) + } + } + + override fun onStop() { + super.onStop() + player?.stop() + player?.release() + } + + override fun onPause() { + super.onPause() + player?.stop() + player?.release() + } override fun onCreateView( inflater: LayoutInflater, @@ -152,6 +202,12 @@ class NowPlayingFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + binding.canvasLayout.apply { + val layoutParamsCopy = layoutParams + layoutParamsCopy.height = getScreenHeight(requireActivity()) + layoutParams = layoutParamsCopy + Log.w("Check Height", layoutParams.height.toString()) + } val activity = requireActivity() val bottom = activity.findViewById(R.id.bottom_navigation_view) val miniplayer = activity.findViewById(R.id.miniplayer) @@ -706,6 +762,11 @@ class NowPlayingFragment : Fragment() { binding.uploaderLayout.visibility = View.VISIBLE binding.infoLayout.visibility = View.VISIBLE binding.tvUploader.text = songInfo.author + binding.tvSmallArtist.text = songInfo.author + binding.ivSmallArtist.load(songInfo.authorThumbnail) { + crossfade(true) + placeholder(R.drawable.holder) + } binding.ivAuthor.load(songInfo.authorThumbnail) { crossfade(true) placeholder(R.drawable.holder_video) @@ -891,6 +952,143 @@ class NowPlayingFragment : Fragment() { } } } + val job20 = launch { + viewModel.canvas.collect { + val canva = it?.canvases?.firstOrNull() + if (canva != null) { + if (canva.canvas_url.contains(".mp4")) { + player?.stop() + player?.release() + player = ExoPlayer.Builder(requireContext()).build() + binding.playerCanvas.visibility = View.VISIBLE + binding.ivCanvas.visibility = View.GONE + player?.setMediaItem( + MediaItem.Builder() + .setUri( + canva.canvas_url.toUri() + ) + .build() + ) + binding.playerCanvas.player = player + binding.playerCanvas.resizeMode = + AspectRatioFrameLayout.RESIZE_MODE_ZOOM + player?.prepare() + player?.play() + player?.repeatMode = Player.REPEAT_MODE_ONE + } else { + binding.playerCanvas.visibility = View.GONE + binding.ivCanvas.visibility = View.VISIBLE + binding.ivCanvas.load(canva.canvas_url) { + crossfade(true) + } + } + + binding.middleLayout.visibility = View.INVISIBLE + binding.canvasLayout.visibility = View.VISIBLE + binding.rootLayout.background = ColorDrawable( + resources.getColor( + R.color.md_theme_dark_background, + null + ) + ) + binding.overlayCanvas.visibility = View.VISIBLE + binding.smallArtistLayout.visibility = View.GONE + val shortAnimationDuration = + resources.getInteger(android.R.integer.config_mediumAnimTime) + canvasOverlayJob = lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + delay(5000) + binding.overlayCanvas.visibility = View.GONE + binding.infoControllerLayout.visibility = View.INVISIBLE + binding.smallArtistLayout.visibility = View.VISIBLE + binding.rootLayout.smoothScrollTo(0, 0) + } + } + binding.root.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> + if (scrollY > 0 && binding.overlayCanvas.visibility == View.GONE) { + binding.overlayCanvas.alpha = 0f + binding.overlayCanvas.apply { + visibility = View.VISIBLE + animate() + .alpha(1f) + .setDuration(shortAnimationDuration.toLong()) + .setListener(null) + } + binding.infoControllerLayout.alpha = 0f + binding.infoControllerLayout.apply { + visibility = View.VISIBLE + animate() + .alpha(1f) + .setDuration(shortAnimationDuration.toLong()) + .setListener(null) + } + binding.smallArtistLayout.visibility = View.GONE + canvasOverlayJob?.cancel() + } else if (scrollY == 0 && binding.overlayCanvas.visibility == View.VISIBLE) { + canvasOverlayJob = lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + delay(5000) + binding.overlayCanvas.visibility = View.GONE + binding.infoControllerLayout.visibility = View.INVISIBLE + binding.smallArtistLayout.visibility = View.VISIBLE + } + } + } + } + binding.helpMeBro.setOnClickListener { + + if (binding.root.scrollY == 0) { + if (binding.overlayCanvas.visibility == View.VISIBLE) { + binding.overlayCanvas.visibility = View.GONE + binding.infoControllerLayout.visibility = View.INVISIBLE + binding.smallArtistLayout.visibility = View.VISIBLE + canvasOverlayJob?.cancel() + } else { + binding.overlayCanvas.alpha = 0f + binding.overlayCanvas.apply { + visibility = View.VISIBLE + animate() + .alpha(1f) + .setDuration(shortAnimationDuration.toLong()) + .setListener(null) + } + binding.infoControllerLayout.alpha = 0f + binding.infoControllerLayout.apply { + visibility = View.VISIBLE + animate() + .alpha(1f) + .setDuration(shortAnimationDuration.toLong()) + .setListener(null) + } + binding.smallArtistLayout.visibility = View.GONE + canvasOverlayJob = lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + delay(5000) + binding.overlayCanvas.visibility = View.GONE + binding.infoControllerLayout.visibility = + View.INVISIBLE + binding.smallArtistLayout.visibility = View.VISIBLE + } + } + } + } + } + } else { + player?.stop() + player?.release() + binding.helpMeBro.setOnClickListener { + + } + binding.root.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> + + } + binding.overlayCanvas.visibility = View.GONE + binding.infoControllerLayout.visibility = View.VISIBLE + binding.canvasLayout.visibility = View.INVISIBLE + binding.middleLayout.visibility = View.VISIBLE + } + } + } job1.join() job2.join() job3.join() @@ -909,6 +1107,7 @@ class NowPlayingFragment : Fragment() { job17.join() job18.join() job19.join() + job20.join() } } binding.tvMore.setOnClickListener { @@ -1064,9 +1263,22 @@ class NowPlayingFragment : Fragment() { putString("channelId", browseId) }) } + binding.smallArtistLayout.setOnClickListener { + val browseId = if (!viewModel.songDB.value?.artistId.isNullOrEmpty()) { + viewModel.songDB.value?.artistId?.firstOrNull() + } else { + runBlocking { viewModel.songInfo.first()?.authorId } + } + findNavController().navigateSafe( + R.id.action_global_artistFragment, + Bundle().apply { + putString("channelId", browseId) + }) + } binding.tvSongArtist.setOnClickListener { if (!viewModel.simpleMediaServiceHandler?.catalogMetadata.isNullOrEmpty()) { - val song = viewModel.simpleMediaServiceHandler!!.catalogMetadata[viewModel.getCurrentMediaItemIndex()] + val song = + viewModel.simpleMediaServiceHandler!!.catalogMetadata[viewModel.getCurrentMediaItemIndex()] if (song.artists?.firstOrNull()?.id != null) { findNavController().navigateSafe( R.id.action_global_artistFragment, @@ -1496,7 +1708,7 @@ class NowPlayingFragment : Fragment() { Log.d("Update UI", "onSuccess: ") if (viewModel.gradientDrawable.value != null) { viewModel.gradientDrawable.observe(viewLifecycleOwner) { - if (it != null) { + if (it != null && viewModel.canvas.value == null) { var start = binding.rootLayout.background if (start == null) { start = ColorDrawable(Color.BLACK) @@ -1697,7 +1909,7 @@ class NowPlayingFragment : Fragment() { Log.d("Update UI", "onSuccess: ") if (viewModel.gradientDrawable.value != null) { viewModel.gradientDrawable.observe(viewLifecycleOwner) { - if (it != null) { + if (it != null && viewModel.canvas.value == null) { var start = binding.rootLayout.background if (start == null) { start = ColorDrawable(Color.BLACK) diff --git a/app/src/main/java/com/maxrave/simpmusic/viewModel/SharedViewModel.kt b/app/src/main/java/com/maxrave/simpmusic/viewModel/SharedViewModel.kt index 9e676ecb..183c33fc 100644 --- a/app/src/main/java/com/maxrave/simpmusic/viewModel/SharedViewModel.kt +++ b/app/src/main/java/com/maxrave/simpmusic/viewModel/SharedViewModel.kt @@ -17,6 +17,7 @@ import androidx.media3.common.util.UnstableApi import androidx.media3.datasource.cache.SimpleCache import androidx.media3.exoplayer.offline.Download import com.maxrave.kotlinytmusicscraper.YouTube +import com.maxrave.kotlinytmusicscraper.models.response.spotify.CanvasResponse import com.maxrave.kotlinytmusicscraper.models.simpmusic.GithubResponse import com.maxrave.kotlinytmusicscraper.models.sponsorblock.SkipSegments import com.maxrave.kotlinytmusicscraper.models.youtube.YouTubeInitialPage @@ -174,6 +175,11 @@ class SharedViewModel @Inject constructor(private var dataStoreManager: DataStor MutableStateFlow(LyricsProvider.MUSIXMATCH) val lyricsProvider: StateFlow = _lyricsProvider + private var _canvas: MutableStateFlow = MutableStateFlow(null) + val canvas: StateFlow = _canvas + + private var canvasJob: Job? = null + var recentPosition: String = 0L.toString() val intent: MutableStateFlow = MutableStateFlow(null) @@ -240,6 +246,7 @@ class SharedViewModel @Inject constructor(private var dataStoreManager: DataStor nowPlaying?.let { now -> _format.value = null _songInfo.value = null + _canvas.value = null getSongInfo(now.mediaId) getSkipSegments(now.mediaId) } @@ -329,6 +336,9 @@ class SharedViewModel @Inject constructor(private var dataStoreManager: DataStor Log.w("Check CPN", formatTemp?.cpn.toString()) formatTemp?.lengthSeconds?.let { getLyricsFromFormat(formatTemp.videoId, it) + if (dataStoreManager.spotifyCanvas.first() == TRUE) { + getCanvas(formatTemp.videoId, it) + } } } } @@ -344,6 +354,18 @@ class SharedViewModel @Inject constructor(private var dataStoreManager: DataStor } } + suspend fun getCanvas(videoId: String, duration: Int) { + canvasJob?.cancel() + viewModelScope.launch { + canvasJob = launch { + mainRepository.getCanvas(videoId, duration).collect { response -> + _canvas.value = response + } + } + canvasJob?.join() + } + } + fun loadMore() { val continuation = Queue.getContinuation() Log.w("Check loadMore", continuation.toString()) diff --git a/app/src/main/res/drawable/bottom_overlay.xml b/app/src/main/res/drawable/bottom_overlay.xml new file mode 100644 index 00000000..551d4603 --- /dev/null +++ b/app/src/main/res/drawable/bottom_overlay.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/canvas_overlay.xml b/app/src/main/res/drawable/canvas_overlay.xml new file mode 100644 index 00000000..6b6402c8 --- /dev/null +++ b/app/src/main/res/drawable/canvas_overlay.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_now_playing.xml b/app/src/main/res/layout/fragment_now_playing.xml index 29348cd5..97968120 100644 --- a/app/src/main/res/layout/fragment_now_playing.xml +++ b/app/src/main/res/layout/fragment_now_playing.xml @@ -1,701 +1,820 @@ - - - + android:layout_height="0dp"> - - - - + android:layout_height="match_parent"> + + + + + + + + + + android:id="@+id/smallArtistLayout" + android:layout_gravity="bottom" + android:layout_marginHorizontal="@dimen/_30sdp" + android:layout_marginBottom="@dimen/_20sdp" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + android:layout_alignParentStart="true" + android:id="@+id/ivSmallArtist" + android:layout_width="40dp" + android:layout_height="40dp" + app:shapeAppearance="@style/circle"> - + android:layout_toEndOf="@+id/ivSmallArtist" + android:layout_marginStart="@dimen/_10sdp" + android:layout_centerVertical="true" + android:maxLines="1" + android:fontFamily="@font/roboto" + android:textStyle="bold" + android:textSize="15sp" + android:id="@+id/tvSmallArtist" + android:text="" + android:textColor="@android:color/white"> + + + - + - - - + android:layout_height="match_parent" + android:layout_above="@+id/bottomOverlayCanvas" + android:background="@drawable/canvas_overlay"> - - - + - + - + - - - - - - - + + + + + + + + + android:layout_below="@+id/topAppBarLayout"> - + - + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:scaleType="fitXY" + android:adjustViewBounds="true" + app:shapeAppearanceOverlay="@style/rounded_thumb"> - - - - + - + android:indeterminate="true" + android:layout_centerInParent="true"> - - + - - + android:layout_height="wrap_content" + android:elevation="8dp" + style="?attr/materialCardViewElevatedStyle" + android:id="@+id/playerLayout" + android:visibility="gone" + android:layout_centerInParent="true" + app:cardCornerRadius="8dp"> - - - - - + android:layout_height="wrap_content"> - + - + + + + + + + + + + + + + + + android:orientation="vertical" + android:layout_marginTop="@dimen/_75sdp" + android:layout_marginHorizontal="@dimen/_25sdp"> - - - - - + android:orientation="vertical"> - + - - - - - - - - - - - - - - - - - + - - - + + + + + + + + + - - + - + - + + + + - + + + - + + android:id="@+id/time_layout" + android:layout_marginStart="8sp" + android:layout_marginEnd="8sp"> + + android:layout_gravity="center_vertical" + android:gravity="start" + android:textSize="15sp" + android:textStyle="normal" + android:fontFamily="@font/roboto" /> - - + android:text="" + android:id="@+id/tvFullTime" + android:layout_weight="1" + android:layout_gravity="center_vertical" + android:gravity="end" + android:textSize="15sp" + android:textStyle="normal" + android:fontFamily="@font/roboto" /> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content" + android:id="@+id/controler_layout" + android:layout_marginTop="5sp"> + + + + + + + + + + + + + + + + + + + + - - - + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginTop="@dimen/_4sdp"> - - - - + - - - - + - - - - - - + - - + + + + + - + + + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="10sp"> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:orientation="vertical"> + + - + + + + + + - - - - - + + - + android:layout_height="250dp" + android:layout_marginTop="15sp" + android:layout_marginBottom="10sp" + app:cardPreventCornerOverlap="true" + android:id="@+id/uploaderLayout" + android:focusable="true" + android:clickable="true" + android:foreground="?android:attr/selectableItemBackground"> - - + android:layout_height="match_parent"> - - - + + + + - + + - - - - + android:text="@string/artists" + android:gravity="start" + style="@style/SubTitleToolBar"> - - - - - - - + - + + + + + + + + + + + + + + + - + android:layout_marginTop="15sp" + android:layout_marginBottom="10sp" + app:cardPreventCornerOverlap="true" + android:id="@+id/infoLayout" + android:focusable="true" + android:clickable="true" + android:foreground="?android:attr/selectableItemBackground"> - + android:layout_margin="10dp" + android:padding="10dp" + android:orientation="vertical"> + + - + - - - - - + - + - + - + - + - + - + - + + - - + - + + + + + + - - + + \ No newline at end of file diff --git a/kotlinYtmusicScraper/build.gradle.kts b/kotlinYtmusicScraper/build.gradle.kts index d6a12087..c072fd9a 100644 --- a/kotlinYtmusicScraper/build.gradle.kts +++ b/kotlinYtmusicScraper/build.gradle.kts @@ -1,5 +1,3 @@ -import java.util.Properties - plugins { id("com.android.library") id("org.jetbrains.kotlin.android") @@ -7,10 +5,6 @@ plugins { id("com.mikepenz.aboutlibraries.plugin") } -val properties = Properties().apply { - load(rootProject.file("local.properties").inputStream()) -} - android { namespace = "com.maxrave.kotlinytmusicscraper" compileSdk = 34 diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/test/main.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/test/main.kt index ae294b4d..9a63a0c1 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/test/main.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/test/main.kt @@ -23,14 +23,14 @@ import kotlin.random.Random fun main() { - testSpotifySearch() + testCanvas() } fun testCanvas() { runBlocking { Ytmusic().getSpotifyCanvas( trackId = "3ZbZtdEw9U0uZW4tZItIwq", - token = "BQDIkioHDShrZmAkWkc1B3s_EvJbO-VrvQyFvB_b9qkURCJU_L-sTXDHbpDeH2jSkEDY5sFQbJVGw7CMfaWSOVUuY0YFNtgxbdZxZ8CJLRNtP-pXIFNDAkbpV62msPKfaes76uTMiegMVH93Ids1z9xnrGHRBD_itq4lMZyNbRDhCxB5Uxcqs_2RvWvuL6NROF7kBs5eZAWuIB4_A-_6q4Qr_gVn3rUxji3VKMFA5DrMSQffhOHvtUd1A3EZb3NDSkiRRBY1cKo11vJhs65wlvR9FflvBfvx_enJxomwmyteU-g_dH62u_CfFmA" + token = "BQCODA5o34QNampQh2AayzKtflNN20t2E5YrCLh7bUsOrgDnCasutSE9TVd0GXd7np4m_hspbyd1PGk-5gbewvdv7y7ReCNMGiqtiJr0kDUZOF81xC2B-R68jVVZDdnOnUroYH8aYBrubIjhKZUQIKezpoIgram1fTJGbyQKmoVX_DLxZd27snQsQQvPQOQRyGwOD1Xyczbicnli8mIsh5XcrTL6UvmUfkB4w3Q7NEFFLddBMJZQxvTD5DllwSd3UA1K_cBIgCayaJpQZH1jIhIFTWUhy_CJDbqNgirX2z7kZ-cLuTtdwmtzzLY" ).apply { println(body()) }