widgets: add compact/mini forms
Add two new widget forms. The compact widget form shows the cover art [this time with a proper aspect ratio] but no controls. The mini form only shows the metadata. This seems to work well enough and provides more choices with how one wants to decorate their homescreen with Auxio's widgets.
This commit is contained in:
parent
fed6902c21
commit
c5b1293a90
8 changed files with 147 additions and 17 deletions
|
@ -99,6 +99,8 @@ dependencies {
|
||||||
// Material
|
// Material
|
||||||
implementation "com.google.android.material:material:1.4.0"
|
implementation "com.google.android.material:material:1.4.0"
|
||||||
|
|
||||||
|
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
|
||||||
|
|
||||||
// --- DEBUG ---
|
// --- DEBUG ---
|
||||||
|
|
||||||
// Lint
|
// Lint
|
||||||
|
|
|
@ -41,7 +41,12 @@ private fun createViews(
|
||||||
return views
|
return views
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RemoteViews.applyState(context: Context, state: WidgetState) {
|
private fun RemoteViews.applyMeta(context: Context, state: WidgetState) {
|
||||||
|
setTextViewText(R.id.widget_song, state.song.name)
|
||||||
|
setTextViewText(R.id.widget_artist, state.song.album.artist.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RemoteViews.applyControls(context: Context, state: WidgetState) {
|
||||||
setOnClickPendingIntent(
|
setOnClickPendingIntent(
|
||||||
R.id.widget_skip_prev,
|
R.id.widget_skip_prev,
|
||||||
context.newBroadcastIntent(
|
context.newBroadcastIntent(
|
||||||
|
@ -63,9 +68,6 @@ private fun RemoteViews.applyState(context: Context, state: WidgetState) {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
setTextViewText(R.id.widget_song, state.song.name)
|
|
||||||
setTextViewText(R.id.widget_artist, state.song.album.artist.name)
|
|
||||||
|
|
||||||
setImageViewResource(
|
setImageViewResource(
|
||||||
R.id.widget_play_pause,
|
R.id.widget_play_pause,
|
||||||
if (state.isPlaying) {
|
if (state.isPlaying) {
|
||||||
|
@ -74,7 +76,9 @@ private fun RemoteViews.applyState(context: Context, state: WidgetState) {
|
||||||
R.drawable.ic_play
|
R.drawable.ic_play
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RemoteViews.applyCover(context: Context, state: WidgetState) {
|
||||||
if (state.albumArt != null) {
|
if (state.albumArt != null) {
|
||||||
setImageViewBitmap(R.id.widget_cover, state.albumArt)
|
setImageViewBitmap(R.id.widget_cover, state.albumArt)
|
||||||
setContentDescription(
|
setContentDescription(
|
||||||
|
@ -90,15 +94,32 @@ fun createDefaultWidget(context: Context): RemoteViews {
|
||||||
return createViews(context, R.layout.widget_default)
|
return createViews(context, R.layout.widget_default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createMiniWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
|
val views = createViews(context, R.layout.widget_mini)
|
||||||
|
views.applyMeta(context, state)
|
||||||
|
return views
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createCompactWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
|
val views = createViews(context, R.layout.widget_compact)
|
||||||
|
views.applyMeta(context, state)
|
||||||
|
views.applyCover(context, state)
|
||||||
|
return views
|
||||||
|
}
|
||||||
|
|
||||||
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
|
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
val views = createViews(context, R.layout.widget_small)
|
val views = createViews(context, R.layout.widget_small)
|
||||||
views.applyState(context, state)
|
views.applyMeta(context, state)
|
||||||
|
views.applyCover(context, state)
|
||||||
|
views.applyControls(context, state)
|
||||||
return views
|
return views
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createFullWidget(context: Context, state: WidgetState): RemoteViews {
|
fun createFullWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
val views = createViews(context, R.layout.widget_full)
|
val views = createViews(context, R.layout.widget_full)
|
||||||
views.applyState(context, state)
|
views.applyMeta(context, state)
|
||||||
|
views.applyCover(context, state)
|
||||||
|
views.applyControls(context, state)
|
||||||
|
|
||||||
views.setOnClickPendingIntent(
|
views.setOnClickPendingIntent(
|
||||||
R.id.widget_loop,
|
R.id.widget_loop,
|
||||||
|
|
|
@ -38,10 +38,13 @@ import org.oxycblt.auxio.ui.isLandscape
|
||||||
* Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively
|
* Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively
|
||||||
* packing what could be considered 3 or 4 widgets into a single responsive widget. More specifically:
|
* packing what could be considered 3 or 4 widgets into a single responsive widget. More specifically:
|
||||||
*
|
*
|
||||||
|
* - For widgets 2x1 or lower, show a text-only view with no controls
|
||||||
|
* - For widgets Wx1 or lower, show a compact view with no controls.
|
||||||
* - For widgets Wx2 or higher, show an expanded view with album art and basic controls
|
* - For widgets Wx2 or higher, show an expanded view with album art and basic controls
|
||||||
* - For widgets 4x2 or higher, show a complete view with all playback controls
|
* - For widgets 4x2 or higher, show a complete view with all playback controls
|
||||||
*
|
*
|
||||||
* Other widget variants might be added if there is sufficient demand.
|
* There are some minor problems with this implementation [notably UI jittering when the widget
|
||||||
|
* picks a new layout below Android 12], but this is tolerable.
|
||||||
*
|
*
|
||||||
* For more specific details about these sub-widgets, see Forms.kt.
|
* For more specific details about these sub-widgets, see Forms.kt.
|
||||||
*/
|
*/
|
||||||
|
@ -68,9 +71,13 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Map each widget form to the cells where it would look at least okay.
|
// Map each widget form to the cells where it would look at least okay.
|
||||||
|
// The large widgets are 140 instead of 110 so that they're backwards compatible
|
||||||
|
// with the old widget size reporting
|
||||||
val views = mapOf(
|
val views = mapOf(
|
||||||
SizeF(180f, 110f) to createSmallWidget(context, state),
|
SizeF(180f, 40f) to createMiniWidget(context, state),
|
||||||
SizeF(250f, 110f) to createFullWidget(context, state)
|
SizeF(250f, 40f) to createCompactWidget(context, state),
|
||||||
|
SizeF(180f, 140f) to createSmallWidget(context, state),
|
||||||
|
SizeF(250f, 140f) to createFullWidget(context, state)
|
||||||
)
|
)
|
||||||
|
|
||||||
appWidgetManager.applyViewsCompat(context, views)
|
appWidgetManager.applyViewsCompat(context, views)
|
||||||
|
@ -138,8 +145,8 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
updateAppWidget(name, RemoteViews(views))
|
updateAppWidget(name, RemoteViews(views))
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we try our best to backport the responsive behavior to older versions.
|
// Otherwise, we try our best to backport the responsive behavior to older versions.
|
||||||
// This is mostly a guess based on RemoteView's documentation. It may be improved when
|
// This is mostly a guess based on RemoteView's documentation. It seems to work well
|
||||||
// Android 12's source is released.
|
// on most launchers. It may be improved when Android 12's source is released.
|
||||||
|
|
||||||
// Each widget has independent dimensions, so we iterate through them all
|
// Each widget has independent dimensions, so we iterate through them all
|
||||||
// and do this for each.
|
// and do this for each.
|
||||||
|
@ -168,8 +175,8 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
height = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
|
height = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
|
||||||
}
|
}
|
||||||
|
|
||||||
height += padW
|
height += padH
|
||||||
width += padH
|
width += padW
|
||||||
|
|
||||||
logD("Assuming true widget dimens are ${width}x$height")
|
logD("Assuming true widget dimens are ${width}x$height")
|
||||||
|
|
||||||
|
|
11
app/src/main/res/drawable/ui_widget_aspect_ratio.xml
Normal file
11
app/src/main/res/drawable/ui_widget_aspect_ratio.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<solid
|
||||||
|
android:color="#00000000" />
|
||||||
|
|
||||||
|
<size
|
||||||
|
android:width="1000px"
|
||||||
|
android:height="1000px"/>
|
||||||
|
</shape>
|
63
app/src/main/res/layout/widget_compact.xml
Normal file
63
app/src/main/res/layout/widget_compact.xml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@android:id/background"
|
||||||
|
android:background="?attr/colorSurface"
|
||||||
|
android:theme="@style/Theme.Widget"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
We can't use ConstraintLayout on widgets due to RemoteView limitations, but
|
||||||
|
for this widget form to work, we need to get the cover to preserve it's aspect
|
||||||
|
ratio. So, we use a fixed-size 1000x1000 drawable and then align the cover view
|
||||||
|
to that so that the bounds will scale properly.
|
||||||
|
This is easily one of the worst layout hacks I've done, but it seems to work.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/widget_aspect_ratio"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ui_widget_aspect_ratio"
|
||||||
|
android:visibility="invisible"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/widget_cover"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_alignStart="@id/widget_aspect_ratio"
|
||||||
|
android:layout_alignTop="@id/widget_aspect_ratio"
|
||||||
|
android:layout_alignEnd="@id/widget_aspect_ratio"
|
||||||
|
android:layout_alignBottom="@id/widget_aspect_ratio"
|
||||||
|
android:src="@drawable/ic_song"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/widget_panel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_toEndOf="@+id/widget_cover"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/spacing_medium">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_song"
|
||||||
|
style="@style/Widget.Component.AppWidget.TextView.Primary"
|
||||||
|
android:text="@string/def_widget_song" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_artist"
|
||||||
|
style="@style/Widget.Component.AppWidget.TextView.Secondary"
|
||||||
|
android:text="@string/def_widget_artist" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
26
app/src/main/res/layout/widget_mini.xml
Normal file
26
app/src/main/res/layout/widget_mini.xml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@android:id/background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:background="?attr/colorSurface"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/spacing_medium"
|
||||||
|
android:theme="@style/Theme.Widget">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_song"
|
||||||
|
style="@style/Widget.Component.AppWidget.TextView.Primary"
|
||||||
|
android:text="@string/def_widget_song" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_artist"
|
||||||
|
style="@style/Widget.Component.AppWidget.TextView.Secondary"
|
||||||
|
android:text="@string/def_widget_artist" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -3,10 +3,10 @@
|
||||||
android:description="@string/info_widget_desc"
|
android:description="@string/info_widget_desc"
|
||||||
android:initialLayout="@layout/widget_small"
|
android:initialLayout="@layout/widget_small"
|
||||||
android:minResizeWidth="180dp"
|
android:minResizeWidth="180dp"
|
||||||
android:minResizeHeight="110dp"
|
android:minResizeHeight="40dp"
|
||||||
android:previewLayout="@layout/widget_small"
|
android:previewLayout="@layout/widget_small"
|
||||||
android:resizeMode="horizontal|vertical"
|
android:resizeMode="horizontal|vertical"
|
||||||
android:targetCellWidth="3"
|
android:minWidth="180dp"
|
||||||
android:targetCellHeight="2"
|
android:minHeight="110dp"
|
||||||
android:updatePeriodMillis="0"
|
android:updatePeriodMillis="0"
|
||||||
android:widgetCategory="home_screen" />
|
android:widgetCategory="home_screen" />
|
|
@ -4,7 +4,7 @@
|
||||||
android:minWidth="180dp"
|
android:minWidth="180dp"
|
||||||
android:minHeight="110dp"
|
android:minHeight="110dp"
|
||||||
android:minResizeWidth="180dp"
|
android:minResizeWidth="180dp"
|
||||||
android:minResizeHeight="110dp"
|
android:minResizeHeight="40dp"
|
||||||
android:previewImage="@drawable/ui_widget_preview"
|
android:previewImage="@drawable/ui_widget_preview"
|
||||||
android:resizeMode="horizontal|vertical"
|
android:resizeMode="horizontal|vertical"
|
||||||
android:updatePeriodMillis="0"
|
android:updatePeriodMillis="0"
|
||||||
|
|
Loading…
Reference in a new issue