diff --git a/app/src/main/java/com/maxrave/simpmusic/data/db/DatabaseDao.kt b/app/src/main/java/com/maxrave/simpmusic/data/db/DatabaseDao.kt index a42673e5..e8cde08f 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/db/DatabaseDao.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/db/DatabaseDao.kt @@ -232,11 +232,14 @@ interface DatabaseDao { @Query("SELECT * FROM album WHERE browseId = :browseId") suspend fun getAlbum(browseId: String): AlbumEntity + @Query("SELECT * FROM album WHERE browseId = :browseId") + fun getAlbumAsFlow(browseId: String): Flow + @Query("SELECT * FROM album WHERE liked = 1") suspend fun getLikedAlbums(): List @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insertAlbum(album: AlbumEntity) + suspend fun insertAlbum(album: AlbumEntity): Long @Query("UPDATE album SET liked = :liked WHERE browseId = :browseId") suspend fun updateAlbumLiked( diff --git a/app/src/main/java/com/maxrave/simpmusic/data/db/LocalDataSource.kt b/app/src/main/java/com/maxrave/simpmusic/data/db/LocalDataSource.kt index a31bc0e1..44166785 100644 --- a/app/src/main/java/com/maxrave/simpmusic/data/db/LocalDataSource.kt +++ b/app/src/main/java/com/maxrave/simpmusic/data/db/LocalDataSource.kt @@ -122,6 +122,8 @@ class LocalDataSource( suspend fun getAlbum(albumId: String) = databaseDao.getAlbum(albumId) + fun getAlbumAsFlow(albumId: String) = databaseDao.getAlbumAsFlow(albumId) + suspend fun getLikedAlbums() = databaseDao.getLikedAlbums() suspend fun updateAlbumInLibrary( 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 388cf1fb..9ad9eec0 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 @@ -263,15 +263,16 @@ class MainRepository( emit(localDataSource.getAlbum(id)) }.flowOn(Dispatchers.IO) + fun getAlbumAsFlow(id: String) = localDataSource.getAlbumAsFlow(id) + fun getLikedAlbums(): Flow> = flow { emit(localDataSource.getLikedAlbums()) }.flowOn(Dispatchers.IO) - suspend fun insertAlbum(albumEntity: AlbumEntity) = - withContext(Dispatchers.IO) { - localDataSource.insertAlbum(albumEntity) - } + fun insertAlbum(albumEntity: AlbumEntity) = flow { + emit(localDataSource.insertAlbum(albumEntity)) + }.flowOn(Dispatchers.IO) suspend fun updateAlbumLiked( albumId: String, diff --git a/app/src/main/java/com/maxrave/simpmusic/di/MediaServiceModule.kt b/app/src/main/java/com/maxrave/simpmusic/di/MediaServiceModule.kt index 256c497f..a331b93f 100644 --- a/app/src/main/java/com/maxrave/simpmusic/di/MediaServiceModule.kt +++ b/app/src/main/java/com/maxrave/simpmusic/di/MediaServiceModule.kt @@ -25,6 +25,7 @@ import androidx.media3.exoplayer.audio.AudioSink import androidx.media3.exoplayer.audio.DefaultAudioSink import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor import androidx.media3.exoplayer.source.DefaultMediaSourceFactory +import androidx.media3.exoplayer.util.EventLogger import androidx.media3.extractor.ExtractorsFactory import androidx.media3.extractor.mkv.MatroskaExtractor import androidx.media3.extractor.mp4.FragmentedMp4Extractor @@ -135,7 +136,9 @@ val mediaServiceModule = get(), ), ).setRenderersFactory(provideRendererFactory(androidContext())) - .build() + .build().also { + it.addAnalyticsListener(EventLogger()) + } } // CoilBitmapLoader single(createdAtStart = true) { diff --git a/app/src/main/java/com/maxrave/simpmusic/di/ViewModelModule.kt b/app/src/main/java/com/maxrave/simpmusic/di/ViewModelModule.kt index aa56755f..66db5ea8 100644 --- a/app/src/main/java/com/maxrave/simpmusic/di/ViewModelModule.kt +++ b/app/src/main/java/com/maxrave/simpmusic/di/ViewModelModule.kt @@ -1,6 +1,7 @@ package com.maxrave.simpmusic.di import androidx.media3.common.util.UnstableApi +import com.maxrave.simpmusic.viewModel.AlbumViewModel import com.maxrave.simpmusic.viewModel.LibraryDynamicPlaylistViewModel import com.maxrave.simpmusic.viewModel.LibraryViewModel import com.maxrave.simpmusic.viewModel.NowPlayingBottomSheetViewModel @@ -25,4 +26,9 @@ val viewModelModule = module { application = androidApplication() ) } + viewModel { + AlbumViewModel( + application = androidApplication() + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/component/DescriptionView.kt b/app/src/main/java/com/maxrave/simpmusic/ui/component/DescriptionView.kt index 38390f8c..c7929bed 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/component/DescriptionView.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/component/DescriptionView.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -41,11 +42,21 @@ fun DescriptionView( var expanded by rememberSaveable { mutableStateOf(false) } + var shouldHideExpandButton by rememberSaveable { + mutableStateOf(false) + } val maxLineAnimated by animateIntAsState( targetValue = if (expanded) 1000 else limitLine ) var layoutResult by remember { mutableStateOf(null) } + LaunchedEffect(layoutResult) { + val lineCount = layoutResult?.lineCount ?: 0 + if (lineCount < limitLine ) { + shouldHideExpandButton = true + } + } + val timeRegex = Regex("""(\d+):(\d+)(?::(\d+))?""") val urlRegex = Regex("""https?://\S+""") @@ -126,14 +137,16 @@ fun DescriptionView( onTextLayout = { layoutResult = it }, style = typo.bodyMedium ) - Spacer(modifier = Modifier.height(5.dp)) - Text( - text = if (expanded) stringResource(id = R.string.less) else stringResource(id = R.string.more), - color = Color.LightGray, - modifier = Modifier.clickable { - expanded = !expanded - }, - style = typo.labelSmall - ) + Spacer(modifier = Modifier.height(8.dp)) + androidx.compose.animation.AnimatedVisibility(!shouldHideExpandButton) { + Text( + text = if (expanded) stringResource(id = R.string.less) else stringResource(id = R.string.more), + color = Color.LightGray, + modifier = Modifier.clickable { + expanded = !expanded + }, + style = typo.labelSmall + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/component/FullWidthItems.kt b/app/src/main/java/com/maxrave/simpmusic/ui/component/FullWidthItems.kt index a01a3ffd..8dfd6520 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/component/FullWidthItems.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/component/FullWidthItems.kt @@ -63,6 +63,7 @@ import org.koin.compose.koinInject @Composable fun SongFullWidthItems( track: Track? = null, + index: Int? = null, songEntity: SongEntity? = null, isPlaying: Boolean, onMoreClickListener: ((videoId: String) -> Unit)? = null, @@ -88,11 +89,14 @@ fun SongFullWidthItems( .fillMaxWidth(), ) { Spacer(modifier = Modifier.width(10.dp)) - Box(modifier = Modifier.size(50.dp)) { + Box( + modifier = Modifier.size(50.dp), + contentAlignment = Alignment.Center + ) { Crossfade(isPlaying) { if (it) { LottieAnimation(composition, iterations = LottieConstants.IterateForever) - } else { + } else if (index == null) { val thumb = track?.thumbnails?.lastOrNull()?.url ?: songEntity?.thumbnails AsyncImage( model = ImageRequest.Builder(LocalContext.current) @@ -109,6 +113,11 @@ fun SongFullWidthItems( Modifier .fillMaxSize(), ) + } else { + Text( + text = ((index ?: 0) + 1).toString(), color = Color.White, style = typo.titleMedium, + modifier = Modifier.align(Alignment.Center) + ) } } } diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/component/LibraryItem.kt b/app/src/main/java/com/maxrave/simpmusic/ui/component/LibraryItem.kt index 053e3ada..2d7bb291 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/component/LibraryItem.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/component/LibraryItem.kt @@ -240,6 +240,7 @@ fun LibraryItem( R.id.action_global_playlistFragment, Bundle().apply { putString("id", item.browseId) + putBoolean("youtube", true) } ) } diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/AlbumFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/AlbumFragment.kt index 3d3c544a..c9194a12 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/AlbumFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/AlbumFragment.kt @@ -1,1248 +1,53 @@ package com.maxrave.simpmusic.ui.fragment.other -import android.content.Intent -import android.graphics.Bitmap -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.GradientDrawable -import android.net.Uri +import android.annotation.SuppressLint 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.core.content.ContextCompat -import androidx.core.graphics.ColorUtils -import androidx.core.net.toUri +import androidx.compose.material3.Scaffold +import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.media3.common.util.UnstableApi -import androidx.media3.exoplayer.offline.DownloadRequest -import androidx.media3.exoplayer.offline.DownloadService -import androidx.navigation.fragment.findNavController -import androidx.palette.graphics.Palette -import androidx.recyclerview.widget.LinearLayoutManager -import coil3.asDrawable -import coil3.imageLoader -import coil3.load -import coil3.request.CachePolicy -import coil3.request.ImageRequest -import coil3.request.placeholder -import coil3.request.transformations -import coil3.size.Size -import coil3.transform.Transformation -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.snackbar.Snackbar -import com.maxrave.simpmusic.R -import com.maxrave.simpmusic.adapter.album.TrackAdapter -import com.maxrave.simpmusic.adapter.artist.SeeArtistOfNowPlayingAdapter -import com.maxrave.simpmusic.adapter.playlist.AddToAPlaylistAdapter -import com.maxrave.simpmusic.common.Config -import com.maxrave.simpmusic.common.DownloadState -import com.maxrave.simpmusic.data.db.entities.LocalPlaylistEntity -import com.maxrave.simpmusic.data.db.entities.PairSongLocalPlaylist -import com.maxrave.simpmusic.data.db.entities.SongEntity -import com.maxrave.simpmusic.data.model.browse.album.Track -import com.maxrave.simpmusic.databinding.BottomSheetAddToAPlaylistBinding -import com.maxrave.simpmusic.databinding.BottomSheetNowPlayingBinding -import com.maxrave.simpmusic.databinding.BottomSheetSeeArtistOfNowPlayingBinding -import com.maxrave.simpmusic.databinding.FragmentAlbumBinding -import com.maxrave.simpmusic.extension.connectArtists -import com.maxrave.simpmusic.extension.indexMap -import com.maxrave.simpmusic.extension.navigateSafe -import com.maxrave.simpmusic.extension.removeConflicts -import com.maxrave.simpmusic.extension.setEnabledAll -import com.maxrave.simpmusic.extension.setStatusBarsColor -import com.maxrave.simpmusic.extension.toAlbumEntity -import com.maxrave.simpmusic.extension.toArrayListTrack -import com.maxrave.simpmusic.extension.toListName -import com.maxrave.simpmusic.extension.toListVideoId -import com.maxrave.simpmusic.extension.toSongEntity -import com.maxrave.simpmusic.extension.toTrack -import com.maxrave.simpmusic.service.PlaylistType -import com.maxrave.simpmusic.service.QueueData -import com.maxrave.simpmusic.service.test.download.MusicDownloadService -import com.maxrave.simpmusic.utils.Resource -import com.maxrave.simpmusic.viewModel.AlbumViewModel -import com.maxrave.simpmusic.viewModel.SharedViewModel -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChangedBy -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import java.time.LocalDateTime -import kotlin.math.abs -import kotlin.random.Random +import androidx.navigation.findNavController +import com.maxrave.simpmusic.ui.screen.other.AlbumScreen +import com.maxrave.simpmusic.ui.theme.AppTheme @UnstableApi class AlbumFragment : Fragment() { - private val viewModel by activityViewModels() - private val sharedViewModel by activityViewModels() - - private var _binding: FragmentAlbumBinding? = null - val binding get() = _binding!! - - private var gradientDrawable: GradientDrawable? = null - private var toolbarBackground: Int? = null - - private lateinit var songsAdapter: TrackAdapter + private var browseId: String? = null + private lateinit var composeView: ComposeView override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View { - _binding = FragmentAlbumBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onDestroyView() { - super.onDestroyView() - requireArguments().clear() - setStatusBarsColor(ContextCompat.getColor(requireContext(), R.color.colorPrimaryDark), requireActivity()) - _binding = null + return ComposeView(requireContext()).also { + composeView = it + } } + @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") override fun onViewCreated( view: View, savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - viewModel.getLocation() - lifecycleScope.launch { - viewModel.liked.collect { liked -> - binding.cbLove.isChecked = liked - } - } - if (viewModel.gradientDrawable.value != null) { - gradientDrawable = viewModel.gradientDrawable.value - toolbarBackground = gradientDrawable?.colors?.get(0) - } - binding.rootLayout.visibility = View.GONE - binding.loadingLayout.visibility = View.VISIBLE - // init Adapter - songsAdapter = TrackAdapter(arrayListOf()) - // init RecyclerView - binding.rvListSong.apply { - adapter = songsAdapter - layoutManager = LinearLayoutManager(requireContext()) - } - var browseId = requireArguments().getString("browseId") - val downloaded = arguments?.getInt("downloaded") - if (browseId == null || browseId == viewModel.browseId.value) { - browseId = viewModel.browseId.value - } - if (browseId != null) { - Log.d("Check null", "onViewCreated: $downloaded") - if (downloaded == null || downloaded == 0) { - viewModel.updateBrowseId(browseId) - fetchData(browseId) - } - if (downloaded == 1) { - viewModel.updateBrowseId(browseId) - fetchData(browseId, downloaded = 1) - } - } - - binding.topAppBar.setNavigationOnClickListener { - findNavController().popBackStack() - } - binding.btArtist.setOnClickListener { - if (viewModel.albumBrowse.value?.data != null) { - Log.d( - "TAG", - "Artist name clicked: ${viewModel.albumBrowse.value?.data?.artists?.get(0)?.id}", - ) - val args = Bundle() - args.putString( - "channelId", - viewModel.albumBrowse.value - ?.data - ?.artists - ?.get(0) - ?.id, - ) - findNavController().navigateSafe(R.id.action_global_artistFragment, args) - } - } - binding.cbLove.setOnCheckedChangeListener { cb, isChecked -> - if (!isChecked) { - viewModel.albumEntity.value?.let { album -> - viewModel.updateAlbumLiked( - false, - album.browseId, - ) - } - } else { - viewModel.albumEntity.value?.let { album -> - viewModel.updateAlbumLiked( - true, - album.browseId, - ) - } - } - } - binding.btShuffle.setOnClickListener { - if (viewModel.albumBrowse.value is Resource.Success && viewModel.albumBrowse.value?.data != null) { - val index = - Random.nextInt( - viewModel.albumBrowse.value - ?.data!! - .tracks.size, - ) - val shuffleList: ArrayList = arrayListOf() - shuffleList.addAll( - viewModel.albumBrowse.value - ?.data!! - .tracks, - ) - val firstPlay = viewModel.albumBrowse.value - ?.data?.tracks?.getOrNull(index) - shuffleList.removeAt( - index - ) - shuffleList.shuffle() - if (firstPlay != null) { - shuffleList.add(0, firstPlay) - viewModel.setQueueData( - QueueData( - listTracks = shuffleList, - firstPlayedTrack = firstPlay, - playlistId = viewModel.albumBrowse.value - ?.data - ?.audioPlaylistId - ?.replaceFirst("VL", "") ?: "", - playlistName = "Album \"${viewModel.albumBrowse.value?.data!!.title}\"", - playlistType = PlaylistType.PLAYLIST, - continuation = null, - ) - ) - viewModel.loadMediaItem( - firstPlay, - Config.ALBUM_CLICK, - 0, - ) - } - - } else if (viewModel.albumEntity.value != null && viewModel.albumEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED) { - val index = - Random.nextInt( - viewModel.albumEntity.value - ?.tracks - ?.size!!, - ) - val shuffleList: ArrayList = arrayListOf() - val firstPlay = viewModel.listTrack.value?.getOrNull(index) - shuffleList.addAll(viewModel.listTrack.value.toArrayListTrack()) - shuffleList.removeAt( - index - ) - shuffleList.shuffle() - if (firstPlay != null) { - viewModel.setQueueData( - QueueData( - shuffleList, - firstPlay.toTrack(), - viewModel.albumEntity.value - ?.browseId - ?.replaceFirst("VL", "") ?: "", - "Album \"${viewModel.albumEntity.value?.title}\"", - PlaylistType.PLAYLIST, - null - ) - ) - viewModel.loadMediaItem( - firstPlay.toTrack(), - Config.ALBUM_CLICK, - 0, - ) - } - } else { - Snackbar.make(requireView(), "Error", Snackbar.LENGTH_SHORT).show() - } - } - binding.btPlayPause.setOnClickListener { - if (viewModel.albumBrowse.value is Resource.Success && - viewModel.albumBrowse.value?.data != null && - songsAdapter - .getList() - .isNotEmpty() - ) { - val firstPlay = songsAdapter.getList().firstOrNull() - if (firstPlay != null) { - viewModel.setQueueData( - QueueData( - songsAdapter.getList(), - firstPlay, - viewModel.albumBrowse.value - ?.data - ?.audioPlaylistId - ?.replaceFirst("VL", "") ?: "", - "Album \"${viewModel.albumBrowse.value?.data!!.title}\"", - PlaylistType.PLAYLIST, - null - ) - ) - viewModel.loadMediaItem( - firstPlay, - Config.ALBUM_CLICK, - 0, - ) - } - } else if (viewModel.albumEntity.value != null && viewModel.albumEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED) { - val firstPlay = songsAdapter.getList().firstOrNull() - if (firstPlay != null) { - viewModel.setQueueData( - QueueData( - songsAdapter.getList(), - firstPlay, - viewModel.albumEntity.value - ?.audioPlaylistId - ?.replaceFirst("VL", "") ?: "", - "Album \"${viewModel.albumEntity.value?.title}\"", - PlaylistType.PLAYLIST, - null - ) - ) - viewModel.loadMediaItem( - firstPlay, - Config.ALBUM_CLICK, - 0, - ) - } - } else { - Snackbar.make(requireView(), "Error", Snackbar.LENGTH_SHORT).show() - } - } - songsAdapter.setOnClickListener( - object : TrackAdapter.OnItemClickListener { - override fun onItemClick(position: Int) { - if (viewModel.albumBrowse.value is Resource.Success && viewModel.albumBrowse.value?.data != null) { - val firstPlay = songsAdapter.getList().getOrNull(position) - if (firstPlay != null) { - viewModel.setQueueData( - QueueData( - songsAdapter.getList(), - firstPlay, - viewModel.albumBrowse.value - ?.data - ?.audioPlaylistId - ?.replaceFirst("VL", "") ?: "", - "Album \"${viewModel.albumBrowse.value?.data!!.title}\"", - PlaylistType.PLAYLIST, - null - ) - ) - viewModel.loadMediaItem( - firstPlay, - Config.ALBUM_CLICK, - position, - ) - } - } else if (viewModel.albumEntity.value != null && viewModel.albumEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED) { - val firstPlay = songsAdapter.getList().getOrNull(position) - if (firstPlay != null) { - viewModel.setQueueData( - QueueData( - songsAdapter.getList(), - firstPlay, - viewModel.albumEntity.value - ?.audioPlaylistId - ?.replaceFirst("VL", "") ?: "", - "Album \"${viewModel.albumEntity.value?.title}\"", - PlaylistType.PLAYLIST, - null - ) - ) - viewModel.loadMediaItem( - firstPlay, - Config.ALBUM_CLICK, - position, - ) - } - } else { - Snackbar.make(requireView(), "Error", Snackbar.LENGTH_SHORT).show() - } - } - }, - ) - songsAdapter.setOnOptionClickListener( - object : TrackAdapter.OnOptionClickListener { - override fun onOptionClick(position: Int) { - val song = songsAdapter.getList().get(position) - viewModel.getSongEntity(song.toSongEntity()) - val dialog = BottomSheetDialog(requireContext()) - val bottomSheetView = BottomSheetNowPlayingBinding.inflate(layoutInflater) - with(bottomSheetView) { - btSleepTimer.visibility = View.GONE - viewModel.songEntity.observe(viewLifecycleOwner) { songEntity -> - if (songEntity != null) { - if (songEntity.liked) { - tvFavorite.text = getString(R.string.liked) - cbFavorite.isChecked = true - } else { - tvFavorite.text = getString(R.string.like) - cbFavorite.isChecked = false - } - } - } - btChangeLyricsProvider.visibility = View.GONE - tvSongTitle.text = song.title - tvSongTitle.isSelected = true - tvSongArtist.text = song.artists.toListName().connectArtists() - tvSongArtist.isSelected = true - if (song.album != null) { - setEnabledAll(btAlbum, true) - tvAlbum.text = song.album.name - } else { - tvAlbum.text = getString(R.string.no_album) - setEnabledAll(btAlbum, false) - } - btAlbum.setOnClickListener { - val albumId = song.album?.id - if (albumId != null) { - findNavController().navigateSafe( - R.id.action_global_albumFragment, - Bundle().apply { - putString("browseId", albumId) - }, - ) - dialog.dismiss() - } else { - Toast - .makeText( - requireContext(), - getString(R.string.no_album), - Toast.LENGTH_SHORT, - ).show() - } - } - btAddQueue.setOnClickListener { - sharedViewModel.addToQueue(song) - } - btPlayNext.setOnClickListener { - sharedViewModel.playNext(song) - } - ivThumbnail.load(song.thumbnails?.lastOrNull()?.url) - btRadio.setOnClickListener { - val args = Bundle() - args.putString("radioId", "RDAMVM${song.videoId}") - args.putString( - "videoId", - song.videoId, - ) - dialog.dismiss() - findNavController().navigateSafe( - R.id.action_global_playlistFragment, - args, - ) - } - btLike.setOnClickListener { - if (cbFavorite.isChecked) { - cbFavorite.isChecked = false - tvFavorite.text = getString(R.string.like) - viewModel.updateLikeStatus(song.videoId, 0) - } else { - cbFavorite.isChecked = true - tvFavorite.text = getString(R.string.liked) - viewModel.updateLikeStatus(song.videoId, 1) - } - } - btSeeArtists.setOnClickListener { - val subDialog = BottomSheetDialog(requireContext()) - val subBottomSheetView = - BottomSheetSeeArtistOfNowPlayingBinding.inflate(layoutInflater) - if (!song.artists.isNullOrEmpty()) { - val artistAdapter = SeeArtistOfNowPlayingAdapter(song.artists) - subBottomSheetView.rvArtists.apply { - adapter = artistAdapter - layoutManager = LinearLayoutManager(requireContext()) - } - artistAdapter.setOnClickListener( - object : - SeeArtistOfNowPlayingAdapter.OnItemClickListener { - override fun onItemClick(position: Int) { - val artist = song.artists[position] - if (artist.id != null) { - findNavController().navigateSafe( - R.id.action_global_artistFragment, - Bundle().apply { - putString("channelId", artist.id) - }, - ) - subDialog.dismiss() - dialog.dismiss() - } - } - }, - ) - } - - subDialog.setCancelable(true) - subDialog.setContentView(subBottomSheetView.root) - subDialog.show() - } - btDownload.visibility = View.GONE - btAddPlaylist.setOnClickListener { - viewModel.getLocalPlaylist() - val listLocalPlaylist: ArrayList = arrayListOf() - val addPlaylistDialog = BottomSheetDialog(requireContext()) - val viewAddPlaylist = - BottomSheetAddToAPlaylistBinding.inflate(layoutInflater) - val addToAPlaylistAdapter = AddToAPlaylistAdapter(arrayListOf()) - addToAPlaylistAdapter.setVideoId(song.videoId) - viewAddPlaylist.rvLocalPlaylists.apply { - adapter = addToAPlaylistAdapter - layoutManager = LinearLayoutManager(requireContext()) - } - viewModel.listLocalPlaylist.observe(viewLifecycleOwner) { list -> - Log.d("Check Local Playlist", list.toString()) - listLocalPlaylist.clear() - listLocalPlaylist.addAll(list) - addToAPlaylistAdapter.updateList(listLocalPlaylist) - } - addToAPlaylistAdapter.setOnItemClickListener( - object : - AddToAPlaylistAdapter.OnItemClickListener { - override fun onItemClick(position: Int) { - val playlist = listLocalPlaylist[position] - viewModel.updateInLibrary(song.videoId) - val tempTrack = ArrayList() - if (playlist.tracks != null) { - tempTrack.addAll(playlist.tracks) - } - if (!tempTrack.contains( - song.videoId, - ) && - playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && - playlist.youtubePlaylistId != null - ) { - viewModel.addToYouTubePlaylist( - playlist.id, - playlist.youtubePlaylistId, - song.videoId, - ) - } - if (!tempTrack.contains(song.videoId)) { - viewModel.insertPairSongLocalPlaylist( - PairSongLocalPlaylist( - playlistId = playlist.id, - songId = song.videoId, - position = playlist.tracks?.size ?: 0, - inPlaylist = LocalDateTime.now(), - ), - ) - tempTrack.add(song.videoId) - } - - viewModel.updateLocalPlaylistTracks( - tempTrack.removeConflicts(), - playlist.id, - ) - addPlaylistDialog.dismiss() - dialog.dismiss() - } - }, - ) - addPlaylistDialog.setContentView(viewAddPlaylist.root) - addPlaylistDialog.setCancelable(true) - addPlaylistDialog.show() - } - btShare.setOnClickListener { - val shareIntent = Intent(Intent.ACTION_SEND) - shareIntent.type = "text/plain" - val url = "https://youtube.com/watch?v=${song.videoId}" - shareIntent.putExtra(Intent.EXTRA_TEXT, url) - val chooserIntent = - Intent.createChooser(shareIntent, getString(R.string.share_url)) - startActivity(chooserIntent) - } - dialog.setCancelable(true) - dialog.setContentView(bottomSheetView.root) - dialog.show() - } - } - }, - ) - - binding.btDownload.setOnClickListener { - if (viewModel.albumEntity.value?.downloadState == DownloadState.STATE_NOT_DOWNLOADED) { - for (i in viewModel.albumBrowse.value - ?.data - ?.tracks!!) { - viewModel.insertSong(i.toSongEntity()) - } - runBlocking { - delay(1000) - viewModel.listJob.emit(arrayListOf()) - } - viewModel.getListTrackForDownload( - viewModel.albumBrowse.value - ?.data - ?.tracks - ?.toListVideoId(), - ) - } else if (viewModel.albumEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED) { - Toast - .makeText(requireContext(), getString(R.string.downloaded), Toast.LENGTH_SHORT) - .show() - } else if (viewModel.albumEntity.value?.downloadState == DownloadState.STATE_DOWNLOADING) { - Toast - .makeText( - requireContext(), - getString(R.string.downloading), - Toast.LENGTH_SHORT, - ).show() - } - } - binding.topAppBarLayout.addOnOffsetChangedListener { it, verticalOffset -> - if (abs(it.totalScrollRange) == abs(verticalOffset)) { - binding.topAppBar.background = viewModel.gradientDrawable.value - if (viewModel.gradientDrawable.value != null) { - if (viewModel.gradientDrawable.value?.colors != null) { - setStatusBarsColor(viewModel.gradientDrawable.value - ?.colors!! - .first(), requireActivity()) - - } - } - } else { - binding.topAppBar.background = null - setStatusBarsColor( - ContextCompat.getColor(requireContext(), R.color.colorPrimaryDark), - requireActivity(), - ) - } - } - lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - val job1 = - launch { - sharedViewModel.downloadList.collect { - songsAdapter.setDownloadedList(it) - } - } - val playingJob = launch { - combine(sharedViewModel.nowPlayingState.distinctUntilChangedBy { - it?.songEntity?.videoId - }, sharedViewModel.controllerState.distinctUntilChangedBy { - it.isPlaying - }) { nowPlaying, controllerState -> - Pair(nowPlaying, controllerState) - }.collect { - val songEntity = it.first?.songEntity - if (songEntity != null && it.second.isPlaying) { - songsAdapter.setNowPlaying(songEntity.videoId) + browseId = arguments?.getString("browseId") + composeView.apply { + setContent { + AppTheme { + Scaffold { + val id = browseId + if (id != null) { + AlbumScreen(id, findNavController()) } else { - songsAdapter.setNowPlaying(null) + findNavController().navigateUp() } } } - val job2 = - launch { - combine( - sharedViewModel.nowPlayingState.distinctUntilChangedBy { - it?.songEntity?.videoId - }, - sharedViewModel.controllerState, - ) { nowPlaying, isPlaying -> - Pair(nowPlaying, isPlaying) - }.collect { - if (it.first != null && it.second.isPlaying) { - songsAdapter.setNowPlaying(it.first?.songEntity?.videoId) - } else { - songsAdapter.setNowPlaying(null) - } - } - } - val job3 = - launch { - viewModel.albumDownloadState.collectLatest { albumDownloadState -> - when (albumDownloadState) { - DownloadState.STATE_PREPARING -> { - binding.btDownload.visibility = View.GONE - binding.animationDownloading.visibility = View.VISIBLE - } - - DownloadState.STATE_DOWNLOADING -> { - binding.btDownload.visibility = View.GONE - binding.animationDownloading.visibility = View.VISIBLE - } - - DownloadState.STATE_DOWNLOADED -> { - binding.btDownload.visibility = View.VISIBLE - binding.animationDownloading.visibility = View.GONE - binding.btDownload.setImageResource(R.drawable.baseline_downloaded) - } - - DownloadState.STATE_NOT_DOWNLOADED -> { - binding.btDownload.visibility = View.VISIBLE - binding.animationDownloading.visibility = View.GONE - binding.btDownload.setImageResource(R.drawable.download_button) - } - } - } - } - val loadingJob = - launch { - viewModel.loading.collectLatest { loading -> - if (loading) { - binding.rootLayout.visibility = View.GONE - binding.loadingLayout.visibility = View.VISIBLE - } else { - binding.rootLayout.visibility = View.VISIBLE - binding.loadingLayout.visibility = View.GONE - } - } - } - val browseIdJob = - launch { - viewModel.browseId.collectLatest { browseId -> - if (browseId != null) { - fetchData(browseId, downloaded ?: 0) - } - } - } - val albumBrowseJob = - launch { - viewModel.albumBrowse.collect { response -> - if (response != null) { - when (response) { - is Resource.Success -> { - response.data.let { - if (it != null) { - (viewModel.browseId.value ?: browseId)?.let { id -> - viewModel.insertAlbum(it.toAlbumEntity(id)) - it.thumbnails?.lastOrNull()?.url?.let { url -> - loadImage( - url, - id, - ) - } - } - with(binding) { - topAppBar.title = it.title - btArtist.text = it.artists[0].name - tvYearAndCategory.text = - context?.getString( - R.string.year_and_category, - it.year, - it.type, - ) - tvTrackCountAndDuration.text = - context?.getString( - R.string.album_length, - it.trackCount.toString(), - it.duration, - ) - if (it.description == null || it.description == "") { - tvDescription.originalText = - getString(R.string.no_description) - } else { - tvDescription.originalText = - it.description.toString() - } - - val tempList = arrayListOf() - for (i in it.tracks) { - tempList.add(i) - } - songsAdapter.updateList(tempList) - binding.rootLayout.visibility = View.VISIBLE - binding.loadingLayout.visibility = View.GONE - } - } - } - } - - is Resource.Error -> { - Snackbar - .make( - binding.root, - response.message.toString(), - Snackbar.LENGTH_LONG, - ).show() - findNavController().popBackStack() - } - } - } - } - } - val albumEntity = - launch { - viewModel.albumEntity.collect { albumEntity -> - if (albumEntity != null) { - with(binding) { - when (albumEntity.downloadState) { - DownloadState.STATE_DOWNLOADED -> { - btDownload.visibility = View.VISIBLE - animationDownloading.visibility = View.GONE - btDownload.setImageResource(R.drawable.baseline_downloaded) - } - - DownloadState.STATE_DOWNLOADING -> { - btDownload.visibility = View.GONE - animationDownloading.visibility = View.VISIBLE - } - - DownloadState.STATE_NOT_DOWNLOADED -> { - btDownload.visibility = View.VISIBLE - animationDownloading.visibility = View.GONE - btDownload.setImageResource(R.drawable.download_button) - } - } - binding.cbLove.isChecked = albumEntity.liked - } - if (viewModel.albumBrowse.value?.data == null) { - with(binding) { - topAppBar.title = albumEntity.title - btArtist.text = albumEntity.artistName?.get(0) ?: "Unknown" - tvYearAndCategory.text = - context?.getString( - R.string.year_and_category, - albumEntity.year, - albumEntity.type, - ) - tvTrackCountAndDuration.text = - context?.getString( - R.string.album_length, - albumEntity.trackCount.toString(), - albumEntity.duration, - ) - if (albumEntity.description == "") { - tvDescription.originalText = - getString(R.string.no_description) - } else { - tvDescription.originalText = albumEntity.description - } - loadImage(albumEntity.thumbnails!!, albumEntity.browseId) - viewModel.getListTrack(albumEntity.tracks) - } - } - } - } - } - val listTrackJob = - launch { - viewModel.listTrack.collect { listTrack -> - if (listTrack != null) { - val tempList = arrayListOf() - for (i in listTrack) { - tempList.add(i) - } - if (viewModel.albumEntity.value != null) { - tempList.sortBy { - ( - viewModel.albumEntity.value - ?.tracks - ?.indexMap() - )?.get( - (it as SongEntity).videoId, - ) - } - } - songsAdapter.updateList(tempList) - } - } - } - val downloadJob = - launch { - viewModel.listTrackForDownload.collect { listTrack -> - if (!listTrack.isNullOrEmpty()) { - val listJob: ArrayList = arrayListOf() - for (song in listTrack) { - if (song.downloadState == DownloadState.STATE_NOT_DOWNLOADED) { - listJob.add(song) - } - } - viewModel.listJob.value = listJob - Log.d("AlbumFragment", "ListJob: ${viewModel.listJob.value}") - viewModel.updatePlaylistDownloadState( - browseId!!, - DownloadState.STATE_DOWNLOADING, - ) - listJob.forEach { job -> - val downloadRequest = - DownloadRequest - .Builder(job.videoId, job.videoId.toUri()) - .setData(job.title.toByteArray()) - .setCustomCacheKey(job.videoId) - .build() - viewModel.updateDownloadState( - job.videoId, - DownloadState.STATE_DOWNLOADING, - ) - DownloadService.sendAddDownload( - requireContext(), - MusicDownloadService::class.java, - downloadRequest, - false, - ) - viewModel.getDownloadStateFromService(job.videoId) - } - } - } - } - job1.join() - job2.join() - job3.join() - loadingJob.join() - browseIdJob.join() - albumBrowseJob.join() - albumEntity.join() - listTrackJob.join() - downloadJob.join() - playingJob.join() -// launch { -// viewModel.listJob.collectLatest {jobs-> -// Log.d("AlbumFragment", "ListJob: $jobs") -// if (jobs.isNotEmpty()){ -// var count = 0 -// jobs.forEach { job -> -// if (job.downloadState == DownloadState.STATE_DOWNLOADED) { -// count++ -// } -// } -// Log.d("AlbumFragment", "Count: $count") -// if (count == jobs.size) { -// viewModel.updatePlaylistDownloadState( -// browseId!!, -// DownloadState.STATE_DOWNLOADED -// ) -// Toast.makeText( -// requireContext(), -// getString(R.string.downloaded), -// Toast.LENGTH_SHORT -// ).show() -// } -// } -// } -// } } - // job2.join() - } - } -// private fun fetchDataFromViewModel() { -// val response = viewModel.albumBrowse.value -// when (response){ -// is Resource.Success -> { -// response.data.let { -// with(binding){ -// topAppBar.title = it?.title -// btArtist.text = it?.artists?.get(0)?.name -// tvYearAndCategory.text= context?.getString(R.string.year_and_category, it?.year, it?.type) -// tvTrackCountAndDuration.text = context?.getString(R.string.album_length, it?.trackCount.toString(), it?.duration) -// if (it?.description == null || it.description == ""){ -// tvDescription.originalText = "No description" -// } -// else { -// tvDescription.originalText = it.description.toString() -// } -// when (it?.thumbnails?.size!!){ -// 1 -> loadImage(it.thumbnails[0].url, viewModel.browseId.value!!) -// 2 -> loadImage(it.thumbnails[1].url, viewModel.browseId.value!!) -// 3 -> loadImage(it.thumbnails[2].url, viewModel.browseId.value!!) -// 4 -> loadImage(it.thumbnails[3].url, viewModel.browseId.value!!) -// else -> {} -// } -// val tempList = arrayListOf() -// for (i in it.tracks){ -// tempList.add(i) -// } -// songsAdapter.updateList(tempList) -// binding.rootLayout.visibility = View.VISIBLE -// binding.loadingLayout.visibility = View.GONE -// val albumEntity = viewModel.albumEntity.value -// if (albumEntity != null) { -// viewModel.checkAllSongDownloaded(it.tracks as ArrayList) -// viewModel.albumEntity.observe(viewLifecycleOwner){albumEntity2 -> -// if (albumEntity2 != null) { -// when (albumEntity2.downloadState) { -// DownloadState.STATE_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.baseline_downloaded) -// } -// -// DownloadState.STATE_DOWNLOADING -> { -// btDownload.visibility = View.GONE -// animationDownloading.visibility = View.VISIBLE -// } -// -// DownloadState.STATE_NOT_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.download_button) -// } -// } -// } -// } -// } -// } -// } -// } -// is Resource.Error -> { -// Snackbar.make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG).show() -// findNavController().popBackStack() -// } -// -// else -> {} -// } -// } - - private fun fetchData( - browseId: String, - downloaded: Int = 0, - ) { - viewModel.clearAlbumBrowse() - if (downloaded == 0) { - viewModel.browseAlbum(browseId) - } else if (downloaded == 1) { - viewModel.getAlbum(browseId) } - -// if (downloaded == 0) { -// viewModel.browseAlbum(browseId) -// viewModel.albumBrowse.observe(viewLifecycleOwner) { response -> -// when (response){ -// is Resource.Success -> { -// response.data.let { -// if (it != null){ -// viewModel.insertAlbum(it.toAlbumEntity(browseId)) -// with(binding){ -// topAppBar.title = it.title -// btArtist.text = it.artists[0].name -// tvYearAndCategory.text= context?.getString(R.string.year_and_category, it.year, it.type) -// tvTrackCountAndDuration.text = context?.getString(R.string.album_length, it.trackCount.toString(), it.duration) -// if (it.description == null || it.description == ""){ -// tvDescription.originalText = getString(R.string.no_description) -// } -// else { -// tvDescription.originalText = it.description.toString() -// } -// when (it.thumbnails?.size!!){ -// 1 -> loadImage(it.thumbnails[0].url, browseId) -// 2 -> loadImage(it.thumbnails[1].url, browseId) -// 3 -> loadImage(it.thumbnails[2].url, browseId) -// 4 -> loadImage(it.thumbnails[3].url, browseId) -// else -> {} -// } -// val tempList = arrayListOf() -// for (i in it.tracks){ -// tempList.add(i) -// } -// songsAdapter.updateList(tempList) -// binding.rootLayout.visibility = View.VISIBLE -// binding.loadingLayout.visibility = View.GONE -// viewModel.albumEntity.observe(viewLifecycleOwner) {albumEntity -> -// if (albumEntity != null) { -// when (albumEntity.downloadState) { -// DownloadState.STATE_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.baseline_downloaded) -// } -// DownloadState.STATE_DOWNLOADING -> { -// btDownload.visibility = View.GONE -// animationDownloading.visibility = View.VISIBLE -// } -// DownloadState.STATE_NOT_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.download_button) -// } -// } -// } -// } -// } -// } -// } -// } -// is Resource.Error -> { -// Snackbar.make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG).show() -// findNavController().popBackStack() -// } -// -// else -> {} -// } -// } -// } -// else if (downloaded == 1){ -// viewModel.getAlbum(browseId) -// with(binding){ -// viewModel.albumEntity.observe(viewLifecycleOwner) {albumEntity -> -// if (albumEntity != null) { -// when (albumEntity.downloadState) { -// DownloadState.STATE_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.baseline_downloaded) -// } -// DownloadState.STATE_DOWNLOADING -> { -// btDownload.visibility = View.GONE -// animationDownloading.visibility = View.VISIBLE -// } -// DownloadState.STATE_NOT_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.download_button) -// } -// } -// } -// if (albumEntity != null) { -// topAppBar.title = albumEntity.title -// btArtist.text = albumEntity.artistName?.get(0) ?: "Unknown" -// tvYearAndCategory.text= context?.getString(R.string.year_and_category, albumEntity.year, albumEntity.type) -// tvTrackCountAndDuration.text = context?.getString(R.string.album_length, albumEntity.trackCount.toString(), albumEntity.duration) -// if (albumEntity.description == ""){ -// tvDescription.originalText = getString(R.string.no_description) -// } -// else { -// tvDescription.originalText = albumEntity.description.toString() -// } -// loadImage(albumEntity.thumbnails!!, browseId) -// viewModel.getListTrack(albumEntity.tracks) -// } -// viewModel.listTrack.observe(viewLifecycleOwner) { listTrack -> -// val tempList = arrayListOf() -// for (i in listTrack){ -// tempList.add(i) -// } -// if (albumEntity != null) { -// tempList.sortBy { (albumEntity.tracks?.indexMap())?.get((it as SongEntity).videoId) } -// } -// songsAdapter.updateList(tempList) -// } -// binding.rootLayout.visibility = View.VISIBLE -// binding.loadingLayout.visibility = View.GONE -// } -// } -// } - } - - private fun loadImage( - url: String, - albumId: String, - ) { - val request = - ImageRequest - .Builder(requireContext()) - .placeholder(R.drawable.holder) - .data(Uri.parse(url)) - .diskCachePolicy(CachePolicy.ENABLED) - .diskCacheKey(url) - .target( - onStart = { - }, - onSuccess = { result -> - binding.ivAlbumArt.setImageDrawable(result.asDrawable(requireContext().resources)) - if (viewModel.gradientDrawable.value != null) { - viewModel.gradientDrawable.observe(viewLifecycleOwner) { - binding.fullRootLayout.background = - it.apply { - setDither(true) - } - toolbarBackground = it.colors?.get(0) - Log.d("TAG", "fetchData: $toolbarBackground") - // binding.topAppBar.background = ColorDrawable(toolbarBackground!!) - binding.topAppBarLayout.background = - ColorDrawable(toolbarBackground!!) - } - } - }, - onError = { - binding.ivAlbumArt.load(R.drawable.holder) - }, - ).transformations( - object : Transformation() { - override val cacheKey: String - get() = url - - override suspend fun transform( - input: Bitmap, - size: Size, - ): Bitmap { - val p = Palette.from(input).generate() - val defaultColor = 0x000000 - var startColor = p.getDarkVibrantColor(defaultColor) - if (startColor == defaultColor) { - startColor = p.getDarkMutedColor(defaultColor) - if (startColor == defaultColor) { - startColor = p.getVibrantColor(defaultColor) - if (startColor == defaultColor) { - startColor = p.getMutedColor(defaultColor) - if (startColor == defaultColor) { - startColor = p.getLightVibrantColor(defaultColor) - if (startColor == defaultColor) { - startColor = p.getLightMutedColor(defaultColor) - } - } - } - } - Log.d("Check Start Color", "transform: $startColor") - } - startColor = ColorUtils.setAlphaComponent(startColor, 150) - val endColor = - resources.getColor(R.color.md_theme_dark_background, null) - val gd = - GradientDrawable( - GradientDrawable.Orientation.TOP_BOTTOM, - intArrayOf(startColor, endColor), - ) - gd.cornerRadius = 0f - gd.gradientType = GradientDrawable.LINEAR_GRADIENT - gd.gradientRadius = 0.5f - viewModel.gradientDrawable.postValue(gd) - return input - } - }, - ).build() - requireContext().imageLoader.enqueue(request) -// binding.ivAlbumArt.load(url) { -// diskCachePolicy(CachePolicy.ENABLED) -// .diskCacheKey(albumId) -// transformations(object : Transformation{ -// override val cacheKey: String -// get() = albumId -// -// override suspend fun transform(input: Bitmap, size: Size): Bitmap { -// val p = Palette.from(input).generate() -// val defaultColor = 0x000000 -// var startColor = p.getDarkVibrantColor(defaultColor) -// if (startColor == defaultColor){ -// startColor = p.getDarkMutedColor(defaultColor) -// if (startColor == defaultColor){ -// startColor = p.getVibrantColor(defaultColor) -// if (startColor == defaultColor){ -// startColor = p.getMutedColor(defaultColor) -// if (startColor == defaultColor){ -// startColor = p.getLightVibrantColor(defaultColor) -// if (startColor == defaultColor){ -// startColor = p.getLightMutedColor(defaultColor) -// } -// } -// } -// } -// Log.d("Check Start Color", "transform: $startColor") -// } -// startColor = ColorUtils.setAlphaComponent(startColor, 150) -// val endColor = resources.getColor(R.color.md_theme_dark_background, null) -// val gd = GradientDrawable( -// GradientDrawable.Orientation.TOP_BOTTOM, -// intArrayOf(startColor, endColor) -// ) -// gd.cornerRadius = 0f -// gd.gradientType = GradientDrawable.LINEAR_GRADIENT -// gd.gradientRadius = 0.5f -// viewModel.gradientDrawable.postValue(gd) -// return input -// } -// -// }) -// } } } \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt index 6a60518a..baee1944 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/fragment/other/PlaylistFragment.kt @@ -1,1764 +1,1765 @@ -package com.maxrave.simpmusic.ui.fragment.other - -import android.content.Intent -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.GradientDrawable -import android.graphics.drawable.TransitionDrawable -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.core.content.ContextCompat -import androidx.core.graphics.ColorUtils -import androidx.core.net.toUri -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.media3.common.util.UnstableApi -import androidx.media3.exoplayer.offline.DownloadRequest -import androidx.media3.exoplayer.offline.DownloadService -import androidx.navigation.fragment.findNavController -import androidx.palette.graphics.Palette -import androidx.recyclerview.widget.LinearLayoutManager -import coil3.asDrawable -import coil3.load -import coil3.request.CachePolicy -import coil3.request.allowHardware -import coil3.request.crossfade -import coil3.request.placeholder -import coil3.toBitmap -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.snackbar.Snackbar -import com.maxrave.simpmusic.R -import com.maxrave.simpmusic.adapter.artist.SeeArtistOfNowPlayingAdapter -import com.maxrave.simpmusic.adapter.playlist.AddToAPlaylistAdapter -import com.maxrave.simpmusic.adapter.playlist.PlaylistItemAdapter -import com.maxrave.simpmusic.common.Config -import com.maxrave.simpmusic.common.DownloadState -import com.maxrave.simpmusic.data.db.entities.LocalPlaylistEntity -import com.maxrave.simpmusic.data.db.entities.PairSongLocalPlaylist -import com.maxrave.simpmusic.data.db.entities.SongEntity -import com.maxrave.simpmusic.data.model.browse.album.Track -import com.maxrave.simpmusic.databinding.BottomSheetAddToAPlaylistBinding -import com.maxrave.simpmusic.databinding.BottomSheetNowPlayingBinding -import com.maxrave.simpmusic.databinding.BottomSheetPlaylistMoreBinding -import com.maxrave.simpmusic.databinding.BottomSheetSeeArtistOfNowPlayingBinding -import com.maxrave.simpmusic.databinding.FragmentPlaylistBinding -import com.maxrave.simpmusic.extension.connectArtists -import com.maxrave.simpmusic.extension.navigateSafe -import com.maxrave.simpmusic.extension.removeConflicts -import com.maxrave.simpmusic.extension.setEnabledAll -import com.maxrave.simpmusic.extension.setStatusBarsColor -import com.maxrave.simpmusic.extension.toArrayListTrack -import com.maxrave.simpmusic.extension.toListName -import com.maxrave.simpmusic.extension.toListVideoId -import com.maxrave.simpmusic.extension.toSongEntity -import com.maxrave.simpmusic.extension.toTrack -import com.maxrave.simpmusic.service.PlaylistType -import com.maxrave.simpmusic.service.QueueData -import com.maxrave.simpmusic.service.test.download.MusicDownloadService -import com.maxrave.simpmusic.viewModel.PlaylistUIState -import com.maxrave.simpmusic.viewModel.PlaylistViewModel -import com.maxrave.simpmusic.viewModel.SharedViewModel -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.distinctUntilChangedBy -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import java.time.LocalDateTime -import kotlin.math.abs - -@UnstableApi -class PlaylistFragment : Fragment() { - private val tag = "PlaylistFragment" - - private val viewModel by activityViewModels() - private val sharedViewModel by activityViewModels() - private var _binding: FragmentPlaylistBinding? = null - val binding get() = _binding!! - - private var gradientDrawable: GradientDrawable? = null - private var toolbarBackground: Int? = null - - private lateinit var playlistItemAdapter: PlaylistItemAdapter - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { - _binding = FragmentPlaylistBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onDestroyView() { - super.onDestroyView() - requireArguments().clear() - setStatusBarsColor( - ContextCompat.getColor(requireContext(), R.color.colorPrimaryDark), - requireActivity(), - ) - _binding = null - } - - @UnstableApi - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - viewModel.getLocation() - lifecycleScope.launch { - viewModel.liked.collect { liked -> - binding.cbLove.isChecked = liked - } - } - if (viewModel.gradientDrawable.value != null) { - gradientDrawable = viewModel.gradientDrawable.value - toolbarBackground = gradientDrawable?.colors?.get(0) - } - - playlistItemAdapter = PlaylistItemAdapter(arrayListOf()) - binding.rvListSong.apply { - adapter = playlistItemAdapter - layoutManager = - LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) - setHasFixedSize(false) - } - val id = requireArguments().getString("id") - val downloaded = arguments?.getInt("downloaded") - val radioId = arguments?.getString("radioId") - val channelId = arguments?.getString("channelId") - Log.w(tag, "radioId: $radioId") - val videoId = arguments?.getString("videoId") - // 3 truong hop: - /* - 1. La Playlist - 2. La Radio - 3. Downloaded Playlist - 4. Luc lai tu ViewModel - */ - if (id != null || radioId != null) { - Log.w(tag, "id: $id") - Log.w(tag, "radioId: $radioId") - if (radioId != null || - id?.startsWith("RDEM") == true || - id?.startsWith("RDAMVM") == true - ) { - viewModel.updateIsRadio(true) - if (radioId != null) { - fetchDataWithRadio(radioId, videoId, channelId) - } else if (id != null) { - fetchDataWithRadio(id) - } - } else if (id != null && id.startsWith("RDAT")) { - viewModel.updateIsRadio(true) - fetchRDATRadio(id) - } else if (id != null) { - viewModel.updateIsRadio(false) - if (!requireArguments().getBoolean("youtube")) { - viewModel.checkSuccess() - } - if (downloaded == null || downloaded == 0) { - fetchData(id) - } - if (downloaded == 1) { - fetchData(id, downloaded = 1) - } - } - } -// if (id == null && radioId == null || id == viewModel.id.value && radioId == null || id == null && radioId == viewModel.id.value) { -// id = viewModel.id.value -// if (id?.startsWith("RDEM") == true || id?.startsWith("RDAMVM") == true || id?.startsWith("RDAT") == true) { -// viewModel.updateIsRadio(true) -// } else { -// viewModel.updateIsRadio(false) -// } -// if (!requireArguments().getBoolean("youtube")) { -// viewModel.checkSuccess() -// } else { -// if (id != null) { -// if (downloaded == null || downloaded == 0) { -// fetchData(id) -// } -// if (downloaded == 1) { -// fetchData(id, downloaded = 1) -// } -// } -// } -// } else if (radioId != null && id == null) { -// viewModel.clearPlaylistBrowse() -// viewModel.updateIsRadio(true) -// if (videoId != null) { -// fetchDataWithRadio(radioId, videoId) -// } else if (channelId != null) { -// fetchDataWithRadio(radioId, null, channelId) -// } -// } else if (id != null && id.startsWith("RDEM") || id != null && id.startsWith("RDAMVM") || id?.startsWith("RDAT") == true) { -// viewModel.clearPlaylistEntity() -// viewModel.clearPlaylistBrowse() -// fetchData(id) -// } else if (id != null) { -// viewModel.updateId(id) -// if (downloaded == null || downloaded == 0) { -// fetchData(id) -// } -// if (downloaded == 1) { -// fetchData(id, downloaded = 1) -// } -// } - - binding.topAppBar.setNavigationOnClickListener { - findNavController().popBackStack() - } - binding.cbLove.setOnCheckedChangeListener { _, isChecked -> - if (!isChecked) { - viewModel.playlistEntity.value?.let { playlist -> - viewModel.updatePlaylistLiked( - false, - playlist.id, - ) - } - } else { - viewModel.playlistEntity.value?.let { playlist -> - viewModel.updatePlaylistLiked( - true, - playlist.id, - ) - } - } - } - binding.btMore.setOnClickListener { - val bottomSheetDialog = BottomSheetDialog(requireContext()) - val moreView = BottomSheetPlaylistMoreBinding.inflate(layoutInflater) - if (viewModel.isRadio.value == false) { - moreView.ivThumbnail.load(viewModel.playlistEntity.value?.thumbnails) - moreView.tvSongTitle.text = viewModel.playlistEntity.value?.title - moreView.tvSongArtist.text = viewModel.playlistEntity.value?.author - moreView.btShare.setOnClickListener { - val shareIntent = Intent(Intent.ACTION_SEND) - shareIntent.type = "text/plain" - val url = "https://youtube.com/playlist?list=${ - viewModel.playlistEntity.value?.id?.replaceFirst( - "VL", - "", - ) - }" - shareIntent.putExtra(Intent.EXTRA_TEXT, url) - val chooserIntent = - Intent.createChooser(shareIntent, getString(R.string.share_url)) - startActivity(chooserIntent) - } - } else { - moreView.ivThumbnail.load( - viewModel.playlistBrowse.value - ?.thumbnails - ?.lastOrNull() - ?.url, - ) - moreView.tvSongTitle.text = - viewModel.playlistBrowse.value - ?.title - moreView.tvSongArtist.text = - viewModel.playlistBrowse.value - ?.author - ?.name - moreView.btShare.setOnClickListener { - val shareIntent = Intent(Intent.ACTION_SEND) - shareIntent.type = "text/plain" - val url = "https://youtube.com/playlist?list=${ - viewModel.playlistBrowse.value?.id?.replaceFirst( - "VL", - "", - ) - }" - shareIntent.putExtra(Intent.EXTRA_TEXT, url) - val chooserIntent = - Intent.createChooser(shareIntent, getString(R.string.share_url)) - startActivity(chooserIntent) - } - } - if (requireArguments().getBoolean("youtube")) { - Log.w(tag, "id check: $id") - moreView.btSync.visibility = View.VISIBLE - viewModel.checkSyncedPlaylist(id) - lifecycleScope.launch { - viewModel.localPlaylistIfYouTubePlaylist.collectLatest { ytPlaylist -> - Log.w(tag, "ytPlaylist: ${ytPlaylist?.youtubePlaylistId}") - Log.w(tag, "id: $id") - if (ytPlaylist != null) { - val tempId = ytPlaylist.youtubePlaylistId - if (tempId == id) { - moreView.tvSync.text = - context?.getString(R.string.saved_to_local_playlist) - setEnabledAll(moreView.btSync, false) - } - } else { - moreView.tvSync.text = - context?.getString(R.string.save_to_local_playlist) - } - } - } - moreView.btSync.setOnClickListener { - if (moreView.tvSync.text == context?.getString(R.string.save_to_local_playlist)) { - val playlist = viewModel.playlistBrowse.value - if (playlist != null) { - viewModel.insertLocalPlaylist(playlist) - moreView.tvSync.text = - context?.getString(R.string.saved_to_local_playlist) - } - } else { - Snackbar - .make( - requireView(), - getString(R.string.if_you_want_to_unsync_this_playlist_please_go_to_local_playlist), - Snackbar.LENGTH_SHORT, - ).show() - } - } - } else { - moreView.btSync.visibility = View.GONE - } - - moreView.btAddToQueue.setOnClickListener { - if (viewModel.playlistBrowse.value != null) { - val list = - viewModel.playlistBrowse.value - ?.tracks as ArrayList - sharedViewModel.addListToQueue(list) - } else if (viewModel.playlistEntity.value != null && - viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED - ) { - val list = viewModel.listTrack.value.toArrayListTrack() - sharedViewModel.addListToQueue(list) - } else { - Snackbar - .make( - requireView(), - getString(R.string.playlist_is_empty), - Snackbar.LENGTH_SHORT, - ).show() - } - } - - bottomSheetDialog.setContentView(moreView.root) - bottomSheetDialog.setCancelable(true) - bottomSheetDialog.show() - } - - binding.btPlayPause.setOnClickListener { - if (!viewModel.isRadio.value) { - if (viewModel.playlistBrowse.value != null) { - viewModel.setQueueData( - QueueData( - listTracks = (viewModel.playlistBrowse.value?.tracks ?: arrayListOf()) as ArrayList, - firstPlayedTrack = - viewModel.playlistBrowse.value - ?.tracks - ?.get(0), - playlistId = - viewModel.playlistBrowse.value - ?.id - ?.replaceFirst("VL", "") ?: "", - playlistName = "Playlist \"${viewModel.playlistBrowse.value?.title}\"", - playlistType = PlaylistType.PLAYLIST, - continuation = null, - ), - ) - viewModel.playlistBrowse.value - ?.tracks - ?.get(0) - ?.let { - viewModel.loadMediaItem( - it, - type = Config.PLAYLIST_CLICK, - index = 0, - ) - } - } else if (viewModel.playlistEntity.value != null && - viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED - ) { - viewModel.setQueueData( - QueueData( - listTracks = viewModel.listTrack.value.toArrayListTrack(), - firstPlayedTrack = - viewModel.listTrack.value - .firstOrNull() - ?.toTrack(), - playlistId = - viewModel.playlistEntity.value - ?.id - ?.replaceFirst("VL", "") ?: "", - playlistName = "Playlist \"${viewModel.playlistEntity.value?.title}\"", - playlistType = PlaylistType.PLAYLIST, - continuation = null, - ), - ) - viewModel.listTrack.value.firstOrNull()?.let { - viewModel.loadMediaItem( - it.toTrack(), - type = Config.PLAYLIST_CLICK, - index = 0, - ) - } - } else { - Snackbar - .make( - requireView(), - getString(R.string.playlist_is_empty), - Snackbar.LENGTH_SHORT, - ).show() - } - } else { - if (viewModel.playlistBrowse.value != null) { - viewModel.setQueueData( - QueueData( - listTracks = (viewModel.playlistBrowse.value?.tracks ?: arrayListOf()) as ArrayList, - firstPlayedTrack = - viewModel.playlistBrowse.value - ?.tracks - ?.get(0), - playlistId = - viewModel.playlistBrowse.value - ?.id - ?.replaceFirst("VL", "") ?: "", - playlistName = "${viewModel.playlistBrowse.value?.title}", - playlistType = PlaylistType.RADIO, - continuation = - viewModel.radioContinuation.value?.let { - if (it.first == viewModel.playlistBrowse.value?.id) it.second else null - }, - ), - ) - viewModel.playlistBrowse.value?.tracks?.firstOrNull()?.let { - viewModel.loadMediaItem( - it, - type = Config.PLAYLIST_CLICK, - index = 0, - ) - } - } else if (viewModel.playlistEntity.value != null && - viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED - ) { - viewModel.setQueueData( - QueueData( - listTracks = viewModel.listTrack.value.toArrayListTrack(), - firstPlayedTrack = - viewModel.listTrack.value - .firstOrNull() - ?.toTrack(), - playlistId = - viewModel.playlistBrowse.value - ?.id - ?.replaceFirst("VL", ""), - playlistName = "${viewModel.playlistBrowse.value?.title}", - playlistType = PlaylistType.RADIO, - continuation = - viewModel.radioContinuation.value?.let { - if (it.first == viewModel.playlistBrowse.value?.id) it.second else null - }, - ), - ) - viewModel.listTrack.value.firstOrNull()?.let { - viewModel.loadMediaItem( - it.toTrack(), - type = Config.PLAYLIST_CLICK, - index = 0, - ) - } - } else { - Snackbar - .make( - requireView(), - getString(R.string.playlist_is_empty), - Snackbar.LENGTH_SHORT, - ).show() - } - } - } - - playlistItemAdapter.setOnClickListener( - object : PlaylistItemAdapter.OnItemClickListener { - override fun onItemClick(position: Int) { - if (!viewModel.isRadio.value) { - if (viewModel.playlistBrowse.value != null) { - viewModel.setQueueData( - QueueData( - listTracks = (viewModel.playlistBrowse.value?.tracks ?: arrayListOf()) as ArrayList, - firstPlayedTrack = - viewModel.playlistBrowse.value - ?.tracks - ?.get(position), - playlistId = - viewModel.playlistBrowse.value - ?.id - ?.replaceFirst("VL", "") ?: "", - playlistName = "Playlist \"${viewModel.playlistBrowse.value?.title}\"", - playlistType = PlaylistType.PLAYLIST, - continuation = null, - ), - ) - viewModel.playlistBrowse.value?.tracks?.get(position)?.let { - Log.w(tag, "track: $it") - viewModel.loadMediaItem( - it, - type = Config.PLAYLIST_CLICK, - index = position, - ) - } - } else if (viewModel.playlistEntity.value != null && - viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED - ) { - viewModel.setQueueData( - QueueData( - listTracks = viewModel.listTrack.value.toArrayListTrack(), - firstPlayedTrack = - viewModel.listTrack.value - .getOrNull(position) - ?.toTrack(), - playlistId = - viewModel.playlistEntity.value - ?.id - ?.replaceFirst("VL", "") ?: "", - playlistName = "Playlist \"${viewModel.playlistEntity.value?.title}\"", - playlistType = PlaylistType.PLAYLIST, - continuation = null, - ), - ) - viewModel.listTrack.value.getOrNull(position)?.let { - Log.w(tag, "track: $it") - viewModel.loadMediaItem( - it.toTrack(), - type = Config.PLAYLIST_CLICK, - index = position, - ) - } - } else { - Snackbar - .make( - requireView(), - getString(R.string.error), - Snackbar.LENGTH_SHORT, - ).show() - } - } else { - if (viewModel.playlistBrowse.value != null) { - viewModel.setQueueData( - QueueData( - listTracks = (viewModel.playlistBrowse.value?.tracks ?: arrayListOf()) as ArrayList, - firstPlayedTrack = - viewModel.playlistBrowse.value - ?.tracks - ?.get(position), - playlistId = - viewModel.playlistBrowse.value - ?.id - ?.replaceFirst("VL", "") ?: "", - playlistName = "${viewModel.playlistBrowse.value?.title}", - playlistType = PlaylistType.RADIO, - continuation = - viewModel.radioContinuation.value?.let { - if (it.first == viewModel.playlistBrowse.value?.id) it.second else null - }, - ), - ) - viewModel.playlistBrowse.value?.tracks?.get(position)?.let { - Log.w(tag, "track: $it") - viewModel.loadMediaItem( - it, - type = Config.PLAYLIST_CLICK, - index = position, - ) - } - } else if (viewModel.playlistEntity.value != null && - viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED - ) { - viewModel.setQueueData( - QueueData( - listTracks = viewModel.listTrack.value.toArrayListTrack(), - firstPlayedTrack = - viewModel.listTrack.value - .getOrNull(position) - ?.toTrack(), - playlistId = - viewModel.playlistBrowse.value - ?.id - ?.replaceFirst("VL", ""), - playlistName = "${viewModel.playlistBrowse.value?.title}", - playlistType = PlaylistType.RADIO, - continuation = - viewModel.radioContinuation.value?.let { - if (it.first == viewModel.playlistBrowse.value?.id) it.second else null - }, - ), - ) - viewModel.listTrack.value.getOrNull(position)?.let { - Log.w(tag, "track: $it") - viewModel.loadMediaItem( - it.toTrack(), - type = Config.PLAYLIST_CLICK, - index = position, - ) - } - } else { - Snackbar - .make( - requireView(), - getString(R.string.error), - Snackbar.LENGTH_SHORT, - ).show() - } - } - } - }, - ) - playlistItemAdapter.setOnOptionClickListener( - object : - PlaylistItemAdapter.OnOptionClickListener { - override fun onOptionClick(position: Int) { - val playListBrowseValue = viewModel.playlistBrowse.value - if (playListBrowseValue != null) { - val song = - playListBrowseValue.tracks[position] - viewModel.getSongEntity(song.toSongEntity()) - val dialog = BottomSheetDialog(requireContext()) - val bottomSheetView = BottomSheetNowPlayingBinding.inflate(layoutInflater) - with(bottomSheetView) { - btSleepTimer.visibility = View.GONE - viewModel.songEntity.observe(viewLifecycleOwner) { songEntity -> - if (songEntity != null) { - if (songEntity.liked) { - tvFavorite.text = getString(R.string.liked) - cbFavorite.isChecked = true - } else { - tvFavorite.text = getString(R.string.like) - cbFavorite.isChecked = false - } - } - } - btChangeLyricsProvider.visibility = View.GONE - tvSongTitle.text = song.title - tvSongTitle.isSelected = true - tvSongArtist.text = song.artists.toListName().connectArtists() - tvSongArtist.isSelected = true - if (song.album != null) { - setEnabledAll(btAlbum, true) - tvAlbum.text = song.album.name - } else { - tvAlbum.text = getString(R.string.no_album) - setEnabledAll(btAlbum, false) - } - btAlbum.setOnClickListener { - val albumId = song.album?.id - if (albumId != null) { - findNavController().navigateSafe( - R.id.action_global_albumFragment, - Bundle().apply { - putString("browseId", albumId) - }, - ) - dialog.dismiss() - } else { - Toast - .makeText( - requireContext(), - getString(R.string.no_album), - Toast.LENGTH_SHORT, - ).show() - } - } - btAddQueue.setOnClickListener { - sharedViewModel.addToQueue(song) - } - btPlayNext.setOnClickListener { - sharedViewModel.playNext(song) - } - ivThumbnail.load(song.thumbnails?.lastOrNull()?.url) - btRadio.setOnClickListener { - val args = Bundle() - args.putString("radioId", "RDAMVM${song.videoId}") - args.putString( - "videoId", - song.videoId, - ) - dialog.dismiss() - findNavController().navigateSafe( - R.id.action_global_playlistFragment, - args, - ) - } - btLike.setOnClickListener { - if (cbFavorite.isChecked) { - cbFavorite.isChecked = false - tvFavorite.text = getString(R.string.like) - viewModel.updateLikeStatus(song.videoId, 0) - } else { - cbFavorite.isChecked = true - tvFavorite.text = getString(R.string.liked) - viewModel.updateLikeStatus(song.videoId, 1) - } - } - btSeeArtists.setOnClickListener { - val subDialog = BottomSheetDialog(requireContext()) - val subBottomSheetView = - BottomSheetSeeArtistOfNowPlayingBinding.inflate(layoutInflater) - if (!song.artists.isNullOrEmpty()) { - val artistAdapter = SeeArtistOfNowPlayingAdapter(song.artists) - subBottomSheetView.rvArtists.apply { - adapter = artistAdapter - layoutManager = LinearLayoutManager(requireContext()) - } - artistAdapter.setOnClickListener( - object : - SeeArtistOfNowPlayingAdapter.OnItemClickListener { - override fun onItemClick(position: Int) { - val artist = song.artists[position] - if (artist.id != null) { - findNavController().navigateSafe( - R.id.action_global_artistFragment, - Bundle().apply { - putString("channelId", artist.id) - }, - ) - subDialog.dismiss() - dialog.dismiss() - } - } - }, - ) - } - - subDialog.setCancelable(true) - subDialog.setContentView(subBottomSheetView.root) - subDialog.show() - } - btDownload.visibility = View.GONE - btAddPlaylist.setOnClickListener { - viewModel.getLocalPlaylist() - val listLocalPlaylist: ArrayList = arrayListOf() - val addPlaylistDialog = BottomSheetDialog(requireContext()) - val viewAddPlaylist = - BottomSheetAddToAPlaylistBinding.inflate(layoutInflater) - val addToAPlaylistAdapter = AddToAPlaylistAdapter(arrayListOf()) - addToAPlaylistAdapter.setVideoId(song.videoId) - viewAddPlaylist.rvLocalPlaylists.apply { - adapter = addToAPlaylistAdapter - layoutManager = LinearLayoutManager(requireContext()) - } - viewModel.listLocalPlaylist.observe(viewLifecycleOwner) { list -> - Log.d("Check Local Playlist", list.toString()) - listLocalPlaylist.clear() - listLocalPlaylist.addAll(list) - addToAPlaylistAdapter.updateList(listLocalPlaylist) - } - addToAPlaylistAdapter.setOnItemClickListener( - object : - AddToAPlaylistAdapter.OnItemClickListener { - override fun onItemClick(position: Int) { - val playlist = listLocalPlaylist[position] - viewModel.updateInLibrary(song.videoId) - val tempTrack = ArrayList() - if (playlist.tracks != null) { - tempTrack.addAll(playlist.tracks) - } - if (!tempTrack.contains( - song.videoId, - ) && - playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && - playlist.youtubePlaylistId != null - ) { - viewModel.addToYouTubePlaylist( - playlist.id, - playlist.youtubePlaylistId, - song.videoId, - ) - } - if (!tempTrack.contains(song.videoId)) { - viewModel.insertPairSongLocalPlaylist( - PairSongLocalPlaylist( - playlistId = playlist.id, - songId = song.videoId, - position = playlist.tracks?.size ?: 0, - inPlaylist = LocalDateTime.now(), - ), - ) - tempTrack.add(song.videoId) - } - - viewModel.updateLocalPlaylistTracks( - tempTrack.removeConflicts(), - playlist.id, - ) - addPlaylistDialog.dismiss() - dialog.dismiss() - } - }, - ) - addPlaylistDialog.setContentView(viewAddPlaylist.root) - addPlaylistDialog.setCancelable(true) - addPlaylistDialog.show() - } - btShare.setOnClickListener { - val shareIntent = Intent(Intent.ACTION_SEND) - shareIntent.type = "text/plain" - val url = "https://youtube.com/watch?v=${song.videoId}" - shareIntent.putExtra(Intent.EXTRA_TEXT, url) - val chooserIntent = - Intent.createChooser(shareIntent, getString(R.string.share_url)) - startActivity(chooserIntent) - } - dialog.setCancelable(true) - dialog.setContentView(bottomSheetView.root) - dialog.show() - } - } - } - }, - ) - binding.topAppBarLayout.addOnOffsetChangedListener { it, verticalOffset -> - if (abs(it.totalScrollRange) == abs(verticalOffset)) { - binding.topAppBar.background = viewModel.gradientDrawable.value - binding.collapsingToolbarLayout.isTitleEnabled = true - if (viewModel.gradientDrawable.value != null) { - if (viewModel.gradientDrawable.value?.colors != null) { - setStatusBarsColor( - viewModel.gradientDrawable.value - ?.colors!! - .first(), - requireActivity(), - ) - } - } - } else { - binding.collapsingToolbarLayout.isTitleEnabled = false - binding.topAppBar.background = null - binding.topAppBarLayout.background = viewModel.gradientDrawable.value - setStatusBarsColor( - ContextCompat.getColor(requireContext(), R.color.colorPrimaryDark), - requireActivity(), - ) - } - } - binding.btShuffle.setOnClickListener { - if (viewModel.playlistBrowse.value != null) { - val shuffledList: ArrayList = arrayListOf() - viewModel.playlistBrowse.value?.tracks?.let { - shuffledList.addAll(it) - } - shuffledList.shuffle() - - val indexInQueue = 0 - val indexInPlaylist = viewModel.playlistBrowse.value?.tracks?.indexOf(shuffledList.first()) ?: 0 - - viewModel.setQueueData( - QueueData( - listTracks = shuffledList, - firstPlayedTrack = viewModel.playlistBrowse.value - ?.tracks - ?.get(indexInPlaylist), - playlistId = viewModel.playlistBrowse.value - ?.id - ?.replaceFirst("VL", "") ?: "", - playlistName = "Playlist \"${viewModel.playlistBrowse.value?.title}\"", - playlistType = PlaylistType.PLAYLIST, - continuation = null, - ), - ) - viewModel.playlistBrowse.value?.tracks?.get(indexInPlaylist)?.let { - viewModel.loadMediaItem( - it, - type = Config.PLAYLIST_CLICK, - index = indexInQueue - ) - } - } else if (viewModel.playlistEntity.value != null && viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED) { - val shuffledList: ArrayList = arrayListOf() - viewModel.listTrack.value - .toArrayListTrack() - .let { it1 -> shuffledList.addAll(it1) } - shuffledList.shuffle() - shuffledList.remove(shuffledList.first()) - - val indexInQueue = 0 - val indexInPlaylist = viewModel.listTrack.value.toArrayListTrack().indexOf(shuffledList.first()) - - viewModel.setQueueData( - QueueData( - listTracks = shuffledList, - firstPlayedTrack = viewModel.listTrack.value - .getOrNull(indexInPlaylist) - ?.toTrack(), - playlistId = viewModel.playlistEntity.value - ?.id - ?.replaceFirst("VL", "") ?: "", - playlistName = "Playlist \"${viewModel.playlistEntity.value?.title}\"", - playlistType = PlaylistType.PLAYLIST, - continuation = null, - ), - ) - viewModel.listTrack.value - .getOrNull(indexInPlaylist) - ?.let { - viewModel.loadMediaItem( - it.toTrack(), - type = Config.PLAYLIST_CLICK, - index = indexInQueue - ) - } - } else { - Snackbar - .make( - requireView(), - getString(R.string.playlist_is_empty), - Snackbar.LENGTH_SHORT, - ).show() - } - } - binding.btDownload.setOnClickListener { - if (id != null) { - if (viewModel.playlistDownloadState.value == DownloadState.STATE_NOT_DOWNLOADED) { -// if (!viewModel.prevPlaylistDownloading.value){ -// viewModel.downloading() - if (viewModel.playlistBrowse.value - ?.tracks - ?.size != viewModel.listTrack.value.size && - viewModel.listTrack.value.isNotEmpty() - ) { - for (i in viewModel.playlistBrowse.value?.tracks!!) { - viewModel.insertSong(i.toSongEntity()) - } - runBlocking { - delay(1000) - viewModel.listJob.emit(arrayListOf()) - } - viewModel.getListTrack( - viewModel.playlistBrowse.value - ?.tracks - ?.toListVideoId(), - ) - } - viewModel.updatePlaylistDownloadState( - id, - DownloadState.STATE_PREPARING, - ) -// } -// else{ -// Toast.makeText(requireContext(), getString(R.string.please_wait_before_playlist_downloaded), Toast.LENGTH_SHORT).show() -// } - } else if (viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED) { - Toast - .makeText(requireContext(), getString(R.string.downloaded), Toast.LENGTH_SHORT) - .show() - } else if (viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADING) { - Toast - .makeText( - requireContext(), - getString(R.string.downloading), - Toast.LENGTH_SHORT, - ).show() - } - } else { - Log.d("binding.btDownload.setOnClickListener", "id was null") - } - } - collectUIState() - collectPlaylist() - collectListTrack() - lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - val job2 = - launch { - combine( - sharedViewModel.nowPlayingState.distinctUntilChangedBy { - it?.songEntity?.videoId - }, - sharedViewModel.controllerState.map { it.isPlaying }.distinctUntilChanged(), - ) { nowPlaying, isPlaying -> - Pair(nowPlaying, isPlaying) - }.collect { - if (it.first != null && it.second) { - playlistItemAdapter.setNowPlaying(it.first?.songEntity?.videoId) - } else { - playlistItemAdapter.setNowPlaying(null) - } - } - } - val job3 = - launch { - combine(viewModel.playlistDownloadState, viewModel.isRadio) { downloadState, isRadio -> - Pair(downloadState, isRadio) - }.collectLatest { pair -> - val playlistDownloadState = pair.first - val isRadio = pair.second - if (!isRadio) { - when (playlistDownloadState) { - DownloadState.STATE_PREPARING -> { - binding.btDownload.visibility = View.GONE - binding.animationDownloading.visibility = View.VISIBLE - } - - DownloadState.STATE_DOWNLOADING -> { - binding.btDownload.visibility = View.GONE - binding.animationDownloading.visibility = View.VISIBLE - } - - DownloadState.STATE_DOWNLOADED -> { - binding.btDownload.visibility = View.VISIBLE - binding.animationDownloading.visibility = View.GONE - binding.btDownload.setImageResource(R.drawable.baseline_downloaded) - } - - DownloadState.STATE_NOT_DOWNLOADED -> { - binding.btDownload.visibility = View.VISIBLE - binding.animationDownloading.visibility = View.GONE - binding.btDownload.setImageResource(R.drawable.download_button) - } - } - if (viewModel.isRadio.value) { - binding.btDownload.visibility = View.GONE - binding.animationDownloading.visibility = View.GONE - } - } else { - binding.btDownload.visibility = View.GONE - binding.animationDownloading.visibility = View.GONE - } - } - } - job2.join() - job3.join() - } - } - } - -// private fun fetchDataFromViewModel() { -// val response = viewModel.playlistBrowse.value -// when (response) { -// is Resource.Success -> { -// response.data.let { -// with(binding) { -// if (it?.id?.startsWith("RDEM") == true || it?.id?.startsWith("RDAMVM") == true) { -// btDownload.visibility = View.GONE -// } -// collapsingToolbarLayout.title = it?.title -// tvTitle.text = it?.title -// tvTitle.isSelected = true -// tvPlaylistAuthor.text = it?.author?.name -// if (it?.year != "") { -// tvYearAndCategory.text = -// requireContext().getString( -// R.string.year_and_category, -// it?.year.toString(), -// "Playlist", -// ) -// } else { -// tvYearAndCategory.text = requireContext().getString(R.string.playlist) -// } -// tvTrackCountAndDuration.text = -// requireContext().getString( -// R.string.album_length, -// it?.trackCount.toString(), -// "", -// ) -// if (it?.description != null && it.description != "") { -// tvDescription.originalText = it.description -// } else { -// tvDescription.originalText = getString(R.string.no_description) -// } -// loadImage(it?.thumbnails?.last()?.url) -// val list: ArrayList = arrayListOf() -// list.addAll(it?.tracks as ArrayList) -// playlistItemAdapter.updateList(list) -// if (viewModel.gradientDrawable.value == null) { -// viewModel.gradientDrawable.observe(viewLifecycleOwner) { gradient -> -// // fullRootLayout.background = gradient -// // toolbarBackground = gradient?.colors?.get(0) -// if (gradient != null) { -// val start = -// topAppBarLayout.background ?: ColorDrawable( -// Color.TRANSPARENT, -// ) -// val transition = -// TransitionDrawable(arrayOf(start, gradient)) -// topAppBarLayout.background = transition -// transition.isCrossFadeEnabled = true -// transition.startTransition(500) -// } -// } -// } else { -// // fullRootLayout.background = gradientDrawable -// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) -// topAppBarLayout.background = gradientDrawable -// } -// binding.rootLayout.visibility = View.VISIBLE -// binding.loadingLayout.visibility = View.GONE -// if (viewModel.isRadio.value == false) { -// val playlistEntity = viewModel.playlistEntity.value -// if (playlistEntity != null) { -// viewModel.checkAllSongDownloaded(it.tracks as ArrayList) -// viewModel.playlistEntity.observe(viewLifecycleOwner) { playlistEntity2 -> -// if (playlistEntity2 != null) { -// when (playlistEntity2.downloadState) { -// DownloadState.STATE_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.baseline_downloaded) -// } -// -// DownloadState.STATE_DOWNLOADING -> { -// btDownload.visibility = View.GONE -// animationDownloading.visibility = View.VISIBLE -// } -// -// DownloadState.STATE_NOT_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.download_button) -// } -// } -// } -// } -// } -// } else { -// btDownload.visibility = View.GONE -// cbLove.visibility = View.GONE -// } -// } -// } -// } -// -// is Resource.Error -> { -// Snackbar -// .make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG) -// .show() -// findNavController().popBackStack() -// } -// -// else -> {} -// } -// } - - private fun collectUIState() { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - val uiStateJob = - launch { - viewModel.uiState.collectLatest { state -> - when (state) { - is PlaylistUIState.Loading -> { - binding.rootLayout.visibility = View.GONE - binding.loadingLayout.visibility = View.VISIBLE - } - - is PlaylistUIState.Error -> { - Snackbar.make(binding.root, state.message ?: getString(R.string.error), Snackbar.LENGTH_LONG).show() - } - - is PlaylistUIState.Success -> { - binding.rootLayout.visibility = View.VISIBLE - binding.loadingLayout.visibility = View.GONE - } - } - } - } - val bgJob = - launch { - viewModel.gradientDrawable.collectLatest { gd -> - if (gd != null) { - with(binding) { - val start = - topAppBarLayout.background ?: ColorDrawable( - Color.TRANSPARENT, - ) - val transition = - TransitionDrawable(arrayOf(start, gd)) - transition.setDither(true) - topAppBarLayout.background = transition - transition.isCrossFadeEnabled = true - transition.startTransition(500) - } - } - } - } - uiStateJob.join() - bgJob.join() - } - } - } - - @UnstableApi - private fun collectListTrack() { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - val job1 = - launch { - combine( - viewModel.listTrack, - viewModel.playlistDownloadState, - ) { listTrack, downloadState -> - Pair(listTrack, downloadState) - }.collectLatest { pair -> - val listTrack = pair.first - val downloadState = pair.second - val tempList = arrayListOf() - for (i in listTrack) { - tempList.add(i) - } - listTrack.let { - viewModel.checkAllSongDownloaded(it.toArrayListTrack()) - } - if (listTrack.isNotEmpty() && downloadState == DownloadState.STATE_PREPARING) { - val listJob: ArrayList = arrayListOf() - for (song in listTrack) { - if (song.downloadState == DownloadState.STATE_NOT_DOWNLOADED) { - listJob.add(song) - } - } - viewModel.listJob.value = listJob - listJob.forEach { job -> - val downloadRequest = - DownloadRequest - .Builder(job.videoId, job.videoId.toUri()) - .setData(job.title.toByteArray()) - .setCustomCacheKey(job.videoId) - .build() - viewModel.updateDownloadState( - job.videoId, - DownloadState.STATE_DOWNLOADING, - ) - DownloadService.sendAddDownload( - requireContext(), - MusicDownloadService::class.java, - downloadRequest, - false, - ) - } - Log.d("PlaylistFragment", "ListJob: ${viewModel.listJob.value}") - viewModel.updatePlaylistDownloadState( - viewModel.id.value!!, - DownloadState.STATE_DOWNLOADING, - ) - } - } - } - val job2 = - launch { - combine(viewModel.downloadedList, viewModel.listTrack) { downloadedList, listTrack -> - Pair(downloadedList, listTrack) - }.collectLatest { pair -> - val list = pair.second - val downloadList = pair.first - val temp = - list.map { it.videoId }.toMutableSet().apply { - removeAll(downloadList.toSet()) - } - Log.w(tag, "DownloadList: $downloadList") - Log.w(tag, "Downloading and not download: $temp") - Log.w(tag, "DownloadList size: ${downloadList.size}") - Log.w(tag, "Downloading and not download size: ${temp.size}") - Log.w(tag, "List size: ${list.size}") - playlistItemAdapter.setDownloadedList(downloadList) - val viewModelId = viewModel.id.value.toString() - if (list.isNotEmpty()) { - if (downloadList.containsAll( - list.map { - it.videoId - }, - ) && - downloadList.isNotEmpty() - ) { - viewModel.updatePlaylistDownloadState( - viewModelId, - DownloadState.STATE_DOWNLOADED, - ) - Log.w(tag, "All downloaded") - } else if (viewModel.downloadUtils.downloadingVideoIds.value - .containsAll(temp) && - temp.isNotEmpty() - ) { - viewModel.updatePlaylistDownloadState( - viewModelId, - DownloadState.STATE_DOWNLOADING, - ) - Log.w(tag, "Downloading") - } else { - viewModel.updatePlaylistDownloadState( - viewModelId, - DownloadState.STATE_NOT_DOWNLOADED, - ) - Log.w(tag, "Not downloaded") - } - } else { - viewModel.updatePlaylistDownloadState( - viewModelId, - DownloadState.STATE_NOT_DOWNLOADED, - ) - Log.w(tag, "Not downloaded") - } - } - - } - job1.join() - job2.join() - } - } - } - - private fun collectPlaylist() { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - val job1 = - launch { - combine(viewModel.playlistBrowse, viewModel.playlistEntity) { playlistBrowse, playlistEntity -> - Pair(playlistBrowse, playlistEntity) - }.collectLatest { pair -> - val playlistBrowse = pair.first - val playlistEntity = pair.second - if (playlistBrowse != null && playlistEntity != null) { - viewModel.checkAllSongDownloaded(playlistBrowse.tracks as ArrayList) - with(binding) { - if (playlistBrowse.id.startsWith("RDEM") || playlistBrowse.id.startsWith("RDAMVM")) { - btDownload.visibility = View.GONE - cbLove.visibility = View.GONE - } - collapsingToolbarLayout.title = playlistBrowse.title - tvTitle.text = playlistBrowse.title - tvTitle.isSelected = true - tvPlaylistAuthor.text = playlistBrowse.author.name - if (playlistBrowse.year != "") { - tvYearAndCategory.text = - requireContext().getString( - R.string.year_and_category, - playlistBrowse.year, - "Playlist", - ) - } else { - tvYearAndCategory.text = - requireContext().getString(R.string.playlist) - } - tvTrackCountAndDuration.text = - requireContext().getString( - R.string.album_length, - playlistBrowse.trackCount.toString(), - "", - ) - if (playlistBrowse.description != null && playlistBrowse.description != "") { - tvDescription.originalText = playlistBrowse.description - } else { - tvDescription.originalText = getString(R.string.no_description) - } - loadImage(playlistBrowse.thumbnails.lastOrNull()?.url) - val list: ArrayList = arrayListOf() - list.addAll(playlistBrowse.tracks) - playlistItemAdapter.updateList(list) - } - } else if (playlistBrowse == null && playlistEntity != null) { - with(binding) { - collapsingToolbarLayout.title = playlistEntity.title - tvTitle.text = playlistEntity.title - tvTitle.isSelected = true - tvPlaylistAuthor.text = playlistEntity.author - tvYearAndCategory.text = - requireContext().getString( - R.string.year_and_category, - playlistEntity.year.toString(), - "Playlist", - ) - tvTrackCountAndDuration.text = - requireContext().getString( - R.string.album_length, - playlistEntity.trackCount.toString(), - "", - ) - if (playlistEntity.description != "") { - tvDescription.originalText = playlistEntity.description - } else { - tvDescription.originalText = getString(R.string.no_description) - } - loadImage(playlistEntity.thumbnails) - } - } - } - } - val job2 = - launch { - combine(viewModel.playlistBrowse, viewModel.listTrack) { playlistBrowse, listTrack -> - Pair(playlistBrowse, listTrack) - }.collectLatest { pair -> - val playlistBrowse = pair.first - val listTrack = pair.second - if (playlistBrowse == null && listTrack.isNotEmpty()) { - val tempList = arrayListOf() - for (i in listTrack) { - tempList.add(i) - } - listTrack.let { - viewModel.checkAllSongDownloaded(it.toArrayListTrack()) - } - playlistItemAdapter.updateList(tempList) - } - } - } - job1.join() - job2.join() - } - } - } - - private fun fetchRDATRadio(radioId: String) { - viewModel.clearPlaylistBrowse() - viewModel.clearPlaylistEntity() - viewModel.updateId(radioId) - viewModel.getRadio(radioId) - } - - private fun fetchDataWithRadio( - radioId: String, - videoId: String? = null, - channelId: String? = null, - ) { - viewModel.clearPlaylistBrowse() - viewModel.clearPlaylistEntity() - viewModel.updateId(radioId) - viewModel.getRadio(radioId, videoId, channelId) -// viewModel.playlistBrowse.observe(viewLifecycleOwner) { response -> -// when (response) { -// is Resource.Success -> { -// response.data.let { -// with(binding) { -// if (it != null) { -// viewModel.insertRadioPlaylist(it.toPlaylistEntity()) -// } -// collapsingToolbarLayout.title = it?.title -// tvTitle.text = it?.title -// tvTitle.isSelected = true -// tvPlaylistAuthor.text = it?.author?.name -// if (it?.year != "") { -// tvYearAndCategory.text = -// requireContext().getString( -// R.string.year_and_category, -// it?.year.toString(), -// "Playlist", -// ) -// } else { -// tvYearAndCategory.text = -// requireContext().getString(R.string.playlist) -// } -// tvTrackCountAndDuration.text = -// requireContext().getString( -// R.string.album_length, -// it?.trackCount.toString(), -// "", -// ) -// if (it?.description != null && it.description != "") { -// tvDescription.originalText = it.description -// } else { -// tvDescription.originalText = getString(R.string.no_description) -// } -// loadImage(it?.thumbnails?.last()?.url) -// val list: ArrayList = arrayListOf() -// list.addAll(it?.tracks as ArrayList) -// playlistItemAdapter.updateList(list) -// if (viewModel.gradientDrawable.value == null) { -// viewModel.gradientDrawable.observe(viewLifecycleOwner) { gradient -> -// // fullRootLayout.background = gradient -// // toolbarBackground = gradient?.colors?.get(0) -// if (gradient != null) { -// val start = -// topAppBarLayout.background ?: ColorDrawable( -// Color.TRANSPARENT, -// ) -// val transition = -// TransitionDrawable(arrayOf(start, gradient)) -// transition.setDither(true) -// topAppBarLayout.background = transition -// transition.isCrossFadeEnabled = true -// transition.startTransition(500) -// } -// } -// } else { -// // fullRootLayout.background = gradientDrawable -// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) -// topAppBarLayout.background = -// gradientDrawable?.apply { -// setDither(true) -// } -// } -// binding.rootLayout.visibility = View.VISIBLE -// binding.loadingLayout.visibility = View.GONE -// btDownload.visibility = View.GONE -// cbLove.visibility = View.GONE -// } -// } -// } -// -// is Resource.Error -> { -// Snackbar -// .make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG) -// .show() -// findNavController().popBackStack() -// } -// -// else -> {} -// } -// } - } - - private fun fetchData( - id: String, - downloaded: Int = 0, - ) { - viewModel.updateId(id) - if (downloaded == 0) { - viewModel.clearPlaylistBrowse() - viewModel.clearPlaylistEntity() - viewModel.browsePlaylist(id) -// viewModel.playlistBrowse.observe(viewLifecycleOwner) { response -> -// when (response) { -// is Resource.Success -> { -// response.data.let { -// with(binding) { -// if (it != null) { -// viewModel.insertPlaylist(it.toPlaylistEntity()) -// } -// collapsingToolbarLayout.title = it?.title -// tvTitle.text = it?.title -// tvTitle.isSelected = true -// tvPlaylistAuthor.text = it?.author?.name -// if (it?.year != "") { -// tvYearAndCategory.text = -// requireContext().getString( -// R.string.year_and_category, -// it?.year.toString(), -// "Playlist", -// ) -// } else { -// tvYearAndCategory.text = -// requireContext().getString(R.string.playlist) -// } -// tvTrackCountAndDuration.text = -// requireContext().getString( -// R.string.album_length, -// it?.trackCount.toString(), -// "", -// ) -// if (it?.description != null && it.description != "") { -// tvDescription.originalText = it.description -// } else { -// tvDescription.originalText = getString(R.string.no_description) -// } -// loadImage(it?.thumbnails?.last()?.url) -// val list: ArrayList = arrayListOf() -// list.addAll(it?.tracks as ArrayList) -// playlistItemAdapter.updateList(list) -// if (viewModel.gradientDrawable.value == null) { -// viewModel.gradientDrawable.observe(viewLifecycleOwner) { gradient -> -// // fullRootLayout.background = gradient -// // toolbarBackground = gradient?.colors?.get(0) -// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) -// if (gradient != null) { -// val start = -// topAppBarLayout.background ?: ColorDrawable( -// Color.TRANSPARENT, -// ) -// val transition = -// TransitionDrawable(arrayOf(start, gradient)) -// transition.setDither(true) -// topAppBarLayout.background = transition -// transition.isCrossFadeEnabled = true -// transition.startTransition(500) -// } -// } -// } else { -// // fullRootLayout.background = gradientDrawable -// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) -// topAppBarLayout.background = -// gradientDrawable?.apply { -// setDither(true) -// } -// } -// binding.rootLayout.visibility = View.VISIBLE -// binding.loadingLayout.visibility = View.GONE -// viewModel.playlistEntity.observe(viewLifecycleOwner) { playlistEntity2 -> -// if (playlistEntity2 != null) { -// when (playlistEntity2.downloadState) { -// DownloadState.STATE_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.baseline_downloaded) -// } -// -// DownloadState.STATE_DOWNLOADING -> { -// btDownload.visibility = View.GONE -// animationDownloading.visibility = View.VISIBLE -// } -// -// DownloadState.STATE_NOT_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.download_button) -// } -// } -// } -// } -// } -// } -// } -// -// is Resource.Error -> { -// Snackbar -// .make( -// binding.root, -// response.message.toString(), -// Snackbar.LENGTH_LONG, -// ).show() -// findNavController().popBackStack() -// } -// -// else -> {} -// } -// } - } else if (downloaded == 1) { - viewModel.clearPlaylistBrowse() - viewModel.clearPlaylistEntity() - viewModel.getPlaylist(id, null, null) -// with(binding) { -// viewModel.playlistEntity.observe(viewLifecycleOwner) { playlistEntity -> -// if (playlistEntity != null) { -// when (playlistEntity.downloadState) { -// DownloadState.STATE_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.baseline_downloaded) -// } -// -// DownloadState.STATE_DOWNLOADING -> { -// btDownload.visibility = View.GONE -// animationDownloading.visibility = View.VISIBLE -// } -// -// DownloadState.STATE_NOT_DOWNLOADED -> { -// btDownload.visibility = View.VISIBLE -// animationDownloading.visibility = View.GONE -// btDownload.setImageResource(R.drawable.download_button) -// } -// } -// collapsingToolbarLayout.title = playlistEntity.title -// tvTitle.text = playlistEntity.title -// tvTitle.isSelected = true -// tvPlaylistAuthor.text = playlistEntity.author -// tvYearAndCategory.text = -// requireContext().getString( -// R.string.year_and_category, -// playlistEntity.year.toString(), -// "Playlist", -// ) -// tvTrackCountAndDuration.text = -// requireContext().getString( -// R.string.album_length, -// playlistEntity.trackCount.toString(), -// "", -// ) -// if (playlistEntity.description != "") { -// tvDescription.originalText = playlistEntity.description -// } else { -// tvDescription.originalText = getString(R.string.no_description) -// } -// loadImage(playlistEntity.thumbnails) -// if (viewModel.gradientDrawable.value == null) { -// viewModel.gradientDrawable.observe(viewLifecycleOwner) { gradient -> -// // fullRootLayout.background = gradient -// // toolbarBackground = gradient?.colors?.get(0) -// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) -// if (gradient != null) { -// val start = -// topAppBarLayout.background -// ?: ColorDrawable(Color.TRANSPARENT) -// val transition = TransitionDrawable(arrayOf(start, gradient)) -// transition.setDither(true) -// topAppBarLayout.background = transition -// transition.isCrossFadeEnabled = true -// transition.startTransition(500) -// } -// } -// } else { -// // fullRootLayout.background = gradientDrawable -// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) -// topAppBarLayout.background = -// gradientDrawable?.apply { -// setDither(true) -// } -// } -// viewModel.getListTrack(playlistEntity.tracks) -// viewModel.listTrack.observe(viewLifecycleOwner) { listTrack -> -// val tempList = arrayListOf() -// for (i in listTrack) { -// tempList.add(i) -// } -// playlistItemAdapter.updateList(tempList) -// } -// } -// } -// } - } - } - - private fun loadImage(url: String?) { - if (url != null) { - binding.ivPlaylistArt.visibility = View.VISIBLE - binding.ivPlaylistArt.background = ColorDrawable(Color.WHITE) - binding.ivPlaylistArt.load(url) { - allowHardware(false) - placeholder(R.drawable.holder) - diskCachePolicy(CachePolicy.ENABLED) - crossfade(true) - crossfade(300) - listener( - onError = { _, er -> - Log.w(tag, "Load Image Error ${er.throwable.message}") - }, - onSuccess = { _, result -> - binding.ivPlaylistArt.setImageDrawable(result.image.asDrawable(resources)) - val p = Palette.from(result.image.toBitmap()).generate() - val defaultColor = 0x000000 - var startColor = p.getDarkVibrantColor(defaultColor) - if (startColor == defaultColor) { - startColor = p.getDarkMutedColor(defaultColor) - if (startColor == defaultColor) { - startColor = p.getVibrantColor(defaultColor) - if (startColor == defaultColor) { - startColor = p.getMutedColor(defaultColor) - if (startColor == defaultColor) { - startColor = p.getLightVibrantColor(defaultColor) - if (startColor == defaultColor) { - startColor = p.getLightMutedColor(defaultColor) - } - } - } - } - } - startColor = ColorUtils.setAlphaComponent(startColor, 150) - val endColor = - resources.getColor(R.color.md_theme_dark_background, null) - val gd = - GradientDrawable( - GradientDrawable.Orientation.TOP_BOTTOM, - intArrayOf(startColor, endColor), - ) - gd.cornerRadius = 0f - gd.gradientType = GradientDrawable.LINEAR_GRADIENT - gd.gradientRadius = 0.5f - viewModel.setGradientDrawable(gd) - }, - ) - } - } - } +package com.maxrave.simpmusic.ui.fragment.other + +import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.TransitionDrawable +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.core.content.ContextCompat +import androidx.core.graphics.ColorUtils +import androidx.core.net.toUri +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.offline.DownloadRequest +import androidx.media3.exoplayer.offline.DownloadService +import androidx.navigation.fragment.findNavController +import androidx.palette.graphics.Palette +import androidx.recyclerview.widget.LinearLayoutManager +import coil3.asDrawable +import coil3.load +import coil3.request.CachePolicy +import coil3.request.allowHardware +import coil3.request.crossfade +import coil3.request.placeholder +import coil3.toBitmap +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.snackbar.Snackbar +import com.maxrave.kotlinytmusicscraper.extension.verifyYouTubePlaylistId +import com.maxrave.simpmusic.R +import com.maxrave.simpmusic.adapter.artist.SeeArtistOfNowPlayingAdapter +import com.maxrave.simpmusic.adapter.playlist.AddToAPlaylistAdapter +import com.maxrave.simpmusic.adapter.playlist.PlaylistItemAdapter +import com.maxrave.simpmusic.common.Config +import com.maxrave.simpmusic.common.DownloadState +import com.maxrave.simpmusic.data.db.entities.LocalPlaylistEntity +import com.maxrave.simpmusic.data.db.entities.PairSongLocalPlaylist +import com.maxrave.simpmusic.data.db.entities.SongEntity +import com.maxrave.simpmusic.data.model.browse.album.Track +import com.maxrave.simpmusic.databinding.BottomSheetAddToAPlaylistBinding +import com.maxrave.simpmusic.databinding.BottomSheetNowPlayingBinding +import com.maxrave.simpmusic.databinding.BottomSheetPlaylistMoreBinding +import com.maxrave.simpmusic.databinding.BottomSheetSeeArtistOfNowPlayingBinding +import com.maxrave.simpmusic.databinding.FragmentPlaylistBinding +import com.maxrave.simpmusic.extension.connectArtists +import com.maxrave.simpmusic.extension.navigateSafe +import com.maxrave.simpmusic.extension.removeConflicts +import com.maxrave.simpmusic.extension.setEnabledAll +import com.maxrave.simpmusic.extension.setStatusBarsColor +import com.maxrave.simpmusic.extension.toArrayListTrack +import com.maxrave.simpmusic.extension.toListName +import com.maxrave.simpmusic.extension.toListVideoId +import com.maxrave.simpmusic.extension.toSongEntity +import com.maxrave.simpmusic.extension.toTrack +import com.maxrave.simpmusic.service.PlaylistType +import com.maxrave.simpmusic.service.QueueData +import com.maxrave.simpmusic.service.test.download.MusicDownloadService +import com.maxrave.simpmusic.viewModel.PlaylistUIState +import com.maxrave.simpmusic.viewModel.PlaylistViewModel +import com.maxrave.simpmusic.viewModel.SharedViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import java.time.LocalDateTime +import kotlin.math.abs + +@UnstableApi +class PlaylistFragment : Fragment() { + private val tag = "PlaylistFragment" + + private val viewModel by activityViewModels() + private val sharedViewModel by activityViewModels() + private var _binding: FragmentPlaylistBinding? = null + val binding get() = _binding!! + + private var gradientDrawable: GradientDrawable? = null + private var toolbarBackground: Int? = null + + private lateinit var playlistItemAdapter: PlaylistItemAdapter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentPlaylistBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + requireArguments().clear() + setStatusBarsColor( + ContextCompat.getColor(requireContext(), R.color.colorPrimaryDark), + requireActivity(), + ) + _binding = null + } + + @UnstableApi + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + viewModel.getLocation() + lifecycleScope.launch { + viewModel.liked.collect { liked -> + binding.cbLove.isChecked = liked + } + } + if (viewModel.gradientDrawable.value != null) { + gradientDrawable = viewModel.gradientDrawable.value + toolbarBackground = gradientDrawable?.colors?.get(0) + } + + playlistItemAdapter = PlaylistItemAdapter(arrayListOf()) + binding.rvListSong.apply { + adapter = playlistItemAdapter + layoutManager = + LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) + setHasFixedSize(false) + } + val id = requireArguments().getString("id") + val downloaded = arguments?.getInt("downloaded") + val radioId = arguments?.getString("radioId") + val channelId = arguments?.getString("channelId") + Log.w(tag, "radioId: $radioId") + val videoId = arguments?.getString("videoId") + // 3 truong hop: + /* + 1. La Playlist + 2. La Radio + 3. Downloaded Playlist + 4. Luc lai tu ViewModel + */ + if (id != null || radioId != null) { + Log.w(tag, "id: $id") + Log.w(tag, "radioId: $radioId") + if (radioId != null || + id?.startsWith("RDEM") == true || + id?.startsWith("RDAMVM") == true + ) { + viewModel.updateIsRadio(true) + if (radioId != null) { + fetchDataWithRadio(radioId, videoId, channelId) + } else if (id != null) { + fetchDataWithRadio(id) + } + } else if (id != null && id.startsWith("RDAT")) { + viewModel.updateIsRadio(true) + fetchRDATRadio(id) + } else if (id != null) { + viewModel.updateIsRadio(false) + if (!requireArguments().getBoolean("youtube")) { + viewModel.checkSuccess() + } + if (downloaded == null || downloaded == 0) { + fetchData(id) + } + if (downloaded == 1) { + fetchData(id, downloaded = 1) + } + } + } +// if (id == null && radioId == null || id == viewModel.id.value && radioId == null || id == null && radioId == viewModel.id.value) { +// id = viewModel.id.value +// if (id?.startsWith("RDEM") == true || id?.startsWith("RDAMVM") == true || id?.startsWith("RDAT") == true) { +// viewModel.updateIsRadio(true) +// } else { +// viewModel.updateIsRadio(false) +// } +// if (!requireArguments().getBoolean("youtube")) { +// viewModel.checkSuccess() +// } else { +// if (id != null) { +// if (downloaded == null || downloaded == 0) { +// fetchData(id) +// } +// if (downloaded == 1) { +// fetchData(id, downloaded = 1) +// } +// } +// } +// } else if (radioId != null && id == null) { +// viewModel.clearPlaylistBrowse() +// viewModel.updateIsRadio(true) +// if (videoId != null) { +// fetchDataWithRadio(radioId, videoId) +// } else if (channelId != null) { +// fetchDataWithRadio(radioId, null, channelId) +// } +// } else if (id != null && id.startsWith("RDEM") || id != null && id.startsWith("RDAMVM") || id?.startsWith("RDAT") == true) { +// viewModel.clearPlaylistEntity() +// viewModel.clearPlaylistBrowse() +// fetchData(id) +// } else if (id != null) { +// viewModel.updateId(id) +// if (downloaded == null || downloaded == 0) { +// fetchData(id) +// } +// if (downloaded == 1) { +// fetchData(id, downloaded = 1) +// } +// } + + binding.topAppBar.setNavigationOnClickListener { + findNavController().popBackStack() + } + binding.cbLove.setOnCheckedChangeListener { _, isChecked -> + if (!isChecked) { + viewModel.playlistEntity.value?.let { playlist -> + viewModel.updatePlaylistLiked( + false, + playlist.id, + ) + } + } else { + viewModel.playlistEntity.value?.let { playlist -> + viewModel.updatePlaylistLiked( + true, + playlist.id, + ) + } + } + } + binding.btMore.setOnClickListener { + val bottomSheetDialog = BottomSheetDialog(requireContext()) + val moreView = BottomSheetPlaylistMoreBinding.inflate(layoutInflater) + if (viewModel.isRadio.value == false) { + moreView.ivThumbnail.load(viewModel.playlistEntity.value?.thumbnails) + moreView.tvSongTitle.text = viewModel.playlistEntity.value?.title + moreView.tvSongArtist.text = viewModel.playlistEntity.value?.author + moreView.btShare.setOnClickListener { + val shareIntent = Intent(Intent.ACTION_SEND) + shareIntent.type = "text/plain" + val url = "https://youtube.com/playlist?list=${ + viewModel.playlistEntity.value?.id?.replaceFirst( + "VL", + "", + ) + }" + shareIntent.putExtra(Intent.EXTRA_TEXT, url) + val chooserIntent = + Intent.createChooser(shareIntent, getString(R.string.share_url)) + startActivity(chooserIntent) + } + } else { + moreView.ivThumbnail.load( + viewModel.playlistBrowse.value + ?.thumbnails + ?.lastOrNull() + ?.url, + ) + moreView.tvSongTitle.text = + viewModel.playlistBrowse.value + ?.title + moreView.tvSongArtist.text = + viewModel.playlistBrowse.value + ?.author + ?.name + moreView.btShare.setOnClickListener { + val shareIntent = Intent(Intent.ACTION_SEND) + shareIntent.type = "text/plain" + val url = "https://youtube.com/playlist?list=${ + viewModel.playlistBrowse.value?.id?.replaceFirst( + "VL", + "", + ) + }" + shareIntent.putExtra(Intent.EXTRA_TEXT, url) + val chooserIntent = + Intent.createChooser(shareIntent, getString(R.string.share_url)) + startActivity(chooserIntent) + } + } + if (requireArguments().getBoolean("youtube")) { + Log.w(tag, "id check: $id") + moreView.btSync.visibility = View.VISIBLE + viewModel.checkSyncedPlaylist(id) + lifecycleScope.launch { + viewModel.localPlaylistIfYouTubePlaylist.collectLatest { ytPlaylist -> + Log.w(tag, "ytPlaylist: ${ytPlaylist?.youtubePlaylistId}") + Log.w(tag, "id: $id") + if (ytPlaylist != null) { + val tempId = ytPlaylist.youtubePlaylistId?.verifyYouTubePlaylistId() + if (tempId == id?.verifyYouTubePlaylistId()) { + moreView.tvSync.text = + context?.getString(R.string.saved_to_local_playlist) + setEnabledAll(moreView.btSync, false) + } + } else { + moreView.tvSync.text = + context?.getString(R.string.save_to_local_playlist) + } + } + } + moreView.btSync.setOnClickListener { + if (moreView.tvSync.text == context?.getString(R.string.save_to_local_playlist)) { + val playlist = viewModel.playlistBrowse.value + if (playlist != null) { + viewModel.insertLocalPlaylist(playlist) + moreView.tvSync.text = + context?.getString(R.string.saved_to_local_playlist) + } + } else { + Snackbar + .make( + requireView(), + getString(R.string.if_you_want_to_unsync_this_playlist_please_go_to_local_playlist), + Snackbar.LENGTH_SHORT, + ).show() + } + } + } else { + moreView.btSync.visibility = View.GONE + } + + moreView.btAddToQueue.setOnClickListener { + if (viewModel.playlistBrowse.value != null) { + val list = + viewModel.playlistBrowse.value + ?.tracks as ArrayList + sharedViewModel.addListToQueue(list) + } else if (viewModel.playlistEntity.value != null && + viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED + ) { + val list = viewModel.listTrack.value.toArrayListTrack() + sharedViewModel.addListToQueue(list) + } else { + Snackbar + .make( + requireView(), + getString(R.string.playlist_is_empty), + Snackbar.LENGTH_SHORT, + ).show() + } + } + + bottomSheetDialog.setContentView(moreView.root) + bottomSheetDialog.setCancelable(true) + bottomSheetDialog.show() + } + + binding.btPlayPause.setOnClickListener { + if (!viewModel.isRadio.value) { + if (viewModel.playlistBrowse.value != null) { + viewModel.setQueueData( + QueueData( + listTracks = (viewModel.playlistBrowse.value?.tracks ?: arrayListOf()) as ArrayList, + firstPlayedTrack = + viewModel.playlistBrowse.value + ?.tracks + ?.get(0), + playlistId = + viewModel.playlistBrowse.value + ?.id + ?.replaceFirst("VL", "") ?: "", + playlistName = "Playlist \"${viewModel.playlistBrowse.value?.title}\"", + playlistType = PlaylistType.PLAYLIST, + continuation = null, + ), + ) + viewModel.playlistBrowse.value + ?.tracks + ?.get(0) + ?.let { + viewModel.loadMediaItem( + it, + type = Config.PLAYLIST_CLICK, + index = 0, + ) + } + } else if (viewModel.playlistEntity.value != null && + viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED + ) { + viewModel.setQueueData( + QueueData( + listTracks = viewModel.listTrack.value.toArrayListTrack(), + firstPlayedTrack = + viewModel.listTrack.value + .firstOrNull() + ?.toTrack(), + playlistId = + viewModel.playlistEntity.value + ?.id + ?.replaceFirst("VL", "") ?: "", + playlistName = "Playlist \"${viewModel.playlistEntity.value?.title}\"", + playlistType = PlaylistType.PLAYLIST, + continuation = null, + ), + ) + viewModel.listTrack.value.firstOrNull()?.let { + viewModel.loadMediaItem( + it.toTrack(), + type = Config.PLAYLIST_CLICK, + index = 0, + ) + } + } else { + Snackbar + .make( + requireView(), + getString(R.string.playlist_is_empty), + Snackbar.LENGTH_SHORT, + ).show() + } + } else { + if (viewModel.playlistBrowse.value != null) { + viewModel.setQueueData( + QueueData( + listTracks = (viewModel.playlistBrowse.value?.tracks ?: arrayListOf()) as ArrayList, + firstPlayedTrack = + viewModel.playlistBrowse.value + ?.tracks + ?.get(0), + playlistId = + viewModel.playlistBrowse.value + ?.id + ?.replaceFirst("VL", "") ?: "", + playlistName = "${viewModel.playlistBrowse.value?.title}", + playlistType = PlaylistType.RADIO, + continuation = + viewModel.radioContinuation.value?.let { + if (it.first == viewModel.playlistBrowse.value?.id) it.second else null + }, + ), + ) + viewModel.playlistBrowse.value?.tracks?.firstOrNull()?.let { + viewModel.loadMediaItem( + it, + type = Config.PLAYLIST_CLICK, + index = 0, + ) + } + } else if (viewModel.playlistEntity.value != null && + viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED + ) { + viewModel.setQueueData( + QueueData( + listTracks = viewModel.listTrack.value.toArrayListTrack(), + firstPlayedTrack = + viewModel.listTrack.value + .firstOrNull() + ?.toTrack(), + playlistId = + viewModel.playlistBrowse.value + ?.id + ?.replaceFirst("VL", ""), + playlistName = "${viewModel.playlistBrowse.value?.title}", + playlistType = PlaylistType.RADIO, + continuation = + viewModel.radioContinuation.value?.let { + if (it.first == viewModel.playlistBrowse.value?.id) it.second else null + }, + ), + ) + viewModel.listTrack.value.firstOrNull()?.let { + viewModel.loadMediaItem( + it.toTrack(), + type = Config.PLAYLIST_CLICK, + index = 0, + ) + } + } else { + Snackbar + .make( + requireView(), + getString(R.string.playlist_is_empty), + Snackbar.LENGTH_SHORT, + ).show() + } + } + } + + playlistItemAdapter.setOnClickListener( + object : PlaylistItemAdapter.OnItemClickListener { + override fun onItemClick(position: Int) { + if (!viewModel.isRadio.value) { + if (viewModel.playlistBrowse.value != null) { + viewModel.setQueueData( + QueueData( + listTracks = (viewModel.playlistBrowse.value?.tracks ?: arrayListOf()) as ArrayList, + firstPlayedTrack = + viewModel.playlistBrowse.value + ?.tracks + ?.get(position), + playlistId = + viewModel.playlistBrowse.value + ?.id + ?.replaceFirst("VL", "") ?: "", + playlistName = "Playlist \"${viewModel.playlistBrowse.value?.title}\"", + playlistType = PlaylistType.PLAYLIST, + continuation = null, + ), + ) + viewModel.playlistBrowse.value?.tracks?.get(position)?.let { + Log.w(tag, "track: $it") + viewModel.loadMediaItem( + it, + type = Config.PLAYLIST_CLICK, + index = position, + ) + } + } else if (viewModel.playlistEntity.value != null && + viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED + ) { + viewModel.setQueueData( + QueueData( + listTracks = viewModel.listTrack.value.toArrayListTrack(), + firstPlayedTrack = + viewModel.listTrack.value + .getOrNull(position) + ?.toTrack(), + playlistId = + viewModel.playlistEntity.value + ?.id + ?.replaceFirst("VL", "") ?: "", + playlistName = "Playlist \"${viewModel.playlistEntity.value?.title}\"", + playlistType = PlaylistType.PLAYLIST, + continuation = null, + ), + ) + viewModel.listTrack.value.getOrNull(position)?.let { + Log.w(tag, "track: $it") + viewModel.loadMediaItem( + it.toTrack(), + type = Config.PLAYLIST_CLICK, + index = position, + ) + } + } else { + Snackbar + .make( + requireView(), + getString(R.string.error), + Snackbar.LENGTH_SHORT, + ).show() + } + } else { + if (viewModel.playlistBrowse.value != null) { + viewModel.setQueueData( + QueueData( + listTracks = (viewModel.playlistBrowse.value?.tracks ?: arrayListOf()) as ArrayList, + firstPlayedTrack = + viewModel.playlistBrowse.value + ?.tracks + ?.get(position), + playlistId = + viewModel.playlistBrowse.value + ?.id + ?.replaceFirst("VL", "") ?: "", + playlistName = "${viewModel.playlistBrowse.value?.title}", + playlistType = PlaylistType.RADIO, + continuation = + viewModel.radioContinuation.value?.let { + if (it.first == viewModel.playlistBrowse.value?.id) it.second else null + }, + ), + ) + viewModel.playlistBrowse.value?.tracks?.get(position)?.let { + Log.w(tag, "track: $it") + viewModel.loadMediaItem( + it, + type = Config.PLAYLIST_CLICK, + index = position, + ) + } + } else if (viewModel.playlistEntity.value != null && + viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED + ) { + viewModel.setQueueData( + QueueData( + listTracks = viewModel.listTrack.value.toArrayListTrack(), + firstPlayedTrack = + viewModel.listTrack.value + .getOrNull(position) + ?.toTrack(), + playlistId = + viewModel.playlistBrowse.value + ?.id + ?.replaceFirst("VL", ""), + playlistName = "${viewModel.playlistBrowse.value?.title}", + playlistType = PlaylistType.RADIO, + continuation = + viewModel.radioContinuation.value?.let { + if (it.first == viewModel.playlistBrowse.value?.id) it.second else null + }, + ), + ) + viewModel.listTrack.value.getOrNull(position)?.let { + Log.w(tag, "track: $it") + viewModel.loadMediaItem( + it.toTrack(), + type = Config.PLAYLIST_CLICK, + index = position, + ) + } + } else { + Snackbar + .make( + requireView(), + getString(R.string.error), + Snackbar.LENGTH_SHORT, + ).show() + } + } + } + }, + ) + playlistItemAdapter.setOnOptionClickListener( + object : + PlaylistItemAdapter.OnOptionClickListener { + override fun onOptionClick(position: Int) { + val playListBrowseValue = viewModel.playlistBrowse.value + if (playListBrowseValue != null) { + val song = + playListBrowseValue.tracks[position] + viewModel.getSongEntity(song.toSongEntity()) + val dialog = BottomSheetDialog(requireContext()) + val bottomSheetView = BottomSheetNowPlayingBinding.inflate(layoutInflater) + with(bottomSheetView) { + btSleepTimer.visibility = View.GONE + viewModel.songEntity.observe(viewLifecycleOwner) { songEntity -> + if (songEntity != null) { + if (songEntity.liked) { + tvFavorite.text = getString(R.string.liked) + cbFavorite.isChecked = true + } else { + tvFavorite.text = getString(R.string.like) + cbFavorite.isChecked = false + } + } + } + btChangeLyricsProvider.visibility = View.GONE + tvSongTitle.text = song.title + tvSongTitle.isSelected = true + tvSongArtist.text = song.artists.toListName().connectArtists() + tvSongArtist.isSelected = true + if (song.album != null) { + setEnabledAll(btAlbum, true) + tvAlbum.text = song.album.name + } else { + tvAlbum.text = getString(R.string.no_album) + setEnabledAll(btAlbum, false) + } + btAlbum.setOnClickListener { + val albumId = song.album?.id + if (albumId != null) { + findNavController().navigateSafe( + R.id.action_global_albumFragment, + Bundle().apply { + putString("browseId", albumId) + }, + ) + dialog.dismiss() + } else { + Toast + .makeText( + requireContext(), + getString(R.string.no_album), + Toast.LENGTH_SHORT, + ).show() + } + } + btAddQueue.setOnClickListener { + sharedViewModel.addToQueue(song) + } + btPlayNext.setOnClickListener { + sharedViewModel.playNext(song) + } + ivThumbnail.load(song.thumbnails?.lastOrNull()?.url) + btRadio.setOnClickListener { + val args = Bundle() + args.putString("radioId", "RDAMVM${song.videoId}") + args.putString( + "videoId", + song.videoId, + ) + dialog.dismiss() + findNavController().navigateSafe( + R.id.action_global_playlistFragment, + args, + ) + } + btLike.setOnClickListener { + if (cbFavorite.isChecked) { + cbFavorite.isChecked = false + tvFavorite.text = getString(R.string.like) + viewModel.updateLikeStatus(song.videoId, 0) + } else { + cbFavorite.isChecked = true + tvFavorite.text = getString(R.string.liked) + viewModel.updateLikeStatus(song.videoId, 1) + } + } + btSeeArtists.setOnClickListener { + val subDialog = BottomSheetDialog(requireContext()) + val subBottomSheetView = + BottomSheetSeeArtistOfNowPlayingBinding.inflate(layoutInflater) + if (!song.artists.isNullOrEmpty()) { + val artistAdapter = SeeArtistOfNowPlayingAdapter(song.artists) + subBottomSheetView.rvArtists.apply { + adapter = artistAdapter + layoutManager = LinearLayoutManager(requireContext()) + } + artistAdapter.setOnClickListener( + object : + SeeArtistOfNowPlayingAdapter.OnItemClickListener { + override fun onItemClick(position: Int) { + val artist = song.artists[position] + if (artist.id != null) { + findNavController().navigateSafe( + R.id.action_global_artistFragment, + Bundle().apply { + putString("channelId", artist.id) + }, + ) + subDialog.dismiss() + dialog.dismiss() + } + } + }, + ) + } + + subDialog.setCancelable(true) + subDialog.setContentView(subBottomSheetView.root) + subDialog.show() + } + btDownload.visibility = View.GONE + btAddPlaylist.setOnClickListener { + viewModel.getLocalPlaylist() + val listLocalPlaylist: ArrayList = arrayListOf() + val addPlaylistDialog = BottomSheetDialog(requireContext()) + val viewAddPlaylist = + BottomSheetAddToAPlaylistBinding.inflate(layoutInflater) + val addToAPlaylistAdapter = AddToAPlaylistAdapter(arrayListOf()) + addToAPlaylistAdapter.setVideoId(song.videoId) + viewAddPlaylist.rvLocalPlaylists.apply { + adapter = addToAPlaylistAdapter + layoutManager = LinearLayoutManager(requireContext()) + } + viewModel.listLocalPlaylist.observe(viewLifecycleOwner) { list -> + Log.d("Check Local Playlist", list.toString()) + listLocalPlaylist.clear() + listLocalPlaylist.addAll(list) + addToAPlaylistAdapter.updateList(listLocalPlaylist) + } + addToAPlaylistAdapter.setOnItemClickListener( + object : + AddToAPlaylistAdapter.OnItemClickListener { + override fun onItemClick(position: Int) { + val playlist = listLocalPlaylist[position] + viewModel.updateInLibrary(song.videoId) + val tempTrack = ArrayList() + if (playlist.tracks != null) { + tempTrack.addAll(playlist.tracks) + } + if (!tempTrack.contains( + song.videoId, + ) && + playlist.syncState == LocalPlaylistEntity.YouTubeSyncState.Synced && + playlist.youtubePlaylistId != null + ) { + viewModel.addToYouTubePlaylist( + playlist.id, + playlist.youtubePlaylistId, + song.videoId, + ) + } + if (!tempTrack.contains(song.videoId)) { + viewModel.insertPairSongLocalPlaylist( + PairSongLocalPlaylist( + playlistId = playlist.id, + songId = song.videoId, + position = playlist.tracks?.size ?: 0, + inPlaylist = LocalDateTime.now(), + ), + ) + tempTrack.add(song.videoId) + } + + viewModel.updateLocalPlaylistTracks( + tempTrack.removeConflicts(), + playlist.id, + ) + addPlaylistDialog.dismiss() + dialog.dismiss() + } + }, + ) + addPlaylistDialog.setContentView(viewAddPlaylist.root) + addPlaylistDialog.setCancelable(true) + addPlaylistDialog.show() + } + btShare.setOnClickListener { + val shareIntent = Intent(Intent.ACTION_SEND) + shareIntent.type = "text/plain" + val url = "https://youtube.com/watch?v=${song.videoId}" + shareIntent.putExtra(Intent.EXTRA_TEXT, url) + val chooserIntent = + Intent.createChooser(shareIntent, getString(R.string.share_url)) + startActivity(chooserIntent) + } + dialog.setCancelable(true) + dialog.setContentView(bottomSheetView.root) + dialog.show() + } + } + } + }, + ) + binding.topAppBarLayout.addOnOffsetChangedListener { it, verticalOffset -> + if (abs(it.totalScrollRange) == abs(verticalOffset)) { + binding.topAppBar.background = viewModel.gradientDrawable.value + binding.collapsingToolbarLayout.isTitleEnabled = true + if (viewModel.gradientDrawable.value != null) { + if (viewModel.gradientDrawable.value?.colors != null) { + setStatusBarsColor( + viewModel.gradientDrawable.value + ?.colors!! + .first(), + requireActivity(), + ) + } + } + } else { + binding.collapsingToolbarLayout.isTitleEnabled = false + binding.topAppBar.background = null + binding.topAppBarLayout.background = viewModel.gradientDrawable.value + setStatusBarsColor( + ContextCompat.getColor(requireContext(), R.color.colorPrimaryDark), + requireActivity(), + ) + } + } + binding.btShuffle.setOnClickListener { + if (viewModel.playlistBrowse.value != null) { + val shuffledList: ArrayList = arrayListOf() + viewModel.playlistBrowse.value?.tracks?.let { + shuffledList.addAll(it) + } + shuffledList.shuffle() + + val indexInQueue = 0 + val indexInPlaylist = viewModel.playlistBrowse.value?.tracks?.indexOf(shuffledList.first()) ?: 0 + + viewModel.setQueueData( + QueueData( + listTracks = shuffledList, + firstPlayedTrack = viewModel.playlistBrowse.value + ?.tracks + ?.get(indexInPlaylist), + playlistId = viewModel.playlistBrowse.value + ?.id + ?.replaceFirst("VL", "") ?: "", + playlistName = "Playlist \"${viewModel.playlistBrowse.value?.title}\"", + playlistType = PlaylistType.PLAYLIST, + continuation = null, + ), + ) + viewModel.playlistBrowse.value?.tracks?.get(indexInPlaylist)?.let { + viewModel.loadMediaItem( + it, + type = Config.PLAYLIST_CLICK, + index = indexInQueue + ) + } + } else if (viewModel.playlistEntity.value != null && viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED) { + val shuffledList: ArrayList = arrayListOf() + viewModel.listTrack.value + .toArrayListTrack() + .let { it1 -> shuffledList.addAll(it1) } + shuffledList.shuffle() + shuffledList.remove(shuffledList.first()) + + val indexInQueue = 0 + val indexInPlaylist = viewModel.listTrack.value.toArrayListTrack().indexOf(shuffledList.first()) + + viewModel.setQueueData( + QueueData( + listTracks = shuffledList, + firstPlayedTrack = viewModel.listTrack.value + .getOrNull(indexInPlaylist) + ?.toTrack(), + playlistId = viewModel.playlistEntity.value + ?.id + ?.replaceFirst("VL", "") ?: "", + playlistName = "Playlist \"${viewModel.playlistEntity.value?.title}\"", + playlistType = PlaylistType.PLAYLIST, + continuation = null, + ), + ) + viewModel.listTrack.value + .getOrNull(indexInPlaylist) + ?.let { + viewModel.loadMediaItem( + it.toTrack(), + type = Config.PLAYLIST_CLICK, + index = indexInQueue + ) + } + } else { + Snackbar + .make( + requireView(), + getString(R.string.playlist_is_empty), + Snackbar.LENGTH_SHORT, + ).show() + } + } + binding.btDownload.setOnClickListener { + if (id != null) { + if (viewModel.playlistDownloadState.value == DownloadState.STATE_NOT_DOWNLOADED) { +// if (!viewModel.prevPlaylistDownloading.value){ +// viewModel.downloading() + if (viewModel.playlistBrowse.value + ?.tracks + ?.size != viewModel.listTrack.value.size && + viewModel.listTrack.value.isNotEmpty() + ) { + for (i in viewModel.playlistBrowse.value?.tracks!!) { + viewModel.insertSong(i.toSongEntity()) + } + runBlocking { + delay(1000) + viewModel.listJob.emit(arrayListOf()) + } + viewModel.getListTrack( + viewModel.playlistBrowse.value + ?.tracks + ?.toListVideoId(), + ) + } + viewModel.updatePlaylistDownloadState( + id, + DownloadState.STATE_PREPARING, + ) +// } +// else{ +// Toast.makeText(requireContext(), getString(R.string.please_wait_before_playlist_downloaded), Toast.LENGTH_SHORT).show() +// } + } else if (viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADED) { + Toast + .makeText(requireContext(), getString(R.string.downloaded), Toast.LENGTH_SHORT) + .show() + } else if (viewModel.playlistEntity.value?.downloadState == DownloadState.STATE_DOWNLOADING) { + Toast + .makeText( + requireContext(), + getString(R.string.downloading), + Toast.LENGTH_SHORT, + ).show() + } + } else { + Log.d("binding.btDownload.setOnClickListener", "id was null") + } + } + collectUIState() + collectPlaylist() + collectListTrack() + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + val job2 = + launch { + combine( + sharedViewModel.nowPlayingState.distinctUntilChangedBy { + it?.songEntity?.videoId + }, + sharedViewModel.controllerState.map { it.isPlaying }.distinctUntilChanged(), + ) { nowPlaying, isPlaying -> + Pair(nowPlaying, isPlaying) + }.collect { + if (it.first != null && it.second) { + playlistItemAdapter.setNowPlaying(it.first?.songEntity?.videoId) + } else { + playlistItemAdapter.setNowPlaying(null) + } + } + } + val job3 = + launch { + combine(viewModel.playlistDownloadState, viewModel.isRadio) { downloadState, isRadio -> + Pair(downloadState, isRadio) + }.collectLatest { pair -> + val playlistDownloadState = pair.first + val isRadio = pair.second + if (!isRadio) { + when (playlistDownloadState) { + DownloadState.STATE_PREPARING -> { + binding.btDownload.visibility = View.GONE + binding.animationDownloading.visibility = View.VISIBLE + } + + DownloadState.STATE_DOWNLOADING -> { + binding.btDownload.visibility = View.GONE + binding.animationDownloading.visibility = View.VISIBLE + } + + DownloadState.STATE_DOWNLOADED -> { + binding.btDownload.visibility = View.VISIBLE + binding.animationDownloading.visibility = View.GONE + binding.btDownload.setImageResource(R.drawable.baseline_downloaded) + } + + DownloadState.STATE_NOT_DOWNLOADED -> { + binding.btDownload.visibility = View.VISIBLE + binding.animationDownloading.visibility = View.GONE + binding.btDownload.setImageResource(R.drawable.download_button) + } + } + if (viewModel.isRadio.value) { + binding.btDownload.visibility = View.GONE + binding.animationDownloading.visibility = View.GONE + } + } else { + binding.btDownload.visibility = View.GONE + binding.animationDownloading.visibility = View.GONE + } + } + } + job2.join() + job3.join() + } + } + } + +// private fun fetchDataFromViewModel() { +// val response = viewModel.playlistBrowse.value +// when (response) { +// is Resource.Success -> { +// response.data.let { +// with(binding) { +// if (it?.id?.startsWith("RDEM") == true || it?.id?.startsWith("RDAMVM") == true) { +// btDownload.visibility = View.GONE +// } +// collapsingToolbarLayout.title = it?.title +// tvTitle.text = it?.title +// tvTitle.isSelected = true +// tvPlaylistAuthor.text = it?.author?.name +// if (it?.year != "") { +// tvYearAndCategory.text = +// requireContext().getString( +// R.string.year_and_category, +// it?.year.toString(), +// "Playlist", +// ) +// } else { +// tvYearAndCategory.text = requireContext().getString(R.string.playlist) +// } +// tvTrackCountAndDuration.text = +// requireContext().getString( +// R.string.album_length, +// it?.trackCount.toString(), +// "", +// ) +// if (it?.description != null && it.description != "") { +// tvDescription.originalText = it.description +// } else { +// tvDescription.originalText = getString(R.string.no_description) +// } +// loadImage(it?.thumbnails?.last()?.url) +// val list: ArrayList = arrayListOf() +// list.addAll(it?.tracks as ArrayList) +// playlistItemAdapter.updateList(list) +// if (viewModel.gradientDrawable.value == null) { +// viewModel.gradientDrawable.observe(viewLifecycleOwner) { gradient -> +// // fullRootLayout.background = gradient +// // toolbarBackground = gradient?.colors?.get(0) +// if (gradient != null) { +// val start = +// topAppBarLayout.background ?: ColorDrawable( +// Color.TRANSPARENT, +// ) +// val transition = +// TransitionDrawable(arrayOf(start, gradient)) +// topAppBarLayout.background = transition +// transition.isCrossFadeEnabled = true +// transition.startTransition(500) +// } +// } +// } else { +// // fullRootLayout.background = gradientDrawable +// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) +// topAppBarLayout.background = gradientDrawable +// } +// binding.rootLayout.visibility = View.VISIBLE +// binding.loadingLayout.visibility = View.GONE +// if (viewModel.isRadio.value == false) { +// val playlistEntity = viewModel.playlistEntity.value +// if (playlistEntity != null) { +// viewModel.checkAllSongDownloaded(it.tracks as ArrayList) +// viewModel.playlistEntity.observe(viewLifecycleOwner) { playlistEntity2 -> +// if (playlistEntity2 != null) { +// when (playlistEntity2.downloadState) { +// DownloadState.STATE_DOWNLOADED -> { +// btDownload.visibility = View.VISIBLE +// animationDownloading.visibility = View.GONE +// btDownload.setImageResource(R.drawable.baseline_downloaded) +// } +// +// DownloadState.STATE_DOWNLOADING -> { +// btDownload.visibility = View.GONE +// animationDownloading.visibility = View.VISIBLE +// } +// +// DownloadState.STATE_NOT_DOWNLOADED -> { +// btDownload.visibility = View.VISIBLE +// animationDownloading.visibility = View.GONE +// btDownload.setImageResource(R.drawable.download_button) +// } +// } +// } +// } +// } +// } else { +// btDownload.visibility = View.GONE +// cbLove.visibility = View.GONE +// } +// } +// } +// } +// +// is Resource.Error -> { +// Snackbar +// .make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG) +// .show() +// findNavController().popBackStack() +// } +// +// else -> {} +// } +// } + + private fun collectUIState() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + val uiStateJob = + launch { + viewModel.uiState.collectLatest { state -> + when (state) { + is PlaylistUIState.Loading -> { + binding.rootLayout.visibility = View.GONE + binding.loadingLayout.visibility = View.VISIBLE + } + + is PlaylistUIState.Error -> { + Snackbar.make(binding.root, state.message ?: getString(R.string.error), Snackbar.LENGTH_LONG).show() + } + + is PlaylistUIState.Success -> { + binding.rootLayout.visibility = View.VISIBLE + binding.loadingLayout.visibility = View.GONE + } + } + } + } + val bgJob = + launch { + viewModel.gradientDrawable.collectLatest { gd -> + if (gd != null) { + with(binding) { + val start = + topAppBarLayout.background ?: ColorDrawable( + Color.TRANSPARENT, + ) + val transition = + TransitionDrawable(arrayOf(start, gd)) + transition.setDither(true) + topAppBarLayout.background = transition + transition.isCrossFadeEnabled = true + transition.startTransition(500) + } + } + } + } + uiStateJob.join() + bgJob.join() + } + } + } + + @UnstableApi + private fun collectListTrack() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + val job1 = + launch { + combine( + viewModel.listTrack, + viewModel.playlistDownloadState, + ) { listTrack, downloadState -> + Pair(listTrack, downloadState) + }.collectLatest { pair -> + val listTrack = pair.first + val downloadState = pair.second + val tempList = arrayListOf() + for (i in listTrack) { + tempList.add(i) + } + listTrack.let { + viewModel.checkAllSongDownloaded(it.toArrayListTrack()) + } + if (listTrack.isNotEmpty() && downloadState == DownloadState.STATE_PREPARING) { + val listJob: ArrayList = arrayListOf() + for (song in listTrack) { + if (song.downloadState == DownloadState.STATE_NOT_DOWNLOADED) { + listJob.add(song) + } + } + viewModel.listJob.value = listJob + listJob.forEach { job -> + val downloadRequest = + DownloadRequest + .Builder(job.videoId, job.videoId.toUri()) + .setData(job.title.toByteArray()) + .setCustomCacheKey(job.videoId) + .build() + viewModel.updateDownloadState( + job.videoId, + DownloadState.STATE_DOWNLOADING, + ) + DownloadService.sendAddDownload( + requireContext(), + MusicDownloadService::class.java, + downloadRequest, + false, + ) + } + Log.d("PlaylistFragment", "ListJob: ${viewModel.listJob.value}") + viewModel.updatePlaylistDownloadState( + viewModel.id.value!!, + DownloadState.STATE_DOWNLOADING, + ) + } + } + } + val job2 = + launch { + combine(viewModel.downloadedList, viewModel.listTrack) { downloadedList, listTrack -> + Pair(downloadedList, listTrack) + }.collectLatest { pair -> + val list = pair.second + val downloadList = pair.first + val temp = + list.map { it.videoId }.toMutableSet().apply { + removeAll(downloadList.toSet()) + } + Log.w(tag, "DownloadList: $downloadList") + Log.w(tag, "Downloading and not download: $temp") + Log.w(tag, "DownloadList size: ${downloadList.size}") + Log.w(tag, "Downloading and not download size: ${temp.size}") + Log.w(tag, "List size: ${list.size}") + playlistItemAdapter.setDownloadedList(downloadList) + val viewModelId = viewModel.id.value.toString() + if (list.isNotEmpty()) { + if (downloadList.containsAll( + list.map { + it.videoId + }, + ) && + downloadList.isNotEmpty() + ) { + viewModel.updatePlaylistDownloadState( + viewModelId, + DownloadState.STATE_DOWNLOADED, + ) + Log.w(tag, "All downloaded") + } else if (viewModel.downloadUtils.downloadingVideoIds.value + .containsAll(temp) && + temp.isNotEmpty() + ) { + viewModel.updatePlaylistDownloadState( + viewModelId, + DownloadState.STATE_DOWNLOADING, + ) + Log.w(tag, "Downloading") + } else { + viewModel.updatePlaylistDownloadState( + viewModelId, + DownloadState.STATE_NOT_DOWNLOADED, + ) + Log.w(tag, "Not downloaded") + } + } else { + viewModel.updatePlaylistDownloadState( + viewModelId, + DownloadState.STATE_NOT_DOWNLOADED, + ) + Log.w(tag, "Not downloaded") + } + } + + } + job1.join() + job2.join() + } + } + } + + private fun collectPlaylist() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + val job1 = + launch { + combine(viewModel.playlistBrowse, viewModel.playlistEntity) { playlistBrowse, playlistEntity -> + Pair(playlistBrowse, playlistEntity) + }.collectLatest { pair -> + val playlistBrowse = pair.first + val playlistEntity = pair.second + if (playlistBrowse != null && playlistEntity != null) { + viewModel.checkAllSongDownloaded(playlistBrowse.tracks as ArrayList) + with(binding) { + if (playlistBrowse.id.startsWith("RDEM") || playlistBrowse.id.startsWith("RDAMVM")) { + btDownload.visibility = View.GONE + cbLove.visibility = View.GONE + } + collapsingToolbarLayout.title = playlistBrowse.title + tvTitle.text = playlistBrowse.title + tvTitle.isSelected = true + tvPlaylistAuthor.text = playlistBrowse.author.name + if (playlistBrowse.year != "") { + tvYearAndCategory.text = + requireContext().getString( + R.string.year_and_category, + playlistBrowse.year, + "Playlist", + ) + } else { + tvYearAndCategory.text = + requireContext().getString(R.string.playlist) + } + tvTrackCountAndDuration.text = + requireContext().getString( + R.string.album_length, + playlistBrowse.trackCount.toString(), + "", + ) + if (playlistBrowse.description != null && playlistBrowse.description != "") { + tvDescription.originalText = playlistBrowse.description + } else { + tvDescription.originalText = getString(R.string.no_description) + } + loadImage(playlistBrowse.thumbnails.lastOrNull()?.url) + val list: ArrayList = arrayListOf() + list.addAll(playlistBrowse.tracks) + playlistItemAdapter.updateList(list) + } + } else if (playlistBrowse == null && playlistEntity != null) { + with(binding) { + collapsingToolbarLayout.title = playlistEntity.title + tvTitle.text = playlistEntity.title + tvTitle.isSelected = true + tvPlaylistAuthor.text = playlistEntity.author + tvYearAndCategory.text = + requireContext().getString( + R.string.year_and_category, + playlistEntity.year.toString(), + "Playlist", + ) + tvTrackCountAndDuration.text = + requireContext().getString( + R.string.album_length, + playlistEntity.trackCount.toString(), + "", + ) + if (playlistEntity.description != "") { + tvDescription.originalText = playlistEntity.description + } else { + tvDescription.originalText = getString(R.string.no_description) + } + loadImage(playlistEntity.thumbnails) + } + } + } + } + val job2 = + launch { + combine(viewModel.playlistBrowse, viewModel.listTrack) { playlistBrowse, listTrack -> + Pair(playlistBrowse, listTrack) + }.collectLatest { pair -> + val playlistBrowse = pair.first + val listTrack = pair.second + if (playlistBrowse == null && listTrack.isNotEmpty()) { + val tempList = arrayListOf() + for (i in listTrack) { + tempList.add(i) + } + listTrack.let { + viewModel.checkAllSongDownloaded(it.toArrayListTrack()) + } + playlistItemAdapter.updateList(tempList) + } + } + } + job1.join() + job2.join() + } + } + } + + private fun fetchRDATRadio(radioId: String) { + viewModel.clearPlaylistBrowse() + viewModel.clearPlaylistEntity() + viewModel.updateId(radioId) + viewModel.getRadio(radioId) + } + + private fun fetchDataWithRadio( + radioId: String, + videoId: String? = null, + channelId: String? = null, + ) { + viewModel.clearPlaylistBrowse() + viewModel.clearPlaylistEntity() + viewModel.updateId(radioId) + viewModel.getRadio(radioId, videoId, channelId) +// viewModel.playlistBrowse.observe(viewLifecycleOwner) { response -> +// when (response) { +// is Resource.Success -> { +// response.data.let { +// with(binding) { +// if (it != null) { +// viewModel.insertRadioPlaylist(it.toPlaylistEntity()) +// } +// collapsingToolbarLayout.title = it?.title +// tvTitle.text = it?.title +// tvTitle.isSelected = true +// tvPlaylistAuthor.text = it?.author?.name +// if (it?.year != "") { +// tvYearAndCategory.text = +// requireContext().getString( +// R.string.year_and_category, +// it?.year.toString(), +// "Playlist", +// ) +// } else { +// tvYearAndCategory.text = +// requireContext().getString(R.string.playlist) +// } +// tvTrackCountAndDuration.text = +// requireContext().getString( +// R.string.album_length, +// it?.trackCount.toString(), +// "", +// ) +// if (it?.description != null && it.description != "") { +// tvDescription.originalText = it.description +// } else { +// tvDescription.originalText = getString(R.string.no_description) +// } +// loadImage(it?.thumbnails?.last()?.url) +// val list: ArrayList = arrayListOf() +// list.addAll(it?.tracks as ArrayList) +// playlistItemAdapter.updateList(list) +// if (viewModel.gradientDrawable.value == null) { +// viewModel.gradientDrawable.observe(viewLifecycleOwner) { gradient -> +// // fullRootLayout.background = gradient +// // toolbarBackground = gradient?.colors?.get(0) +// if (gradient != null) { +// val start = +// topAppBarLayout.background ?: ColorDrawable( +// Color.TRANSPARENT, +// ) +// val transition = +// TransitionDrawable(arrayOf(start, gradient)) +// transition.setDither(true) +// topAppBarLayout.background = transition +// transition.isCrossFadeEnabled = true +// transition.startTransition(500) +// } +// } +// } else { +// // fullRootLayout.background = gradientDrawable +// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) +// topAppBarLayout.background = +// gradientDrawable?.apply { +// setDither(true) +// } +// } +// binding.rootLayout.visibility = View.VISIBLE +// binding.loadingLayout.visibility = View.GONE +// btDownload.visibility = View.GONE +// cbLove.visibility = View.GONE +// } +// } +// } +// +// is Resource.Error -> { +// Snackbar +// .make(binding.root, response.message.toString(), Snackbar.LENGTH_LONG) +// .show() +// findNavController().popBackStack() +// } +// +// else -> {} +// } +// } + } + + private fun fetchData( + id: String, + downloaded: Int = 0, + ) { + viewModel.updateId(id) + if (downloaded == 0) { + viewModel.clearPlaylistBrowse() + viewModel.clearPlaylistEntity() + viewModel.browsePlaylist(id) +// viewModel.playlistBrowse.observe(viewLifecycleOwner) { response -> +// when (response) { +// is Resource.Success -> { +// response.data.let { +// with(binding) { +// if (it != null) { +// viewModel.insertPlaylist(it.toPlaylistEntity()) +// } +// collapsingToolbarLayout.title = it?.title +// tvTitle.text = it?.title +// tvTitle.isSelected = true +// tvPlaylistAuthor.text = it?.author?.name +// if (it?.year != "") { +// tvYearAndCategory.text = +// requireContext().getString( +// R.string.year_and_category, +// it?.year.toString(), +// "Playlist", +// ) +// } else { +// tvYearAndCategory.text = +// requireContext().getString(R.string.playlist) +// } +// tvTrackCountAndDuration.text = +// requireContext().getString( +// R.string.album_length, +// it?.trackCount.toString(), +// "", +// ) +// if (it?.description != null && it.description != "") { +// tvDescription.originalText = it.description +// } else { +// tvDescription.originalText = getString(R.string.no_description) +// } +// loadImage(it?.thumbnails?.last()?.url) +// val list: ArrayList = arrayListOf() +// list.addAll(it?.tracks as ArrayList) +// playlistItemAdapter.updateList(list) +// if (viewModel.gradientDrawable.value == null) { +// viewModel.gradientDrawable.observe(viewLifecycleOwner) { gradient -> +// // fullRootLayout.background = gradient +// // toolbarBackground = gradient?.colors?.get(0) +// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) +// if (gradient != null) { +// val start = +// topAppBarLayout.background ?: ColorDrawable( +// Color.TRANSPARENT, +// ) +// val transition = +// TransitionDrawable(arrayOf(start, gradient)) +// transition.setDither(true) +// topAppBarLayout.background = transition +// transition.isCrossFadeEnabled = true +// transition.startTransition(500) +// } +// } +// } else { +// // fullRootLayout.background = gradientDrawable +// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) +// topAppBarLayout.background = +// gradientDrawable?.apply { +// setDither(true) +// } +// } +// binding.rootLayout.visibility = View.VISIBLE +// binding.loadingLayout.visibility = View.GONE +// viewModel.playlistEntity.observe(viewLifecycleOwner) { playlistEntity2 -> +// if (playlistEntity2 != null) { +// when (playlistEntity2.downloadState) { +// DownloadState.STATE_DOWNLOADED -> { +// btDownload.visibility = View.VISIBLE +// animationDownloading.visibility = View.GONE +// btDownload.setImageResource(R.drawable.baseline_downloaded) +// } +// +// DownloadState.STATE_DOWNLOADING -> { +// btDownload.visibility = View.GONE +// animationDownloading.visibility = View.VISIBLE +// } +// +// DownloadState.STATE_NOT_DOWNLOADED -> { +// btDownload.visibility = View.VISIBLE +// animationDownloading.visibility = View.GONE +// btDownload.setImageResource(R.drawable.download_button) +// } +// } +// } +// } +// } +// } +// } +// +// is Resource.Error -> { +// Snackbar +// .make( +// binding.root, +// response.message.toString(), +// Snackbar.LENGTH_LONG, +// ).show() +// findNavController().popBackStack() +// } +// +// else -> {} +// } +// } + } else if (downloaded == 1) { + viewModel.clearPlaylistBrowse() + viewModel.clearPlaylistEntity() + viewModel.getPlaylist(id, null, null) +// with(binding) { +// viewModel.playlistEntity.observe(viewLifecycleOwner) { playlistEntity -> +// if (playlistEntity != null) { +// when (playlistEntity.downloadState) { +// DownloadState.STATE_DOWNLOADED -> { +// btDownload.visibility = View.VISIBLE +// animationDownloading.visibility = View.GONE +// btDownload.setImageResource(R.drawable.baseline_downloaded) +// } +// +// DownloadState.STATE_DOWNLOADING -> { +// btDownload.visibility = View.GONE +// animationDownloading.visibility = View.VISIBLE +// } +// +// DownloadState.STATE_NOT_DOWNLOADED -> { +// btDownload.visibility = View.VISIBLE +// animationDownloading.visibility = View.GONE +// btDownload.setImageResource(R.drawable.download_button) +// } +// } +// collapsingToolbarLayout.title = playlistEntity.title +// tvTitle.text = playlistEntity.title +// tvTitle.isSelected = true +// tvPlaylistAuthor.text = playlistEntity.author +// tvYearAndCategory.text = +// requireContext().getString( +// R.string.year_and_category, +// playlistEntity.year.toString(), +// "Playlist", +// ) +// tvTrackCountAndDuration.text = +// requireContext().getString( +// R.string.album_length, +// playlistEntity.trackCount.toString(), +// "", +// ) +// if (playlistEntity.description != "") { +// tvDescription.originalText = playlistEntity.description +// } else { +// tvDescription.originalText = getString(R.string.no_description) +// } +// loadImage(playlistEntity.thumbnails) +// if (viewModel.gradientDrawable.value == null) { +// viewModel.gradientDrawable.observe(viewLifecycleOwner) { gradient -> +// // fullRootLayout.background = gradient +// // toolbarBackground = gradient?.colors?.get(0) +// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) +// if (gradient != null) { +// val start = +// topAppBarLayout.background +// ?: ColorDrawable(Color.TRANSPARENT) +// val transition = TransitionDrawable(arrayOf(start, gradient)) +// transition.setDither(true) +// topAppBarLayout.background = transition +// transition.isCrossFadeEnabled = true +// transition.startTransition(500) +// } +// } +// } else { +// // fullRootLayout.background = gradientDrawable +// // topAppBarLayout.background = ColorDrawable(toolbarBackground!!) +// topAppBarLayout.background = +// gradientDrawable?.apply { +// setDither(true) +// } +// } +// viewModel.getListTrack(playlistEntity.tracks) +// viewModel.listTrack.observe(viewLifecycleOwner) { listTrack -> +// val tempList = arrayListOf() +// for (i in listTrack) { +// tempList.add(i) +// } +// playlistItemAdapter.updateList(tempList) +// } +// } +// } +// } + } + } + + private fun loadImage(url: String?) { + if (url != null) { + binding.ivPlaylistArt.visibility = View.VISIBLE + binding.ivPlaylistArt.background = ColorDrawable(Color.WHITE) + binding.ivPlaylistArt.load(url) { + allowHardware(false) + placeholder(R.drawable.holder) + diskCachePolicy(CachePolicy.ENABLED) + crossfade(true) + crossfade(300) + listener( + onError = { _, er -> + Log.w(tag, "Load Image Error ${er.throwable.message}") + }, + onSuccess = { _, result -> + binding.ivPlaylistArt.setImageDrawable(result.image.asDrawable(resources)) + val p = Palette.from(result.image.toBitmap()).generate() + val defaultColor = 0x000000 + var startColor = p.getDarkVibrantColor(defaultColor) + if (startColor == defaultColor) { + startColor = p.getDarkMutedColor(defaultColor) + if (startColor == defaultColor) { + startColor = p.getVibrantColor(defaultColor) + if (startColor == defaultColor) { + startColor = p.getMutedColor(defaultColor) + if (startColor == defaultColor) { + startColor = p.getLightVibrantColor(defaultColor) + if (startColor == defaultColor) { + startColor = p.getLightMutedColor(defaultColor) + } + } + } + } + } + startColor = ColorUtils.setAlphaComponent(startColor, 150) + val endColor = + resources.getColor(R.color.md_theme_dark_background, null) + val gd = + GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(startColor, endColor), + ) + gd.cornerRadius = 0f + gd.gradientType = GradientDrawable.LINEAR_GRADIENT + gd.gradientRadius = 0.5f + viewModel.setGradientDrawable(gd) + }, + ) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/screen/MiniPlayer.kt b/app/src/main/java/com/maxrave/simpmusic/ui/screen/MiniPlayer.kt index 02f2856c..f41c7379 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/screen/MiniPlayer.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/screen/MiniPlayer.kt @@ -287,7 +287,7 @@ fun MiniPlayer( placeholder = painterResource(R.drawable.holder), error = painterResource(R.drawable.holder), contentDescription = null, - contentScale = ContentScale.Crop, + contentScale = ContentScale.FillWidth, onSuccess = { bitmap = it.result.image.toBitmap().asImageBitmap() }, diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/screen/other/AlbumScreen.kt b/app/src/main/java/com/maxrave/simpmusic/ui/screen/other/AlbumScreen.kt new file mode 100644 index 00000000..49390f11 --- /dev/null +++ b/app/src/main/java/com/maxrave/simpmusic/ui/screen/other/AlbumScreen.kt @@ -0,0 +1,481 @@ +package com.maxrave.simpmusic.ui.screen.other + +import android.os.Bundle +import android.widget.Toast +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.Crossfade +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.media3.common.util.UnstableApi +import androidx.navigation.NavController +import coil3.compose.AsyncImage +import coil3.request.CachePolicy +import coil3.request.ImageRequest +import coil3.request.crossfade +import coil3.toBitmap +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.LottieConstants.IterateForever +import com.airbnb.lottie.compose.rememberLottieComposition +import com.kmpalette.rememberPaletteState +import com.maxrave.simpmusic.R +import com.maxrave.simpmusic.common.DownloadState +import com.maxrave.simpmusic.data.model.browse.album.Track +import com.maxrave.simpmusic.extension.angledGradientBackground +import com.maxrave.simpmusic.extension.getColorFromPalette +import com.maxrave.simpmusic.extension.navigateSafe +import com.maxrave.simpmusic.extension.toSongEntity +import com.maxrave.simpmusic.ui.component.CenterLoadingBox +import com.maxrave.simpmusic.ui.component.DescriptionView +import com.maxrave.simpmusic.ui.component.EndOfPage +import com.maxrave.simpmusic.ui.component.NowPlayingBottomSheet +import com.maxrave.simpmusic.ui.component.RippleIconButton +import com.maxrave.simpmusic.ui.component.SongFullWidthItems +import com.maxrave.simpmusic.ui.theme.md_theme_dark_background +import com.maxrave.simpmusic.ui.theme.typo +import com.maxrave.simpmusic.viewModel.AlbumViewModel +import com.maxrave.simpmusic.viewModel.uiState.LocalPlaylistState +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged +import org.koin.androidx.compose.koinViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@UnstableApi +fun AlbumScreen( + browseId: String, + navController: NavController, + viewModel: AlbumViewModel = koinViewModel() +) { + val context = LocalContext.current + val uriHandler = LocalUriHandler.current + + val playingVideoId by viewModel.nowPlayingVideoId.collectAsStateWithLifecycle() + + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + var showBottomSheet by rememberSaveable { mutableStateOf(false) } + var chosenSong: Track? by remember { mutableStateOf(null) } + + val composition by rememberLottieComposition( + LottieCompositionSpec.RawRes(R.raw.downloading_animation), + ) + + LaunchedEffect(browseId) { + viewModel.updateBrowseId(browseId) + } + + val lazyState = rememberLazyListState() + val firstItemVisible by remember { + derivedStateOf { + lazyState.firstVisibleItemIndex == 0 + } + } + var shouldHideTopBar by rememberSaveable { mutableStateOf(false) } + LaunchedEffect(key1 = firstItemVisible) { + shouldHideTopBar = !firstItemVisible + } + val paletteState = rememberPaletteState() + var bitmap by remember { + mutableStateOf(null) + } + + LaunchedEffect(bitmap) { + val bm = bitmap + if (bm != null) { + paletteState.generate(bm) + } + } + + LaunchedEffect(Unit) { + snapshotFlow { paletteState.palette } + .distinctUntilChanged() + .collectLatest { + viewModel.setBrush(listOf(it.getColorFromPalette(), md_theme_dark_background)) + } + } + + Crossfade(uiState.loadState) { + when (it) { + LocalPlaylistState.PlaylistLoadState.Success -> { + LazyColumn( + modifier = + Modifier + .fillMaxWidth() + .background(Color.Black), + state = lazyState, + ) { + item(contentType = "header") { + Box( + modifier = + Modifier + .fillMaxWidth() + .wrapContentHeight() + .background(Color.Transparent), + ) { + Box( + modifier = + Modifier + .fillMaxWidth(), + ) { + Box( + modifier = + Modifier + .fillMaxWidth() + .aspectRatio(1f) + .clip( + RoundedCornerShape(8.dp), + ).angledGradientBackground(uiState.colors, 25f), + ) + Box( + modifier = + Modifier + .fillMaxWidth() + .height(180.dp) + .align(Alignment.BottomCenter) + .background( + brush = + Brush.verticalGradient( + listOf( + Color.Transparent, + Color(0x75000000), + Color.Black, + ), + ), + ), + ) + } + Column( + Modifier + .background(Color.Transparent), + ) { + Row( + modifier = + Modifier + .wrapContentWidth() + .padding(16.dp) + .windowInsetsPadding(WindowInsets.statusBars), + ) { + RippleIconButton( + resId = R.drawable.baseline_arrow_back_ios_new_24, + ) { + navController.popBackStack() + } + } + Column( + horizontalAlignment = Alignment.Start, + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(uiState.thumbnail) + .diskCachePolicy(CachePolicy.ENABLED) + .diskCacheKey(uiState.thumbnail) + .crossfade(true) + .build(), + placeholder = painterResource(R.drawable.holder), + error = painterResource(R.drawable.holder), + contentDescription = null, + contentScale = ContentScale.FillHeight, + onSuccess = { + bitmap = it.result.image.toBitmap().asImageBitmap() + }, + modifier = + Modifier + .height(250.dp) + .wrapContentWidth() + .align(Alignment.CenterHorizontally) + .clip( + RoundedCornerShape(8.dp), + ), + ) + Box( + modifier = + Modifier + .fillMaxWidth() + .wrapContentHeight(), + ) { + Column(Modifier.padding(horizontal = 32.dp)) { + Spacer(modifier = Modifier.size(25.dp)) + Text( + text = uiState.title, + style = typo.titleLarge, + color = Color.White, + ) + Column( + modifier = Modifier.padding(vertical = 8.dp), + ) { + // Author clickable + Text( + text = uiState.artist.name, + style = typo.titleSmall, + color = Color.White, + modifier = Modifier.clickable { + navController.navigateSafe(R.id.action_global_artistFragment, Bundle().apply { + putString("channelId", uiState.artist.id ?: return@clickable) + }) + } + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = + stringResource( + id = R.string.year_and_category, uiState.year, stringResource(R.string.album) + ), + style = typo.bodyMedium, + color = Color(0xC4FFFFFF), + ) + } + Row( + modifier = + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + RippleIconButton( + resId = R.drawable.baseline_play_circle_24, + fillMaxSize = true, + modifier = Modifier.size(36.dp), + ) { + viewModel.playTrack(uiState.listTrack.firstOrNull() ?: return@RippleIconButton) + } + Spacer(modifier = Modifier.size(5.dp)) + Crossfade(targetState = uiState.downloadState) { + when (it) { + DownloadState.STATE_DOWNLOADED -> { + Box( + modifier = + Modifier + .size(36.dp) + .clip( + CircleShape, + ) + .clickable { + Toast + .makeText( + context, + context.getString(R.string.downloaded), + Toast.LENGTH_SHORT, + ) + .show() + }, + ) { + Icon( + painter = painterResource(id = R.drawable.baseline_downloaded), + tint = Color(0xFF00A0CB), + contentDescription = "", + modifier = + Modifier + .size(36.dp) + .padding(2.dp), + ) + } + } + + DownloadState.STATE_DOWNLOADING -> { + Box( + modifier = + Modifier + .size(36.dp) + .clip( + CircleShape, + ) + .clickable { + Toast + .makeText( + context, + context.getString(R.string.downloading), + Toast.LENGTH_SHORT, + ) + .show() + }, + ) { + LottieAnimation( + composition, + iterations = IterateForever, + modifier = Modifier.fillMaxSize(), + ) + } + } + + else -> { + RippleIconButton( + fillMaxSize = true, + resId = R.drawable.download_button, + modifier = Modifier.size(36.dp), + ) { + viewModel.downloadFullAlbum() + } + } + } + } + Spacer(Modifier.weight(1f)) + Spacer(Modifier.size(5.dp)) + RippleIconButton( + modifier = + Modifier.size(36.dp), + resId = R.drawable.baseline_shuffle_24, + fillMaxSize = true, + ) { + viewModel.shuffle() + } + } + DescriptionView( + text = uiState.description?.let { + it.ifEmpty { null } + } ?: stringResource(R.string.no_description), + onTimeClicked = { raw -> + // Don't handle time click + }, + onURLClicked = { url -> + uriHandler.openUri( + url, + ) + }, + modifier = Modifier.padding(vertical = 8.dp) + ) + Text( + text = + stringResource( + id = R.string.album_length, + (uiState.trackCount).toString(), + uiState.length, + ), + color = Color.White, + style = typo.bodyMedium, + modifier = Modifier.padding(vertical = 8.dp), + ) + } + } + } + } + } + } + items(count = uiState.trackCount, key = { index -> + val item = uiState.listTrack.getOrNull(index) + item?.videoId ?: "item_$index" + }) { index -> + val item = uiState.listTrack.getOrNull(index) + if (item != null) { + SongFullWidthItems( + isPlaying = item.videoId == playingVideoId, + index = index, + track = item, + onMoreClickListener = { + chosenSong = item + showBottomSheet = true + }, + onClickListener = { + viewModel.playTrack(item) + }, + modifier = Modifier.animateItem(), + ) + } + } + item { + EndOfPage() + } + } + AnimatedVisibility( + visible = shouldHideTopBar, + enter = fadeIn() + slideInVertically(), + exit = fadeOut() + slideOutVertically(), + ) { + TopAppBar( + title = { + Text( + text = uiState.title, + style = typo.titleMedium, + ) + }, + navigationIcon = { + Box(Modifier.padding(horizontal = 5.dp)) { + RippleIconButton( + R.drawable.baseline_arrow_back_ios_new_24, + Modifier + .size(32.dp), + true, + ) { + navController.popBackStack() + } + } + }, + colors = + TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + ), + modifier = Modifier.angledGradientBackground(uiState.colors, 90f), + ) + } + if (showBottomSheet) { + NowPlayingBottomSheet( + onDismiss = { + showBottomSheet = false + chosenSong = null + }, + navController = navController, + song = chosenSong?.toSongEntity() + ) + } + } + + LocalPlaylistState.PlaylistLoadState.Error -> { + navController.navigateUp() + } + LocalPlaylistState.PlaylistLoadState.Loading -> { + CenterLoadingBox( + modifier = Modifier.fillMaxSize() + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/screen/player/NowPlayingScreen.kt b/app/src/main/java/com/maxrave/simpmusic/ui/screen/player/NowPlayingScreen.kt index 41a0726a..35b1ca77 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/screen/player/NowPlayingScreen.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/screen/player/NowPlayingScreen.kt @@ -2,7 +2,6 @@ package com.maxrave.simpmusic.ui.screen.player -import android.content.res.Configuration import android.util.Log import androidx.compose.animation.Animatable import androidx.compose.animation.AnimatedVisibility @@ -99,7 +98,6 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp @@ -127,7 +125,6 @@ import com.maxrave.simpmusic.ui.component.HeartCheckBox import com.maxrave.simpmusic.ui.component.LyricsView import com.maxrave.simpmusic.ui.component.MediaPlayerView import com.maxrave.simpmusic.ui.component.NowPlayingBottomSheet -import com.maxrave.simpmusic.ui.theme.AppTheme import com.maxrave.simpmusic.ui.theme.blackMoreOverlay import com.maxrave.simpmusic.ui.theme.md_theme_dark_background import com.maxrave.simpmusic.ui.theme.overlay @@ -1562,13 +1559,4 @@ fun NowPlayingScreen( } } } -} - -@UnstableApi -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=1080px,height=5000px,dpi=440") -@Composable -fun NowPlayingScreenPreview() { - AppTheme { -// NowPlayingScreen() - } } \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/viewModel/AlbumViewModel.kt b/app/src/main/java/com/maxrave/simpmusic/viewModel/AlbumViewModel.kt index 2b9a76bb..1b0bb98f 100644 --- a/app/src/main/java/com/maxrave/simpmusic/viewModel/AlbumViewModel.kt +++ b/app/src/main/java/com/maxrave/simpmusic/viewModel/AlbumViewModel.kt @@ -1,32 +1,32 @@ package com.maxrave.simpmusic.viewModel import android.app.Application -import android.graphics.drawable.GradientDrawable import android.util.Log -import android.widget.Toast -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData +import androidx.compose.ui.graphics.Color import androidx.lifecycle.viewModelScope import androidx.media3.common.util.UnstableApi import com.maxrave.simpmusic.R +import com.maxrave.simpmusic.common.Config import com.maxrave.simpmusic.common.DownloadState -import com.maxrave.simpmusic.common.SELECTED_LANGUAGE -import com.maxrave.simpmusic.data.db.entities.AlbumEntity -import com.maxrave.simpmusic.data.db.entities.LocalPlaylistEntity -import com.maxrave.simpmusic.data.db.entities.PairSongLocalPlaylist -import com.maxrave.simpmusic.data.db.entities.SongEntity -import com.maxrave.simpmusic.data.model.browse.album.AlbumBrowse import com.maxrave.simpmusic.data.model.browse.album.Track +import com.maxrave.simpmusic.data.model.searchResult.songs.Artist +import com.maxrave.simpmusic.extension.toAlbumEntity +import com.maxrave.simpmusic.extension.toArrayListTrack +import com.maxrave.simpmusic.extension.toSongEntity +import com.maxrave.simpmusic.service.PlaylistType +import com.maxrave.simpmusic.service.QueueData import com.maxrave.simpmusic.service.test.download.DownloadUtils import com.maxrave.simpmusic.utils.Resource import com.maxrave.simpmusic.viewModel.base.BaseViewModel -import kotlinx.coroutines.Dispatchers +import com.maxrave.simpmusic.viewModel.uiState.AlbumUIState +import com.maxrave.simpmusic.viewModel.uiState.LocalPlaylistState +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.singleOrNull +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext import org.koin.android.annotation.KoinViewModel import org.koin.core.component.inject import java.time.LocalDateTime @@ -41,283 +41,210 @@ class AlbumViewModel( override val tag: String = "AlbumViewModel" - var gradientDrawable: MutableLiveData = MutableLiveData() - private var _loading: MutableStateFlow = MutableStateFlow(false) - var loading: MutableStateFlow = _loading - - private var _albumBrowse: MutableStateFlow?> = MutableStateFlow(null) - val albumBrowse: StateFlow?> = _albumBrowse - - private var _browseId: MutableStateFlow = MutableStateFlow(null) - val browseId: StateFlow = _browseId - - private var _albumEntity: MutableStateFlow = MutableStateFlow(null) - val albumEntity: StateFlow = _albumEntity - - private var _liked: MutableStateFlow = MutableStateFlow(false) - var liked: MutableStateFlow = _liked - private var regionCode: String? = null - private var language: String? = null - - init { - regionCode = runBlocking { dataStoreManager.location.first() } - language = runBlocking { dataStoreManager.getString(SELECTED_LANGUAGE).first() } - } - - fun updateBrowseId(browseId: String){ - _browseId.value = browseId - } - fun browseAlbum(browseId: String){ - loading.value = true - viewModelScope.launch { - mainRepository.getAlbumData(browseId).collect { values -> - Log.w("AlbumViewModel", "Browse Album $values") - _albumBrowse.value = values - withContext(Dispatchers.Main){ - loading.value = false - } - } - } - } - - fun insertAlbum(albumEntity: AlbumEntity){ - viewModelScope.launch { - mainRepository.insertAlbum(albumEntity) - mainRepository.getAlbum(albumEntity.browseId).collect{ values -> - _liked.value = values.liked - val list = values.tracks - var count = 0 - list?.forEach { track -> - mainRepository.getSongById(track).collect { song -> - if (song != null) { - if (song.downloadState == DownloadState.STATE_DOWNLOADED) { - count++ + private val _uiState: MutableStateFlow = MutableStateFlow(AlbumUIState.initial()) + val uiState: StateFlow = _uiState + + private var job: Job? = null + private var collectDownloadStateJob: Job? = null + + fun updateBrowseId(browseId: String) { + viewModelScope.launch { + _uiState.update { it.copy(browseId = browseId) } + mainRepository.getAlbumData(browseId).collectLatest { res -> + when (res) { + is Resource.Success -> { + val data = res.data + if (data != null) { + _uiState.update { + it.copy( + browseId = browseId, + title = data.title, + thumbnail = data.thumbnails?.lastOrNull()?.url, + artist = data.artists.firstOrNull() ?: Artist( + id = null, + name = "" + ), + year = data.year ?: LocalDateTime.now().year.toString(), + trackCount = data.trackCount, + description = data.description, + length = data.duration ?: "", + listTrack = data.tracks, + loadState = LocalPlaylistState.PlaylistLoadState.Success + ) + } + mainRepository.getAlbum(browseId).singleOrNull().let { album -> + if (album != null) { + _uiState.update { + it.copy( + downloadState = album.downloadState, + liked = album.liked + ) + } + mainRepository.updateAlbumInLibrary(LocalDateTime.now(), browseId) + } else { + mainRepository.insertAlbum(data.toAlbumEntity(browseId)).singleOrNull().let { + log("Insert Album $it", Log.DEBUG) + data.tracks.forEach { track -> + mainRepository.insertSong(track.toSongEntity()).singleOrNull()?.let { + log("Insert Song $it", Log.DEBUG) + } + } + } + } + } + getAlbumFlow(browseId) + } else { + makeToast(getString(R.string.error) + ": Null data") + _uiState.update { + it.copy( + loadState = LocalPlaylistState.PlaylistLoadState.Error + ) } } } - } - if (count == list?.size) { - updateAlbumDownloadState(albumEntity.browseId, DownloadState.STATE_DOWNLOADED) - } - else { - updateAlbumDownloadState(albumEntity.browseId, DownloadState.STATE_NOT_DOWNLOADED) - } - mainRepository.getAlbum(albumEntity.browseId).collect { album -> - _albumEntity.value = album - } - } - } - } - - fun getAlbum(browseId: String){ - viewModelScope.launch { - mainRepository.getAlbum(browseId).collect{ values -> - _liked.value = values.liked - val list = values.tracks - var count = 0 - list?.forEach { track -> - mainRepository.getSongById(track).collect { song -> - if (song != null) { - if (song.downloadState == DownloadState.STATE_DOWNLOADED) { - count++ + is Resource.Error -> { + mainRepository.getAlbum(browseId).singleOrNull().let { albumEntity -> + if (albumEntity != null) { + _uiState.update { + it.copy( + browseId = browseId, + title = albumEntity.title, + thumbnail = albumEntity.thumbnails, + artist = Artist( + id = albumEntity.artistId?.firstOrNull(), + name = albumEntity.artistName?.firstOrNull() ?: "" + ), + year = albumEntity.year ?: LocalDateTime.now().year.toString(), + trackCount = albumEntity.trackCount, + description = albumEntity.description, + length = albumEntity.duration ?: "", + listTrack = (mainRepository.getSongsByListVideoId(albumEntity.tracks ?: emptyList()) + .singleOrNull() ?: emptyList()).toArrayListTrack(), + loadState = LocalPlaylistState.PlaylistLoadState.Success + ) + } + } else { + log("Error: ${res.message}", Log.ERROR) + makeToast(getString(R.string.error) + ": ${res.message}") + _uiState.update { + it.copy( + loadState = LocalPlaylistState.PlaylistLoadState.Error + ) + } } } } } - if (count == list?.size) { - updateAlbumDownloadState(browseId, DownloadState.STATE_DOWNLOADED) - } - else { - updateAlbumDownloadState(browseId, DownloadState.STATE_NOT_DOWNLOADED) - } - mainRepository.getAlbum(browseId).collect { album -> - _albumEntity.value = album - } } } } - fun updateAlbumLiked(liked: Boolean, browseId: String){ - viewModelScope.launch { - val tempLiked = if(liked) 1 else 0 - mainRepository.updateAlbumLiked(browseId, tempLiked) - mainRepository.getAlbum(browseId).collect{ values -> - _albumEntity.value = values - _liked.value = values.liked - } + fun setBrush(brush: List) { + _uiState.update { + it.copy( + colors = brush, + ) } } - val albumDownloadState: MutableStateFlow = MutableStateFlow(DownloadState.STATE_NOT_DOWNLOADED) - - private fun updateAlbumDownloadState(browseId: String, state: Int) { - viewModelScope.launch { - mainRepository.getAlbum(browseId).collect { album -> - _albumEntity.value = album - mainRepository.updateAlbumDownloadState(browseId, state) - albumDownloadState.value = state + private fun getAlbumFlow(browseId: String) { + job?.cancel() + collectDownloadStateJob?.cancel() + job = viewModelScope.launch { + mainRepository.getAlbumAsFlow(browseId).collectLatest { album -> + if (album != null) { + _uiState.update { + it.copy( + downloadState = album.downloadState, + liked = album.liked + ) + } + } } } - } - - fun checkAllSongDownloaded(list: ArrayList) { - viewModelScope.launch { - var count = 0 - list.forEach { track -> - mainRepository.getSongById(track.videoId).collect { song -> - if (song != null) { - if (song.downloadState == DownloadState.STATE_DOWNLOADED) { - count++ - } + collectDownloadStateJob = viewModelScope.launch { + downloadUtils.downloadTask.collectLatest { downloadTask -> + var count = 0 + uiState.value.listTrack.forEach { track -> + if (downloadTask.get(track.videoId) == DownloadState.STATE_DOWNLOADED) { + count++ } } - } - if (count == list.size) { - updateAlbumDownloadState(browseId.value!!, DownloadState.STATE_DOWNLOADED) - } - mainRepository.getAlbum(browseId.value!!).collect { album -> - if (albumEntity.value?.downloadState != album.downloadState) { - _albumEntity.value = album + if (count == uiState.value.listTrack.size) { + mainRepository.updateAlbumDownloadState(uiState.value.browseId, DownloadState.STATE_DOWNLOADED) + _uiState.update { + it.copy( + downloadState = DownloadState.STATE_DOWNLOADED + ) + } } } } } - val listJob: MutableStateFlow> = MutableStateFlow(arrayListOf()) - - fun updatePlaylistDownloadState(id: String, state: Int) { - viewModelScope.launch { - mainRepository.getAlbum(id).collect { playlist -> - _albumEntity.value = playlist - mainRepository.updateAlbumDownloadState(id, state) - albumDownloadState.value = state - } - } - } - fun updateDownloadState(videoId: String, state: Int) { - viewModelScope.launch { - mainRepository.updateDownloadState(videoId, state) - } - } - - @UnstableApi - fun getDownloadStateFromService(videoId: String) { - } - - private var _listTrack: MutableStateFlow?> = MutableStateFlow(null) - var listTrack: StateFlow?> = _listTrack - - fun getListTrack(tracks: List?) { - viewModelScope.launch { - mainRepository.getSongsByListVideoId(tracks!!).collect { values -> - _listTrack.value = values - } - } - } - private var _listTrackForDownload: MutableStateFlow?> = MutableStateFlow(null) - var listTrackForDownload: StateFlow?> = _listTrackForDownload - - fun getListTrackForDownload(tracks: List?) { - viewModelScope.launch { - mainRepository.getSongsByListVideoId(tracks!!).collect { values -> - _listTrackForDownload.value = values - } - } + fun playTrack(track: Track) { + setQueueData( + QueueData( + listTracks = uiState.value.listTrack.toCollection(ArrayList()), + firstPlayedTrack = track, + playlistId = uiState.value.browseId.replaceFirst("VL", ""), + playlistName = "${getString(R.string.album)} \"${uiState.value.title}\"", + playlistType = PlaylistType.PLAYLIST, + continuation = null, + ) + ) + val index = uiState.value.listTrack.indexOf(track) + loadMediaItem(track, Config.ALBUM_CLICK, if (index == -1) 0 else index) } - fun clearAlbumBrowse() { - _albumBrowse.value = null - _albumEntity.value = null - } - - fun getLocation() { - regionCode = runBlocking { dataStoreManager.location.first() } - language = runBlocking { dataStoreManager.getString(SELECTED_LANGUAGE).first() } - } - - fun insertSong(songEntity: SongEntity) { - viewModelScope.launch { - mainRepository.insertSong(songEntity).collect { - println("Insert Song $it") - } + fun shuffle() { + if (uiState.value.listTrack.isEmpty()) { + makeToast(getString(R.string.playlist_is_empty)) + return } + val shuffleList = uiState.value.listTrack.shuffled() + val randomIndex = shuffleList.indices.random() + setQueueData( + QueueData( + listTracks = shuffleList.toCollection(ArrayList()), + firstPlayedTrack = shuffleList[randomIndex], + playlistId = uiState.value.browseId.replaceFirst("VL", ""), + playlistName = "${getString(R.string.album)} \"${uiState.value.title}\"", + playlistType = PlaylistType.PLAYLIST, + continuation = null, + ) + ) + loadMediaItem(shuffleList[randomIndex], Config.ALBUM_CLICK, randomIndex) } - private var _songEntity: MutableLiveData = MutableLiveData(null) - val songEntity: LiveData = _songEntity - - private var _listLocalPlaylist: MutableLiveData> = MutableLiveData() - val listLocalPlaylist: LiveData> = _listLocalPlaylist - - fun getSongEntity(song: SongEntity) { + fun downloadFullAlbum() { viewModelScope.launch { - mainRepository.insertSong(song).first().let { - println("Insert song $it") + // Insert all song to database + uiState.value.listTrack.forEach { track -> + mainRepository.insertSong(track.toSongEntity()).singleOrNull()?.let { + log("Insert Song $it", Log.DEBUG) + } } - mainRepository.getSongById(song.videoId).collect { values -> - _songEntity.value = values + val fullListSong = mainRepository.getSongsByListVideoId(uiState.value.listTrack.map { it.videoId }) + .singleOrNull() ?: emptyList() + log("Full list song: $fullListSong", Log.DEBUG) + if (fullListSong.isEmpty()) { + makeToast(getString(R.string.playlist_is_empty)) + return@launch } - } - } - fun updateLikeStatus(videoId: String, likeStatus: Int) { - viewModelScope.launch { - mainRepository.updateLikeStatus(likeStatus = likeStatus, videoId = videoId) - } - } - fun getLocalPlaylist() { - viewModelScope.launch { - mainRepository.getAllLocalPlaylists().collect { values -> - _listLocalPlaylist.postValue(values) + val listJob = fullListSong.filter { it.downloadState != DownloadState.STATE_DOWNLOADED } + log("List job: $listJob", Log.DEBUG) + if (listJob.isEmpty()) { + makeToast(getString(R.string.downloaded)) + return@launch } - } - } - - fun updateInLibrary(videoId: String) { - viewModelScope.launch { - mainRepository.updateSongInLibrary(LocalDateTime.now(), videoId) - } - } - - fun addToYouTubePlaylist(localPlaylistId: Long, youtubePlaylistId: String, videoId: String) { - viewModelScope.launch { - mainRepository.updateLocalPlaylistYouTubePlaylistSyncState(localPlaylistId, LocalPlaylistEntity.YouTubeSyncState.Syncing) - mainRepository.addYouTubePlaylistItem(youtubePlaylistId, videoId).collect { response -> - if (response == "STATUS_SUCCEEDED") { - mainRepository.updateLocalPlaylistYouTubePlaylistSyncState(localPlaylistId, LocalPlaylistEntity.YouTubeSyncState.Synced) - Toast.makeText(application, application.getString(R.string.added_to_youtube_playlist), Toast.LENGTH_SHORT).show() - } - else { - mainRepository.updateLocalPlaylistYouTubePlaylistSyncState(localPlaylistId, LocalPlaylistEntity.YouTubeSyncState.NotSynced) - Toast.makeText(application, application.getString(R.string.error), Toast.LENGTH_SHORT).show() - } - } - } - } - fun insertPairSongLocalPlaylist(pairSongLocalPlaylist: PairSongLocalPlaylist) { - viewModelScope.launch { - mainRepository.insertPairSongLocalPlaylist(pairSongLocalPlaylist) - } - } - - fun updateLocalPlaylistTracks(list: List, id: Long) { - viewModelScope.launch { - mainRepository.getSongsByListVideoId(list).collect { values -> - var count = 0 - values.forEach { song -> - if (song.downloadState == DownloadState.STATE_DOWNLOADED){ - count++ - } - } - mainRepository.updateLocalPlaylistTracks(list, id) - Toast.makeText(getApplication(), application.getString(R.string.added_to_playlist), Toast.LENGTH_SHORT).show() - if (count == values.size) { - mainRepository.updateLocalPlaylistDownloadState(DownloadState.STATE_DOWNLOADED, id) - } - else { - mainRepository.updateLocalPlaylistDownloadState(DownloadState.STATE_NOT_DOWNLOADED, id) - } + mainRepository.updateAlbumDownloadState(uiState.value.browseId, DownloadState.STATE_DOWNLOADING) + listJob.forEach { + log("Download: ${it.videoId} ${it.thumbnails}", Log.DEBUG) + downloadUtils.downloadTrack( + it.videoId, it.title, it.thumbnails ?: "" + ) } } } - } \ No newline at end of file diff --git a/app/src/main/java/com/maxrave/simpmusic/viewModel/LocalPlaylistViewModel.kt b/app/src/main/java/com/maxrave/simpmusic/viewModel/LocalPlaylistViewModel.kt index 2c13cb6c..68c1183a 100644 --- a/app/src/main/java/com/maxrave/simpmusic/viewModel/LocalPlaylistViewModel.kt +++ b/app/src/main/java/com/maxrave/simpmusic/viewModel/LocalPlaylistViewModel.kt @@ -1,11 +1,9 @@ package com.maxrave.simpmusic.viewModel import android.app.Application -import android.graphics.drawable.GradientDrawable import android.util.Log import android.widget.Toast import androidx.compose.ui.graphics.Color -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import androidx.media3.common.util.UnstableApi import androidx.paging.LoadState @@ -76,8 +74,6 @@ class LocalPlaylistViewModel( _offset.value = offset } - var gradientDrawable: MutableLiveData = MutableLiveData() - var loading: MutableStateFlow = MutableStateFlow(false) private val _uiState: MutableStateFlow = MutableStateFlow(LocalPlaylistState.initial()) diff --git a/app/src/main/java/com/maxrave/simpmusic/viewModel/uiState/AlbumUIState.kt b/app/src/main/java/com/maxrave/simpmusic/viewModel/uiState/AlbumUIState.kt new file mode 100644 index 00000000..b1a43f84 --- /dev/null +++ b/app/src/main/java/com/maxrave/simpmusic/viewModel/uiState/AlbumUIState.kt @@ -0,0 +1,31 @@ +package com.maxrave.simpmusic.viewModel.uiState + +import androidx.compose.ui.graphics.Color +import com.maxrave.simpmusic.common.DownloadState +import com.maxrave.simpmusic.data.model.browse.album.Track +import com.maxrave.simpmusic.data.model.searchResult.songs.Artist +import com.maxrave.simpmusic.ui.theme.md_theme_dark_background +import java.time.LocalDateTime + +data class AlbumUIState( + val browseId: String = "", + val title: String = "", + val thumbnail: String? = null, + val colors: List = listOf(Color.Black, md_theme_dark_background), + val artist: Artist = Artist( + id = null, name = "" + ), + val year: String = LocalDateTime.now().year.toString(), + val downloadState: Int = DownloadState.STATE_NOT_DOWNLOADED, + val liked: Boolean = false, + val trackCount: Int = 0, + val description: String? = null, + val length: String = "", + val listTrack: List = emptyList(), + val loadState: LocalPlaylistState.PlaylistLoadState = LocalPlaylistState.PlaylistLoadState.Loading, +) { + companion object { + fun initial(): AlbumUIState = + AlbumUIState() + } +} \ No newline at end of file diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/pages/RelatedPage.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/pages/RelatedPage.kt index af2b54e0..9d6492dc 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/pages/RelatedPage.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/pages/RelatedPage.kt @@ -100,19 +100,27 @@ data class RelatedPage( renderer.thumbnailRenderer.musicThumbnailRenderer?.getThumbnailUrl() ?: return null, playEndpoint = - renderer.thumbnailOverlay - ?.musicItemThumbnailOverlayRenderer?.content - ?.musicPlayButtonRenderer?.playNavigationEndpoint - ?.watchPlaylistEndpoint ?: return null, + renderer.thumbnailOverlay + ?.musicItemThumbnailOverlayRenderer?.content + ?.musicPlayButtonRenderer?.playNavigationEndpoint + ?.watchPlaylistEndpoint ?: return null, + // If the playlist is radio, shuffle is not available shuffleEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint - ?: return null, + ?: renderer.thumbnailOverlay + .musicItemThumbnailOverlayRenderer.content + .musicPlayButtonRenderer.playNavigationEndpoint + .watchPlaylistEndpoint, radioEndpoint = - renderer.menu.menuRenderer.items.find { - it.menuNavigationItemRenderer?.icon?.iconType == "MIX" - }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint, + renderer.menu?.menuRenderer?.items?.find { + it.menuNavigationItemRenderer?.icon?.iconType == "MIX" + }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint + ?: renderer.thumbnailOverlay + .musicItemThumbnailOverlayRenderer.content + .musicPlayButtonRenderer.playNavigationEndpoint + .watchPlaylistEndpoint, ) renderer.isArtist -> {