/* * 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.activities.base import android.content.res.ColorStateList import android.graphics.Color import android.os.Bundle import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import android.widget.FrameLayout import androidx.core.animation.doOnEnd import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.commit import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.retromusic.R import code.name.monkey.retromusic.RetroBottomSheetBehavior import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.NowPlayingScreen import code.name.monkey.retromusic.fragments.NowPlayingScreen.* import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.other.MiniPlayerFragment import code.name.monkey.retromusic.fragments.player.adaptive.AdaptiveFragment import code.name.monkey.retromusic.fragments.player.blur.BlurPlayerFragment import code.name.monkey.retromusic.fragments.player.card.CardFragment import code.name.monkey.retromusic.fragments.player.cardblur.CardBlurFragment import code.name.monkey.retromusic.fragments.player.circle.CirclePlayerFragment import code.name.monkey.retromusic.fragments.player.classic.ClassicPlayerFragment import code.name.monkey.retromusic.fragments.player.color.ColorFragment import code.name.monkey.retromusic.fragments.player.fit.FitFragment import code.name.monkey.retromusic.fragments.player.flat.FlatPlayerFragment import code.name.monkey.retromusic.fragments.player.full.FullPlayerFragment import code.name.monkey.retromusic.fragments.player.gradient.GradientPlayerFragment import code.name.monkey.retromusic.fragments.player.material.MaterialFragment import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment import code.name.monkey.retromusic.fragments.player.peak.PeakPlayerFragment import code.name.monkey.retromusic.fragments.player.plain.PlainPlayerFragment import code.name.monkey.retromusic.fragments.player.simple.SimplePlayerFragment import code.name.monkey.retromusic.fragments.player.tiny.TinyPlayerFragment import code.name.monkey.retromusic.fragments.queue.PlayingQueueFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.model.CategoryInfo import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil import com.google.android.material.bottomsheet.BottomSheetBehavior.* import org.koin.androidx.viewmodel.ext.android.viewModel abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { companion object { val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName } private var windowInsets: WindowInsetsCompat? = null protected val libraryViewModel by viewModel() private lateinit var bottomSheetBehavior: RetroBottomSheetBehavior private var playerFragment: AbsPlayerFragment? = null private var miniPlayerFragment: MiniPlayerFragment? = null private var nowPlayingScreen: NowPlayingScreen? = null private var taskColor: Int = 0 private var paletteColor: Int = Color.WHITE protected abstract fun createContentView(): SlidingMusicPanelLayoutBinding private val panelState: Int get() = bottomSheetBehavior.state private lateinit var binding: SlidingMusicPanelLayoutBinding private val bottomSheetCallbackList = object : BottomSheetCallback() { override fun onSlide(bottomSheet: View, slideOffset: Float) { setMiniPlayerAlphaProgress(slideOffset) } override fun onStateChanged(bottomSheet: View, newState: Int) { when (newState) { STATE_EXPANDED -> { onPanelExpanded() } STATE_COLLAPSED -> { onPanelCollapsed() } else -> { println("Do something") } } } } fun getBottomSheetBehavior() = bottomSheetBehavior override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = createContentView() setContentView(binding.root) ViewCompat.setOnApplyWindowInsetsListener( binding.root ) { _, insets -> windowInsets = insets insets } bottomNavigationView.drawAboveSystemBarsWithPadding() if (RetroUtil.isLandscape()) { binding.slidingPanel.drawAboveSystemBarsWithPadding(true) } chooseFragmentForTheme() setupSlidingUpPanel() setupBottomSheet() updateColor() binding.slidingPanel.backgroundTintList = ColorStateList.valueOf(darkAccentColor()) bottomNavigationView.backgroundTintList = ColorStateList.valueOf(darkAccentColor()) } private fun setupBottomSheet() { bottomSheetBehavior = from(binding.slidingPanel) as RetroBottomSheetBehavior bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList) bottomSheetBehavior.isHideable = false setMiniPlayerAlphaProgress(0F) } override fun onResume() { super.onResume() if (nowPlayingScreen != PreferenceUtil.nowPlayingScreen) { postRecreate() } if (bottomSheetBehavior.state == STATE_EXPANDED) { setMiniPlayerAlphaProgress(1f) } } override fun onDestroy() { super.onDestroy() bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList) } protected fun wrapSlidingMusicPanel(): SlidingMusicPanelLayoutBinding { return SlidingMusicPanelLayoutBinding.inflate(layoutInflater) } fun collapsePanel() { bottomSheetBehavior.state = STATE_COLLAPSED } fun expandPanel() { bottomSheetBehavior.state = STATE_EXPANDED } private fun setMiniPlayerAlphaProgress(progress: Float) { if (progress < 0) return val alpha = 1 - progress miniPlayerFragment?.view?.alpha = 1 - (progress / 0.2F) miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE binding.bottomNavigationView.translationY = progress * 500 binding.bottomNavigationView.alpha = alpha binding.playerFragmentContainer.alpha = (progress - 0.2F) / 0.2F } open fun onPanelCollapsed() { setMiniPlayerAlphaProgress(0F) // restore values setLightStatusBarAuto(surfaceColor()) setLightNavigationAuto() setTaskDescriptionColor(taskColor) } open fun onPanelExpanded() { setMiniPlayerAlphaProgress(1F) onPaletteColorChanged() } private fun setupSlidingUpPanel() { binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) if (nowPlayingScreen != Peak) { val params = binding.slidingPanel.layoutParams as ViewGroup.LayoutParams params.height = ViewGroup.LayoutParams.MATCH_PARENT binding.slidingPanel.layoutParams = params } when (panelState) { STATE_EXPANDED -> onPanelExpanded() STATE_COLLAPSED -> onPanelCollapsed() else -> { // playerFragment!!.onHide() } } } }) } val bottomNavigationView get() = binding.bottomNavigationView override fun onServiceConnected() { super.onServiceConnected() if (MusicPlayerRemote.playingQueue.isNotEmpty()) { binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) hideBottomSheet(false) } }) } // don't call hideBottomSheet(true) here as it causes a bug with the SlidingUpPanelLayout } override fun onQueueChanged() { super.onQueueChanged() // Mini player should be hidden in Playing Queue // it may pop up if hideBottomSheet is called if (currentFragment(R.id.fragment_container) !is PlayingQueueFragment && bottomSheetBehavior.state != STATE_EXPANDED ) { hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty()) } } override fun onBackPressed() { if (!handleBackPress()) super.onBackPressed() } private fun handleBackPress(): Boolean { if (bottomSheetBehavior.peekHeight != 0 && playerFragment!!.onBackPressed()) return true if (panelState == STATE_EXPANDED) { collapsePanel() return true } return false } private fun onPaletteColorChanged() { if (panelState == STATE_EXPANDED) { setTaskDescColor(paletteColor) val isColorLight = ColorUtil.isColorLight(paletteColor) if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat)) { setLightNavigationBar(true) setLightStatusBar(isColorLight) } else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) { setLightStatusBar(false) setLightNavigationBar(true) } else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) { setLightNavigationBar(isColorLight) setLightStatusBar(isColorLight) } else if (nowPlayingScreen == Full) { setLightNavigationBar(isColorLight) setLightStatusBar(false) } else if (nowPlayingScreen == Classic) { setLightStatusBar(false) } else if (nowPlayingScreen == Fit) { setLightStatusBar(false) } else { setLightStatusBar( ColorUtil.isColorLight( ATHUtil.resolveColor( this, android.R.attr.windowBackground ) ) ) setLightNavigationBar(true) } } } private fun setTaskDescColor(color: Int) { taskColor = color if (panelState == STATE_COLLAPSED) { setTaskDescriptionColor(color) } } fun updateTabs() { binding.bottomNavigationView.menu.clear() val currentTabs: List = PreferenceUtil.libraryCategory for (tab in currentTabs) { if (tab.visible) { val menu = tab.category binding.bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes) .setIcon(menu.icon) } } if (binding.bottomNavigationView.menu.size() == 1) { binding.bottomNavigationView.hide() } } private fun updateColor() { libraryViewModel.paletteColor.observe(this, { color -> this.paletteColor = color onPaletteColorChanged() }) } fun setBottomNavVisibility(visible: Boolean, animate: Boolean = false) { binding.bottomNavigationView.translateYAnimate(if (visible) 0F else dip(R.dimen.bottom_nav_height).toFloat() + windowInsets.safeGetBottomInsets()) .apply { doOnEnd { binding.bottomNavigationView.bringToFront() } hideBottomSheet( hide = MusicPlayerRemote.playingQueue.isEmpty(), animate = animate, isBottomNavVisible = visible ) } } fun hideBottomSheet( hide: Boolean, animate: Boolean = false, isBottomNavVisible: Boolean = bottomNavigationView.isVisible ) { val heightOfBar = windowInsets.safeGetBottomInsets() + if (MusicPlayerRemote.isCasting) dip(R.dimen.cast_mini_player_height) else dip(R.dimen.mini_player_height) val heightOfBarWithTabs = heightOfBar + dip(R.dimen.bottom_nav_height) if (hide) { bottomSheetBehavior.peekHeight = -windowInsets.safeGetBottomInsets() bottomSheetBehavior.state = STATE_COLLAPSED libraryViewModel.setFabMargin(if (isBottomNavVisible) dip(R.dimen.bottom_nav_height) else 0) ViewCompat.setElevation(binding.slidingPanel, 0f) ViewCompat.setElevation(binding.bottomNavigationView, 10f) } else { if (MusicPlayerRemote.playingQueue.isNotEmpty()) { ViewCompat.setElevation(binding.slidingPanel, 10f) ViewCompat.setElevation(binding.bottomNavigationView, 10f) if (isBottomNavVisible) { println("List") if (animate) { bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs) } else { bottomSheetBehavior.peekHeight = heightOfBarWithTabs } binding.bottomNavigationView.bringToFront() libraryViewModel.setFabMargin(dip(R.dimen.mini_player_height_expanded)) } else { println("Details") if (animate) { bottomSheetBehavior.peekHeightAnimate(heightOfBar).doOnEnd { binding.slidingPanel.bringToFront() } } else { bottomSheetBehavior.peekHeight = heightOfBar binding.slidingPanel.bringToFront() } libraryViewModel.setFabMargin(dip(R.dimen.mini_player_height)) } } } } fun setAllowDragging(allowDragging: Boolean) { bottomSheetBehavior.setAllowDragging(allowDragging) hideBottomSheet(false) } private fun chooseFragmentForTheme() { nowPlayingScreen = PreferenceUtil.nowPlayingScreen val fragment: Fragment = when (nowPlayingScreen) { Blur -> BlurPlayerFragment() Adaptive -> AdaptiveFragment() Normal -> PlayerFragment() Card -> CardFragment() BlurCard -> CardBlurFragment() Fit -> FitFragment() Flat -> FlatPlayerFragment() Full -> FullPlayerFragment() Plain -> PlainPlayerFragment() Simple -> SimplePlayerFragment() Material -> MaterialFragment() Color -> ColorFragment() Gradient -> GradientPlayerFragment() Tiny -> TinyPlayerFragment() Peak -> PeakPlayerFragment() Circle -> CirclePlayerFragment() Classic -> ClassicPlayerFragment() else -> PlayerFragment() } // must implement AbsPlayerFragment supportFragmentManager.commit { replace(R.id.playerFragmentContainer, fragment) } supportFragmentManager.executePendingTransactions() playerFragment = whichFragment(R.id.playerFragmentContainer) miniPlayerFragment = whichFragment(R.id.miniPlayerFragment) miniPlayerFragment?.view?.setOnClickListener { expandPanel() } } }