/* * Copyright (c) 2020 Hemanth Savarla. * * Licensed under the GNU General Public License v3 * * This is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * */ package code.name.monkey.retromusic.fragments.player.classic import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.Color import android.graphics.PorterDuff import android.os.Bundle import android.view.View import android.view.animation.LinearInterpolator import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.core.view.ViewCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.retromusic.R import code.name.monkey.retromusic.RetroBottomSheetBehavior import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter import code.name.monkey.retromusic.databinding.FragmentClassicPlayerBinding import code.name.monkey.retromusic.extensions.getSongInfo import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.goToAlbum import code.name.monkey.retromusic.fragments.base.goToArtist import code.name.monkey.retromusic.fragments.other.VolumeFragment import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.color.MediaNotificationProcessor import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.card.MaterialCardView import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.ShapeAppearanceModel import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils class ClassicPlayerFragment : AbsPlayerFragment(R.layout.fragment_classic_player), View.OnLayoutChangeListener, MusicProgressViewUpdateHelper.Callback { private var _binding: FragmentClassicPlayerBinding? = null private val binding get() = _binding!! private var lastColor: Int = 0 private var lastPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0 private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper private var volumeFragment: VolumeFragment? = null private lateinit var shapeDrawable: MaterialShapeDrawable private lateinit var wrappedAdapter: RecyclerView.Adapter<*> private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null private var playingQueueAdapter: PlayingQueueAdapter? = null private lateinit var linearLayoutManager: LinearLayoutManager private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() { override fun onSlide(bottomSheet: View, slideOffset: Float) { mainActivity.getBottomSheetBehavior().setAllowDragging(false) binding.playerQueueSheet.setContentPadding( binding.playerQueueSheet.contentPaddingLeft, (slideOffset * binding.statusBar.height).toInt(), binding.playerQueueSheet.contentPaddingRight, binding.playerQueueSheet.contentPaddingBottom ) shapeDrawable.interpolation = 1 - slideOffset } override fun onStateChanged(bottomSheet: View, newState: Int) { when (newState) { BottomSheetBehavior.STATE_EXPANDED, BottomSheetBehavior.STATE_DRAGGING -> { mainActivity.getBottomSheetBehavior().setAllowDragging(false) } BottomSheetBehavior.STATE_COLLAPSED -> { resetToCurrentPosition() mainActivity.getBottomSheetBehavior().setAllowDragging(true) } else -> { mainActivity.getBottomSheetBehavior().setAllowDragging(true) } } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) } @SuppressLint("ClickableViewAccessibility") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) _binding = FragmentClassicPlayerBinding.bind(view) setupPanel() setUpMusicControllers() setUpPlayerToolbar() hideVolumeIfAvailable() setupRecyclerView() val coverFragment = childFragmentManager.findFragmentById(R.id.playerAlbumCoverFragment) as PlayerAlbumCoverFragment coverFragment.setCallbacks(this) getQueuePanel().addBottomSheetCallback(bottomSheetCallbackList) shapeDrawable = MaterialShapeDrawable( ShapeAppearanceModel.builder( requireContext(), R.style.ClassicThemeOverLay, 0 ).build() ) shapeDrawable.fillColor = ColorStateList.valueOf(ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)) binding.playerQueueSheet.background = shapeDrawable binding.playerQueueSheet.setOnTouchListener { _, _ -> mainActivity.getBottomSheetBehavior().setAllowDragging(false) getQueuePanel().setAllowDragging(true) return@setOnTouchListener false } ToolbarContentTintHelper.colorizeToolbar( binding.playerToolbar, Color.WHITE, requireActivity() ) binding.title.setOnClickListener { goToAlbum(requireActivity()) } binding.text.setOnClickListener { goToArtist(requireActivity()) } } private fun hideVolumeIfAvailable() { if (PreferenceUtil.isVolumeVisibilityMode) { childFragmentManager.beginTransaction() .replace(R.id.volumeFragmentContainer, VolumeFragment.newInstance()) .commit() childFragmentManager.executePendingTransactions() volumeFragment = childFragmentManager.findFragmentById(R.id.volumeFragmentContainer) as VolumeFragment? } } override fun onDestroyView() { super.onDestroyView() getQueuePanel().removeBottomSheetCallback(bottomSheetCallbackList) if (recyclerViewDragDropManager != null) { recyclerViewDragDropManager?.release() recyclerViewDragDropManager = null } if (recyclerViewSwipeManager != null) { recyclerViewSwipeManager?.release() recyclerViewSwipeManager = null } WrapperAdapterUtils.releaseAll(wrappedAdapter) _binding = null } private fun updateSong() { val song = MusicPlayerRemote.currentSong binding.title.text = song.title binding.text.text = song.artistName if (PreferenceUtil.isSongInfo) { binding.playerControlsContainer.songInfo.text = getSongInfo(song) binding.playerControlsContainer.songInfo.show() } else { binding.playerControlsContainer.songInfo.hide() } } override fun onResume() { super.onResume() progressViewUpdateHelper.start() } override fun onPause() { recyclerViewDragDropManager?.cancelDrag() super.onPause() progressViewUpdateHelper.stop() } override fun onServiceConnected() { super.onServiceConnected() updateSong() updatePlayPauseDrawableState() updateQueue() } override fun onPlayStateChanged() { updatePlayPauseDrawableState() } override fun onRepeatModeChanged() { updateRepeatState() } override fun onShuffleModeChanged() { updateShuffleState() } override fun onPlayingMetaChanged() { super.onPlayingMetaChanged() updateSong() updateQueuePosition() } override fun onQueueChanged() { super.onQueueChanged() updateQueue() } override fun playerToolbar(): Toolbar { return binding.playerToolbar } override fun onShow() { } override fun onHide() { } override fun onBackPressed(): Boolean { var wasExpanded = false if (getQueuePanel().state == BottomSheetBehavior.STATE_EXPANDED) { wasExpanded = getQueuePanel().state == BottomSheetBehavior.STATE_EXPANDED getQueuePanel().state = BottomSheetBehavior.STATE_COLLAPSED return wasExpanded } return wasExpanded } override fun toolbarIconColor(): Int { return Color.WHITE } override val paletteColor: Int get() = lastColor override fun onColorChanged(color: MediaNotificationProcessor) { lastColor = color.backgroundColor libraryViewModel.updateColor(color.backgroundColor) lastPlaybackControlsColor = color.primaryTextColor lastDisabledPlaybackControlsColor = ColorUtil.withAlpha(color.primaryTextColor, 0.3f) binding.playerContainer.setBackgroundColor(color.backgroundColor) binding.playerControlsContainer.songInfo.setTextColor(color.primaryTextColor) binding.playerQueueSubHeader.setTextColor(color.primaryTextColor) binding.playerControlsContainer.songCurrentProgress.setTextColor(lastPlaybackControlsColor) binding.playerControlsContainer.songTotalTime.setTextColor(lastPlaybackControlsColor) ViewUtil.setProgressDrawable( binding.playerControlsContainer.progressSlider, color.primaryTextColor, true ) volumeFragment?.setTintableColor(color.primaryTextColor) TintHelper.setTintAuto( binding.playerControlsContainer.playPauseButton, color.primaryTextColor, true ) TintHelper.setTintAuto( binding.playerControlsContainer.playPauseButton, color.backgroundColor, false ) updateRepeatState() updateShuffleState() updatePrevNextColor() ToolbarContentTintHelper.colorizeToolbar( binding.playerToolbar, Color.WHITE, requireActivity() ) } override fun toggleFavorite(song: Song) { super.toggleFavorite(song) if (song.id == MusicPlayerRemote.currentSong.id) { updateIsFavorite() } } override fun onFavoriteToggled() { toggleFavorite(MusicPlayerRemote.currentSong) } override fun onUpdateProgressViews(progress: Int, total: Int) { binding.playerControlsContainer.progressSlider.max = total val animator = ObjectAnimator.ofInt( binding.playerControlsContainer.progressSlider, "progress", progress ) animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME animator.interpolator = LinearInterpolator() animator.start() binding.playerControlsContainer.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) binding.playerControlsContainer.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) } private fun updateQueuePosition() { playingQueueAdapter?.setCurrent(MusicPlayerRemote.position) resetToCurrentPosition() } private fun updateQueue() { playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position) resetToCurrentPosition() } private fun resetToCurrentPosition() { binding.recyclerView.stopScroll() linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) } private fun getQueuePanel(): RetroBottomSheetBehavior { return RetroBottomSheetBehavior.from(binding.playerQueueSheet) as RetroBottomSheetBehavior } private fun setupPanel() { if (!ViewCompat.isLaidOut(binding.playerContainer) || binding.playerContainer.isLayoutRequested) { binding.playerContainer.addOnLayoutChangeListener(this) return } val height = binding.playerContainer.height val width = binding.playerContainer.width val finalHeight = height - width val panel = getQueuePanel() panel.peekHeight = finalHeight } private fun setUpPlayerToolbar() { binding.playerToolbar.inflateMenu(R.menu.menu_player) binding.playerToolbar.setNavigationOnClickListener { requireActivity().onBackPressed() } binding.playerToolbar.setOnMenuItemClickListener(this) ToolbarContentTintHelper.colorizeToolbar( binding.playerToolbar, Color.WHITE, requireActivity() ) } private fun setupRecyclerView() { playingQueueAdapter = PlayingQueueAdapter( requireActivity() as AppCompatActivity, MusicPlayerRemote.playingQueue.toMutableList(), MusicPlayerRemote.position, R.layout.item_queue ) linearLayoutManager = LinearLayoutManager(requireContext()) recyclerViewTouchActionGuardManager = RecyclerViewTouchActionGuardManager() recyclerViewDragDropManager = RecyclerViewDragDropManager() recyclerViewSwipeManager = RecyclerViewSwipeManager() val animator = DraggableItemAnimator() animator.supportsChangeAnimations = false wrappedAdapter = recyclerViewDragDropManager?.createWrappedAdapter(playingQueueAdapter!!) as RecyclerView.Adapter<*> wrappedAdapter = recyclerViewSwipeManager?.createWrappedAdapter(wrappedAdapter) as RecyclerView.Adapter<*> binding.recyclerView.layoutManager = linearLayoutManager binding.recyclerView.adapter = wrappedAdapter binding.recyclerView.itemAnimator = animator recyclerViewTouchActionGuardManager?.attachRecyclerView(binding.recyclerView) recyclerViewDragDropManager?.attachRecyclerView(binding.recyclerView) recyclerViewSwipeManager?.attachRecyclerView(binding.recyclerView) linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) } fun setUpProgressSlider() { binding.playerControlsContainer.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { MusicPlayerRemote.seekTo(progress) onUpdateProgressViews( MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis ) } } }) } private fun setUpPlayPauseFab() { binding.playerControlsContainer.playPauseButton.setOnClickListener( PlayPauseButtonOnClickHandler() ) } private fun updatePlayPauseDrawableState() { if (MusicPlayerRemote.isPlaying) { binding.playerControlsContainer.playPauseButton.setImageResource(R.drawable.ic_pause) } else { binding.playerControlsContainer.playPauseButton.setImageResource(R.drawable.ic_play_arrow) } } private fun setUpMusicControllers() { setUpPlayPauseFab() setUpPrevNext() setUpRepeatButton() setUpShuffleButton() setUpProgressSlider() } private fun setUpPrevNext() { updatePrevNextColor() binding.playerControlsContainer.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() } binding.playerControlsContainer.previousButton.setOnClickListener { MusicPlayerRemote.back() } } private fun updatePrevNextColor() { binding.playerControlsContainer.nextButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) binding.playerControlsContainer.previousButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } private fun setUpShuffleButton() { binding.playerControlsContainer.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } } fun updateShuffleState() { when (MusicPlayerRemote.shuffleMode) { MusicService.SHUFFLE_MODE_SHUFFLE -> binding.playerControlsContainer.shuffleButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) else -> binding.playerControlsContainer.shuffleButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } } private fun setUpRepeatButton() { binding.playerControlsContainer.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } } fun updateRepeatState() { when (MusicPlayerRemote.repeatMode) { MusicService.REPEAT_MODE_NONE -> { binding.playerControlsContainer.repeatButton.setImageResource(R.drawable.ic_repeat) binding.playerControlsContainer.repeatButton.setColorFilter( lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_ALL -> { binding.playerControlsContainer.repeatButton.setImageResource(R.drawable.ic_repeat) binding.playerControlsContainer.repeatButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } MusicService.REPEAT_MODE_THIS -> { binding.playerControlsContainer.repeatButton.setImageResource(R.drawable.ic_repeat_one) binding.playerControlsContainer.repeatButton.setColorFilter( lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN ) } } } override fun onLayoutChange( v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int ) { val height = binding.playerContainer.height val width = binding.playerContainer.width val finalHeight = height - (binding.playerControlsContainer.root.height + width) val panel = getQueuePanel() panel.peekHeight = finalHeight } }