widgets: add controls to minimal widget

Backtrack and add controls to the minimal widget, mostly for usability.
This commit is contained in:
OxygenCobalt 2021-08-03 09:03:28 -06:00
parent 8673995630
commit d3e738b973
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
15 changed files with 260 additions and 95 deletions

View file

@ -5,20 +5,19 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.support.v4.media.session.MediaSessionCompat
import androidx.annotation.DrawableRes
import androidx.core.app.NotificationCompat
import androidx.media.app.NotificationCompat.MediaStyle
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.MainActivity
import org.oxycblt.auxio.R
import org.oxycblt.auxio.coil.loadBitmap
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.ui.newBroadcastIntent
import org.oxycblt.auxio.ui.newMainIntent
/**
* The unified notification for [PlaybackService]. This is not self-sufficient, updates have
@ -35,24 +34,18 @@ class PlaybackNotification private constructor(
else 0
init {
val activityIntent = PendingIntent.getActivity(
context, REQUEST_CODE,
Intent(context, MainActivity::class.java),
pendingIntentFlags
)
setSmallIcon(R.drawable.ic_song)
setCategory(NotificationCompat.CATEGORY_SERVICE)
setShowWhen(false)
setSilent(true)
setContentIntent(activityIntent)
setContentIntent(context.newMainIntent())
setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
addAction(buildLoopAction(context, LoopMode.NONE))
addAction(buildAction(context, ACTION_SKIP_PREV, R.drawable.ic_skip_prev))
addAction(buildAction(context, PlaybackService.ACTION_SKIP_PREV, R.drawable.ic_skip_prev))
addAction(buildPlayPauseAction(context, true))
addAction(buildAction(context, ACTION_SKIP_NEXT, R.drawable.ic_skip_next))
addAction(buildAction(context, ACTION_EXIT, R.drawable.ic_exit))
addAction(buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next))
addAction(buildAction(context, PlaybackService.ACTION_EXIT, R.drawable.ic_exit))
setStyle(
MediaStyle()
@ -130,7 +123,7 @@ class PlaybackNotification private constructor(
): NotificationCompat.Action {
val drawableRes = if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play
return buildAction(context, ACTION_PLAY_PAUSE, drawableRes)
return buildAction(context, PlaybackService.ACTION_PLAY_PAUSE, drawableRes)
}
private fun buildLoopAction(
@ -143,7 +136,7 @@ class PlaybackNotification private constructor(
LoopMode.TRACK -> R.drawable.ic_loop_one
}
return buildAction(context, ACTION_LOOP, drawableRes)
return buildAction(context, PlaybackService.ACTION_LOOP, drawableRes)
}
private fun buildShuffleAction(
@ -152,7 +145,7 @@ class PlaybackNotification private constructor(
): NotificationCompat.Action {
val drawableRes = if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_shuffle_inactive
return buildAction(context, ACTION_SHUFFLE, drawableRes)
return buildAction(context, PlaybackService.ACTION_SHUFFLE, drawableRes)
}
private fun buildAction(
@ -162,10 +155,7 @@ class PlaybackNotification private constructor(
): NotificationCompat.Action {
val action = NotificationCompat.Action.Builder(
iconRes, actionName,
PendingIntent.getBroadcast(
context, REQUEST_CODE,
Intent(actionName), pendingIntentFlags
)
context.newBroadcastIntent(actionName)
)
return action.build()
@ -174,14 +164,6 @@ class PlaybackNotification private constructor(
companion object {
const val CHANNEL_ID = "CHANNEL_AUXIO_PLAYBACK"
const val NOTIFICATION_ID = 0xA0A0
const val REQUEST_CODE = 0xA0C0
const val ACTION_LOOP = BuildConfig.APPLICATION_ID + ".action.LOOP"
const val ACTION_SHUFFLE = BuildConfig.APPLICATION_ID + ".action.SHUFFLE"
const val ACTION_SKIP_PREV = BuildConfig.APPLICATION_ID + ".action.PREV"
const val ACTION_PLAY_PAUSE = BuildConfig.APPLICATION_ID + ".action.PLAY_PAUSE"
const val ACTION_SKIP_NEXT = BuildConfig.APPLICATION_ID + ".action.NEXT"
const val ACTION_EXIT = BuildConfig.APPLICATION_ID + ".action.EXIT"
/**
* Build a new instance of [PlaybackNotification].

View file

@ -43,7 +43,7 @@ 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.MinimalWidgetProvider
import org.oxycblt.auxio.widgets.WidgetController
/**
* A service that manages the system-side aspects of playback, such as:
@ -56,6 +56,8 @@ import org.oxycblt.auxio.widgets.MinimalWidgetProvider
* This service relies on [PlaybackStateManager.Callback] and [SettingsManager.Callback],
* so therefore there's no need to bind to it to deliver commands.
* @author OxygenCobalt
*
* TODO: Try to split up this god object somewhat, such as making the notification state-aware.
*/
class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback {
@ -71,15 +73,13 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
// System backend components
private lateinit var audioReactor: AudioReactor
private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var widgets: WidgetController
private val systemReceiver = SystemEventReceiver()
// Managers
private val playbackManager = PlaybackStateManager.getInstance()
private val settingsManager = SettingsManager.getInstance()
// Widgets
private val minimalWidget = MinimalWidgetProvider()
// State
private var isForeground = false
@ -113,12 +113,14 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
false
)
// --- SYSTEM SETUP ---
audioReactor = AudioReactor(this, player)
wakeLock = getSystemServiceSafe(PowerManager::class).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, this::class.simpleName
)
// --- CALLBACKS ---
widgets = WidgetController(this)
// Set up the media button callbacks
mediaSession = MediaSessionCompat(this, packageName).apply {
@ -129,12 +131,12 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
// Then the notif/headset callbacks
IntentFilter().apply {
addAction(PlaybackNotification.ACTION_LOOP)
addAction(PlaybackNotification.ACTION_SHUFFLE)
addAction(PlaybackNotification.ACTION_SKIP_PREV)
addAction(PlaybackNotification.ACTION_PLAY_PAUSE)
addAction(PlaybackNotification.ACTION_SKIP_NEXT)
addAction(PlaybackNotification.ACTION_EXIT)
addAction(ACTION_LOOP)
addAction(ACTION_SHUFFLE)
addAction(ACTION_SKIP_PREV)
addAction(ACTION_PLAY_PAUSE)
addAction(ACTION_SKIP_NEXT)
addAction(ACTION_EXIT)
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
@ -177,12 +179,9 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
connector.release()
mediaSession.release()
audioReactor.release()
widgets.release()
releaseWakelock()
// Technically the widgets don't *have* to be reset, but any commands from them
// won't work if the service is dead, so we do it anyway
minimalWidget.stop(this)
playbackManager.removeCallback(this)
settingsManager.removeCallback(this)
@ -250,14 +249,11 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
this, song, settingsManager.colorizeNotif, ::startForegroundOrNotify
)
minimalWidget.update(this, playbackManager)
return
}
// Clear if there's nothing to play.
player.stop()
minimalWidget.stop(this)
stopForegroundAndNotification()
}
@ -458,22 +454,22 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
// --- NOTIFICATION CASES ---
PlaybackNotification.ACTION_PLAY_PAUSE -> playbackManager.setPlaying(
ACTION_PLAY_PAUSE -> playbackManager.setPlaying(
!playbackManager.isPlaying
)
PlaybackNotification.ACTION_LOOP -> playbackManager.setLoopMode(
ACTION_LOOP -> playbackManager.setLoopMode(
playbackManager.loopMode.increment()
)
PlaybackNotification.ACTION_SHUFFLE -> playbackManager.setShuffling(
ACTION_SHUFFLE -> playbackManager.setShuffling(
!playbackManager.isShuffling, keepSong = true
)
PlaybackNotification.ACTION_SKIP_PREV -> playbackManager.prev()
PlaybackNotification.ACTION_SKIP_NEXT -> playbackManager.next()
ACTION_SKIP_PREV -> playbackManager.prev()
ACTION_SKIP_NEXT -> playbackManager.next()
PlaybackNotification.ACTION_EXIT -> {
ACTION_EXIT -> {
playbackManager.setPlaying(false)
stopForegroundAndNotification()
}
@ -499,14 +495,9 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
}
}
// Newly added widgets need PlaybackService to
BaseWidget.ACTION_WIDGET_UPDATE -> {
when (intent.getIntExtra(BaseWidget.KEY_WIDGET_TYPE, -1)) {
MinimalWidgetProvider.TYPE -> minimalWidget?.update(
this@PlaybackService, playbackManager
)
}
}
BaseWidget.ACTION_WIDGET_UPDATE -> widgets.initWidget(
intent.getIntExtra(BaseWidget.ACTION_WIDGET_UPDATE, -1)
)
}
}
@ -539,6 +530,11 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
private const val WAKELOCK_TIME = 25000L
private const val POS_POLL_INTERVAL = 500L
const val BROADCAST_WIDGET_START = BuildConfig.APPLICATION_ID + ".key.WIDGETS_START"
const val ACTION_LOOP = BuildConfig.APPLICATION_ID + ".action.LOOP"
const val ACTION_SHUFFLE = BuildConfig.APPLICATION_ID + ".action.SHUFFLE"
const val ACTION_SKIP_PREV = BuildConfig.APPLICATION_ID + ".action.PREV"
const val ACTION_PLAY_PAUSE = BuildConfig.APPLICATION_ID + ".action.PLAY_PAUSE"
const val ACTION_SKIP_NEXT = BuildConfig.APPLICATION_ID + ".action.NEXT"
const val ACTION_EXIT = BuildConfig.APPLICATION_ID + ".action.EXIT"
}
}

View file

@ -85,7 +85,7 @@ class PlaybackSessionConnector(
override fun onStop() {
// Get the service to shut down with the ACTION_EXIT intent
context.sendBroadcast(Intent(PlaybackNotification.ACTION_EXIT))
context.sendBroadcast(Intent(PlaybackService.ACTION_EXIT))
}
// --- PLAYBACKSTATEMANAGER CALLBACKS ---

View file

@ -1,7 +1,9 @@
package org.oxycblt.auxio.ui
import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.content.res.Resources
@ -24,6 +26,7 @@ import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.MainActivity
import org.oxycblt.auxio.R
import org.oxycblt.auxio.logE
import kotlin.reflect.KClass
@ -151,6 +154,32 @@ fun Context.showToast(@StringRes str: Int) {
Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show()
}
const val INTENT_REQUEST_CODE = 0xA0A0
/**
* Create a broadcast [PendingIntent]
*/
fun Context.newBroadcastIntent(what: String): PendingIntent {
return PendingIntent.getBroadcast(
this, INTENT_REQUEST_CODE, Intent(what),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
PendingIntent.FLAG_IMMUTABLE
else 0
)
}
/**
* Create a [PendingIntent] that leads to Auxio's [MainActivity]
*/
fun Context.newMainIntent(): PendingIntent {
return PendingIntent.getActivity(
this, INTENT_REQUEST_CODE, Intent(this, MainActivity::class.java),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
PendingIntent.FLAG_IMMUTABLE
else 0
)
}
/**
* Assert that we are on a background thread.
*/

View file

@ -1,19 +1,18 @@
package org.oxycblt.auxio.widgets
import android.app.PendingIntent
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.widget.RemoteViews
import androidx.annotation.LayoutRes
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.MainActivity
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.
@ -21,11 +20,16 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
abstract class BaseWidget : AppWidgetProvider() {
abstract val type: Int
/*
* Returns the default view for this widget. This should be the "No music playing" screen
* in pretty much all cases.
*/
protected abstract fun getDefaultViews(context: Context): RemoteViews
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
@ -59,7 +63,7 @@ abstract class BaseWidget : AppWidgetProvider() {
logD("Stopping widget")
val manager = AppWidgetManager.getInstance(context)
manager.applyViews(context, getDefaultViews(context))
manager.applyViews(context, defaultViews(context))
}
override fun onUpdate(
@ -67,7 +71,7 @@ abstract class BaseWidget : AppWidgetProvider() {
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
appWidgetManager.applyViews(context, getDefaultViews(context))
appWidgetManager.applyViews(context, defaultViews(context))
logD("Sending update intent to PlaybackService")
@ -78,20 +82,12 @@ abstract class BaseWidget : AppWidgetProvider() {
context.sendBroadcast(intent)
}
protected fun getRemoteViews(context: Context, @LayoutRes layout: Int): RemoteViews {
val views = RemoteViews(context.packageName, layout)
views.setOnClickPendingIntent(
android.R.id.background,
PendingIntent.getActivity(
context, 0xA0A0, Intent(context, MainActivity::class.java),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
PendingIntent.FLAG_IMMUTABLE
else 0
)
@SuppressLint("RemoteViewLayout")
protected fun defaultViews(context: Context): RemoteViews {
return RemoteViews(
context.packageName,
R.layout.widget_default
)
return views
}
private fun AppWidgetManager.applyViews(context: Context, views: RemoteViews) {

View file

@ -7,16 +7,40 @@ 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. This widget only shows the album, song name, and artist without any
* controls. Because you know. Minimalism.
* The minimal widget, which shows the primary song controls and basic playback information.
*/
class MinimalWidgetProvider : BaseWidget() {
override val type: Int get() = TYPE
override fun getDefaultViews(context: Context): RemoteViews {
return getRemoteViews(context, R.layout.widget_default)
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(
@ -29,29 +53,48 @@ class MinimalWidgetProvider : BaseWidget() {
if (song != null) {
logD("updating view to ${song.name}")
val views = getRemoteViews(context, R.layout.widget_minimal)
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 {
onDone(getDefaultViews(context))
onDone(defaultViews(context))
}
}
companion object {
const val TYPE = 0xA0D0
fun new(): MinimalWidgetProvider? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
MinimalWidgetProvider()

View file

@ -0,0 +1,33 @@
package org.oxycblt.auxio.widgets
import android.content.Context
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackStateManager
class WidgetController(private val context: Context) : PlaybackStateManager.Callback {
private val manager = PlaybackStateManager.getInstance()
private val minimal = MinimalWidgetProvider()
init {
manager.addCallback(this)
}
fun initWidget(type: Int) {
when (type) {
MinimalWidgetProvider.TYPE -> minimal.update(context, manager)
}
}
fun release() {
minimal.stop(context)
manager.removeCallback(this)
}
override fun onSongUpdate(song: Song?) {
minimal.update(context, manager)
}
override fun onPlayingUpdate(isPlaying: Boolean) {
minimal.update(context, manager)
}
}

View file

@ -2,7 +2,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path

View file

@ -49,6 +49,36 @@
android:textColor="?android:attr/textColorSecondary"
tools:text="Artist Name" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="@dimen/spacing_medium"
android:orientation="horizontal">
<ImageButton
android:id="@+id/widget_skip_prev"
style="@style/Widget.Button.Unbounded"
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"
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"
android:layout_weight="1"
android:contentDescription="@string/description_skip_next"
android:src="@drawable/ic_skip_next" />
</LinearLayout>
</android.widget.LinearLayout>
</LinearLayout>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
@ -12,8 +11,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.3"
android:contentDescription="@string/description_placeholder_cover"
android:scaleType="centerCrop"
tools:ignore="contentDescription"
android:src="@drawable/ic_song" />
<android.widget.TextView
@ -21,8 +20,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:fontFamily="@font/inter"
android:gravity="center"
android:padding="@dimen/spacing_medium"
android:text="@string/placeholder_playback"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"

View file

@ -23,6 +23,7 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/colorBackground"
android:elevation="@dimen/elevation_normal"
android:orientation="vertical"
android:padding="@dimen/spacing_medium">
@ -49,6 +50,45 @@
android:textColor="?android:attr/textColorSecondary"
tools:text="Artist Name" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_medium"
android:gravity="center"
android:orientation="horizontal">
<!--
Can't use a normal unbounded ripple here since it causes a weird bug
where the ripples will have a fixed starting size. Default to the
uglier system ripple instead.
-->
<ImageButton
android:id="@+id/widget_skip_prev"
style="@style/Widget.Button.Unbounded"
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"
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"
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>

View file

@ -6,6 +6,8 @@
useless. -->
<item name="colorPrimary">?android:attr/colorAccent</item>
<item name="colorSecondary">?android:attr/colorAccent</item>
<item name="colorControlNormal">@color/control_color</item>
<item name="colorControlHighlight">?android:attr/colorControlHighlight</item>
<item name="colorSurface">@color/surface_color</item>
<item name="android:windowBackground">?attr/colorSurface</item>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Widget" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="colorPrimary">?android:attr/colorAccent</item>
<item name="colorSecondary">?android:attr/colorAccent</item>
<item name="colorControlNormal">?android:attr/colorControlNormal</item>
<item name="colorControlHighlight">?android:attr/colorControlHighlight</item>
<item name="colorSurface">@color/surface_color</item>
<item name="android:windowBackground">?attr/colorSurface</item>
<item name="android:colorBackground">?attr/colorSurface</item>
</style>
</resources>

View file

@ -12,6 +12,7 @@
<!-- Height Namespace | Height for UI elements -->
<dimen name="height_compact_progress">2dp</dimen>
<dimen name="height_minimal_btn">28dp</dimen>
<!-- Width Namespace | Width for UI elements -->
<dimen name="width_track_number">32dp</dimen>

View file

@ -126,6 +126,7 @@
<string name="description_error">Error</string>
<string name="description_auxio_icon">Auxio icon</string>
<string name="description_placeholder_cover">Album cover</string>
<string name="description_album_cover">Album Cover for %s</string>
<string name="description_artist_image">Artist Image for %s</string>
<string name="description_genre_image">Genre Image for %s</string>