Skip to content

Commit

Permalink
widgets: add 1x3/1x4 widget
Browse files Browse the repository at this point in the history
Add a 1x3/1x4 widget that displays the cover and controls

Also requires another widget type that just displays controls to
accomodate landscape devices.

Resolves #420.
  • Loading branch information
OxygenCobalt committed Jan 16, 2024
1 parent 1954988 commit 881df0f
Show file tree
Hide file tree
Showing 19 changed files with 446 additions and 146 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Gapless playback is now used whenever possible
- Added "Remember pause" setting that makes remain paused when skipping
or editing queue
- Added 1x4 and 1x3 widget forms

#### What's Improved
- The playback state is now saved more often, improving persistence
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ constructor(
*
* @param song [Queue.currentSong]
* @param cover A pre-loaded album cover [Bitmap] for [song].
* @param cover A pre-loaded album cover [Bitmap] for [song], with rounded corners.
* @param isPlaying [PlaybackStateManager.playerState]
* @param repeatMode [PlaybackStateManager.repeatMode]
* @param isShuffled [Queue.isShuffled]
Expand Down
83 changes: 61 additions & 22 deletions app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,14 @@ class WidgetProvider : AppWidgetProvider() {
// the widget elements, plus some leeway for text sizing.
val views =
mapOf(
SizeF(180f, 100f) to newThinLayout(context, uiSettings, state),
SizeF(180f, 152f) to newSmallLayout(context, uiSettings, state),
SizeF(272f, 152f) to newWideLayout(context, uiSettings, state),
SizeF(180f, 272f) to newMediumLayout(context, uiSettings, state),
SizeF(272f, 272f) to newLargeLayout(context, uiSettings, state))
SizeF(180f, 48f) to newThinStickLayout(context, state),
SizeF(304f, 48f) to newWideStickLayout(context, state),
SizeF(180f, 100f) to newThinWaferLayout(context, uiSettings, state),
SizeF(304f, 100f) to newWideWaferLayout(context, uiSettings, state),
SizeF(180f, 152f) to newThinDockedLayout(context, uiSettings, state),
SizeF(304f, 152f) to newWideDockedLayout(context, uiSettings, state),
SizeF(180f, 272f) to newThinPaneLayout(context, uiSettings, state),
SizeF(304f, 272f) to newWidePaneLayout(context, uiSettings, state))

// Manually update AppWidgetManager with the new views.
val awm = AppWidgetManager.getInstance(context)
Expand Down Expand Up @@ -139,60 +142,78 @@ class WidgetProvider : AppWidgetProvider() {
private fun newDefaultLayout(context: Context) =
newRemoteViews(context, R.layout.widget_default)

private fun newThinLayout(
private fun newThinStickLayout(context: Context, state: WidgetComponent.PlaybackState) =
newRemoteViews(context, R.layout.widget_stick_thin).setupTimelineControls(context, state)

private fun newWideStickLayout(context: Context, state: WidgetComponent.PlaybackState) =
newRemoteViews(context, R.layout.widget_stick_wide).setupFullControls(context, state)

private fun newThinWaferLayout(
context: Context,
uiSettings: UISettings,
state: WidgetComponent.PlaybackState
) =
newRemoteViews(context, R.layout.widget_thin)
newRemoteViews(context, R.layout.widget_wafer_thin)
.setupBackground(
uiSettings,
)
.setupPlaybackState(context, state)
.setupCover(context, state.takeIf { canDisplayWaferCover(uiSettings) })
.setupTimelineControls(context, state)

private fun newSmallLayout(
private fun newWideWaferLayout(
context: Context,
uiSettings: UISettings,
state: WidgetComponent.PlaybackState
) =
newRemoteViews(context, R.layout.widget_small)
.setupBar(
newRemoteViews(context, R.layout.widget_wafer_wide)
.setupBackground(
uiSettings,
)
.setupCover(context, state)
.setupTimelineControls(context, state)
.setupCover(context, state.takeIf { canDisplayWaferCover(uiSettings) })
.setupFullControls(context, state)

private fun newMediumLayout(
private fun newThinDockedLayout(
context: Context,
uiSettings: UISettings,
state: WidgetComponent.PlaybackState
) =
newRemoteViews(context, R.layout.widget_medium)
.setupBackground(
newRemoteViews(context, R.layout.widget_docked_thin)
.setupBar(
uiSettings,
)
.setupPlaybackState(context, state)
.setupCover(context, state)
.setupTimelineControls(context, state)

private fun newWideLayout(
private fun newWideDockedLayout(
context: Context,
uiSettings: UISettings,
state: WidgetComponent.PlaybackState
) =
newRemoteViews(context, R.layout.widget_wide)
newRemoteViews(context, R.layout.widget_docked_wide)
.setupBar(
uiSettings,
)
.setupCover(context, state)
.setupFullControls(context, state)

private fun newLargeLayout(
private fun newThinPaneLayout(
context: Context,
uiSettings: UISettings,
state: WidgetComponent.PlaybackState
) =
newRemoteViews(context, R.layout.widget_large)
newRemoteViews(context, R.layout.widget_pane_thin)
.setupBackground(
uiSettings,
)
.setupPlaybackState(context, state)
.setupTimelineControls(context, state)

private fun newWidePaneLayout(
context: Context,
uiSettings: UISettings,
state: WidgetComponent.PlaybackState
) =
newRemoteViews(context, R.layout.widget_pane_wide)
.setupBackground(
uiSettings,
)
Expand Down Expand Up @@ -246,8 +267,14 @@ class WidgetProvider : AppWidgetProvider() {
*/
private fun RemoteViews.setupCover(
context: Context,
state: WidgetComponent.PlaybackState
state: WidgetComponent.PlaybackState?
): RemoteViews {
if (state == null) {
setImageViewBitmap(R.id.widget_cover, null)
setContentDescription(R.id.widget_cover, null)
return this
}

if (state.cover != null) {
setImageViewBitmap(R.id.widget_cover, state.cover)
setContentDescription(
Expand Down Expand Up @@ -388,6 +415,18 @@ class WidgetProvider : AppWidgetProvider() {
return this
}

private fun useRoundedRemoteViews(uiSettings: UISettings) =
uiSettings.roundMode || Build.VERSION.SDK_INT >= Build.VERSION_CODES.S

private fun canDisplayWaferCover(uiSettings: UISettings) =
// We cannot display album covers in the wafer-style widget when round mode is enabled
// below Android 12, as:
// - We cannot rely on system widget corner clipping, like on Android 12+
// - We cannot manually clip the widget ourselves due to broken clipToOutline support
// - We cannot determine the exact widget height that would allow us to clip the loaded
// image itself
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || !uiSettings.roundMode

companion object {
/**
* Broadcast when [WidgetProvider] desires to update it's widget with new information.
Expand Down
11 changes: 0 additions & 11 deletions app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import kotlin.math.sqrt
import org.oxycblt.auxio.ui.UISettings
import org.oxycblt.auxio.util.isLandscape
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.newMainPendingIntent
Expand Down Expand Up @@ -139,13 +138,3 @@ fun AppWidgetManager.updateAppWidgetCompat(
}
}
}

/**
* Returns whether rounded UI elements are appropriate for the widget, either based on the current
* settings or if the widget has to fit in aesthetically with other widgets.
*
* @param [uiSettings] [UISettings] required to obtain round mode configuration.
* @return true if to use round mode, false otherwise.
*/
fun useRoundedRemoteViews(uiSettings: UISettings) =
uiSettings.roundMode || Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/colorSurface" />
<corners android:radius="@android:dimen/system_app_widget_background_radius" />
</shape>
15 changes: 11 additions & 4 deletions app/src/main/res/drawable/ic_remote_default_cover_24.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@
android:viewportHeight="24">
<path
android:fillColor="?attr/colorSurfaceInverse"
android:pathData="M12,16.5Q13.875,16.5 15.188,15.188Q16.5,13.875 16.5,12Q16.5,10.125 15.188,8.812Q13.875,7.5 12,7.5Q10.125,7.5 8.812,8.812Q7.5,10.125 7.5,12Q7.5,13.875 8.812,15.188Q10.125,16.5 12,16.5ZM12,13Q11.575,13 11.288,12.712Q11,12.425 11,12Q11,11.575 11.288,11.287Q11.575,11 12,11Q12.425,11 12.713,11.287Q13,11.575 13,12Q13,12.425 12.713,12.712Q12.425,13 12,13ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,20Q15.35,20 17.675,17.675Q20,15.35 20,12Q20,8.65 17.675,6.325Q15.35,4 12,4Q8.65,4 6.325,6.325Q4,8.65 4,12Q4,15.35 6.325,17.675Q8.65,20 12,20ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z" />
<path
android:fillColor="?attr/colorOnSurfaceInverse"
android:pathData="M 11.999784 1.9998779 A 10 9.999999 0 0 1 22.000208 11.999784 C 22.000208 10.616452 21.737475 9.3164294 21.212142 8.099764 C 20.687476 6.8830985 19.974804 5.8247631 19.074805 4.924764 C 18.174806 4.0247649 17.11647 3.3122428 15.899805 2.78691 C 14.683139 2.2622439 13.383116 1.9998779 11.999784 1.9998779 z M 11.999784 1.9998779 C 10.616452 1.9998779 9.3164294 2.2622439 8.099764 2.78691 C 6.8830985 3.3122428 5.8247631 4.0247649 4.924764 4.924764 C 4.0247649 5.8247631 3.3126097 6.8830985 2.7879435 8.099764 C 2.2626107 9.3164294 1.9998779 10.616452 1.9998779 11.999784 A 10 9.999999 0 0 1 11.999784 1.9998779 z M 1.9998779 11.999784 C 1.9998779 13.383116 2.2626107 14.683139 2.7879435 15.899805 C 3.3126097 17.11647 4.0247649 18.174806 4.924764 19.074805 C 5.8247631 19.974804 6.8830985 20.687476 8.099764 21.212142 C 9.3164294 21.737475 10.616452 22.000208 11.999784 22.000208 A 10 9.999999 0 0 1 1.9998779 11.999784 z M 11.999784 22.000208 C 13.383116 22.000208 14.683139 21.737475 15.899805 21.212142 C 17.11647 20.687476 18.174806 19.974804 19.074805 19.074805 C 19.974804 18.174806 20.687476 17.11647 21.212142 15.899805 C 21.737475 14.683139 22.000208 13.383116 22.000208 11.999784 A 10 9.999999 0 0 1 11.999784 22.000208 z M 11.999784 3.9997559 C 9.7664532 3.9997559 7.8751938 4.7751969 6.3251953 6.3251953 C 4.7751969 7.8751938 3.9997559 9.7664532 3.9997559 11.999784 C 3.9997559 14.233115 4.7751969 16.124892 6.3251953 17.67489 C 7.8751938 19.224889 9.7664532 19.999813 11.999784 19.999813 C 14.233115 19.999813 16.124892 19.224889 17.67489 17.67489 C 19.224889 16.124892 19.999813 14.233115 19.999813 11.999784 C 19.999813 9.7664532 19.224889 7.8751938 17.67489 6.3251953 C 16.124892 4.7751969 14.233115 3.9997559 11.999784 3.9997559 z M 11.999784 7.4998006 C 13.249783 7.4998006 14.312888 7.9371994 15.18822 8.8118652 C 16.062886 9.6871977 16.499768 10.749786 16.499768 11.999784 C 16.499768 13.249783 16.062886 14.312888 15.18822 15.18822 C 14.312888 16.062886 13.249783 16.499768 11.999784 16.499768 C 10.749786 16.499768 9.6871977 16.062886 8.8118652 15.18822 C 7.9371994 14.312888 7.4998006 13.249783 7.4998006 11.999784 C 7.4998006 10.749786 7.9371994 9.6871977 8.8118652 8.8118652 C 9.6871977 7.9371994 10.749786 7.4998006 11.999784 7.4998006 z M 11.999784 10.999845 C 11.716451 10.999845 11.479533 11.095833 11.2882 11.287166 C 11.0962 11.479166 10.999845 11.716451 10.999845 11.999784 C 10.999845 12.283117 11.0962 12.520552 11.2882 12.711886 C 11.479533 12.903885 11.716451 13.00024 11.999784 13.00024 C 12.283117 13.00024 12.520919 12.903885 12.712919 12.711886 C 12.904252 12.520552 13.00024 12.283117 13.00024 11.999784 C 13.00024 11.716451 12.904252 11.479166 12.712919 11.287166 C 12.520919 11.095833 12.283117 10.999845 11.999784 10.999845 z " />
android:pathData="M 2.6000008,9.3143836e-7 H 21.399999 c 1.4404,0 2.6,1.15959996856164 2.6,2.59999986856164 V 21.399999 c 0,1.4404 -1.1596,2.6 -2.6,2.6 H 2.6000008 c -1.4403999,0 -2.59999986856164,-1.1596 -2.59999986856164,-2.6 V 2.6000008 C 9.3143836e-7,1.1596009 1.1596009,9.3143836e-7 2.6000008,9.3143836e-7 Z" />
<group
android:scaleX="0.5"
android:scaleY="0.5"
android:translateX="6"
android:translateY="6">
<path
android:fillColor="@android:color/white"
android:pathData="M10,21Q8.35,21 7.175,19.825Q6,18.65 6,17Q6,15.35 7.175,14.175Q8.35,13 10,13Q10.575,13 11.062,13.137Q11.55,13.275 12,13.55V3H18V7H14V17Q14,18.65 12.825,19.825Q11.65,21 10,21Z" />
</group>

</vector>
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/ui_widget_circle_button_bg.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="?attr/colorSurface" />
</shape>
6 changes: 6 additions & 0 deletions app/src/main/res/drawable/ui_widget_rectangle_button_bg.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/colorSurface" />
<corners android:radius="@dimen/spacing_mid_medium" />
</shape>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
55 changes: 55 additions & 0 deletions app/src/main/res/layout/widget_stick_thin.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal"
android:theme="@style/Theme.Auxio.Widget">


<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/ui_widget_rectangle_button_bg">

<android.widget.ImageButton
android:id="@+id/widget_skip_prev"
style="@style/Widget.Auxio.MaterialButton.AppWidget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/desc_skip_prev"
android:src="@drawable/ic_skip_prev_24" />
</FrameLayout>


<android.widget.ImageButton
android:id="@+id/widget_play_pause"
style="@style/Widget.Auxio.MaterialButton.AppWidget.PlayPause"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:contentDescription="@string/desc_play_pause"
android:src="@drawable/ic_play_24" />

<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/ui_widget_rectangle_button_bg">

<android.widget.ImageButton
android:id="@+id/widget_skip_next"
style="@style/Widget.Auxio.MaterialButton.AppWidget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/desc_skip_next"
android:src="@drawable/ic_skip_next_24" />

</FrameLayout>

</LinearLayout>


84 changes: 84 additions & 0 deletions app/src/main/res/layout/widget_stick_wide.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal"
android:theme="@style/Theme.Auxio.Widget">


<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/ui_widget_rectangle_button_bg">
<android.widget.ImageButton
android:id="@+id/widget_repeat"
style="@style/Widget.Auxio.MaterialButton.AppWidget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/desc_change_repeat"
android:src="@drawable/ic_repeat_off_24" />
</FrameLayout>

<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/ui_widget_rectangle_button_bg">

<android.widget.ImageButton
android:id="@+id/widget_skip_prev"
style="@style/Widget.Auxio.MaterialButton.AppWidget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/desc_skip_prev"
android:src="@drawable/ic_skip_prev_24" />
</FrameLayout>


<android.widget.ImageButton
android:id="@+id/widget_play_pause"
style="@style/Widget.Auxio.MaterialButton.AppWidget.PlayPause"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:contentDescription="@string/desc_play_pause"
android:src="@drawable/ic_play_24" />

<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/ui_widget_rectangle_button_bg">

<android.widget.ImageButton
android:id="@+id/widget_skip_next"
style="@style/Widget.Auxio.MaterialButton.AppWidget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/desc_skip_next"
android:src="@drawable/ic_skip_next_24" />

</FrameLayout>

<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/ui_widget_rectangle_button_bg">

<android.widget.ImageButton
android:id="@+id/widget_shuffle"
style="@style/Widget.Auxio.MaterialButton.AppWidget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/desc_shuffle"
android:src="@drawable/ic_shuffle_off_24" />
</FrameLayout>

</LinearLayout>


Loading

0 comments on commit 881df0f

Please sign in to comment.