diff --git a/CHANGELOG.md b/CHANGELOG.md
index 341e7e8cf..88897910b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
#### What's Improved
- Playback bar now has a marquee effect
+- Play/pause button now changes from square to circle depending on the state
- Added a way to access the system equalizer from the playback menu.
#### What's Fixed
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/AnimatedMaterialButton.kt b/app/src/main/java/org/oxycblt/auxio/playback/AnimatedMaterialButton.kt
new file mode 100644
index 000000000..d0f599a11
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/playback/AnimatedMaterialButton.kt
@@ -0,0 +1,62 @@
+/*
+ * 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 .
+ */
+
+package org.oxycblt.auxio.playback
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.util.AttributeSet
+import com.google.android.material.button.MaterialButton
+
+/**
+ * A [MaterialButton] that automatically morphs from a circle to a squircle shape appearance when
+ * it is activated.
+ */
+class AnimatedMaterialButton
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ MaterialButton(context, attrs, defStyleAttr) {
+ private var currentCornerRadiusRatio = 0f
+ private var animator: ValueAnimator? = null
+
+ override fun setActivated(activated: Boolean) {
+ super.setActivated(activated)
+
+ val target = if (activated) 0.3f else 0.5f
+ if (!isLaidOut) {
+ updateCornerRadiusRatio(target)
+ return
+ }
+
+ animator?.cancel()
+ animator =
+ ValueAnimator.ofFloat(currentCornerRadiusRatio, target).apply {
+ duration = ACTIVATION_DURATION
+ addUpdateListener { updateCornerRadiusRatio(animatedValue as Float) }
+ start()
+ }
+ }
+
+ private fun updateCornerRadiusRatio(ratio: Float) {
+ currentCornerRadiusRatio = ratio
+ shapeAppearanceModel = shapeAppearanceModel.withCornerSize { it.width() * ratio }
+ }
+
+ companion object {
+ const val ACTIVATION_DURATION = 150L
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt
index 12610ec6f..058be34e5 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt
@@ -17,6 +17,7 @@
package org.oxycblt.auxio.playback
+import android.animation.ValueAnimator
import android.content.ActivityNotFoundException
import android.content.Intent
import android.media.audiofx.AudioEffect
@@ -49,8 +50,10 @@ class PlaybackPanelFragment :
MenuFragment(),
StyledSeekBar.Callback,
Toolbar.OnMenuItemClickListener {
+ private var animator: ValueAnimator? = null
+ private var radius = 0.3f
- // AudioEffect expects you to use startActivityFoResult with the panel intent. Use
+ // AudioEffect expects you to use startActivityForResult with the panel intent. Use
// the contract analogue for this since there is no built-in contract for AudioEffect.
private val activityLauncher by lifecycleObject {
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@@ -179,7 +182,7 @@ class PlaybackPanelFragment :
}
private fun updatePlaying(isPlaying: Boolean) {
- requireBinding().playbackPlayPause.apply { isActivated = isPlaying }
+ requireBinding().playbackPlayPause.isActivated = isPlaying
}
private fun updateShuffled(isShuffled: Boolean) {
diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt
index 196bb050e..601532157 100644
--- a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt
@@ -145,13 +145,22 @@ private fun RemoteViews.applyPlayPauseControls(
R.id.widget_play_pause,
context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE))
- setImageViewResource(
- R.id.widget_play_pause,
- if (state.isPlaying) {
- R.drawable.ic_pause_24
- } else {
- R.drawable.ic_play_24
- })
+ // Like the Android 13 media controls, use a circular fab when paused, and a squircle fab
+ // when playing.
+
+ val icon: Int
+ val container: Int
+
+ if (state.isPlaying) {
+ icon = R.drawable.ic_pause_24
+ container = R.drawable.ui_remote_fab_container_playing
+ } else {
+ icon = R.drawable.ic_play_24
+ container = R.drawable.ui_remote_fab_container_paused
+ }
+
+ setImageViewResource(R.id.widget_play_pause, icon)
+ setInt(R.id.widget_play_pause, "setBackgroundResource", container)
return this
}
diff --git a/app/src/main/res/drawable/ui_remote_fab_container_paused.xml b/app/src/main/res/drawable/ui_remote_fab_container_paused.xml
new file mode 100644
index 000000000..599b2b7ca
--- /dev/null
+++ b/app/src/main/res/drawable/ui_remote_fab_container_paused.xml
@@ -0,0 +1,20 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ui_remote_fab_bg.xml b/app/src/main/res/drawable/ui_remote_fab_container_playing.xml
similarity index 100%
rename from app/src/main/res/drawable/ui_remote_fab_bg.xml
rename to app/src/main/res/drawable/ui_remote_fab_container_playing.xml
diff --git a/app/src/main/res/layout-h600dp/fragment_playback_panel.xml b/app/src/main/res/layout-h600dp/fragment_playback_panel.xml
index 50e604d7c..f095e598c 100644
--- a/app/src/main/res/layout-h600dp/fragment_playback_panel.xml
+++ b/app/src/main/res/layout-h600dp/fragment_playback_panel.xml
@@ -121,7 +121,7 @@
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
-
-
-
\ No newline at end of file