diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 4aa6d7582..a7d7703e9 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -1,5 +1,6 @@ package org.oxycblt.auxio +import android.animation.LayoutTransition import android.content.res.ColorStateList import android.os.Bundle import android.view.LayoutInflater @@ -9,6 +10,7 @@ import android.view.ViewGroup import androidx.core.graphics.ColorUtils import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.navigation.NavController import androidx.navigation.NavOptions import androidx.navigation.fragment.NavHostFragment @@ -69,6 +71,13 @@ class MainFragment : Fragment() { binding.lifecycleOwner = this + // Apply custom interpolation effects + // The container layout types differ in the land/port layouts so it has to be cast to their parent instead. + (binding.controlsContainer as ViewGroup).layoutTransition = LayoutTransition().apply { + setInterpolator(LayoutTransition.APPEARING, FastOutSlowInInterpolator()) + setInterpolator(LayoutTransition.DISAPPEARING, FastOutSlowInInterpolator()) + } + binding.navBar.apply { itemIconTintList = navTints itemTextColor = navTints @@ -150,7 +159,9 @@ class MainFragment : Fragment() { if (song == null) { logD("Hiding CompactPlaybackFragment since no song is being played.") - if (!isLandscape(resources)) { + if (isLandscape(resources)) { + binding.compactPlayback.visibility = View.INVISIBLE + } else { binding.compactPlayback.visibility = View.GONE } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt index e8cb2d2d9..27d7eca69 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt @@ -57,10 +57,6 @@ class CompactPlaybackFragment : Fragment() { } } - if (playbackModel.song.value == null) { - setInvisible(true) - } - // --- VIEWMODEL SETUP --- playbackModel.song.observe(viewLifecycleOwner) { @@ -69,9 +65,6 @@ class CompactPlaybackFragment : Fragment() { binding.song = it binding.playbackProgress.max = it.seconds.toInt() - setInvisible(false) - } else { - setInvisible(true) } } @@ -117,20 +110,4 @@ class CompactPlaybackFragment : Fragment() { } } } - - /** - * Set this fragment to be invisible, if needed. Only runs in landscape mode. - */ - private fun setInvisible(invisible: Boolean) { - // Does not run in landscape - if (!isLandscape(resources)) return - - val visibility = if (invisible) View.INVISIBLE else View.VISIBLE - - binding.playbackLayout.children.forEach { - it.visibility = visibility - } - - binding.root.isEnabled = !invisible - } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/SlideLinearLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/SlideLinearLayout.kt new file mode 100644 index 000000000..8f01cbc75 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/ui/SlideLinearLayout.kt @@ -0,0 +1,98 @@ +package org.oxycblt.auxio.ui + +import android.content.Context +import android.graphics.Canvas +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import org.oxycblt.auxio.logD +import org.oxycblt.auxio.logE +import java.lang.reflect.Field + +/** + * Hack layout that fixes an issue where disappearing views would draw over non-disappearing + * views when animated with a stock LayoutTransition. Adapted from this StackOverflow answer: + * https://stackoverflow.com/a/35087229 + */ +class SlideLinearLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = -1 +) : LinearLayout(context, attrs, defStyleAttr) { + private val disappearingChildrenField: Field? = try { + ViewGroup::class.java.getDeclaredField("mDisappearingChildren").also { + it.isAccessible = true + } + } catch (e: NoSuchFieldException) { + logE("Could not get mDisappearingChildren.") + null + } + + private var disappearingChildren: List? = null + + private var dumpView: View? = null + private var doDrawingTrick: Boolean = false + + init { + if (disappearingChildrenField != null) { + // Create a junk view and add it, which makes all the magic happen [I think??]. + dumpView = View(context) + addView(dumpView, 0, 0) + } + } + + override fun dispatchDraw(canvas: Canvas?) { + doDrawingTrick = beforeDispatchDraw() + super.dispatchDraw(canvas) + + if (doDrawingTrick) { + doDrawingTrick = false + } + } + + override fun drawChild(canvas: Canvas?, child: View?, drawingTime: Long): Boolean { + val children = getDisappearingChildren() + + // I have no idea what this code does. + if (doDrawingTrick && children != null) { + if (child == dumpView) { + var more = false + children.forEach { + more = more or super.drawChild(canvas, it, drawingTime) + } + return more + } else if (children.contains(child)) { + return false + } + } + + return super.drawChild(canvas, child, drawingTime) + } + + private fun beforeDispatchDraw(): Boolean { + val children = getDisappearingChildren() + + if (children == null || children.isEmpty() || childCount <= 1) { // Junk view included + return false + } + + return true + } + + @Suppress("UNCHECKED_CAST") + private fun getDisappearingChildren(): List? { + if (disappearingChildrenField == null || disappearingChildren != null) { + return disappearingChildren + } + + // If there is no list of disappearing children yet, attempt to get it. + try { + disappearingChildren = disappearingChildrenField.get(this) as List? + } catch (e: IllegalAccessException) { + logD("Could not get list of disappearing children.") + } + + return disappearingChildren + } +} diff --git a/app/src/main/res/layout-land/fragment_main.xml b/app/src/main/res/layout-land/fragment_main.xml index 4be1c20c9..ce1985d26 100644 --- a/app/src/main/res/layout-land/fragment_main.xml +++ b/app/src/main/res/layout-land/fragment_main.xml @@ -17,34 +17,41 @@ android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@+id/compact_playback" + app:layout_constraintBottom_toTopOf="@+id/controls_container" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/nav_explore" tools:layout="@layout/fragment_library" /> - + android:baselineAligned="false" + android:animateLayoutChanges="true" + android:elevation="@dimen/elevation_normal" + app:layout_constraintBottom_toBottomOf="parent"> - + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 112e9bffe..994f5ccac 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -17,33 +17,40 @@ android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@+id/compact_playback" + app:layout_constraintBottom_toTopOf="@+id/controls_container" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/nav_explore" tools:layout="@layout/fragment_library" /> - + android:animateLayoutChanges="true" + android:elevation="@dimen/elevation_normal" + app:layout_constraintBottom_toBottomOf="parent"> - + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 50100a07d..b6945f526 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -49,7 +49,6 @@ -