widgets: make widget responsive
The plans for widgets have changed somewhat. Instead of 4 or so variants, there will instead be one unified widget that chooses different layouts depending on its size. The first one added is the full widget, which shows more controls as long as theres enough space.
This commit is contained in:
parent
5d7d86b17e
commit
6f8f333b72
24 changed files with 655 additions and 289 deletions
|
|
@ -57,8 +57,8 @@
|
|||
android:roundIcon="@mipmap/ic_launcher_round" />
|
||||
|
||||
<receiver
|
||||
android:label="@string/info_widget_minimal"
|
||||
android:name=".widgets.MinimalWidgetProvider"
|
||||
android:label="@string/info_channel_name"
|
||||
android:name=".widgets.WidgetProvider"
|
||||
android:exported="false">
|
||||
|
||||
<intent-filter>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ import org.oxycblt.auxio.ui.isNight
|
|||
|
||||
/**
|
||||
* The single [AppCompatActivity] for Auxio.
|
||||
* TODO: Port widgets to non-12 android
|
||||
* TODO: Migrate to colorAccent
|
||||
*
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private val playbackModel: PlaybackViewModel by viewModels()
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ import org.oxycblt.auxio.playback.state.LoopMode
|
|||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.ui.getSystemServiceSafe
|
||||
import org.oxycblt.auxio.widgets.BaseWidget
|
||||
import org.oxycblt.auxio.widgets.WidgetController
|
||||
import org.oxycblt.auxio.widgets.WidgetProvider
|
||||
|
||||
/**
|
||||
* A service that manages the system-side aspects of playback, such as:
|
||||
|
|
@ -140,7 +140,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
|
|||
addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
||||
addAction(Intent.ACTION_HEADSET_PLUG)
|
||||
|
||||
addAction(BaseWidget.ACTION_WIDGET_UPDATE)
|
||||
addAction(WidgetProvider.ACTION_WIDGET_UPDATE)
|
||||
|
||||
registerReceiver(systemReceiver, this)
|
||||
}
|
||||
|
|
@ -495,10 +495,8 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
|
|||
}
|
||||
}
|
||||
|
||||
BaseWidget.ACTION_WIDGET_UPDATE -> {
|
||||
widgets.initWidget(
|
||||
intent.getIntExtra(BaseWidget.KEY_WIDGET_TYPE, -1)
|
||||
)
|
||||
WidgetProvider.ACTION_WIDGET_UPDATE -> {
|
||||
widgets.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.MainActivity
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.logE
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
|
@ -101,7 +102,7 @@ fun @receiver:ColorRes Int.toColor(context: Context): Int {
|
|||
return try {
|
||||
ContextCompat.getColor(context, this)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
logE("Attempted color load failed.")
|
||||
logE("Attempted color load failed: ${e.stackTraceToString()}")
|
||||
|
||||
// Default to the emergency color [Black] if the loading fails.
|
||||
ContextCompat.getColor(context, android.R.color.black)
|
||||
|
|
@ -146,6 +147,8 @@ fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
|
|||
resolvedAttr.data
|
||||
}
|
||||
|
||||
logD(context.theme)
|
||||
|
||||
return color.toColor(context)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,109 +0,0 @@
|
|||
package org.oxycblt.auxio.widgets
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViews
|
||||
import androidx.annotation.LayoutRes
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.ui.newMainIntent
|
||||
|
||||
/**
|
||||
* The base widget class for all widget implementations in Auxio.
|
||||
*/
|
||||
abstract class BaseWidget : AppWidgetProvider() {
|
||||
abstract val type: Int
|
||||
|
||||
protected open fun createViews(context: Context, @LayoutRes layout: Int): RemoteViews {
|
||||
val views = RemoteViews(context.packageName, layout)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
android.R.id.background,
|
||||
context.newMainIntent()
|
||||
)
|
||||
|
||||
return views
|
||||
}
|
||||
|
||||
/*
|
||||
* Update views based off of the playback state. This job is asynchronous and should be
|
||||
* notified as completed by calling [onDone]
|
||||
*/
|
||||
protected abstract fun updateViews(
|
||||
context: Context,
|
||||
playbackManager: PlaybackStateManager,
|
||||
onDone: (RemoteViews) -> Unit
|
||||
)
|
||||
|
||||
/*
|
||||
* Update the widget based on the playback state.
|
||||
*/
|
||||
fun update(context: Context, playbackManager: PlaybackStateManager) {
|
||||
val manager = AppWidgetManager.getInstance(context)
|
||||
|
||||
// View updates are often async due to image loading, so only push the views
|
||||
// when the callback is called.
|
||||
updateViews(context, playbackManager) { views ->
|
||||
manager.applyViews(context, views)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Revert this widget to its default view
|
||||
*/
|
||||
fun reset(context: Context) {
|
||||
logD("Resetting widget")
|
||||
|
||||
val manager = AppWidgetManager.getInstance(context)
|
||||
manager.applyViews(context, defaultViews(context))
|
||||
}
|
||||
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray
|
||||
) {
|
||||
logD("Sending update intent to PlaybackService")
|
||||
|
||||
appWidgetManager.applyViews(context, defaultViews(context))
|
||||
|
||||
val intent = Intent(ACTION_WIDGET_UPDATE)
|
||||
.putExtra(KEY_WIDGET_TYPE, type)
|
||||
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
|
||||
|
||||
context.sendBroadcast(intent)
|
||||
}
|
||||
|
||||
@SuppressLint("RemoteViewLayout")
|
||||
protected fun defaultViews(context: Context): RemoteViews {
|
||||
return RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.widget_default
|
||||
)
|
||||
}
|
||||
|
||||
private fun AppWidgetManager.applyViews(context: Context, views: RemoteViews) {
|
||||
val ids = getAppWidgetIds(ComponentName(context, this::class.java))
|
||||
|
||||
if (ids.isNotEmpty()) {
|
||||
// Existing widgets found, update those
|
||||
ids.forEach { id ->
|
||||
updateAppWidget(id, views)
|
||||
}
|
||||
} else {
|
||||
// No existing widgets found. Fall back to the name of the widget class
|
||||
updateAppWidget(ComponentName(context, this@BaseWidget::class.java), views)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTION_WIDGET_UPDATE = BuildConfig.APPLICATION_ID + ".action.WIDGET_UPDATE"
|
||||
const val KEY_WIDGET_TYPE = BuildConfig.APPLICATION_ID + ".key.WIDGET_TYPE"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
package org.oxycblt.auxio.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.widget.RemoteViews
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.coil.loadBitmap
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.playback.system.PlaybackService
|
||||
import org.oxycblt.auxio.ui.newBroadcastIntent
|
||||
|
||||
/**
|
||||
* The minimal widget, which shows the primary song controls and basic playback information.
|
||||
*/
|
||||
class MinimalWidgetProvider : BaseWidget() {
|
||||
override val type: Int get() = TYPE
|
||||
|
||||
override fun createViews(context: Context, layout: Int): RemoteViews {
|
||||
val views = super.createViews(context, layout)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_skip_prev,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_SKIP_PREV
|
||||
)
|
||||
)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_play_pause,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_PLAY_PAUSE
|
||||
)
|
||||
)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_skip_next,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_SKIP_NEXT
|
||||
)
|
||||
)
|
||||
|
||||
return views
|
||||
}
|
||||
|
||||
override fun updateViews(
|
||||
context: Context,
|
||||
playbackManager: PlaybackStateManager,
|
||||
onDone: (RemoteViews) -> Unit
|
||||
) {
|
||||
val song = playbackManager.song
|
||||
|
||||
if (song != null) {
|
||||
logD("Updating view to ${song.name}")
|
||||
|
||||
val views = createViews(context, R.layout.widget_minimal)
|
||||
|
||||
// Update the metadata
|
||||
views.setTextViewText(R.id.widget_song, song.name)
|
||||
views.setTextViewText(R.id.widget_artist, song.album.artist.name)
|
||||
|
||||
views.setInt(
|
||||
R.id.widget_play_pause,
|
||||
"setImageResource",
|
||||
if (playbackManager.isPlaying) {
|
||||
R.drawable.ic_pause
|
||||
} else {
|
||||
R.drawable.ic_play
|
||||
}
|
||||
)
|
||||
|
||||
// loadBitmap is async, hence the need for onDone
|
||||
loadBitmap(context, song) { bitmap ->
|
||||
if (bitmap != null) {
|
||||
views.setBitmap(R.id.widget_cover, "setImageBitmap", bitmap)
|
||||
views.setCharSequence(
|
||||
R.id.widget_cover, "setContentDescription",
|
||||
context.getString(R.string.description_album_cover, song.album.name)
|
||||
)
|
||||
} else {
|
||||
views.setInt(R.id.widget_cover, "setImageResource", R.drawable.ic_song)
|
||||
views.setCharSequence(
|
||||
R.id.widget_cover, "setContentDescription",
|
||||
context.getString(R.string.description_placeholder_cover)
|
||||
)
|
||||
}
|
||||
|
||||
onDone(views)
|
||||
}
|
||||
} else {
|
||||
logD("No song playing, reverting to default view")
|
||||
|
||||
onDone(defaultViews(context))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TYPE = 0xA0D0
|
||||
|
||||
fun new(): MinimalWidgetProvider? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
MinimalWidgetProvider()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +1,36 @@
|
|||
package org.oxycblt.auxio.widgets
|
||||
|
||||
import android.content.Context
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.LoopMode
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
|
||||
/**
|
||||
* A wrapper around each widget subclass that manages which updates to deliver from the
|
||||
* main process's [PlaybackStateManager] and [SettingsManager] instances to the widgets themselves.
|
||||
* A wrapper around each [WidgetProvider] that plugs into the main Auxio process and updates the
|
||||
* widget state based off of that. This cannot be rolled into [WidgetProvider] directly.
|
||||
*/
|
||||
class WidgetController(private val context: Context) :
|
||||
PlaybackStateManager.Callback,
|
||||
SettingsManager.Callback {
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val minimal = MinimalWidgetProvider()
|
||||
private val widget = WidgetProvider()
|
||||
|
||||
init {
|
||||
playbackManager.addCallback(this)
|
||||
settingsManager.addCallback(this)
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a newly added widget. This usually comes from the WIDGET_UPDATE intent.
|
||||
*/
|
||||
fun initWidget(type: Int) {
|
||||
logD("Updating new widget $type")
|
||||
|
||||
when (type) {
|
||||
MinimalWidgetProvider.TYPE -> minimal.update(context, playbackManager)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Update every widget, regardless of whether it needs to or not.
|
||||
*/
|
||||
fun update() {
|
||||
logD("Updating all widgets")
|
||||
|
||||
minimal.update(context, playbackManager)
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
/*
|
||||
* Release this instance, removing the callbacks and resetting all widgets
|
||||
*/
|
||||
fun release() {
|
||||
logD("Resetting widgets")
|
||||
|
||||
minimal.reset(context)
|
||||
widget.reset(context)
|
||||
playbackManager.removeCallback(this)
|
||||
settingsManager.removeCallback(this)
|
||||
}
|
||||
|
|
@ -56,20 +38,28 @@ class WidgetController(private val context: Context) :
|
|||
// --- PLAYBACKSTATEMANAGER CALLBACKS ---
|
||||
|
||||
override fun onSongUpdate(song: Song?) {
|
||||
minimal.update(context, playbackManager)
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
override fun onPlayingUpdate(isPlaying: Boolean) {
|
||||
minimal.update(context, playbackManager)
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
override fun onShuffleUpdate(isShuffling: Boolean) {
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
override fun onLoopUpdate(loopMode: LoopMode) {
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
// --- SETTINGSMANAGER CALLBACKS ---
|
||||
|
||||
override fun onShowCoverUpdate(showCovers: Boolean) {
|
||||
minimal.update(context, playbackManager)
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
override fun onQualityCoverUpdate(doQualityCovers: Boolean) {
|
||||
minimal.update(context, playbackManager)
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
146
app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
Normal file
146
app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
package org.oxycblt.auxio.widgets
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.util.SizeF
|
||||
import android.widget.RemoteViews
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.coil.loadBitmap
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.ui.newMainIntent
|
||||
import org.oxycblt.auxio.widgets.forms.FullWidgetForm
|
||||
import org.oxycblt.auxio.widgets.forms.SmallWidgetForm
|
||||
import org.oxycblt.auxio.widgets.forms.WidgetForm
|
||||
|
||||
/**
|
||||
* 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:
|
||||
*
|
||||
* - TODO?: For widgets 3x1 or lower, show a text-only view with minimal controls
|
||||
* - TODO?: For widgets 4x1, show a minimized view with album art
|
||||
* - 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 more specific details about these sub-widgets, see [WidgetForm].
|
||||
*/
|
||||
class WidgetProvider : AppWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray
|
||||
) {
|
||||
logD("Sending update intent to PlaybackService")
|
||||
|
||||
appWidgetManager.applyViews(context, defaultViews(context))
|
||||
|
||||
val intent = Intent(ACTION_WIDGET_UPDATE)
|
||||
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
|
||||
|
||||
context.sendBroadcast(intent)
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the widget based on the playback state.
|
||||
*/
|
||||
fun update(context: Context, playbackManager: PlaybackStateManager) {
|
||||
val manager = AppWidgetManager.getInstance(context)
|
||||
|
||||
val song = playbackManager.song
|
||||
|
||||
if (song == null) {
|
||||
manager.applyViews(context, defaultViews(context))
|
||||
return
|
||||
}
|
||||
|
||||
// What we do here depends on how we're responding to layout changes.
|
||||
// If we are on Android 11 or below, then we use the current widget form and default
|
||||
// to SmallWidgetForm if one couldn't be figured out.
|
||||
// If we are using Android S, we use the standard method of creating each RemoteView
|
||||
// instance and putting them into a Map with their size ranges. This isn't as nice
|
||||
// as it means we have to always load album art, even with the text only widget forms.
|
||||
// But it's still preferable than to the Pre-12 method.
|
||||
|
||||
// FIXME: Fix the race conditions with the bitmap loading.
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
loadBitmap(context, song) { bitmap ->
|
||||
val state = WidgetState(
|
||||
song,
|
||||
bitmap,
|
||||
playbackManager.isPlaying,
|
||||
playbackManager.isShuffling,
|
||||
playbackManager.loopMode
|
||||
)
|
||||
|
||||
// Map each widget form to the rough dimensions where it would look nice.
|
||||
// This might need to be adjusted.
|
||||
val views = mapOf(
|
||||
SizeF(110f, 110f) to SmallWidgetForm().createViews(context, state),
|
||||
SizeF(272f, 110f) to FullWidgetForm().createViews(context, state)
|
||||
)
|
||||
|
||||
manager.applyViews(context, RemoteViews(views))
|
||||
}
|
||||
} else {
|
||||
loadBitmap(context, song) { bitmap ->
|
||||
val state = WidgetState(
|
||||
song,
|
||||
bitmap,
|
||||
playbackManager.isPlaying,
|
||||
playbackManager.isShuffling,
|
||||
playbackManager.loopMode
|
||||
)
|
||||
|
||||
// TODO: Make sub-12 widgets responsive.
|
||||
manager.applyViews(context, FullWidgetForm().createViews(context, state))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Revert this widget to its default view
|
||||
*/
|
||||
fun reset(context: Context) {
|
||||
logD("Resetting widget")
|
||||
|
||||
val manager = AppWidgetManager.getInstance(context)
|
||||
manager.applyViews(context, defaultViews(context))
|
||||
}
|
||||
|
||||
@SuppressLint("RemoteViewLayout")
|
||||
private fun defaultViews(context: Context): RemoteViews {
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_default)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
android.R.id.background,
|
||||
context.newMainIntent()
|
||||
)
|
||||
|
||||
return views
|
||||
}
|
||||
|
||||
private fun AppWidgetManager.applyViews(context: Context, views: RemoteViews) {
|
||||
val ids = getAppWidgetIds(ComponentName(context, this::class.java))
|
||||
|
||||
if (ids.isNotEmpty()) {
|
||||
// Existing widgets found, update those
|
||||
ids.forEach { id ->
|
||||
updateAppWidget(id, views)
|
||||
}
|
||||
} else {
|
||||
// No existing widgets found. Fall back to the name of the widget class
|
||||
updateAppWidget(ComponentName(context, this@WidgetProvider::class.java), views)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTION_WIDGET_UPDATE = BuildConfig.APPLICATION_ID + ".action.WIDGET_UPDATE"
|
||||
}
|
||||
}
|
||||
17
app/src/main/java/org/oxycblt/auxio/widgets/WidgetState.kt
Normal file
17
app/src/main/java/org/oxycblt/auxio/widgets/WidgetState.kt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package org.oxycblt.auxio.widgets
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.LoopMode
|
||||
|
||||
/*
|
||||
* A condensed variant of the current playback state, used so that PlaybackStateManager does not
|
||||
* need to be queried directly.
|
||||
*/
|
||||
data class WidgetState(
|
||||
val song: Song,
|
||||
val albumArt: Bitmap?,
|
||||
val isPlaying: Boolean,
|
||||
val isShuffled: Boolean,
|
||||
val loopMode: LoopMode,
|
||||
)
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package org.oxycblt.auxio.widgets.forms
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.RemoteViews
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.playback.state.LoopMode
|
||||
import org.oxycblt.auxio.playback.system.PlaybackService
|
||||
import org.oxycblt.auxio.ui.newBroadcastIntent
|
||||
import org.oxycblt.auxio.widgets.WidgetState
|
||||
|
||||
class FullWidgetForm : WidgetForm(R.layout.widget_full) {
|
||||
override fun createViews(context: Context, state: WidgetState): RemoteViews {
|
||||
val views = super.createViews(context, state)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_loop,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_LOOP
|
||||
)
|
||||
)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_skip_prev,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_SKIP_PREV
|
||||
)
|
||||
)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_play_pause,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_PLAY_PAUSE
|
||||
)
|
||||
)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_skip_next,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_SKIP_NEXT
|
||||
)
|
||||
)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_shuffle,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_SHUFFLE
|
||||
)
|
||||
)
|
||||
|
||||
views.setTextViewText(R.id.widget_song, state.song.name)
|
||||
views.setTextViewText(R.id.widget_artist, state.song.album.artist.name)
|
||||
|
||||
views.setImageViewResource(
|
||||
R.id.widget_play_pause,
|
||||
if (state.isPlaying) {
|
||||
R.drawable.ic_pause
|
||||
} else {
|
||||
R.drawable.ic_play
|
||||
}
|
||||
)
|
||||
|
||||
if (state.albumArt != null) {
|
||||
views.setImageViewBitmap(R.id.widget_cover, state.albumArt)
|
||||
views.setCharSequence(
|
||||
R.id.widget_cover, "setContentDescription",
|
||||
context.getString(R.string.description_album_cover, state.song.album.name)
|
||||
)
|
||||
} else {
|
||||
views.setImageViewResource(R.id.widget_cover, R.drawable.ic_song)
|
||||
views.setCharSequence(
|
||||
R.id.widget_cover,
|
||||
"setContentDescription",
|
||||
context.getString(R.string.description_placeholder_cover)
|
||||
)
|
||||
}
|
||||
|
||||
// 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,
|
||||
// attempting to dynamically color anything will result in an error. More duplicate
|
||||
// resources it is, then. This is getting really tiring.
|
||||
|
||||
val shuffleRes = if (state.isShuffled)
|
||||
R.drawable.ic_shuffle_tinted
|
||||
else
|
||||
R.drawable.ic_shuffle
|
||||
|
||||
val loopRes = when (state.loopMode) {
|
||||
LoopMode.NONE -> R.drawable.ic_loop
|
||||
LoopMode.ALL -> R.drawable.ic_loop_all_tinted
|
||||
LoopMode.TRACK -> R.drawable.ic_loop_one_tinted
|
||||
}
|
||||
|
||||
views.setImageViewResource(R.id.widget_shuffle, shuffleRes)
|
||||
views.setImageViewResource(R.id.widget_loop, loopRes)
|
||||
|
||||
return views
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package org.oxycblt.auxio.widgets.forms
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.RemoteViews
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.playback.system.PlaybackService
|
||||
import org.oxycblt.auxio.ui.newBroadcastIntent
|
||||
import org.oxycblt.auxio.widgets.WidgetState
|
||||
|
||||
class SmallWidgetForm : WidgetForm(R.layout.widget_small) {
|
||||
override fun createViews(context: Context, state: WidgetState): RemoteViews {
|
||||
val views = super.createViews(context, state)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_skip_prev,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_SKIP_PREV
|
||||
)
|
||||
)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_play_pause,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_PLAY_PAUSE
|
||||
)
|
||||
)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_skip_next,
|
||||
context.newBroadcastIntent(
|
||||
PlaybackService.ACTION_SKIP_NEXT
|
||||
)
|
||||
)
|
||||
|
||||
views.setTextViewText(R.id.widget_song, state.song.name)
|
||||
views.setTextViewText(R.id.widget_artist, state.song.album.artist.name)
|
||||
|
||||
views.setImageViewResource(
|
||||
R.id.widget_play_pause,
|
||||
if (state.isPlaying) {
|
||||
R.drawable.ic_pause
|
||||
} else {
|
||||
R.drawable.ic_play
|
||||
}
|
||||
)
|
||||
|
||||
if (state.albumArt != null) {
|
||||
views.setImageViewBitmap(R.id.widget_cover, state.albumArt)
|
||||
views.setCharSequence(
|
||||
R.id.widget_cover, "setContentDescription",
|
||||
context.getString(R.string.description_album_cover, state.song.album.name)
|
||||
)
|
||||
} else {
|
||||
views.setImageViewResource(R.id.widget_cover, R.drawable.ic_song)
|
||||
views.setCharSequence(
|
||||
R.id.widget_cover,
|
||||
"setContentDescription",
|
||||
context.getString(R.string.description_placeholder_cover)
|
||||
)
|
||||
}
|
||||
|
||||
return views
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package org.oxycblt.auxio.widgets.forms
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.RemoteViews
|
||||
import androidx.annotation.LayoutRes
|
||||
import org.oxycblt.auxio.ui.newMainIntent
|
||||
import org.oxycblt.auxio.widgets.WidgetState
|
||||
|
||||
abstract class WidgetForm(@LayoutRes private val layout: Int) {
|
||||
open fun createViews(context: Context, state: WidgetState): RemoteViews {
|
||||
val views = RemoteViews(context.packageName, layout)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
android.R.id.background,
|
||||
context.newMainIntent()
|
||||
)
|
||||
|
||||
return views
|
||||
}
|
||||
}
|
||||
11
app/src/main/res/drawable/ic_loop_all_tinted.xml
Normal file
11
app/src/main/res/drawable/ic_loop_all_tinted.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z" />
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_loop_one_tinted.xml
Normal file
11
app/src/main/res/drawable/ic_loop_one_tinted.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4zm-4-2V9h-1l-2 1v1h1.5v4H13z" />
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_shuffle_tinted.xml
Normal file
11
app/src/main/res/drawable/ic_shuffle_tinted.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20 17.96 7.46 20 9.5V4h-5.5zm0.33 9.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04 2.04-3.13-3.13z" />
|
||||
</vector>
|
||||
101
app/src/main/res/layout-v31/widget_full.xml
Normal file
101
app/src/main/res/layout-v31/widget_full.xml
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?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:orientation="vertical"
|
||||
android:theme="@style/Theme.Widget">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/colorSurface"
|
||||
android:scaleType="centerCrop"
|
||||
tools:ignore="contentDescription"
|
||||
android:src="@drawable/ic_song" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="?attr/colorSurface"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/spacing_medium">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_song"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/inter"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/placeholder_widget_song" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_artist"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/inter"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="@string/placeholder_widget_artist" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_loop"
|
||||
style="@style/Widget.Button.Unbounded.Widget.V31"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_change_loop"
|
||||
android:src="@drawable/ic_loop" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_skip_prev"
|
||||
style="@style/Widget.Button.Unbounded.Widget.V31"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_skip_prev"
|
||||
android:src="@drawable/ic_skip_prev" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_play_pause"
|
||||
style="@style/Widget.Button.Unbounded.Widget.V31"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_play_pause"
|
||||
android:src="@drawable/ic_play" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_skip_next"
|
||||
style="@style/Widget.Button.Unbounded.Widget.V31"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_skip_next"
|
||||
android:src="@drawable/ic_skip_next" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_shuffle"
|
||||
style="@style/Widget.Button.Unbounded.Widget.V31"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_shuffle_off"
|
||||
android:src="@drawable/ic_shuffle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
android:orientation="vertical"
|
||||
android:theme="@style/Theme.Widget">
|
||||
|
||||
<android.widget.ImageView
|
||||
<ImageView
|
||||
android:id="@+id/widget_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
tools:ignore="contentDescription"
|
||||
android:src="@drawable/ic_song" />
|
||||
|
||||
<android.widget.LinearLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
android:orientation="vertical"
|
||||
android:padding="@dimen/spacing_medium">
|
||||
|
||||
<android.widget.TextView
|
||||
<TextView
|
||||
android:id="@+id/widget_song"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
android:textStyle="bold"
|
||||
android:text="@string/placeholder_widget_song" />
|
||||
|
||||
<android.widget.TextView
|
||||
<TextView
|
||||
android:id="@+id/widget_artist"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -51,32 +51,35 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_skip_prev"
|
||||
style="@style/Widget.Button.Unbounded"
|
||||
style="@style/Widget.Button.Unbounded.Widget.V31"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_skip_prev"
|
||||
android:src="@drawable/ic_skip_prev" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_play_pause"
|
||||
style="@style/Widget.Button.Unbounded"
|
||||
style="@style/Widget.Button.Unbounded.Widget.V31"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_play_pause"
|
||||
android:src="@drawable/ic_play" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_skip_next"
|
||||
style="@style/Widget.Button.Unbounded"
|
||||
style="@style/Widget.Button.Unbounded.Widget.V31"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_skip_next"
|
||||
android:src="@drawable/ic_skip_next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.widget.LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
android:background="?attr/colorSurface"
|
||||
android:theme="@style/Theme.Widget">
|
||||
|
||||
<android.widget.ImageView
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:alpha="0.3"
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_song" />
|
||||
|
||||
<android.widget.TextView
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
|
|
|
|||
101
app/src/main/res/layout/widget_full.xml
Normal file
101
app/src/main/res/layout/widget_full.xml
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?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:orientation="vertical"
|
||||
android:theme="@style/Theme.Widget">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/colorSurface"
|
||||
android:scaleType="centerCrop"
|
||||
tools:ignore="contentDescription"
|
||||
android:src="@drawable/ic_song" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="?attr/colorSurface"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/spacing_medium">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_song"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/inter"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/placeholder_widget_song" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_artist"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/inter"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="@string/placeholder_widget_artist" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_loop"
|
||||
style="@style/Widget.Button.Unbounded.Widget"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_change_loop"
|
||||
android:src="@drawable/ic_loop" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_skip_prev"
|
||||
style="@style/Widget.Button.Unbounded.Widget"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_skip_prev"
|
||||
android:src="@drawable/ic_skip_prev" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_play_pause"
|
||||
style="@style/Widget.Button.Unbounded.Widget"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_play_pause"
|
||||
android:src="@drawable/ic_play" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_skip_next"
|
||||
style="@style/Widget.Button.Unbounded.Widget"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_skip_next"
|
||||
android:src="@drawable/ic_skip_next" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_shuffle"
|
||||
style="@style/Widget.Button.Unbounded.Widget"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/description_shuffle_off"
|
||||
android:src="@drawable/ic_shuffle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
android:orientation="vertical"
|
||||
android:theme="@style/Theme.Widget">
|
||||
|
||||
<android.widget.ImageView
|
||||
<ImageView
|
||||
android:id="@+id/widget_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
|
|
@ -17,16 +17,16 @@
|
|||
tools:ignore="contentDescription"
|
||||
tools:src="@drawable/ic_song" />
|
||||
|
||||
<android.widget.LinearLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:background="?attr/colorSurface"
|
||||
android:elevation="@dimen/elevation_normal"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/spacing_medium">
|
||||
|
||||
<android.widget.TextView
|
||||
<TextView
|
||||
android:id="@+id/widget_song"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
android:textStyle="bold"
|
||||
tools:text="Song Name" />
|
||||
|
||||
<android.widget.TextView
|
||||
<TextView
|
||||
android:id="@+id/widget_artist"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_small">
|
||||
android:layout_marginTop="@dimen/spacing_medium">
|
||||
|
||||
<!--
|
||||
Can't use a normal unbounded ripple here since it causes a weird bug
|
||||
|
|
@ -62,30 +62,30 @@
|
|||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_skip_prev"
|
||||
style="@style/Widget.Button.Unbounded"
|
||||
style="@style/Widget.Button.Unbounded.Widget"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/description_skip_prev"
|
||||
android:src="@drawable/ic_skip_prev" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_play_pause"
|
||||
style="@style/Widget.Button.Unbounded"
|
||||
style="@style/Widget.Button.Unbounded.Widget"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/description_play_pause"
|
||||
android:src="@drawable/ic_play" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_skip_next"
|
||||
style="@style/Widget.Button.Unbounded"
|
||||
style="@style/Widget.Button.Unbounded.Widget"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/description_skip_next"
|
||||
android:src="@drawable/ic_skip_next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.widget.LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<!-- Info namespace | App labels -->
|
||||
<string name="info_app_desc">A simple, rational music player for android.</string>
|
||||
<string name="info_channel_name">Music Playback</string>
|
||||
<string name="info_widget_minimal">Minimal</string>
|
||||
<string name="info_widget_desc">See and control playing music</string>
|
||||
|
||||
<!-- Label Namespace | Static Labels -->
|
||||
<string name="label_retry">Retry</string>
|
||||
|
|
|
|||
|
|
@ -87,4 +87,15 @@
|
|||
<item name="android:fontFamily">@font/inter_semibold</item>
|
||||
<item name="android:textColor">?attr/colorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Button.Unbounded.Widget" parent="Widget.AppCompat.Button.Borderless">
|
||||
<item name="android:layout_height">32dp</item>
|
||||
<item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
|
||||
<item name="android:scaleType">fitCenter</item>
|
||||
<item name="android:padding">@dimen/spacing_micro</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Button.Unbounded.Widget.V31" parent="Widget.Button.Unbounded.Widget">
|
||||
<item name="android:background">@drawable/ui_unbounded_ripple</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:initialLayout="@layout/widget_minimal"
|
||||
android:initialLayout="@layout/widget_small"
|
||||
android:targetCellWidth="2"
|
||||
android:targetCellHeight="2"
|
||||
android:minResizeWidth="110dp"
|
||||
android:minResizeHeight="110dp"
|
||||
android:previewLayout="@layout/widget_minimal"
|
||||
android:previewLayout="@layout/widget_small"
|
||||
android:description="@string/info_widget_desc"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="0"
|
||||
android:widgetCategory="home_screen">
|
||||
|
||||
</appwidget-provider>
|
||||
android:widgetCategory="home_screen" />
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:initialLayout="@layout/widget_minimal"
|
||||
android:initialLayout="@layout/widget_small"
|
||||
android:minWidth="110dp"
|
||||
android:minHeight="110dp"
|
||||
android:minResizeWidth="110dp"
|
||||
android:minResizeHeight="110dp"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="0"
|
||||
android:widgetCategory="home_screen">
|
||||
|
||||
</appwidget-provider>
|
||||
android:widgetCategory="home_screen" />
|
||||
Loading…
Reference in a new issue