widgets: rework tiny widget
Rework the tiny widget to cram even more information into it. The tiny widget is nominally meant for edge cases like exceptionally small screens or landscape mode, but apparently it's triggered on some devices in normal use because of platform fragmentation and OEM insanity. Update the tiny widget layout with some new buttons in order to make it more usable in mnormal use. This is still nowehre near ideal. For example, when triggering the layout on my device, it ends up squishing the buttons. But it should probably work better outside of those edge cases.
This commit is contained in:
parent
485c35d74c
commit
eb293022e8
16 changed files with 85 additions and 91 deletions
|
@ -39,7 +39,8 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
/**
|
/**
|
||||||
* The single [AppCompatActivity] for Auxio.
|
* The single [AppCompatActivity] for Auxio.
|
||||||
*
|
*
|
||||||
* TODO: Add a new view for crashes with a stack trace
|
* TODO: Add crash reporting and error screens. This likely has to be an external activity, so it is
|
||||||
|
* blocked by eliminating exitProcess from the app.
|
||||||
*
|
*
|
||||||
* TODO: Custom language support
|
* TODO: Custom language support
|
||||||
*
|
*
|
||||||
|
|
|
@ -44,8 +44,6 @@ import org.oxycblt.auxio.util.logD
|
||||||
* A wrapper around the home fragment that shows the playback fragment and controls the more
|
* A wrapper around the home fragment that shows the playback fragment and controls the more
|
||||||
* high-level navigation features.
|
* high-level navigation features.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*
|
|
||||||
* TODO: Add a new view with a stack trace whenever the music loading process fails.
|
|
||||||
*/
|
*/
|
||||||
class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
|
class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
|
|
@ -41,6 +41,7 @@ import org.oxycblt.auxio.music.Song
|
||||||
class BitmapProvider(private val context: Context) {
|
class BitmapProvider(private val context: Context) {
|
||||||
private var currentRequest: Request? = null
|
private var currentRequest: Request? = null
|
||||||
|
|
||||||
|
/* If this provider is currently attempting to load something. */
|
||||||
val isBusy: Boolean
|
val isBusy: Boolean
|
||||||
get() = currentRequest?.run { !disposable.isDisposed } ?: false
|
get() = currentRequest?.run { !disposable.isDisposed } ?: false
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ class BitmapProvider(private val context: Context) {
|
||||||
currentRequest = null
|
currentRequest = null
|
||||||
|
|
||||||
val request =
|
val request =
|
||||||
target.setupRequest(
|
target.onConfigRequest(
|
||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
.data(song)
|
.data(song)
|
||||||
.size(Size.ORIGINAL)
|
.size(Size.ORIGINAL)
|
||||||
|
@ -76,8 +77,15 @@ class BitmapProvider(private val context: Context) {
|
||||||
|
|
||||||
private data class Request(val disposable: Disposable, val callback: Target)
|
private data class Request(val disposable: Disposable, val callback: Target)
|
||||||
|
|
||||||
|
/** Represents the target for a request. */
|
||||||
interface Target {
|
interface Target {
|
||||||
fun setupRequest(builder: ImageRequest.Builder): ImageRequest.Builder = builder
|
/** Modify the default request with custom attributes. */
|
||||||
|
fun onConfigRequest(builder: ImageRequest.Builder): ImageRequest.Builder = builder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the loading process is completed. [bitmap] will be null if there was an
|
||||||
|
* error.
|
||||||
|
*/
|
||||||
fun onCompleted(bitmap: Bitmap?)
|
fun onCompleted(bitmap: Bitmap?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,9 +71,8 @@ class AlbumArtFetcher private constructor(private val context: Context, private
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumFactory : Fetcher.Factory<Album> {
|
class AlbumFactory : Fetcher.Factory<Album> {
|
||||||
override fun create(data: Album, options: Options, imageLoader: ImageLoader): Fetcher {
|
override fun create(data: Album, options: Options, imageLoader: ImageLoader) =
|
||||||
return AlbumArtFetcher(options.context, data)
|
AlbumArtFetcher(options.context, data)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,14 +89,12 @@ private constructor(
|
||||||
override suspend fun fetch(): FetchResult? {
|
override suspend fun fetch(): FetchResult? {
|
||||||
val albums = Sort.ByName(true).albums(artist.albums)
|
val albums = Sort.ByName(true).albums(artist.albums)
|
||||||
val results = albums.mapAtMost(4) { album -> fetchArt(context, album) }
|
val results = albums.mapAtMost(4) { album -> fetchArt(context, album) }
|
||||||
|
|
||||||
return createMosaic(context, results, size)
|
return createMosaic(context, results, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory : Fetcher.Factory<Artist> {
|
class Factory : Fetcher.Factory<Artist> {
|
||||||
override fun create(data: Artist, options: Options, imageLoader: ImageLoader): Fetcher {
|
override fun create(data: Artist, options: Options, imageLoader: ImageLoader) =
|
||||||
return ArtistImageFetcher(options.context, options.size, data)
|
ArtistImageFetcher(options.context, options.size, data)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,14 +112,12 @@ private constructor(
|
||||||
// Don't sort here to preserve compatibility with previous versions of this image.
|
// Don't sort here to preserve compatibility with previous versions of this image.
|
||||||
val albums = genre.songs.groupBy { it.album }.keys
|
val albums = genre.songs.groupBy { it.album }.keys
|
||||||
val results = albums.mapAtMost(4) { album -> fetchArt(context, album) }
|
val results = albums.mapAtMost(4) { album -> fetchArt(context, album) }
|
||||||
|
|
||||||
return createMosaic(context, results, size)
|
return createMosaic(context, results, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory : Fetcher.Factory<Genre> {
|
class Factory : Fetcher.Factory<Genre> {
|
||||||
override fun create(data: Genre, options: Options, imageLoader: ImageLoader): Fetcher {
|
override fun create(data: Genre, options: Options, imageLoader: ImageLoader) =
|
||||||
return GenreImageFetcher(options.context, options.size, data)
|
GenreImageFetcher(options.context, options.size, data)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,9 +119,7 @@ sealed class Sort(open val isAscending: Boolean) {
|
||||||
genres.sortWith(compareByDynamic(NameComparator()) { it })
|
genres.sortWith(compareByDynamic(NameComparator()) { it })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ascending(newIsAscending: Boolean): Sort {
|
override fun ascending(newIsAscending: Boolean) = ByName(newIsAscending)
|
||||||
return ByName(newIsAscending)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sort by the album of an item, only supported by [Song] */
|
/** Sort by the album of an item, only supported by [Song] */
|
||||||
|
@ -140,9 +138,7 @@ sealed class Sort(open val isAscending: Boolean) {
|
||||||
compareBy(NameComparator()) { it }))
|
compareBy(NameComparator()) { it }))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ascending(newIsAscending: Boolean): Sort {
|
override fun ascending(newIsAscending: Boolean) = ByAlbum(newIsAscending)
|
||||||
return ByAlbum(newIsAscending)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sort by the artist of an item, only supported by [Album] and [Song] */
|
/** Sort by the artist of an item, only supported by [Album] and [Song] */
|
||||||
|
@ -171,9 +167,7 @@ sealed class Sort(open val isAscending: Boolean) {
|
||||||
compareBy(NameComparator()) { it }))
|
compareBy(NameComparator()) { it }))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ascending(newIsAscending: Boolean): Sort {
|
override fun ascending(newIsAscending: Boolean) = ByArtist(newIsAscending)
|
||||||
return ByArtist(newIsAscending)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sort by the year of an item, only supported by [Album] and [Song] */
|
/** Sort by the year of an item, only supported by [Album] and [Song] */
|
||||||
|
@ -200,9 +194,7 @@ sealed class Sort(open val isAscending: Boolean) {
|
||||||
compareBy(NameComparator()) { it }))
|
compareBy(NameComparator()) { it }))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ascending(newIsAscending: Boolean): Sort {
|
override fun ascending(newIsAscending: Boolean) = ByYear(newIsAscending)
|
||||||
return ByYear(newIsAscending)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sort by the duration of the item. Supports all items. */
|
/** Sort by the duration of the item. Supports all items. */
|
||||||
|
@ -237,9 +229,7 @@ sealed class Sort(open val isAscending: Boolean) {
|
||||||
compareByDynamic { it.durationSecs }, compareBy(NameComparator()) { it }))
|
compareByDynamic { it.durationSecs }, compareBy(NameComparator()) { it }))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ascending(newIsAscending: Boolean): Sort {
|
override fun ascending(newIsAscending: Boolean) = ByDuration(newIsAscending)
|
||||||
return ByDuration(newIsAscending)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sort by the amount of songs. Only applicable to music parents. */
|
/** Sort by the amount of songs. Only applicable to music parents. */
|
||||||
|
@ -268,9 +258,7 @@ sealed class Sort(open val isAscending: Boolean) {
|
||||||
compareByDynamic { it.songs.size }, compareBy(NameComparator()) { it }))
|
compareByDynamic { it.songs.size }, compareBy(NameComparator()) { it }))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ascending(newIsAscending: Boolean): Sort {
|
override fun ascending(newIsAscending: Boolean) = ByCount(newIsAscending)
|
||||||
return ByCount(newIsAscending)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -293,9 +281,7 @@ sealed class Sort(open val isAscending: Boolean) {
|
||||||
compareBy(NameComparator()) { it }))
|
compareBy(NameComparator()) { it }))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ascending(newIsAscending: Boolean): Sort {
|
override fun ascending(newIsAscending: Boolean) = ByDisc(newIsAscending)
|
||||||
return ByDisc(newIsAscending)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -317,9 +303,7 @@ sealed class Sort(open val isAscending: Boolean) {
|
||||||
compareBy(NameComparator()) { it }))
|
compareBy(NameComparator()) { it }))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ascending(newIsAscending: Boolean): Sort {
|
override fun ascending(newIsAscending: Boolean) = ByTrack(newIsAscending)
|
||||||
return ByTrack(newIsAscending)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val intCode: Int
|
val intCode: Int
|
||||||
|
|
|
@ -41,7 +41,6 @@ import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
import org.oxycblt.auxio.util.getColorStateListSafe
|
||||||
import org.oxycblt.auxio.util.getDrawableSafe
|
import org.oxycblt.auxio.util.getDrawableSafe
|
||||||
import org.oxycblt.auxio.util.logD
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An [AppCompatImageView] that applies many of the stylistic choices that Auxio uses regarding
|
* An [AppCompatImageView] that applies many of the stylistic choices that Auxio uses regarding
|
||||||
|
@ -130,7 +129,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
logD(src.alpha)
|
|
||||||
src.bounds.set(canvas.clipBounds)
|
src.bounds.set(canvas.clipBounds)
|
||||||
val adjustWidth = src.bounds.width() / 4
|
val adjustWidth = src.bounds.width() / 4
|
||||||
val adjustHeight = src.bounds.height() / 4
|
val adjustHeight = src.bounds.height() / 4
|
||||||
|
|
|
@ -35,13 +35,13 @@ fun createDefaultWidget(context: Context): RemoteViews {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The tiny widget is for an edge-case situation where a 2xN widget happens to be smaller than
|
* The tiny widget is for an edge-case situation where a widget falls under the size class of the
|
||||||
* 100dp. It just shows the cover, titles, and a button.
|
* small widget, either via landscape mode or exceptionally small screens.
|
||||||
*/
|
*/
|
||||||
fun createTinyWidget(context: Context, state: WidgetComponent.WidgetState): RemoteViews {
|
fun createTinyWidget(context: Context, state: WidgetComponent.WidgetState): RemoteViews {
|
||||||
return createViews(context, R.layout.widget_tiny)
|
return createViews(context, R.layout.widget_tiny)
|
||||||
.applyMeta(context, state)
|
.applyMeta(context, state)
|
||||||
.applyPlayControls(context, state)
|
.applyBasicControls(context, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -50,6 +50,10 @@ class WidgetComponent(private val context: Context) :
|
||||||
init {
|
init {
|
||||||
playbackManager.addCallback(this)
|
playbackManager.addCallback(this)
|
||||||
settingsManager.addCallback(this)
|
settingsManager.addCallback(this)
|
||||||
|
|
||||||
|
if (playbackManager.isInitialized) {
|
||||||
|
update()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -76,7 +80,7 @@ class WidgetComponent(private val context: Context) :
|
||||||
provider.load(
|
provider.load(
|
||||||
song,
|
song,
|
||||||
object : BitmapProvider.Target {
|
object : BitmapProvider.Target {
|
||||||
override fun setupRequest(builder: ImageRequest.Builder): ImageRequest.Builder {
|
override fun onConfigRequest(builder: ImageRequest.Builder): ImageRequest.Builder {
|
||||||
// The widget has two distinct styles that we must transform the album art to
|
// The widget has two distinct styles that we must transform the album art to
|
||||||
// accommodate:
|
// accommodate:
|
||||||
// - Before Android 12, the widget has hard edges, so we don't need to round
|
// - Before Android 12, the widget has hard edges, so we don't need to round
|
||||||
|
|
|
@ -122,6 +122,13 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
val name = ComponentName(context, WidgetProvider::class.java)
|
val name = ComponentName(context, WidgetProvider::class.java)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
for (id in getAppWidgetIds(name)) {
|
||||||
|
val options = getAppWidgetOptions(id)
|
||||||
|
logD(
|
||||||
|
options.getParcelableArrayList<SizeF>(AppWidgetManager.OPTION_APPWIDGET_SIZES)
|
||||||
|
?: "no sizes")
|
||||||
|
}
|
||||||
|
|
||||||
// Widgets are automatically responsive on Android 12, no need to do anything.
|
// Widgets are automatically responsive on Android 12, no need to do anything.
|
||||||
updateAppWidget(name, RemoteViews(views))
|
updateAppWidget(name, RemoteViews(views))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,5 +7,5 @@
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6 -1.41,-1.41z" />
|
android:pathData="M 18.119999,7.0600006 12,13.166666 5.8800004,7.0600006 4.0000006,8.9400004 12,16.939999 19.999999,8.9400004 Z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
a terrible API.
|
a terrible API.
|
||||||
-->
|
-->
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="?attr/colorPrimary"
|
||||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,16.5c-2.49,0 -4.5,-2.01 -4.5,-4.5S9.51,7.5 12,7.5s4.5,2.01 4.5,4.5 -2.01,4.5 -4.5,4.5zM12,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1z" />
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,16.5c-2.49,0 -4.5,-2.01 -4.5,-4.5S9.51,7.5 12,7.5s4.5,2.01 4.5,4.5 -2.01,4.5 -4.5,4.5zM12,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1z" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="?attr/colorSurface"
|
android:fillColor="?attr/colorSurface"
|
||||||
|
|
|
@ -8,14 +8,7 @@
|
||||||
android:theme="@style/Theme.Widget">
|
android:theme="@style/Theme.Widget">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
For this widget form to work, we need to scale the ImageView across a 1:1 aspect ratio.
|
See widget_small.xml for an explanation for the ImageView setup
|
||||||
However, since we are working with a RemoteViews instance, we can't just use ConstraintLayout
|
|
||||||
to achieve this. We can use RelativeLayout, but there's no way to force an aspect ratio with
|
|
||||||
that layout. However, if we create an invisible ImageView that contains a massive fixed-size
|
|
||||||
drawable and then clamp our main ImageView to it, we can make the view scale on a 1:1 aspect
|
|
||||||
ratio.
|
|
||||||
|
|
||||||
This is easily one of the worst layout hacks I've done, but it seems to work.
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<android.widget.ImageView
|
<android.widget.ImageView
|
||||||
|
|
|
@ -8,14 +8,7 @@
|
||||||
android:theme="@style/Theme.Widget">
|
android:theme="@style/Theme.Widget">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
For this widget form to work, we need to scale the ImageView across a 1:1 aspect ratio.
|
See widget_small.xml for an explanation for the ImageView setup
|
||||||
However, since we are working with a RemoteViews instance, we can't just use ConstraintLayout
|
|
||||||
to achieve this. We can use RelativeLayout, but there's no way to force an aspect ratio with
|
|
||||||
that layout. However, if we create an invisible ImageView that contains a massive fixed-size
|
|
||||||
drawable and then clamp our main ImageView to it, we can make the view scale on a 1:1 aspect
|
|
||||||
ratio.
|
|
||||||
|
|
||||||
This is easily one of the worst layout hacks I've done, but it seems to work.
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<android.widget.ImageView
|
<android.widget.ImageView
|
||||||
|
|
|
@ -8,11 +8,6 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:theme="@style/Theme.Widget">
|
android:theme="@style/Theme.Widget">
|
||||||
|
|
||||||
<!--
|
|
||||||
This is a throwaway layout designed for the rare edge-case where a 2xN widget is shown
|
|
||||||
in landscape mode.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<android.widget.LinearLayout
|
<android.widget.LinearLayout
|
||||||
style="@style/Widget.Auxio.AppWidget.Panel"
|
style="@style/Widget.Auxio.AppWidget.Panel"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -22,7 +17,8 @@
|
||||||
|
|
||||||
<android.widget.ImageView
|
<android.widget.ImageView
|
||||||
android:id="@+id/widget_cover"
|
android:id="@+id/widget_cover"
|
||||||
style="@style/Widget.Auxio.Image.Medium"
|
android:layout_width="88dp"
|
||||||
|
android:layout_height="88dp"
|
||||||
android:layout_marginEnd="@dimen/spacing_medium"
|
android:layout_marginEnd="@dimen/spacing_medium"
|
||||||
android:contentDescription="@string/desc_no_cover"
|
android:contentDescription="@string/desc_no_cover"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
|
@ -37,28 +33,51 @@
|
||||||
<android.widget.TextView
|
<android.widget.TextView
|
||||||
android:id="@+id/widget_song"
|
android:id="@+id/widget_song"
|
||||||
style="@style/Widget.Auxio.TextView.Primary.AppWidget"
|
style="@style/Widget.Auxio.TextView.Primary.AppWidget"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/def_widget_song" />
|
android:text="@string/def_widget_song" />
|
||||||
|
|
||||||
<android.widget.TextView
|
<android.widget.TextView
|
||||||
android:id="@+id/widget_artist"
|
android:id="@+id/widget_artist"
|
||||||
style="@style/Widget.Auxio.TextView.Secondary.AppWidget"
|
style="@style/Widget.Auxio.TextView.Secondary.AppWidget"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/def_widget_artist" />
|
android:text="@string/def_widget_artist" />
|
||||||
|
|
||||||
|
<android.widget.LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_small"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<android.widget.ImageButton
|
||||||
|
android:id="@+id/widget_skip_prev"
|
||||||
|
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:contentDescription="@string/desc_skip_prev"
|
||||||
|
android:src="@drawable/ic_skip_prev" />
|
||||||
|
|
||||||
|
<android.widget.ImageButton
|
||||||
|
android:id="@+id/widget_play_pause"
|
||||||
|
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:contentDescription="@string/desc_play_pause"
|
||||||
|
android:src="@drawable/ic_play" />
|
||||||
|
|
||||||
|
<android.widget.ImageButton
|
||||||
|
android:id="@+id/widget_skip_next"
|
||||||
|
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:contentDescription="@string/desc_skip_next"
|
||||||
|
android:src="@drawable/ic_skip_next" />
|
||||||
|
|
||||||
|
</android.widget.LinearLayout>
|
||||||
</android.widget.LinearLayout>
|
</android.widget.LinearLayout>
|
||||||
|
|
||||||
<android.widget.ImageButton
|
|
||||||
android:id="@+id/widget_play_pause"
|
|
||||||
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:contentDescription="@string/desc_play_pause"
|
|
||||||
android:minWidth="@dimen/size_btn_small"
|
|
||||||
android:minHeight="@dimen/size_btn_small"
|
|
||||||
android:src="@drawable/sel_playing_state" />
|
|
||||||
|
|
||||||
</android.widget.LinearLayout>
|
</android.widget.LinearLayout>
|
||||||
</android.widget.LinearLayout>
|
</android.widget.LinearLayout>
|
||||||
|
|
|
@ -8,14 +8,7 @@
|
||||||
android:theme="@style/Theme.Widget">
|
android:theme="@style/Theme.Widget">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
For this widget form to work, we need to scale the ImageView across a 1:1 aspect ratio.
|
See widget_small.xml for an explanation for the ImageView setup
|
||||||
However, since we are working with a RemoteViews instance, we can't just use ConstraintLayout
|
|
||||||
to achieve this. We can use RelativeLayout, but there's no way to force an aspect ratio with
|
|
||||||
that layout. However, if we create an invisible ImageView that contains a massive fixed-size
|
|
||||||
drawable and then clamp our main ImageView to it, we can make the view scale on a 1:1 aspect
|
|
||||||
ratio.
|
|
||||||
|
|
||||||
This is easily one of the worst layout hacks I've done, but it seems to work.
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<android.widget.ImageView
|
<android.widget.ImageView
|
||||||
|
|
|
@ -178,6 +178,7 @@
|
||||||
<string name="clr_orange">Orange</string>
|
<string name="clr_orange">Orange</string>
|
||||||
<string name="clr_brown">Brown</string>
|
<string name="clr_brown">Brown</string>
|
||||||
<string name="clr_grey">Grey</string>
|
<string name="clr_grey">Grey</string>
|
||||||
|
<string name="clr_dynamic">Dynamic</string>
|
||||||
|
|
||||||
<!-- Format Namespace | Value formatting/plurals -->
|
<!-- Format Namespace | Value formatting/plurals -->
|
||||||
<string name="fmt_disc_no">Disc %d</string>
|
<string name="fmt_disc_no">Disc %d</string>
|
||||||
|
|
Loading…
Reference in a new issue