home: reset selection when navigating
Reset the selection in the home view when navigating to other selectable screens. Without a unified Toolbar, this makes no sense.
This commit is contained in:
parent
c353ffd705
commit
3d03194878
16 changed files with 105 additions and 104 deletions
|
@ -26,6 +26,8 @@ import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavDestination
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
|
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
|
||||||
|
@ -43,15 +45,8 @@ import org.oxycblt.auxio.playback.queue.QueueSheetBehavior
|
||||||
import org.oxycblt.auxio.ui.MainNavigationAction
|
import org.oxycblt.auxio.ui.MainNavigationAction
|
||||||
import org.oxycblt.auxio.ui.NavigationViewModel
|
import org.oxycblt.auxio.ui.NavigationViewModel
|
||||||
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
import org.oxycblt.auxio.ui.selection.SelectionViewModel
|
||||||
import org.oxycblt.auxio.util.collect
|
import org.oxycblt.auxio.util.*
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
|
||||||
import org.oxycblt.auxio.util.context
|
|
||||||
import org.oxycblt.auxio.util.coordinatorLayoutBehavior
|
|
||||||
import org.oxycblt.auxio.util.getAttrColorCompat
|
|
||||||
import org.oxycblt.auxio.util.getDimen
|
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around the home fragment that shows the playback fragment and controls the more
|
* A wrapper around the home fragment that shows the playback fragment and controls the more
|
||||||
|
@ -88,6 +83,7 @@ class MainFragment :
|
||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Send meaningful accessibility events for bottom sheets
|
// Send meaningful accessibility events for bottom sheets
|
||||||
ViewCompat.setAccessibilityPaneTitle(
|
ViewCompat.setAccessibilityPaneTitle(
|
||||||
binding.playbackSheet, context.getString(R.string.lbl_playback))
|
binding.playbackSheet, context.getString(R.string.lbl_playback))
|
||||||
|
@ -222,6 +218,8 @@ class MainFragment :
|
||||||
when (action) {
|
when (action) {
|
||||||
is MainNavigationAction.Expand -> tryExpandAll()
|
is MainNavigationAction.Expand -> tryExpandAll()
|
||||||
is MainNavigationAction.Collapse -> tryCollapseAll()
|
is MainNavigationAction.Collapse -> tryCollapseAll()
|
||||||
|
// No need to reset selection despite navigating to another screen, as the
|
||||||
|
// main fragment destinations don't have selections
|
||||||
is MainNavigationAction.Directions -> findNavController().navigate(action.directions)
|
is MainNavigationAction.Directions -> findNavController().navigate(action.directions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ import androidx.core.view.iterator
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
@ -59,8 +58,8 @@ import org.oxycblt.auxio.music.system.Indexer
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.MainNavigationAction
|
import org.oxycblt.auxio.ui.MainNavigationAction
|
||||||
import org.oxycblt.auxio.ui.NavigationViewModel
|
import org.oxycblt.auxio.ui.NavigationViewModel
|
||||||
import org.oxycblt.auxio.ui.selection.SelectionViewModel
|
|
||||||
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
|
||||||
|
import org.oxycblt.auxio.ui.selection.SelectionViewModel
|
||||||
import org.oxycblt.auxio.util.*
|
import org.oxycblt.auxio.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,7 +72,6 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
private val homeModel: HomeViewModel by androidActivityViewModels()
|
private val homeModel: HomeViewModel by androidActivityViewModels()
|
||||||
private val musicModel: MusicViewModel by activityViewModels()
|
private val musicModel: MusicViewModel by activityViewModels()
|
||||||
private val navModel: NavigationViewModel by activityViewModels()
|
private val navModel: NavigationViewModel by activityViewModels()
|
||||||
// Makes no sense to share selections across screens
|
|
||||||
private val selectionModel: SelectionViewModel by activityViewModels()
|
private val selectionModel: SelectionViewModel by activityViewModels()
|
||||||
|
|
||||||
// lifecycleObject builds this in the creation step, so doing this is okay.
|
// lifecycleObject builds this in the creation step, so doing this is okay.
|
||||||
|
@ -108,7 +106,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
addOnOffsetChangedListener { _, offset ->
|
addOnOffsetChangedListener { _, offset ->
|
||||||
val range = binding.homeAppbar.totalScrollRange
|
val range = binding.homeAppbar.totalScrollRange
|
||||||
|
|
||||||
binding.homeToolbarOverlay.alpha = 1f - (abs(offset.toFloat()) / (range.toFloat() / 2))
|
binding.homeToolbarOverlay.alpha =
|
||||||
|
1f - (abs(offset.toFloat()) / (range.toFloat() / 2))
|
||||||
|
|
||||||
binding.homeContent.updatePadding(
|
binding.homeContent.updatePadding(
|
||||||
bottom = binding.homeAppbar.totalScrollRange + offset)
|
bottom = binding.homeAppbar.totalScrollRange + offset)
|
||||||
|
@ -116,9 +115,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.homeToolbarOverlay.registerListeners(
|
binding.homeToolbarOverlay.registerListeners(
|
||||||
onExit = { selectionModel.consume() },
|
onExit = { selectionModel.consume() }, onMenuItemClick = this)
|
||||||
onMenuItemClick = this
|
|
||||||
)
|
|
||||||
|
|
||||||
binding.homeToolbar.setOnMenuItemClickListener(this@HomeFragment)
|
binding.homeToolbar.setOnMenuItemClickListener(this@HomeFragment)
|
||||||
|
|
||||||
|
@ -187,6 +184,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
|
|
||||||
R.id.action_search -> {
|
R.id.action_search -> {
|
||||||
logD("Navigating to search")
|
logD("Navigating to search")
|
||||||
|
// Reset selection (navigating to another selectable screen)
|
||||||
|
selectionModel.consume()
|
||||||
initAxisTransitions(MaterialSharedAxis.Z)
|
initAxisTransitions(MaterialSharedAxis.Z)
|
||||||
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
|
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
|
||||||
}
|
}
|
||||||
|
@ -217,12 +216,10 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
playbackModel.playNext(selectionModel.consume())
|
playbackModel.playNext(selectionModel.consume())
|
||||||
requireContext().showToast(R.string.lng_queue_added)
|
requireContext().showToast(R.string.lng_queue_added)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_queue_add_selection -> {
|
R.id.action_queue_add_selection -> {
|
||||||
playbackModel.addToQueue(selectionModel.consume())
|
playbackModel.addToQueue(selectionModel.consume())
|
||||||
requireContext().showToast(R.string.lng_queue_added)
|
requireContext().showToast(R.string.lng_queue_added)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// Sorting option was selected, mark it as selected and update the mode
|
// Sorting option was selected, mark it as selected and update the mode
|
||||||
item.isChecked = true
|
item.isChecked = true
|
||||||
|
@ -296,7 +293,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
val toolbarParams = binding.homeToolbarOverlay.layoutParams as AppBarLayout.LayoutParams
|
val toolbarParams = binding.homeToolbarOverlay.layoutParams as AppBarLayout.LayoutParams
|
||||||
if (homeModel.tabs.size == 1) {
|
if (homeModel.tabs.size == 1) {
|
||||||
// A single tag makes the tab layout redundant, hide it and disable the collapsing
|
// A single tab makes the tab layout redundant, hide it and disable the collapsing
|
||||||
// behavior.
|
// behavior.
|
||||||
binding.homeTabs.isVisible = false
|
binding.homeTabs.isVisible = false
|
||||||
binding.homeAppbar.setExpanded(true, false)
|
binding.homeAppbar.setExpanded(true, false)
|
||||||
|
@ -402,13 +399,12 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
|
|
||||||
private fun updateSelection(selected: List<Music>) {
|
private fun updateSelection(selected: List<Music>) {
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
if (binding.homeToolbarOverlay.updateSelectionAmount(selected.size) && selected.isNotEmpty()) {
|
if (binding.homeToolbarOverlay.updateSelectionAmount(selected.size) &&
|
||||||
|
selected.isNotEmpty()) {
|
||||||
logD("Significant selection occurred, expanding AppBar")
|
logD("Significant selection occurred, expanding AppBar")
|
||||||
// Significant enough change where we want to expand the RecyclerView
|
// Significant enough change where we want to expand the RecyclerView
|
||||||
binding.homeAppbar.expandWithRecycler(
|
binding.homeAppbar.expandWithRecycler(
|
||||||
binding.homePager.findViewById(
|
binding.homePager.findViewById(getRecyclerId(homeModel.currentTab.value)))
|
||||||
getRecyclerId(homeModel.currentTab.value))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,8 +418,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset selection (navigating to another selectable screen)
|
||||||
|
selectionModel.consume()
|
||||||
initAxisTransitions(MaterialSharedAxis.X)
|
initAxisTransitions(MaterialSharedAxis.X)
|
||||||
|
|
||||||
findNavController().navigate(action)
|
findNavController().navigate(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +435,17 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
reenterTransition = MaterialSharedAxis(axis, false)
|
reenterTransition = MaterialSharedAxis(axis, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of a RecyclerView that the given [tab] contains
|
||||||
|
*/
|
||||||
|
private fun getRecyclerId(tab: MusicMode) =
|
||||||
|
when (tab) {
|
||||||
|
MusicMode.SONGS -> R.id.home_song_recycler
|
||||||
|
MusicMode.ALBUMS -> R.id.home_album_recycler
|
||||||
|
MusicMode.ARTISTS -> R.id.home_artist_recycler
|
||||||
|
MusicMode.GENRES -> R.id.home_genre_recycler
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, ViewPager2's sensitivity is high enough to result in vertical scroll events being
|
* By default, ViewPager2's sensitivity is high enough to result in vertical scroll events being
|
||||||
* registered as horizontal scroll events. Reflect into the internal recyclerview and change the
|
* registered as horizontal scroll events. Reflect into the internal recyclerview and change the
|
||||||
|
@ -456,14 +464,6 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
adapter = HomePagerAdapter()
|
adapter = HomePagerAdapter()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRecyclerId(tab: MusicMode) =
|
|
||||||
when (tab) {
|
|
||||||
MusicMode.SONGS -> R.id.home_song_recycler
|
|
||||||
MusicMode.ALBUMS -> R.id.home_album_recycler
|
|
||||||
MusicMode.ARTISTS -> R.id.home_artist_recycler
|
|
||||||
MusicMode.GENRES -> R.id.home_genre_recycler
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class HomePagerAdapter :
|
private inner class HomePagerAdapter :
|
||||||
FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) {
|
FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) {
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,11 @@ import androidx.fragment.app.activityViewModels
|
||||||
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
import org.oxycblt.auxio.home.HomeViewModel
|
import org.oxycblt.auxio.home.HomeViewModel
|
||||||
import org.oxycblt.auxio.music.Music
|
import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.ui.selection.SelectionViewModel
|
|
||||||
import org.oxycblt.auxio.ui.fastscroll.FastScrollRecyclerView
|
import org.oxycblt.auxio.ui.fastscroll.FastScrollRecyclerView
|
||||||
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
import org.oxycblt.auxio.ui.fragment.MenuFragment
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||||
|
import org.oxycblt.auxio.ui.selection.SelectionViewModel
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -27,9 +27,7 @@ import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.core.view.updateMarginsRelative
|
import androidx.core.view.updateMarginsRelative
|
||||||
import androidx.transition.TransitionManager
|
|
||||||
import com.google.android.material.shape.MaterialShapeDrawable
|
import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
import com.google.android.material.transition.MaterialFade
|
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
|
@ -186,11 +184,10 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
fadeAnimator = null
|
fadeAnimator = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fadeAnimator = ValueAnimator.ofFloat(selectionIndicator.alpha, targetAlpha).apply {
|
fadeAnimator =
|
||||||
|
ValueAnimator.ofFloat(selectionIndicator.alpha, targetAlpha).apply {
|
||||||
duration = targetDuration
|
duration = targetDuration
|
||||||
addUpdateListener {
|
addUpdateListener { selectionIndicator.alpha = it.animatedValue as Float }
|
||||||
selectionIndicator.alpha = it.animatedValue as Float
|
|
||||||
}
|
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,9 @@ import org.oxycblt.auxio.util.getColorCompat
|
||||||
import org.oxycblt.auxio.util.getDrawableCompat
|
import org.oxycblt.auxio.util.getDrawableCompat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View that displays the playback indicator. Nominally emulates [StyledImageView], but
|
* View that displays the playback indicator. Nominally emulates [StyledImageView], but relies on
|
||||||
* relies on the existing ImageView infrastructure to achieve the same result while also
|
* the existing ImageView infrastructure to achieve the same result while also allowing animation to
|
||||||
* allowing animation to work.
|
* work.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class IndicatorView
|
class IndicatorView
|
||||||
|
|
|
@ -312,8 +312,8 @@ class Task(context: Context, private val raw: Song.Raw) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies and sanitizes this string under the assumption that it is UTF-8. This should
|
* Copies and sanitizes this string under the assumption that it is UTF-8. This should launder
|
||||||
* launder away any weird UTF-8 issues that ExoPlayer may cause.
|
* away any weird UTF-8 issues that ExoPlayer may cause.
|
||||||
*/
|
*/
|
||||||
private fun String.sanitize() = String(encodeToByteArray())
|
private fun String.sanitize() = String(encodeToByteArray())
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,8 @@ import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.audio.AudioProcessor
|
import com.google.android.exoplayer2.audio.AudioProcessor
|
||||||
import com.google.android.exoplayer2.audio.BaseAudioProcessor
|
import com.google.android.exoplayer2.audio.BaseAudioProcessor
|
||||||
import com.google.android.exoplayer2.metadata.Metadata
|
import com.google.android.exoplayer2.metadata.Metadata
|
||||||
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
|
|
||||||
import com.google.android.exoplayer2.metadata.id3.InternalFrame
|
import com.google.android.exoplayer2.metadata.id3.InternalFrame
|
||||||
|
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
|
||||||
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
|
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
|
@ -42,8 +42,8 @@ import org.oxycblt.auxio.util.logD
|
||||||
/**
|
/**
|
||||||
* The component managing the [MediaSessionCompat] instance, alongside the [NotificationComponent].
|
* The component managing the [MediaSessionCompat] instance, alongside the [NotificationComponent].
|
||||||
*
|
*
|
||||||
* Auxio does not directly rely on MediaSession, as it is extremely poorly designed. We instead
|
* Auxio does not directly rely on MediaSession, as it is extremely poorly designed. We instead just
|
||||||
* just mirror the playback state into the media session.
|
* mirror the playback state into the media session.
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -27,8 +27,8 @@ import org.oxycblt.auxio.util.inflater
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around [Slider] that shows not only position and duration values, but also hacks
|
* A wrapper around [Slider] that shows not only position and duration values, but also hacks in
|
||||||
* in bounds checking to avoid app crashes if bad position input comes in.
|
* bounds checking to avoid app crashes if bad position input comes in.
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -29,11 +29,10 @@ import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import org.oxycblt.auxio.util.coordinatorLayoutBehavior
|
import org.oxycblt.auxio.util.coordinatorLayoutBehavior
|
||||||
import org.oxycblt.auxio.util.logD
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An [AppBarLayout] that fixes several bugs with the default implementation where the lifted
|
* An [AppBarLayout] that fixes several bugs with the default implementation where the lifted state
|
||||||
* state will not properly respond to RecyclerView events.
|
* will not properly respond to RecyclerView events.
|
||||||
*
|
*
|
||||||
* **Note:** This layout relies on [AppBarLayout.liftOnScrollTargetViewId] to figure out what
|
* **Note:** This layout relies on [AppBarLayout.liftOnScrollTargetViewId] to figure out what
|
||||||
* scrolling view to use. Failure to specify this will result in the layout not working.
|
* scrolling view to use. Failure to specify this will result in the layout not working.
|
||||||
|
@ -64,14 +63,11 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand this app bar layout with the given recyclerview, preventing it from
|
* Expand this app bar layout with the given recyclerview, preventing it from jumping around.
|
||||||
* jumping around.
|
|
||||||
*/
|
*/
|
||||||
fun expandWithRecycler(recycler: RecyclerView?) {
|
fun expandWithRecycler(recycler: RecyclerView?) {
|
||||||
setExpanded(true)
|
setExpanded(true)
|
||||||
recycler?.let {
|
recycler?.let { addOnOffsetChangedListener(ExpansionHackListener(it)) }
|
||||||
addOnOffsetChangedListener(ExpansionHackListener(it))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
override fun onDetachedFromWindow() {
|
||||||
|
@ -104,20 +100,20 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hack to prevent RecyclerView jumping when the appbar expands.
|
* Hack to prevent RecyclerView jumping when the appbar expands. Adapted from Material Files:
|
||||||
* Adapted from Material Files:
|
|
||||||
* https://github.com/zhanghai/MaterialFiles/blob/master/app/src/main/java/me/zhanghai/android/files/ui/AppBarLayoutExpandHackListener.kt
|
* https://github.com/zhanghai/MaterialFiles/blob/master/app/src/main/java/me/zhanghai/android/files/ui/AppBarLayoutExpandHackListener.kt
|
||||||
*/
|
*/
|
||||||
private class ExpansionHackListener(private val recycler: RecyclerView) : OnOffsetChangedListener {
|
private class ExpansionHackListener(private val recycler: RecyclerView) :
|
||||||
private val offsetAnimationMaxEndTime = (AnimationUtils.currentAnimationTimeMillis()
|
OnOffsetChangedListener {
|
||||||
+ 600)
|
private val offsetAnimationMaxEndTime = (AnimationUtils.currentAnimationTimeMillis() + 600)
|
||||||
|
|
||||||
private var lastVerticalOffset: Int? = null
|
private var lastVerticalOffset: Int? = null
|
||||||
|
|
||||||
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
|
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
|
||||||
if (verticalOffset == 0
|
if (verticalOffset == 0 ||
|
||||||
|| AnimationUtils.currentAnimationTimeMillis() > offsetAnimationMaxEndTime) {
|
AnimationUtils.currentAnimationTimeMillis() > offsetAnimationMaxEndTime) {
|
||||||
// AppBarLayout crashes with IndexOutOfBoundsException when a non-last listener removes
|
// AppBarLayout crashes with IndexOutOfBoundsException when a non-last listener
|
||||||
|
// removes
|
||||||
// itself, so we have to do the removal asynchronously.
|
// itself, so we have to do the removal asynchronously.
|
||||||
appBarLayout.postOnAnimation { appBarLayout.removeOnOffsetChangedListener(this) }
|
appBarLayout.postOnAnimation { appBarLayout.removeOnOffsetChangedListener(this) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,6 @@ import org.oxycblt.auxio.util.getDimenSize
|
||||||
import org.oxycblt.auxio.util.getDrawableCompat
|
import org.oxycblt.auxio.util.getDrawableCompat
|
||||||
import org.oxycblt.auxio.util.isRtl
|
import org.oxycblt.auxio.util.isRtl
|
||||||
import org.oxycblt.auxio.util.isUnder
|
import org.oxycblt.auxio.util.isUnder
|
||||||
import org.oxycblt.auxio.util.logD
|
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,7 +20,6 @@ package org.oxycblt.auxio.ui.fragment
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.MenuRes
|
import androidx.annotation.MenuRes
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.view.children
|
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import org.oxycblt.auxio.MainFragmentDirections
|
import org.oxycblt.auxio.MainFragmentDirections
|
||||||
|
@ -184,9 +183,7 @@ abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun musicMenuImpl(anchor: View, @MenuRes menuRes: Int, onSelect: (Int) -> Boolean) {
|
private fun musicMenuImpl(anchor: View, @MenuRes menuRes: Int, onSelect: (Int) -> Boolean) {
|
||||||
menu(anchor, menuRes) {
|
menu(anchor, menuRes) { setOnMenuItemClickListener { item -> onSelect(item.itemId) } }
|
||||||
setOnMenuItemClickListener { item -> onSelect(item.itemId) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 Auxio Project
|
||||||
|
*
|
||||||
|
* This program 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 program 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.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui.selection
|
package org.oxycblt.auxio.ui.selection
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
|
@ -12,8 +29,8 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around a Toolbar that enables an overlaid toolbar showing information about
|
* A wrapper around a Toolbar that enables an overlaid toolbar showing information about an item
|
||||||
* an item selection.
|
* selection.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class SelectionToolbarOverlay
|
class SelectionToolbarOverlay
|
||||||
|
@ -21,7 +38,8 @@ class SelectionToolbarOverlay
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||||
FrameLayout(context, attrs, defStyleAttr) {
|
FrameLayout(context, attrs, defStyleAttr) {
|
||||||
private lateinit var innerToolbar: MaterialToolbar
|
private lateinit var innerToolbar: MaterialToolbar
|
||||||
private val selectionToolbar = MaterialToolbar(context).apply {
|
private val selectionToolbar =
|
||||||
|
MaterialToolbar(context).apply {
|
||||||
inflateMenu(R.menu.menu_selection_actions)
|
inflateMenu(R.menu.menu_selection_actions)
|
||||||
setNavigationIcon(R.drawable.ic_close_24)
|
setNavigationIcon(R.drawable.ic_close_24)
|
||||||
}
|
}
|
||||||
|
@ -38,20 +56,18 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
addView(selectionToolbar)
|
addView(selectionToolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Add listeners for the selection toolbar. */
|
||||||
* Add listeners for the selection toolbar.
|
fun registerListeners(
|
||||||
*/
|
onExit: OnClickListener,
|
||||||
fun registerListeners(onExit: OnClickListener,
|
onMenuItemClick: Toolbar.OnMenuItemClickListener
|
||||||
onMenuItemClick: Toolbar.OnMenuItemClickListener) {
|
) {
|
||||||
selectionToolbar.apply {
|
selectionToolbar.apply {
|
||||||
setNavigationOnClickListener(onExit)
|
setNavigationOnClickListener(onExit)
|
||||||
setOnMenuItemClickListener(onMenuItemClick)
|
setOnMenuItemClickListener(onMenuItemClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Unregister listeners for this instance. */
|
||||||
* Unregister listeners for this instance.
|
|
||||||
*/
|
|
||||||
fun unregisterListeners() {
|
fun unregisterListeners() {
|
||||||
selectionToolbar.apply {
|
selectionToolbar.apply {
|
||||||
setNavigationOnClickListener(null)
|
setNavigationOnClickListener(null)
|
||||||
|
@ -60,8 +76,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the selection amount in the selection Toolbar. This will animate the selection
|
* Update the selection amount in the selection Toolbar. This will animate the selection Toolbar
|
||||||
* Toolbar into focus if there is now a selection to show.
|
* into focus if there is now a selection to show.
|
||||||
*/
|
*/
|
||||||
fun updateSelectionAmount(amount: Int): Boolean {
|
fun updateSelectionAmount(amount: Int): Boolean {
|
||||||
logD("Updating selection amount to $amount")
|
logD("Updating selection amount to $amount")
|
||||||
|
@ -83,11 +99,13 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
if (selectionVisible) {
|
if (selectionVisible) {
|
||||||
targetInnerAlpha = 0f
|
targetInnerAlpha = 0f
|
||||||
targetSelectionAlpha = 1f
|
targetSelectionAlpha = 1f
|
||||||
targetDuration = context.resources.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
targetDuration =
|
||||||
|
context.resources.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
||||||
} else {
|
} else {
|
||||||
targetInnerAlpha = 1f
|
targetInnerAlpha = 1f
|
||||||
targetSelectionAlpha = 0f
|
targetSelectionAlpha = 0f
|
||||||
targetDuration = context.resources.getInteger(R.integer.anim_fade_exit_duration).toLong()
|
targetDuration =
|
||||||
|
context.resources.getInteger(R.integer.anim_fade_exit_duration).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (innerToolbar.alpha == targetInnerAlpha &&
|
if (innerToolbar.alpha == targetInnerAlpha &&
|
||||||
|
@ -107,11 +125,10 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
fadeThroughAnimator = null
|
fadeThroughAnimator = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fadeThroughAnimator = ValueAnimator.ofFloat(innerToolbar.alpha, targetInnerAlpha).apply {
|
fadeThroughAnimator =
|
||||||
|
ValueAnimator.ofFloat(innerToolbar.alpha, targetInnerAlpha).apply {
|
||||||
duration = targetDuration
|
duration = targetDuration
|
||||||
addUpdateListener {
|
addUpdateListener { changeToolbarAlpha(it.animatedValue as Float) }
|
||||||
changeToolbarAlpha(it.animatedValue as Float)
|
|
||||||
}
|
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,7 @@ class SelectionViewModel : ViewModel() {
|
||||||
|
|
||||||
/** Clear and return all selected items. */
|
/** Clear and return all selected items. */
|
||||||
fun consume(): List<Music> {
|
fun consume(): List<Music> {
|
||||||
return _selected.value.also {
|
return _selected.value.also { _selected.value = listOf() }
|
||||||
_selected.value = listOf()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
|
|
|
@ -202,7 +202,6 @@ fun <R> SQLiteDatabase.queryAll(tableName: String, block: (Cursor) -> R) =
|
||||||
// Note: WindowInsetsCompat and it's related methods cause too many issues.
|
// Note: WindowInsetsCompat and it's related methods cause too many issues.
|
||||||
// Use our own compat methods instead.
|
// Use our own compat methods instead.
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve system bar insets in a version-aware manner. This can be used to apply padding to a view
|
* Resolve system bar insets in a version-aware manner. This can be used to apply padding to a view
|
||||||
* that properly follows all the changes that were made between Android 8-11.
|
* that properly follows all the changes that were made between Android 8-11.
|
||||||
|
|
Loading…
Reference in a new issue