PlayerAndroid/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt

513 lines
19 KiB
Kotlin

package code.name.monkey.retromusic.fragments.player.gradient
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.PorterDuff
import android.os.AsyncTask
import android.os.Bundle
import android.view.View
import android.view.animation.LinearInterpolator
import android.widget.PopupMenu
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.RetroBottomSheetBehavior
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.VolumeFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
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.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
import kotlinx.android.synthetic.main.fragment_gradient_controls.*
import kotlinx.android.synthetic.main.fragment_gradient_player.*
import kotlinx.android.synthetic.main.status_bar.*
class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_player),
MusicProgressViewUpdateHelper.Callback,
View.OnLayoutChangeListener, PopupMenu.OnMenuItemClickListener {
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 wrappedAdapter: RecyclerView.Adapter<*>
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null
private var updateIsFavoriteTask: AsyncTask<*, *, *>? = null
private lateinit var linearLayoutManager: LinearLayoutManager
private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior()
.setAllowDragging(false)
playerQueueSheet.setPadding(
playerQueueSheet.paddingLeft,
(slideOffset * status_bar.height).toInt(),
playerQueueSheet.paddingRight,
playerQueueSheet.paddingBottom
)
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
val activity = requireActivity() as AbsSlidingMusicPanelActivity
when (newState) {
BottomSheetBehavior.STATE_EXPANDED,
BottomSheetBehavior.STATE_DRAGGING -> {
activity.getBottomSheetBehavior().setAllowDragging(false)
}
BottomSheetBehavior.STATE_COLLAPSED -> {
resetToCurrentPosition()
activity.getBottomSheetBehavior().setAllowDragging(true)
}
else -> {
activity.getBottomSheetBehavior().setAllowDragging(true)
}
}
}
}
private fun setupFavourite() {
songFavourite.setOnClickListener {
toggleFavorite(MusicPlayerRemote.currentSong)
}
}
private fun setupMenu() {
playerMenu.setOnClickListener {
val popupMenu = PopupMenu(requireContext(), it)
popupMenu.setOnMenuItemClickListener(this)
popupMenu.inflate(R.menu.menu_player)
popupMenu.show()
}
}
private fun setupPanel() {
if (!ViewCompat.isLaidOut(colorBackground) || colorBackground.isLayoutRequested) {
colorBackground.addOnLayoutChangeListener(this)
return
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
hideVolumeIfAvailable()
setUpMusicControllers()
setupPanel()
setupRecyclerView()
setupSheet()
setupMenu()
setupFavourite()
}
@SuppressLint("ClickableViewAccessibility")
private fun setupSheet() {
getQueuePanel().addBottomSheetCallback(bottomSheetCallbackList)
playerQueueSheet.setOnTouchListener { _, _ ->
(requireActivity() as AbsSlidingMusicPanelActivity).getBottomSheetBehavior()
.setAllowDragging(false)
getQueuePanel().setAllowDragging(true)
return@setOnTouchListener false
}
}
private fun getQueuePanel(): RetroBottomSheetBehavior<ConstraintLayout> {
return RetroBottomSheetBehavior.from(playerQueueSheet) as RetroBottomSheetBehavior<ConstraintLayout>
}
override fun onResume() {
super.onResume()
progressViewUpdateHelper.start()
}
override fun onPause() {
recyclerViewDragDropManager?.cancelDrag()
super.onPause()
progressViewUpdateHelper.stop()
}
override fun playerToolbar(): Toolbar? {
return null
}
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)
mask.backgroundTintList = ColorStateList.valueOf(color.backgroundColor)
colorBackground.setBackgroundColor(color.backgroundColor)
playerQueueSheet.setBackgroundColor(ColorUtil.darkenColor(color.backgroundColor))
lastPlaybackControlsColor = color.primaryTextColor
lastDisabledPlaybackControlsColor = ColorUtil.withAlpha(color.primaryTextColor, 0.3f)
title.setTextColor(lastPlaybackControlsColor)
text.setTextColor(lastDisabledPlaybackControlsColor)
playPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
songFavourite.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
queueIcon.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
playerMenu.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
songCurrentProgress.setTextColor(lastDisabledPlaybackControlsColor)
songTotalTime.setTextColor(lastDisabledPlaybackControlsColor)
nextSong.setTextColor(lastPlaybackControlsColor)
songInfo.setTextColor(lastDisabledPlaybackControlsColor)
volumeFragment?.setTintableColor(lastPlaybackControlsColor.ripAlpha())
ViewUtil.setProgressDrawable(progressSlider, color.primaryTextColor.ripAlpha(), true)
updateRepeatState()
updateShuffleState()
updatePrevNextColor()
}
override fun toggleFavorite(song: Song) {
super.toggleFavorite(song)
MusicUtil.toggleFavorite(requireContext(), song)
if (song.id == MusicPlayerRemote.currentSong.id) {
updateFavorite()
}
}
override fun onFavoriteToggled() {
toggleFavorite(MusicPlayerRemote.currentSong)
}
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 onServiceConnected() {
super.onServiceConnected()
updateSong()
updatePlayPauseDrawableState()
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()
updateLabel()
}
private fun updateSong() {
val song = MusicPlayerRemote.currentSong
title.text = song.title
text.text = song.artistName
updateLabel()
if (PreferenceUtil.isSongInfo) {
songInfo.text = getSongInfo(song)
songInfo.show()
} else {
songInfo.hide()
}
}
private fun setUpMusicControllers() {
setUpPlayPauseFab()
setUpPrevNext()
setUpRepeatButton()
setUpShuffleButton()
setUpProgressSlider()
title.isSelected = true
text.isSelected = true
}
private fun updatePlayPauseDrawableState() {
if (MusicPlayerRemote.isPlaying) {
playPauseButton.setImageResource(R.drawable.ic_pause_white_64dp)
} else {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_64dp)
}
}
private fun setUpPlayPauseFab() {
playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
}
private fun setUpPrevNext() {
updatePrevNextColor()
nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() }
previousButton.setOnClickListener { MusicPlayerRemote.back() }
}
private fun updatePrevNextColor() {
nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
}
private fun setUpShuffleButton() {
shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() }
}
fun updateShuffleState() {
when (MusicPlayerRemote.shuffleMode) {
MusicService.SHUFFLE_MODE_SHUFFLE ->
shuffleButton.setColorFilter(
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
else -> shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
}
}
private fun setUpRepeatButton() {
repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() }
}
fun updateRepeatState() {
when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat)
repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
}
MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat)
repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
}
MusicService.REPEAT_MODE_THIS -> {
repeatButton.setImageResource(R.drawable.ic_repeat_one)
repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
}
}
}
private fun updateLabel() {
(MusicPlayerRemote.playingQueue.size - 1).apply {
if (this == (MusicPlayerRemote.position)) {
nextSong.hide()
} else {
val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title
nextSong.apply {
text = "Next: $title"
show()
}
}
}
}
override fun onLayoutChange(
v: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
val panel = getQueuePanel()
panel.peekHeight = container.height
}
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<*>
recyclerView.layoutManager = linearLayoutManager
recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator
recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
recyclerViewSwipeManager?.attachRecyclerView(recyclerView)
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
}
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)
}
private fun updateQueuePosition() {
playingQueueAdapter?.setCurrent(MusicPlayerRemote.position)
resetToCurrentPosition()
}
private fun updateQueue() {
playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position)
resetToCurrentPosition()
}
private fun resetToCurrentPosition() {
recyclerView.stopScroll()
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
}
fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress)
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
})
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
@SuppressLint("StaticFieldLeak")
private fun updateFavorite() {
if (updateIsFavoriteTask != null) {
updateIsFavoriteTask?.cancel(false)
}
updateIsFavoriteTask =
object : AsyncTask<Song, Void, Boolean>() {
override fun doInBackground(vararg params: Song): Boolean? {
val activity = activity
return if (activity != null) {
MusicUtil.isFavorite(requireActivity(), params[0])
} else {
cancel(false)
null
}
}
override fun onPostExecute(isFavorite: Boolean?) {
val activity = activity
if (activity != null) {
val res = if (isFavorite!!)
R.drawable.ic_favorite
else
R.drawable.ic_favorite_border
val drawable = TintHelper.createTintedDrawable(activity, res, Color.WHITE)
songFavourite?.setImageDrawable(drawable)
}
}
}.execute(MusicPlayerRemote.currentSong)
}
}