widgets: improve preview
Use a 5x5 instead of a 4x5 preview for the widget so that it lines up better with most default launcher configurations.
This commit is contained in:
parent
4cc433f7ef
commit
97459fdcca
7 changed files with 82 additions and 82 deletions
|
@ -173,8 +173,8 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
|
||||||
val list = mutableListOf<Song>()
|
val list = mutableListOf<Song>()
|
||||||
|
|
||||||
songs.groupBy { it.album }.entries.sortedWith(compareByDescending { it.key.year }).forEach { entry ->
|
songs.groupBy { it.album }.entries.sortedWith(compareByDescending { it.key.year }).forEach { entry ->
|
||||||
list.addAll(entry.value.sortedWith(compareBy { it.track }))
|
list.addAll(entry.value.sortedWith(compareBy { it.track }))
|
||||||
}
|
}
|
||||||
|
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,7 @@ import org.oxycblt.auxio.ui.newMainIntent
|
||||||
|
|
||||||
private fun createViews(
|
private fun createViews(
|
||||||
context: Context,
|
context: Context,
|
||||||
@LayoutRes layout: Int,
|
@LayoutRes layout: Int
|
||||||
state: WidgetState
|
|
||||||
): RemoteViews {
|
): RemoteViews {
|
||||||
val views = RemoteViews(context.packageName, layout)
|
val views = RemoteViews(context.packageName, layout)
|
||||||
|
|
||||||
|
@ -39,31 +38,35 @@ private fun createViews(
|
||||||
context.newMainIntent()
|
context.newMainIntent()
|
||||||
)
|
)
|
||||||
|
|
||||||
views.setOnClickPendingIntent(
|
return views
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun RemoteViews.applyState(context: Context, state: WidgetState) {
|
||||||
|
setOnClickPendingIntent(
|
||||||
R.id.widget_skip_prev,
|
R.id.widget_skip_prev,
|
||||||
context.newBroadcastIntent(
|
context.newBroadcastIntent(
|
||||||
PlaybackService.ACTION_SKIP_PREV
|
PlaybackService.ACTION_SKIP_PREV
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
views.setOnClickPendingIntent(
|
setOnClickPendingIntent(
|
||||||
R.id.widget_play_pause,
|
R.id.widget_play_pause,
|
||||||
context.newBroadcastIntent(
|
context.newBroadcastIntent(
|
||||||
PlaybackService.ACTION_PLAY_PAUSE
|
PlaybackService.ACTION_PLAY_PAUSE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
views.setOnClickPendingIntent(
|
setOnClickPendingIntent(
|
||||||
R.id.widget_skip_next,
|
R.id.widget_skip_next,
|
||||||
context.newBroadcastIntent(
|
context.newBroadcastIntent(
|
||||||
PlaybackService.ACTION_SKIP_NEXT
|
PlaybackService.ACTION_SKIP_NEXT
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
views.setTextViewText(R.id.widget_song, state.song.name)
|
setTextViewText(R.id.widget_song, state.song.name)
|
||||||
views.setTextViewText(R.id.widget_artist, state.song.album.artist.name)
|
setTextViewText(R.id.widget_artist, state.song.album.artist.name)
|
||||||
|
|
||||||
views.setImageViewResource(
|
setImageViewResource(
|
||||||
R.id.widget_play_pause,
|
R.id.widget_play_pause,
|
||||||
if (state.isPlaying) {
|
if (state.isPlaying) {
|
||||||
R.drawable.ic_pause
|
R.drawable.ic_pause
|
||||||
|
@ -73,24 +76,29 @@ private fun createViews(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (state.albumArt != null) {
|
if (state.albumArt != null) {
|
||||||
views.setImageViewBitmap(R.id.widget_cover, state.albumArt)
|
setImageViewBitmap(R.id.widget_cover, state.albumArt)
|
||||||
views.setContentDescription(
|
setContentDescription(
|
||||||
R.id.widget_cover, context.getString(R.string.desc_album_cover, state.song.album.name)
|
R.id.widget_cover, context.getString(R.string.desc_album_cover, state.song.album.name)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
views.setImageViewResource(R.id.widget_cover, R.drawable.ic_song)
|
setImageViewResource(R.id.widget_cover, R.drawable.ic_song)
|
||||||
views.setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover))
|
setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return views
|
fun createDefaultWidget(context: Context): RemoteViews {
|
||||||
|
return createViews(context, R.layout.widget_default)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
|
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
return createViews(context, R.layout.widget_small, state)
|
val views = createViews(context, R.layout.widget_small)
|
||||||
|
views.applyState(context, state)
|
||||||
|
return views
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createFullWidget(context: Context, state: WidgetState): RemoteViews {
|
fun createFullWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
val views = createViews(context, R.layout.widget_full, state)
|
val views = createViews(context, R.layout.widget_full)
|
||||||
|
views.applyState(context, state)
|
||||||
|
|
||||||
views.setOnClickPendingIntent(
|
views.setOnClickPendingIntent(
|
||||||
R.id.widget_loop,
|
R.id.widget_loop,
|
||||||
|
@ -107,9 +115,9 @@ fun createFullWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
)
|
)
|
||||||
|
|
||||||
// The main way the large widget differs from the other widgets is the addition of extra
|
// The main way the large widget differs from the other widgets is the addition of extra
|
||||||
// controls. However, since the context we use to load attributes is from the main process,
|
// controls. However, since we can't retrieve the context of our views here, we cant
|
||||||
// attempting to dynamically color anything will result in an error. More duplicate
|
// dynamically set the image view attributes. More duplicate resources it is. This is
|
||||||
// resources it is. This is getting really tiring.
|
// getting really tiring.
|
||||||
|
|
||||||
val shuffleRes = when {
|
val shuffleRes = when {
|
||||||
state.isShuffled -> R.drawable.ic_shuffle_tinted
|
state.isShuffled -> R.drawable.ic_shuffle_tinted
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.widgets
|
package org.oxycblt.auxio.widgets
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.appwidget.AppWidgetHostView
|
import android.appwidget.AppWidgetHostView
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.appwidget.AppWidgetProvider
|
import android.appwidget.AppWidgetProvider
|
||||||
|
@ -30,12 +29,10 @@ import android.os.Bundle
|
||||||
import android.util.SizeF
|
import android.util.SizeF
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
|
||||||
import org.oxycblt.auxio.coil.loadBitmap
|
import org.oxycblt.auxio.coil.loadBitmap
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.ui.isLandscape
|
import org.oxycblt.auxio.ui.isLandscape
|
||||||
import org.oxycblt.auxio.ui.newMainIntent
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -44,15 +41,61 @@ import org.oxycblt.auxio.ui.newMainIntent
|
||||||
* - 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.
|
||||||
|
*
|
||||||
* For more specific details about these sub-widgets, see Forms.kt.
|
* For more specific details about these sub-widgets, see Forms.kt.
|
||||||
*/
|
*/
|
||||||
class WidgetProvider : AppWidgetProvider() {
|
class WidgetProvider : AppWidgetProvider() {
|
||||||
|
/*
|
||||||
|
* Update the widget based on the playback state.
|
||||||
|
*/
|
||||||
|
fun update(context: Context, playbackManager: PlaybackStateManager) {
|
||||||
|
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||||
|
val song = playbackManager.song
|
||||||
|
|
||||||
|
if (song == null) {
|
||||||
|
reset(context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loadBitmap(context, song) { bitmap ->
|
||||||
|
val state = WidgetState(
|
||||||
|
song,
|
||||||
|
bitmap,
|
||||||
|
playbackManager.isPlaying,
|
||||||
|
playbackManager.isShuffling,
|
||||||
|
playbackManager.loopMode
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map each widget form to the cells where it would look at least okay.
|
||||||
|
val views = mapOf(
|
||||||
|
SizeF(180f, 110f) to createSmallWidget(context, state),
|
||||||
|
SizeF(250f, 110f) to createFullWidget(context, state)
|
||||||
|
)
|
||||||
|
|
||||||
|
appWidgetManager.applyViewsCompat(context, views)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Revert this widget to its default view
|
||||||
|
*/
|
||||||
|
fun reset(context: Context) {
|
||||||
|
logD("Resetting widget")
|
||||||
|
|
||||||
|
AppWidgetManager.getInstance(context).updateAppWidget(
|
||||||
|
ComponentName(context, this::class.java), createDefaultWidget(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// / --- OVERRIDES ---
|
||||||
|
|
||||||
override fun onUpdate(
|
override fun onUpdate(
|
||||||
context: Context,
|
context: Context,
|
||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
appWidgetIds: IntArray
|
appWidgetIds: IntArray
|
||||||
) {
|
) {
|
||||||
applyDefaultViews(context, appWidgetManager)
|
reset(context)
|
||||||
requestUpdate(context)
|
requestUpdate(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,44 +114,15 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// / --- INTERNAL METHODS ---
|
||||||
* Update the widget based on the playback state.
|
|
||||||
*/
|
|
||||||
fun update(context: Context, playbackManager: PlaybackStateManager) {
|
|
||||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
|
||||||
val song = playbackManager.song
|
|
||||||
|
|
||||||
if (song == null) {
|
private fun requestUpdate(context: Context) {
|
||||||
applyDefaultViews(context, appWidgetManager)
|
logD("Sending update intent to PlaybackService")
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loadBitmap(context, song) { bitmap ->
|
val intent = Intent(ACTION_WIDGET_UPDATE)
|
||||||
val state = WidgetState(
|
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
|
||||||
song,
|
|
||||||
bitmap,
|
|
||||||
playbackManager.isPlaying,
|
|
||||||
playbackManager.isShuffling,
|
|
||||||
playbackManager.loopMode
|
|
||||||
)
|
|
||||||
|
|
||||||
// Map each widget form to the rough dimensions where it would look at least okay.
|
context.sendBroadcast(intent)
|
||||||
val views = mapOf(
|
|
||||||
SizeF(110f, 110f) to createSmallWidget(context, state),
|
|
||||||
SizeF(250f, 110f) to createFullWidget(context, state)
|
|
||||||
)
|
|
||||||
|
|
||||||
appWidgetManager.applyViewsCompat(context, views)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Revert this widget to its default view
|
|
||||||
*/
|
|
||||||
fun reset(context: Context) {
|
|
||||||
logD("Resetting widget")
|
|
||||||
|
|
||||||
applyDefaultViews(context, AppWidgetManager.getInstance(context))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AppWidgetManager.applyViewsCompat(
|
private fun AppWidgetManager.applyViewsCompat(
|
||||||
|
@ -190,27 +204,6 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RemoteViewLayout")
|
|
||||||
private fun applyDefaultViews(context: Context, manager: AppWidgetManager) {
|
|
||||||
val views = RemoteViews(context.packageName, R.layout.widget_default)
|
|
||||||
|
|
||||||
views.setOnClickPendingIntent(
|
|
||||||
android.R.id.background,
|
|
||||||
context.newMainIntent()
|
|
||||||
)
|
|
||||||
|
|
||||||
manager.updateAppWidget(ComponentName(context, this::class.java), views)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requestUpdate(context: Context) {
|
|
||||||
logD("Sending update intent to PlaybackService")
|
|
||||||
|
|
||||||
val intent = Intent(ACTION_WIDGET_UPDATE)
|
|
||||||
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
|
|
||||||
|
|
||||||
context.sendBroadcast(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_WIDGET_UPDATE = BuildConfig.APPLICATION_ID + ".action.WIDGET_UPDATE"
|
const val ACTION_WIDGET_UPDATE = BuildConfig.APPLICATION_ID + ".action.WIDGET_UPDATE"
|
||||||
}
|
}
|
||||||
|
|
BIN
app/src/main/res/drawable-nodpi/ui_widget_preview.png
Normal file
BIN
app/src/main/res/drawable-nodpi/ui_widget_preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
Before Width: | Height: | Size: 17 KiB |
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Widget.Component.AppWidget.Button" parent="Widget.Component.AppWidget.Button.Base">
|
<style name="Widget.Component.AppWidget.Button" parent="Widget.Component.AppWidget.Button.Base">
|
||||||
<item name="android:background">@drawable/ui_unbounded_ripple</item>
|
<item name="android:background">@drawable/ui_unbounded_ripple</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="surface_day">#fffafafa</color>
|
<color name="surface_day">#fffafafa</color>
|
||||||
<color name="surface_night">#ff121212</color>
|
<color name="surface_night">#ff151515</color>
|
||||||
<color name="surface_black">#ff000000</color>
|
<color name="surface_black">#ff000000</color>
|
||||||
|
|
||||||
<color name="surface">@color/surface_day</color>
|
<color name="surface">@color/surface_day</color>
|
||||||
|
|
Loading…
Reference in a new issue