diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f2afad7ce..23b51c595 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,6 @@ - @@ -37,7 +36,6 @@ android:exported="false" android:foregroundServiceType="mediaPlayback" android:icon="@mipmap/ic_launcher" - android:roundIcon="@mipmap/ic_launcher_round" - android:stopWithTask="false" /> + android:roundIcon="@mipmap/ic_launcher_round" /> \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 19eeec09b..4942044bf 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -150,11 +150,11 @@ class MainFragment : Fragment() { if (song == null) { logD("Hiding CompactPlaybackFragment since no song is being played.") - if (isLandscape(resources)) { - binding.compactPlayback.visibility = View.INVISIBLE - } else { - binding.compactPlayback.visibility = View.GONE - } + binding.compactPlayback.visibility = + if (isLandscape(resources)) + View.INVISIBLE + else + View.GONE playbackModel.disableAnimation() } else { diff --git a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt index 90c9b22d7..53c1ca915 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt @@ -51,7 +51,8 @@ fun ImageView.bindGenreImage(genre: Genre) { } /** - * Custom extension function similar to the stock coil load extensions, but allows for any type. + * Custom extension function similar to the stock coil load extensions, but handles whether + * to even show images and custom fetchers. */ inline fun ImageView.load( data: T, @@ -101,4 +102,4 @@ fun loadBitmap(context: Context, song: Song, onDone: (Bitmap?) -> Unit) { ) .build() ) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt index 6f7324108..5d3d86b3b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt @@ -55,7 +55,7 @@ data class Song( /** * Apply a genre to a song. - * @throws IllegalArgumentException When a genre is already applied. + * @throws IllegalStateException When a genre is already applied. */ fun applyGenre(genre: Genre) { check(mGenre == null) { "Genre is already applied" } @@ -65,7 +65,7 @@ data class Song( /** * Apply an album to a song. - * @throws IllegalArgumentException When an album is already applied. + * @throws IllegalStateException When an album is already applied. */ fun applyAlbum(album: Album) { check(mAlbum == null) { "Album is already applied" } 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 dc718bef8..d1ae2a2ce 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt @@ -1,16 +1,13 @@ package org.oxycblt.auxio.playback -import android.graphics.drawable.AnimatedVectorDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import org.oxycblt.auxio.MainFragmentDirections -import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentCompactPlaybackBinding import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.logD @@ -77,33 +74,15 @@ class CompactPlaybackFragment : Fragment() { override fun onResume() { super.onResume() + playbackModel.disableAnimation() - val iconPauseToPlay = ContextCompat.getDrawable( - requireContext(), R.drawable.ic_pause_to_play - ) as AnimatedVectorDrawable - - val iconPlayToPause = ContextCompat.getDrawable( - requireContext(), R.drawable.ic_play_to_pause - ) as AnimatedVectorDrawable - playbackModel.isPlaying.observe(viewLifecycleOwner) { - if (playbackModel.canAnimate) { - if (it) { - // Animate the icon transition when the playing status switches - binding.playbackControls.setImageDrawable(iconPlayToPause) - iconPlayToPause.start() - } else { - binding.playbackControls.setImageDrawable(iconPauseToPlay) - iconPauseToPlay.start() - } + if (it) { + binding.playbackPlayPause.showPause(playbackModel.canAnimate) + playbackModel.enableAnimation() } else { - // Use static icons on the first firing of this observer so that the icons - // don't animate on startup, which looks weird. - binding.playbackControls.setImageResource( - if (it) R.drawable.ic_pause_large else R.drawable.ic_play_large - ) - + binding.playbackPlayPause.showPlay(playbackModel.canAnimate) playbackModel.enableAnimation() } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlayPauseButton.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlayPauseButton.kt new file mode 100644 index 000000000..d4207f004 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlayPauseButton.kt @@ -0,0 +1,62 @@ +package org.oxycblt.auxio.playback + +import android.content.Context +import android.graphics.drawable.Animatable2 +import android.graphics.drawable.Drawable +import android.os.Build +import android.util.AttributeSet +import androidx.annotation.RequiresApi +import androidx.appcompat.widget.AppCompatImageButton +import org.oxycblt.auxio.R +import org.oxycblt.auxio.ui.getAnimatedDrawable + +/** + * Custom [AppCompatImageButton] that handles the animated play/pause icons. + */ +class PlayPauseButton @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = -1 +) : AppCompatImageButton(context, attrs, defStyleAttr) { + private val iconPauseToPlay = context.getAnimatedDrawable(R.drawable.ic_pause_to_play) + private val iconPlayToPause = context.getAnimatedDrawable(R.drawable.ic_play_to_pause) + + init { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + fixSeams() + } + } + + fun showPlay(animated: Boolean) { + if (animated) { + setImageDrawable(iconPauseToPlay) + iconPauseToPlay.start() + } else { + setImageResource(R.drawable.ic_play_large) + } + } + + fun showPause(animated: Boolean) { + if (animated) { + setImageDrawable(iconPlayToPause) + iconPlayToPause.start() + } else { + setImageResource(R.drawable.ic_pause_large) + } + } + + /** + * Hack that fixes an issue where a seam will display on the play button on certain display + * sizes due to floating point precision problems (Gotta love IEEE 754) + * This is done by detecting when the animation has ended and then reverting this + * view to the normal static image. Not possible below API 23 though. + */ + @RequiresApi(Build.VERSION_CODES.M) + private fun fixSeams() { + iconPauseToPlay.registerAnimationCallback(object : Animatable2.AnimationCallback() { + override fun onAnimationEnd(drawable: Drawable?) { + setImageResource(R.drawable.ic_play_large) + } + }) + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt index 8c116a31c..069b4cc08 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -1,7 +1,6 @@ package org.oxycblt.auxio.playback import android.content.res.ColorStateList -import android.graphics.drawable.AnimatedVectorDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem @@ -187,41 +186,18 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { override fun onResume() { super.onResume() + playbackModel.disableAnimation() - val iconPauseToPlay = ContextCompat.getDrawable( - requireContext(), R.drawable.ic_pause_to_play - ) as AnimatedVectorDrawable - - val iconPlayToPause = ContextCompat.getDrawable( - requireContext(), R.drawable.ic_play_to_pause - ) as AnimatedVectorDrawable - playbackModel.isPlaying.observe(viewLifecycleOwner) { if (it) { - if (playbackModel.canAnimate) { - binding.playbackPlayPause.setImageDrawable(iconPlayToPause) - iconPlayToPause.start() - } else { - // Use a static icon the first time around to fix premature animation - // [Which looks weird] - binding.playbackPlayPause.setImageResource(R.drawable.ic_pause_large) - - playbackModel.enableAnimation() - } - + binding.playbackPlayPause.showPause(playbackModel.canAnimate) binding.playbackPlayPause.backgroundTintList = accentColor + playbackModel.enableAnimation() } else { - if (playbackModel.canAnimate) { - binding.playbackPlayPause.setImageDrawable(iconPauseToPlay) - iconPauseToPlay.start() - } else { - binding.playbackPlayPause.setImageResource(R.drawable.ic_play_large) - - playbackModel.enableAnimation() - } - + binding.playbackPlayPause.showPlay(playbackModel.canAnimate) binding.playbackPlayPause.backgroundTintList = controlColor + playbackModel.enableAnimation() } } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt index 3e6f8f466..0d5114e59 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt @@ -6,6 +6,7 @@ import android.content.res.ColorStateList import android.content.res.Configuration import android.content.res.Resources import android.graphics.Point +import android.graphics.drawable.AnimatedVectorDrawable import android.os.Build import android.text.Spanned import android.util.DisplayMetrics @@ -17,6 +18,7 @@ import android.widget.TextView import android.widget.Toast import androidx.annotation.ColorInt import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes import androidx.annotation.PluralsRes import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat @@ -78,6 +80,13 @@ fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String { */ val Context.inflater: LayoutInflater get() = LayoutInflater.from(this) +/** + * Shortcut to get an [AnimatedVectorDrawable] from a [Context] + */ +fun Context.getAnimatedDrawable(@DrawableRes drawableRes: Int): AnimatedVectorDrawable { + return ContextCompat.getDrawable(this, drawableRes) as AnimatedVectorDrawable +} + /** * Create a [Toast] from a [String] * @param context [Context] required to create the toast diff --git a/app/src/main/res/color/ui_state_color.xml b/app/src/main/res/color/color_scroll_tints.xml similarity index 100% rename from app/src/main/res/color/ui_state_color.xml rename to app/src/main/res/color/color_scroll_tints.xml diff --git a/app/src/main/res/drawable/ic_play_large.xml b/app/src/main/res/drawable/ic_play_large.xml index 859b8f3fc..d4a3e0b50 100644 --- a/app/src/main/res/drawable/ic_play_large.xml +++ b/app/src/main/res/drawable/ic_play_large.xml @@ -11,15 +11,22 @@ android:pivotX="12" android:pivotY="12"> + + android:strokeColor="#00000000" + android:pathData="m 8.2501424,6.0001505 v 5.9996335 6.000151 L 18.749759,11.999784 Z" /> + android:strokeColor="#00000000" + android:pathData="m 8.2501424,6.0001505 v 5.9996335 6.000151 L 18.749759,11.999784 Z" /> diff --git a/app/src/main/res/layout-land/fragment_compact_playback.xml b/app/src/main/res/layout-land/fragment_compact_playback.xml index dc618a3d2..9de59445d 100644 --- a/app/src/main/res/layout-land/fragment_compact_playback.xml +++ b/app/src/main/res/layout-land/fragment_compact_playback.xml @@ -52,7 +52,7 @@ android:textAlignment="viewStart" android:textAppearance="@style/TextAppearance.SmallHeader" app:layout_constraintBottom_toTopOf="@+id/playback_info" - app:layout_constraintEnd_toStartOf="@+id/playback_controls" + app:layout_constraintEnd_toStartOf="@+id/playback_play_pause" app:layout_constraintStart_toEndOf="@+id/playback_cover" app:layout_constraintTop_toTopOf="@+id/playback_cover" app:layout_constraintVertical_chainStyle="packed" @@ -70,13 +70,13 @@ android:textAlignment="viewStart" android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" app:layout_constraintBottom_toBottomOf="@+id/playback_cover" - app:layout_constraintEnd_toStartOf="@+id/playback_controls" + app:layout_constraintEnd_toStartOf="@+id/playback_play_pause" app:layout_constraintStart_toEndOf="@+id/playback_cover" app:layout_constraintTop_toBottomOf="@+id/playback_song" tools:text="Artist Name / Album Name" /> - + tools:src="@drawable/ic_pause_large" /> - + tools:src="@drawable/ic_pause_large" /> + tools:textColor="@color/control_color" /> + tools:backgroundTint="@color/control_color" /> + tools:textColor="@color/control_color" /> + tools:backgroundTint="@color/control_color" /> + tools:textColor="@color/control_color" /> + tools:backgroundTint="@color/control_color" /> + tools:textColor="@color/control_color" /> + tools:backgroundTint="@color/control_color" /> + tools:textColor="@color/control_color" /> + tools:backgroundTint="@color/control_color" /> + tools:textColor="@color/control_color" /> + tools:backgroundTint="@color/control_color" /> - + tools:src="@drawable/ic_pause_large" /> - - + tools:src="@drawable/ic_pause_large" /> - + tools:src="@drawable/ic_play_large" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_playback.xml b/app/src/main/res/layout/fragment_playback.xml index 6636914d9..4e5771d99 100644 --- a/app/src/main/res/layout/fragment_playback.xml +++ b/app/src/main/res/layout/fragment_playback.xml @@ -171,7 +171,7 @@ app:layout_constraintStart_toEndOf="@+id/playback_loop" app:layout_constraintTop_toTopOf="@+id/playback_play_pause" /> - + tools:src="@drawable/ic_pause_large" /> + tools:textColor="@color/control_color" /> + tools:backgroundTint="@color/control_color" /> + tools:textColor="@color/control_color" /> + tools:backgroundTint="@color/control_color" /> + tools:textColor="@color/control_color" /> + tools:backgroundTint="@color/control_color" />