From 881df0fc02af673ba26461dbcff40383f224de66 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Mon, 15 Jan 2024 20:47:21 -0700 Subject: [PATCH] widgets: add 1x3/1x4 widget 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. --- CHANGELOG.md | 1 + .../oxycblt/auxio/widgets/WidgetComponent.kt | 1 + .../oxycblt/auxio/widgets/WidgetProvider.kt | 93 +++++++++---- .../org/oxycblt/auxio/widgets/WidgetUtil.kt | 11 -- .../ui_widget_rectangle_button_bg.xml | 6 + .../drawable/ic_remote_default_cover_24.xml | 15 +- .../drawable/ui_widget_circle_button_bg.xml | 5 + .../ui_widget_rectangle_button_bg.xml | 6 + ...idget_small.xml => widget_docked_thin.xml} | 0 ...widget_wide.xml => widget_docked_wide.xml} | 0 ...widget_medium.xml => widget_pane_thin.xml} | 0 ...{widget_large.xml => widget_pane_wide.xml} | 0 app/src/main/res/layout/widget_stick_thin.xml | 55 ++++++++ app/src/main/res/layout/widget_stick_wide.xml | 84 ++++++++++++ app/src/main/res/layout/widget_thin.xml | 106 --------------- app/src/main/res/layout/widget_wafer_thin.xml | 85 ++++++++++++ app/src/main/res/layout/widget_wafer_wide.xml | 128 ++++++++++++++++++ app/src/main/res/xml-v31/widget_info.xml | 4 +- app/src/main/res/xml/widget_info.xml | 2 +- 19 files changed, 451 insertions(+), 151 deletions(-) create mode 100644 app/src/main/res/drawable-v31/ui_widget_rectangle_button_bg.xml create mode 100644 app/src/main/res/drawable/ui_widget_circle_button_bg.xml create mode 100644 app/src/main/res/drawable/ui_widget_rectangle_button_bg.xml rename app/src/main/res/layout/{widget_small.xml => widget_docked_thin.xml} (100%) rename app/src/main/res/layout/{widget_wide.xml => widget_docked_wide.xml} (100%) rename app/src/main/res/layout/{widget_medium.xml => widget_pane_thin.xml} (100%) rename app/src/main/res/layout/{widget_large.xml => widget_pane_wide.xml} (100%) create mode 100644 app/src/main/res/layout/widget_stick_thin.xml create mode 100644 app/src/main/res/layout/widget_stick_wide.xml delete mode 100644 app/src/main/res/layout/widget_thin.xml create mode 100644 app/src/main/res/layout/widget_wafer_thin.xml create mode 100644 app/src/main/res/layout/widget_wafer_wide.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index adecf82f2..4688896cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index d67892300..9280d2ea2 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -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] diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt index 153b7bccf..7a3bc6c40 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt @@ -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) @@ -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) + newRemoteViews(context, R.layout.widget_wafer_wide) + .setupBackground( + uiSettings, + ) + .setupCover(context, state.takeIf { canDisplayWaferCover(uiSettings) }) + .setupFullControls(context, state) + + private fun newThinDockedLayout( + context: Context, + uiSettings: UISettings, + state: WidgetComponent.PlaybackState + ) = + newRemoteViews(context, R.layout.widget_docked_thin) .setupBar( uiSettings, ) .setupCover(context, state) .setupTimelineControls(context, state) - private fun newMediumLayout( + private fun newWideDockedLayout( context: Context, uiSettings: UISettings, state: WidgetComponent.PlaybackState ) = - newRemoteViews(context, R.layout.widget_medium) - .setupBackground( - uiSettings, - ) - .setupPlaybackState(context, state) - .setupTimelineControls(context, state) - - private fun newWideLayout( - 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, ) @@ -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( @@ -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. diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt index cd7151b13..799aa8a67 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt @@ -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 @@ -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 diff --git a/app/src/main/res/drawable-v31/ui_widget_rectangle_button_bg.xml b/app/src/main/res/drawable-v31/ui_widget_rectangle_button_bg.xml new file mode 100644 index 000000000..feba7d550 --- /dev/null +++ b/app/src/main/res/drawable-v31/ui_widget_rectangle_button_bg.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_remote_default_cover_24.xml b/app/src/main/res/drawable/ic_remote_default_cover_24.xml index fa860ad09..d849d7106 100644 --- a/app/src/main/res/drawable/ic_remote_default_cover_24.xml +++ b/app/src/main/res/drawable/ic_remote_default_cover_24.xml @@ -6,8 +6,15 @@ android:viewportHeight="24"> - + 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" /> + + + + diff --git a/app/src/main/res/drawable/ui_widget_circle_button_bg.xml b/app/src/main/res/drawable/ui_widget_circle_button_bg.xml new file mode 100644 index 000000000..75c293f4b --- /dev/null +++ b/app/src/main/res/drawable/ui_widget_circle_button_bg.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ui_widget_rectangle_button_bg.xml b/app/src/main/res/drawable/ui_widget_rectangle_button_bg.xml new file mode 100644 index 000000000..b03eb6f1c --- /dev/null +++ b/app/src/main/res/drawable/ui_widget_rectangle_button_bg.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/widget_small.xml b/app/src/main/res/layout/widget_docked_thin.xml similarity index 100% rename from app/src/main/res/layout/widget_small.xml rename to app/src/main/res/layout/widget_docked_thin.xml diff --git a/app/src/main/res/layout/widget_wide.xml b/app/src/main/res/layout/widget_docked_wide.xml similarity index 100% rename from app/src/main/res/layout/widget_wide.xml rename to app/src/main/res/layout/widget_docked_wide.xml diff --git a/app/src/main/res/layout/widget_medium.xml b/app/src/main/res/layout/widget_pane_thin.xml similarity index 100% rename from app/src/main/res/layout/widget_medium.xml rename to app/src/main/res/layout/widget_pane_thin.xml diff --git a/app/src/main/res/layout/widget_large.xml b/app/src/main/res/layout/widget_pane_wide.xml similarity index 100% rename from app/src/main/res/layout/widget_large.xml rename to app/src/main/res/layout/widget_pane_wide.xml diff --git a/app/src/main/res/layout/widget_stick_thin.xml b/app/src/main/res/layout/widget_stick_thin.xml new file mode 100644 index 000000000..776d26525 --- /dev/null +++ b/app/src/main/res/layout/widget_stick_thin.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget_stick_wide.xml b/app/src/main/res/layout/widget_stick_wide.xml new file mode 100644 index 000000000..ff5a97d6b --- /dev/null +++ b/app/src/main/res/layout/widget_stick_wide.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget_thin.xml b/app/src/main/res/layout/widget_thin.xml deleted file mode 100644 index a3201fc54..000000000 --- a/app/src/main/res/layout/widget_thin.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/widget_wafer_thin.xml b/app/src/main/res/layout/widget_wafer_thin.xml new file mode 100644 index 000000000..fe7ec01dc --- /dev/null +++ b/app/src/main/res/layout/widget_wafer_thin.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget_wafer_wide.xml b/app/src/main/res/layout/widget_wafer_wide.xml new file mode 100644 index 000000000..f32d97b4b --- /dev/null +++ b/app/src/main/res/layout/widget_wafer_wide.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml-v31/widget_info.xml b/app/src/main/res/xml-v31/widget_info.xml index 11fcafb44..1d12aaff8 100644 --- a/app/src/main/res/xml-v31/widget_info.xml +++ b/app/src/main/res/xml-v31/widget_info.xml @@ -4,10 +4,10 @@ android:initialLayout="@layout/widget_default" android:minWidth="@dimen/widget_def_width" android:minHeight="@dimen/widget_def_height" + android:minResizeHeight="0dp" android:minResizeWidth="@dimen/widget_def_width" - android:minResizeHeight="@dimen/widget_def_height" android:previewImage="@drawable/ui_widget_preview" - android:previewLayout="@layout/widget_small" + android:previewLayout="@layout/widget_docked_thin" android:resizeMode="horizontal|vertical" android:targetCellWidth="3" android:targetCellHeight="2" diff --git a/app/src/main/res/xml/widget_info.xml b/app/src/main/res/xml/widget_info.xml index 84ac71fc7..35608e938 100644 --- a/app/src/main/res/xml/widget_info.xml +++ b/app/src/main/res/xml/widget_info.xml @@ -3,8 +3,8 @@ android:initialLayout="@layout/widget_default" android:minWidth="@dimen/widget_def_width" android:minHeight="@dimen/widget_def_height" + android:minResizeHeight="0dp" android:minResizeWidth="@dimen/widget_def_width" - android:minResizeHeight="@dimen/widget_def_height" android:previewImage="@drawable/ui_widget_preview" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="0"