image: refactor views
Refactor StyledImageView and ImageGroup into a new class called CoverView. This new view is more sensibly designed and should be capable of handling non-square album covers when implemented.
This commit is contained in:
parent
10d05b1f26
commit
b7c15e0cc5
30 changed files with 517 additions and 611 deletions
|
@ -85,7 +85,16 @@ private constructor(private val binding: ItemDetailHeaderBinding) :
|
|||
editedPlaylist: List<Song>?,
|
||||
listener: DetailHeaderAdapter.Listener
|
||||
) {
|
||||
binding.detailCover.bind(playlist, editedPlaylist)
|
||||
if (editedPlaylist != null) {
|
||||
logD("Binding edited playlist image")
|
||||
binding.detailCover.bind(
|
||||
editedPlaylist,
|
||||
binding.context.getString(R.string.desc_playlist_image, playlist.name),
|
||||
R.drawable.ic_playlist_24)
|
||||
} else {
|
||||
binding.detailCover.bind(playlist)
|
||||
}
|
||||
|
||||
binding.detailType.text = binding.context.getString(R.string.lbl_playlist)
|
||||
binding.detailName.text = playlist.name.resolve(binding.context)
|
||||
// Nothing about a playlist is applicable to the sub-head text.
|
||||
|
|
|
@ -22,6 +22,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.IntegerTable
|
||||
import org.oxycblt.auxio.R
|
||||
|
@ -162,31 +163,33 @@ private class AlbumSongViewHolder private constructor(private val binding: ItemA
|
|||
fun bind(song: Song, listener: SelectableListListener<Song>) {
|
||||
listener.bind(song, this, menuButton = binding.songMenu)
|
||||
|
||||
binding.songTrack.apply {
|
||||
if (song.track != null) {
|
||||
// Instead of an album cover, we show the track number, as the song list
|
||||
// within the album detail view would have homogeneous album covers otherwise.
|
||||
val track = song.track
|
||||
if (track != null) {
|
||||
binding.songTrackCover.contentDescription =
|
||||
binding.context.getString(R.string.desc_track_number, track)
|
||||
binding.songTrackText.apply {
|
||||
isVisible = true
|
||||
text = context.getString(R.string.fmt_number, song.track)
|
||||
isInvisible = false
|
||||
contentDescription = context.getString(R.string.desc_track_number, song.track)
|
||||
} else {
|
||||
// No track, do not show a number, instead showing a generic icon.
|
||||
text = ""
|
||||
isInvisible = true
|
||||
contentDescription = context.getString(R.string.def_track)
|
||||
}
|
||||
binding.songTrackPlaceholder.isInvisible = true
|
||||
} else {
|
||||
binding.songTrackCover.contentDescription =
|
||||
binding.context.getString(R.string.def_track)
|
||||
binding.songTrackText.apply {
|
||||
isInvisible = true
|
||||
text = null
|
||||
}
|
||||
binding.songTrackPlaceholder.isVisible = true
|
||||
}
|
||||
|
||||
binding.songName.text = song.name.resolve(binding.context)
|
||||
|
||||
// Use duration instead of album or artist for each song, as this text would
|
||||
// be homogenous otherwise.
|
||||
// Use duration instead of album or artist for each song to be more contextually relevant.
|
||||
binding.songDuration.text = song.durationMs.formatDurationMs(false)
|
||||
}
|
||||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.root.isSelected = isActive
|
||||
binding.songTrackBg.isPlaying = isPlaying
|
||||
binding.songTrackCover.setPlaying(isPlaying)
|
||||
}
|
||||
|
||||
override fun updateSelectionIndicator(isSelected: Boolean) {
|
||||
|
|
|
@ -110,7 +110,7 @@ private class ArtistAlbumViewHolder private constructor(private val binding: Ite
|
|||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.root.isSelected = isActive
|
||||
binding.parentImage.isPlaying = isPlaying
|
||||
binding.parentImage.setPlaying(isPlaying)
|
||||
}
|
||||
|
||||
override fun updateSelectionIndicator(isSelected: Boolean) {
|
||||
|
@ -162,7 +162,7 @@ private class ArtistSongViewHolder private constructor(private val binding: Item
|
|||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.root.isSelected = isActive
|
||||
binding.songAlbumCover.isPlaying = isPlaying
|
||||
binding.songAlbumCover.setPlaying(isPlaying)
|
||||
}
|
||||
|
||||
override fun updateSelectionIndicator(isSelected: Boolean) {
|
||||
|
|
|
@ -256,7 +256,7 @@ private constructor(private val binding: ItemEditableSongBinding) :
|
|||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.interactBody.isSelected = isActive
|
||||
binding.songAlbumCover.isPlaying = isPlaying
|
||||
binding.songAlbumCover.setPlaying(isPlaying)
|
||||
}
|
||||
|
||||
override fun updateEditing(editing: Boolean) {
|
||||
|
|
|
@ -97,16 +97,14 @@ constructor(
|
|||
ImageRequest.Builder(context)
|
||||
.data(listOf(song))
|
||||
// Use ORIGINAL sizing, as we are not loading into any View-like component.
|
||||
.size(Size.ORIGINAL)
|
||||
.transformations(SquareFrameTransform.INSTANCE))
|
||||
.size(Size.ORIGINAL))
|
||||
// Override the target in order to deliver the bitmap to the given
|
||||
// listener.
|
||||
.transformations(SquareFrameTransform.INSTANCE)
|
||||
.target(
|
||||
onSuccess = {
|
||||
synchronized(this) {
|
||||
if (currentHandle == handle) {
|
||||
// Has not been superseded by a new request, can deliver
|
||||
// this result.
|
||||
target.onCompleted(it.toBitmap())
|
||||
}
|
||||
}
|
||||
|
@ -114,8 +112,6 @@ constructor(
|
|||
onError = {
|
||||
synchronized(this) {
|
||||
if (currentHandle == handle) {
|
||||
// Has not been superseded by a new request, can deliver
|
||||
// this result.
|
||||
target.onCompleted(null)
|
||||
}
|
||||
}
|
||||
|
|
349
app/src/main/java/org/oxycblt/auxio/image/CoverView.kt
Normal file
349
app/src/main/java/org/oxycblt/auxio/image/CoverView.kt
Normal file
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* CoverView.kt is part of Auxio.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.image
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.updateMarginsRelative
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.util.CoilUtils
|
||||
import com.google.android.material.R as MR
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.ui.UISettings
|
||||
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||
import org.oxycblt.auxio.util.getColorCompat
|
||||
import org.oxycblt.auxio.util.getDimenPixels
|
||||
import org.oxycblt.auxio.util.getDrawableCompat
|
||||
import org.oxycblt.auxio.util.getInteger
|
||||
|
||||
/**
|
||||
* Auxio's extension of [ImageView] that enables cover art loading and playing indicator and
|
||||
* selection badge. In practice, it's three [ImageView]'s in a [FrameLayout] trenchcoat. By default,
|
||||
* all of this functionality is enabled. The playback indicator and selection badge selectively
|
||||
* disabled with the "playbackIndicatorEnabled" and "selectionBadgeEnabled" attributes, and image
|
||||
* itself can be overridden if populated like a normal [FrameLayout].
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*
|
||||
* TODO: Enable non-square covers as soon as I can confirm that my workaround is okay
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class CoverView
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
FrameLayout(context, attrs, defStyleAttr) {
|
||||
@Inject lateinit var imageLoader: ImageLoader
|
||||
@Inject lateinit var uiSettings: UISettings
|
||||
|
||||
private val image: ImageView
|
||||
private val playbackIndicator: PlaybackIndicatorView?
|
||||
private val selectionBadge: ImageView?
|
||||
private val cornerRadius: Float
|
||||
|
||||
private var fadeAnimator: ValueAnimator? = null
|
||||
|
||||
init {
|
||||
// Obtain some StyledImageView attributes to use later when theming the custom view.
|
||||
@SuppressLint("CustomViewStyleable")
|
||||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.CoverView)
|
||||
|
||||
// Keep track of our corner radius so that we can apply the same attributes to the custom
|
||||
// view.
|
||||
cornerRadius =
|
||||
if (uiSettings.roundMode) {
|
||||
styledAttrs.getDimension(R.styleable.CoverView_cornerRadius, 0f)
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
|
||||
val playbackIndicatorEnabled =
|
||||
styledAttrs.getBoolean(R.styleable.CoverView_enablePlaybackIndicator, true)
|
||||
|
||||
val selectionBadgeEnabled =
|
||||
styledAttrs.getBoolean(R.styleable.CoverView_enableSelectionBadge, true)
|
||||
|
||||
styledAttrs.recycle()
|
||||
|
||||
image = ImageView(context, attrs)
|
||||
|
||||
// Initialize the playback indicator if enabled.
|
||||
playbackIndicator =
|
||||
if (playbackIndicatorEnabled) {
|
||||
PlaybackIndicatorView(context)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Initialize the selection badge if enabled.
|
||||
selectionBadge =
|
||||
if (selectionBadgeEnabled) {
|
||||
ImageView(context).apply {
|
||||
imageTintList = context.getAttrColorCompat(MR.attr.colorOnPrimary)
|
||||
setImageResource(R.drawable.ic_check_20)
|
||||
setBackgroundResource(R.drawable.ui_selection_badge_bg)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
|
||||
// The image isn't added if other children have populated the body. This is by design.
|
||||
if (childCount == 0) {
|
||||
addView(image)
|
||||
}
|
||||
|
||||
playbackIndicator?.let(::addView)
|
||||
|
||||
// Add backgrounds to each children. This creates visual consistency between each view,
|
||||
// and also enables views to be hidden without clunky visibility changes.
|
||||
for (child in children) {
|
||||
child.apply {
|
||||
// If there are rounded corners, we want to make sure view content will be cropped
|
||||
// with it.
|
||||
clipToOutline = true
|
||||
background =
|
||||
MaterialShapeDrawable().apply {
|
||||
fillColor = context.getColorCompat(R.color.sel_cover_bg)
|
||||
setCornerSize(cornerRadius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The selection badge has it's own background we don't want overridden, add it after
|
||||
// all other elements.
|
||||
selectionBadge?.let {
|
||||
addView(
|
||||
it,
|
||||
// Position the selection badge to the bottom right.
|
||||
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
||||
// Override the layout params of the indicator so that it's in the
|
||||
// bottom left corner.
|
||||
gravity = Gravity.BOTTOM or Gravity.END
|
||||
val spacing = context.getDimenPixels(R.dimen.spacing_tiny)
|
||||
updateMarginsRelative(bottom = spacing, end = spacing)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
invalidateRootAlpha()
|
||||
invalidatePlaybackIndicatorAlpha(playbackIndicator ?: return)
|
||||
invalidateSelectionIndicatorAlpha(selectionBadge ?: return)
|
||||
}
|
||||
|
||||
override fun setSelected(selected: Boolean) {
|
||||
super.setSelected(selected)
|
||||
invalidateRootAlpha()
|
||||
invalidatePlaybackIndicatorAlpha(playbackIndicator ?: return)
|
||||
}
|
||||
|
||||
override fun setActivated(activated: Boolean) {
|
||||
super.setActivated(activated)
|
||||
invalidateSelectionIndicatorAlpha(selectionBadge ?: return)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the playback indicator should be indicated ongoing or paused playback.
|
||||
*
|
||||
* @param playing Whether playback is ongoing or paused.
|
||||
*/
|
||||
fun setPlaying(playing: Boolean) {
|
||||
playbackIndicator?.setPlaying(playing)
|
||||
}
|
||||
|
||||
private fun invalidateRootAlpha() {
|
||||
alpha = if (isSelected || isEnabled) 1f else 0.5f
|
||||
}
|
||||
|
||||
private fun invalidatePlaybackIndicatorAlpha(playbackIndicator: ImageView) {
|
||||
playbackIndicator.alpha = if (isSelected) 1f else 0f
|
||||
}
|
||||
|
||||
private fun invalidateSelectionIndicatorAlpha(selectionBadge: ImageView) {
|
||||
// Set up a target transition for the selection indicator.
|
||||
val targetAlpha: Float
|
||||
val targetDuration: Long
|
||||
|
||||
if (isActivated) {
|
||||
// View is "activated" (i.e marked as selected), so show the selection indicator.
|
||||
targetAlpha = 1f
|
||||
targetDuration = context.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
||||
} else {
|
||||
// View is not "activated", hide the selection indicator.
|
||||
targetAlpha = 0f
|
||||
targetDuration = context.getInteger(R.integer.anim_fade_exit_duration).toLong()
|
||||
}
|
||||
|
||||
if (selectionBadge.alpha == targetAlpha) {
|
||||
// Nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
if (!isLaidOut) {
|
||||
// Not laid out, initialize it without animation before drawing.
|
||||
selectionBadge.alpha = targetAlpha
|
||||
return
|
||||
}
|
||||
|
||||
if (fadeAnimator != null) {
|
||||
// Cancel any previous animation.
|
||||
fadeAnimator?.cancel()
|
||||
fadeAnimator = null
|
||||
}
|
||||
|
||||
fadeAnimator =
|
||||
ValueAnimator.ofFloat(selectionBadge.alpha, targetAlpha).apply {
|
||||
duration = targetDuration
|
||||
addUpdateListener { selectionBadge.alpha = it.animatedValue as Float }
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a [Song]'s image to this view.
|
||||
*
|
||||
* @param song The [Song] to bind to the view.
|
||||
*/
|
||||
fun bind(song: Song) = bind(song.album)
|
||||
|
||||
/**
|
||||
* Bind an [Album]'s image to this view.
|
||||
*
|
||||
* @param album The [Album] to bind to the view.
|
||||
*/
|
||||
fun bind(album: Album) =
|
||||
bind(
|
||||
album.songs,
|
||||
context.getString(R.string.desc_album_cover, album.name),
|
||||
R.drawable.ic_album_24)
|
||||
|
||||
/**
|
||||
* Bind an [Artist]'s image to this view.
|
||||
*
|
||||
* @param artist The [Artist] to bind to the view.
|
||||
*/
|
||||
fun bind(artist: Artist) =
|
||||
bind(
|
||||
artist.songs,
|
||||
context.getString(R.string.desc_artist_image, artist.name),
|
||||
R.drawable.ic_artist_24)
|
||||
|
||||
/**
|
||||
* Bind a [Genre]'s image to this view.
|
||||
*
|
||||
* @param genre The [Genre] to bind to the view.
|
||||
*/
|
||||
fun bind(genre: Genre) =
|
||||
bind(
|
||||
genre.songs,
|
||||
context.getString(R.string.desc_genre_image, genre.name),
|
||||
R.drawable.ic_genre_24)
|
||||
|
||||
/**
|
||||
* Bind a [Playlist]'s image to this view.
|
||||
*
|
||||
* @param playlist the [Playlist] to bind.
|
||||
*/
|
||||
fun bind(playlist: Playlist) =
|
||||
bind(
|
||||
playlist.songs,
|
||||
context.getString(R.string.desc_playlist_image, playlist.name),
|
||||
R.drawable.ic_playlist_24)
|
||||
|
||||
/**
|
||||
* Bind the covers of a generic list of [Song]s.
|
||||
*
|
||||
* @param songs The [Song]s to bind.
|
||||
* @param desc The content description to describe the bound data.
|
||||
* @param errorRes The resource of the error drawable to use if the cover cannot be loaded.
|
||||
*/
|
||||
fun bind(songs: List<Song>, desc: String, @DrawableRes errorRes: Int) {
|
||||
val request =
|
||||
ImageRequest.Builder(context)
|
||||
.data(songs)
|
||||
.error(StyledDrawable(context, context.getDrawableCompat(errorRes)))
|
||||
.transformations(SquareFrameTransform.INSTANCE)
|
||||
.target(image)
|
||||
.build()
|
||||
// Dispose of any previous image request and load a new image.
|
||||
CoilUtils.dispose(image)
|
||||
imageLoader.enqueue(request)
|
||||
contentDescription = desc
|
||||
}
|
||||
|
||||
private class StyledDrawable(context: Context, private val inner: Drawable) : Drawable() {
|
||||
init {
|
||||
// Re-tint the drawable to use the analogous "on surface" color for
|
||||
// StyledImageView.
|
||||
DrawableCompat.setTintList(inner, context.getColorCompat(R.color.sel_on_cover_bg))
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
// Resize the drawable such that it's always 1/4 the size of the image and
|
||||
// centered in the middle of the canvas.
|
||||
val adjustWidth = bounds.width() / 4
|
||||
val adjustHeight = bounds.height() / 4
|
||||
inner.bounds.set(
|
||||
adjustWidth,
|
||||
adjustHeight,
|
||||
bounds.width() - adjustWidth,
|
||||
bounds.height() - adjustHeight)
|
||||
inner.draw(canvas)
|
||||
}
|
||||
|
||||
// Required drawable overrides. Just forward to the wrapped drawable.
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
inner.alpha = alpha
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
inner.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
||||
}
|
||||
}
|
|
@ -1,266 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Auxio Project
|
||||
* ImageGroup.kt is part of Auxio.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.image
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.core.view.updateMarginsRelative
|
||||
import com.google.android.material.R as MR
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||
import org.oxycblt.auxio.util.getColorCompat
|
||||
import org.oxycblt.auxio.util.getDimenPixels
|
||||
import org.oxycblt.auxio.util.getInteger
|
||||
|
||||
/**
|
||||
* A super-charged [StyledImageView]. This class enables the following features in addition to
|
||||
* [StyledImageView]:
|
||||
* - A selection indicator
|
||||
* - An activation (playback) indicator
|
||||
* - Support for ONE custom view
|
||||
*
|
||||
* This class is primarily intended for list items. For other uses, [StyledImageView] is more
|
||||
* suitable.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*
|
||||
* TODO: Rework content descriptions here
|
||||
* TODO: Attempt unification with StyledImageView with some kind of dynamic configuration to avoid
|
||||
* superfluous elements
|
||||
* TODO: Handle non-square covers by gracefully placing them in the layout
|
||||
*/
|
||||
class ImageGroup
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
FrameLayout(context, attrs, defStyleAttr) {
|
||||
private val innerImageView: StyledImageView
|
||||
private var customView: View? = null
|
||||
private val playbackIndicatorView: PlaybackIndicatorView
|
||||
private val selectionIndicatorView: ImageView
|
||||
|
||||
private var fadeAnimator: ValueAnimator? = null
|
||||
private val cornerRadius: Float
|
||||
|
||||
init {
|
||||
// Obtain some StyledImageView attributes to use later when theming the custom view.
|
||||
@SuppressLint("CustomViewStyleable")
|
||||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
|
||||
// Keep track of our corner radius so that we can apply the same attributes to the custom
|
||||
// view.
|
||||
cornerRadius = styledAttrs.getDimension(R.styleable.StyledImageView_cornerRadius, 0f)
|
||||
styledAttrs.recycle()
|
||||
|
||||
// Initialize what views we can here.
|
||||
innerImageView = StyledImageView(context, attrs)
|
||||
playbackIndicatorView =
|
||||
PlaybackIndicatorView(context).apply { cornerRadius = this@ImageGroup.cornerRadius }
|
||||
selectionIndicatorView =
|
||||
ImageView(context).apply {
|
||||
imageTintList = context.getAttrColorCompat(MR.attr.colorOnPrimary)
|
||||
setImageResource(R.drawable.ic_check_20)
|
||||
setBackgroundResource(R.drawable.ui_selection_badge_bg)
|
||||
}
|
||||
|
||||
// The inner StyledImageView should be at the bottom and hidden by any other elements
|
||||
// if they become visible.
|
||||
addView(innerImageView)
|
||||
}
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
// Due to innerImageView, the max child count is actually 2 and not 1.
|
||||
check(childCount < 3) { "Only one custom view is allowed" }
|
||||
|
||||
// Get the second inflated child, making sure we customize it to align with
|
||||
// the rest of this view.
|
||||
customView =
|
||||
getChildAt(1)?.apply {
|
||||
background =
|
||||
MaterialShapeDrawable().apply {
|
||||
fillColor = context.getColorCompat(R.color.sel_cover_bg)
|
||||
setCornerSize(cornerRadius)
|
||||
}
|
||||
}
|
||||
|
||||
// Playback indicator should sit above the inner StyledImageView and custom view/
|
||||
addView(playbackIndicatorView)
|
||||
// Selection indicator should never be obscured, so place it at the top.
|
||||
addView(
|
||||
selectionIndicatorView,
|
||||
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
||||
// Override the layout params of the indicator so that it's in the
|
||||
// bottom left corner.
|
||||
gravity = Gravity.BOTTOM or Gravity.END
|
||||
val spacing = context.getDimenPixels(R.dimen.spacing_tiny)
|
||||
updateMarginsRelative(bottom = spacing, end = spacing)
|
||||
})
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
// Initialize each component before this view is drawn.
|
||||
invalidateImageAlpha()
|
||||
invalidatePlayingIndicator()
|
||||
invalidateSelectionIndicator()
|
||||
}
|
||||
|
||||
override fun setActivated(activated: Boolean) {
|
||||
super.setActivated(activated)
|
||||
invalidateSelectionIndicator()
|
||||
}
|
||||
|
||||
override fun setEnabled(enabled: Boolean) {
|
||||
super.setEnabled(enabled)
|
||||
invalidateImageAlpha()
|
||||
invalidatePlayingIndicator()
|
||||
}
|
||||
|
||||
override fun setSelected(selected: Boolean) {
|
||||
super.setSelected(selected)
|
||||
invalidateImageAlpha()
|
||||
invalidatePlayingIndicator()
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a [Song] to the internal [StyledImageView].
|
||||
*
|
||||
* @param song The [Song] to bind to the view.
|
||||
* @see StyledImageView.bind
|
||||
*/
|
||||
fun bind(song: Song) = innerImageView.bind(song)
|
||||
|
||||
/**
|
||||
* Bind a [Album] to the internal [StyledImageView].
|
||||
*
|
||||
* @param album The [Album] to bind to the view.
|
||||
* @see StyledImageView.bind
|
||||
*/
|
||||
fun bind(album: Album) = innerImageView.bind(album)
|
||||
|
||||
/**
|
||||
* Bind a [Genre] to the internal [StyledImageView].
|
||||
*
|
||||
* @param artist The [Artist] to bind to the view.
|
||||
* @see StyledImageView.bind
|
||||
*/
|
||||
fun bind(artist: Artist) = innerImageView.bind(artist)
|
||||
|
||||
/**
|
||||
* Bind a [Genre] to the internal [StyledImageView].
|
||||
*
|
||||
* @param genre The [Genre] to bind to the view.
|
||||
* @see StyledImageView.bind
|
||||
*/
|
||||
fun bind(genre: Genre) = innerImageView.bind(genre)
|
||||
|
||||
/**
|
||||
* Bind a [Playlist]'s image to the internal [StyledImageView].
|
||||
*
|
||||
* @param playlist the [Playlist] to bind.
|
||||
* @see StyledImageView.bind
|
||||
*/
|
||||
fun bind(playlist: Playlist) = innerImageView.bind(playlist)
|
||||
|
||||
/**
|
||||
* Whether this view should be indicated to have ongoing playback or not. See
|
||||
* PlaybackIndicatorView for more information on what occurs here. Note: It's expected for this
|
||||
* view to already be marked as playing with setSelected (not the same thing) before this is set
|
||||
* to true.
|
||||
*/
|
||||
var isPlaying: Boolean
|
||||
get() = playbackIndicatorView.isPlaying
|
||||
set(value) {
|
||||
playbackIndicatorView.isPlaying = value
|
||||
}
|
||||
|
||||
private fun invalidateImageAlpha() {
|
||||
// If this view is disabled, show it at half-opacity, *unless* it is also marked
|
||||
// as playing, in which we still want to show it at full-opacity.
|
||||
alpha = if (isSelected || isEnabled) 1f else 0.5f
|
||||
}
|
||||
|
||||
private fun invalidatePlayingIndicator() {
|
||||
if (isSelected) {
|
||||
// View is "selected" (actually marked as playing), so show the playing indicator
|
||||
// and hide all other elements except for the selection indicator.
|
||||
// TODO: Animate the other indicators?
|
||||
customView?.alpha = 0f
|
||||
innerImageView.alpha = 0f
|
||||
playbackIndicatorView.alpha = 1f
|
||||
} else {
|
||||
// View is not "selected", hide the playing indicator.
|
||||
customView?.alpha = 1f
|
||||
innerImageView.alpha = 1f
|
||||
playbackIndicatorView.alpha = 0f
|
||||
}
|
||||
}
|
||||
|
||||
private fun invalidateSelectionIndicator() {
|
||||
// Set up a target transition for the selection indicator.
|
||||
val targetAlpha: Float
|
||||
val targetDuration: Long
|
||||
|
||||
if (isActivated) {
|
||||
// View is "activated" (i.e marked as selected), so show the selection indicator.
|
||||
targetAlpha = 1f
|
||||
targetDuration = context.getInteger(R.integer.anim_fade_enter_duration).toLong()
|
||||
} else {
|
||||
// View is not "activated", hide the selection indicator.
|
||||
targetAlpha = 0f
|
||||
targetDuration = context.getInteger(R.integer.anim_fade_exit_duration).toLong()
|
||||
}
|
||||
|
||||
if (selectionIndicatorView.alpha == targetAlpha) {
|
||||
// Nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
if (!isLaidOut) {
|
||||
// Not laid out, initialize it without animation before drawing.
|
||||
selectionIndicatorView.alpha = targetAlpha
|
||||
return
|
||||
}
|
||||
|
||||
if (fadeAnimator != null) {
|
||||
// Cancel any previous animation.
|
||||
fadeAnimator?.cancel()
|
||||
fadeAnimator = null
|
||||
}
|
||||
|
||||
fadeAnimator =
|
||||
ValueAnimator.ofFloat(selectionIndicatorView.alpha, targetAlpha).apply {
|
||||
duration = targetDuration
|
||||
addUpdateListener { selectionIndicatorView.alpha = it.animatedValue as Float }
|
||||
start()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,12 +26,9 @@ import android.util.AttributeSet
|
|||
import androidx.annotation.AttrRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.ui.UISettings
|
||||
import org.oxycblt.auxio.util.getColorCompat
|
||||
import org.oxycblt.auxio.util.getDrawableCompat
|
||||
|
||||
|
@ -39,8 +36,8 @@ import org.oxycblt.auxio.util.getDrawableCompat
|
|||
* A view that displays an activation (i.e playback) indicator, with an accented styling and an
|
||||
* animated equalizer icon.
|
||||
*
|
||||
* This is only meant for use with [ImageGroup]. Due to limitations with [AnimationDrawable]
|
||||
* instances within custom views, this cannot be merged with [ImageGroup].
|
||||
* This is only meant for use with [CoverView]. Due to limitations with [AnimationDrawable]
|
||||
* instances within custom views, this cannot be merged with [CoverView].
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
|
@ -56,39 +53,16 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
private val indicatorMatrix = Matrix()
|
||||
private val indicatorMatrixSrc = RectF()
|
||||
private val indicatorMatrixDst = RectF()
|
||||
@Inject lateinit var uiSettings: UISettings
|
||||
|
||||
/**
|
||||
* The corner radius of this view. This allows the outer ImageGroup to apply it's corner radius
|
||||
* to this view without any attribute hacks.
|
||||
*/
|
||||
var cornerRadius = 0f
|
||||
set(value) {
|
||||
field = value
|
||||
(background as? MaterialShapeDrawable)?.let { bg ->
|
||||
if (uiSettings.roundMode) {
|
||||
bg.setCornerSize(value)
|
||||
} else {
|
||||
bg.setCornerSize(0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this view should be indicated to have ongoing playback or not. If true, the animated
|
||||
* playing icon will be shown. If false, the static paused icon will be shown.
|
||||
*/
|
||||
var isPlaying: Boolean
|
||||
get() = drawable == playingIndicatorDrawable
|
||||
set(value) {
|
||||
if (value) {
|
||||
playingIndicatorDrawable.start()
|
||||
setImageDrawable(playingIndicatorDrawable)
|
||||
} else {
|
||||
playingIndicatorDrawable.stop()
|
||||
setImageDrawable(pausedIndicatorDrawable)
|
||||
}
|
||||
fun setPlaying(isPlaying: Boolean) {
|
||||
if (isPlaying) {
|
||||
playingIndicatorDrawable.start()
|
||||
setImageDrawable(playingIndicatorDrawable)
|
||||
} else {
|
||||
playingIndicatorDrawable.stop()
|
||||
setImageDrawable(pausedIndicatorDrawable)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// We will need to manually re-scale the playing/paused drawables to align with
|
||||
|
@ -96,19 +70,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
scaleType = ScaleType.MATRIX
|
||||
// Tint the playing/paused drawables so they are harmonious with the background.
|
||||
ImageViewCompat.setImageTintList(this, context.getColorCompat(R.color.sel_on_cover_bg))
|
||||
|
||||
// Use clipToOutline and a background drawable to crop images. While Coil's transformation
|
||||
// could theoretically be used to round corners, the corner radius is dependent on the
|
||||
// dimensions of the image, which will result in inconsistent corners across different
|
||||
// album covers unless we resize all covers to be the same size. clipToOutline is both
|
||||
// cheaper and more elegant. As a side-note, this also allows us to re-use the same
|
||||
// background for both the tonal background color and the corner rounding.
|
||||
clipToOutline = true
|
||||
background =
|
||||
MaterialShapeDrawable().apply {
|
||||
fillColor = context.getColorCompat(R.color.sel_cover_bg)
|
||||
setCornerSize(cornerRadius)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
|
|
|
@ -1,201 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Auxio Project
|
||||
* StyledImageView.kt is part of Auxio.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.image
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.util.CoilUtils
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.ui.UISettings
|
||||
import org.oxycblt.auxio.util.getColorCompat
|
||||
import org.oxycblt.auxio.util.getDrawableCompat
|
||||
|
||||
/**
|
||||
* An [AppCompatImageView] with some additional styling, including:
|
||||
* - Tonal background
|
||||
* - Rounded corners based on user preferences
|
||||
* - Built-in support for binding image data or using a static icon with the same styling as
|
||||
* placeholder drawables.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class StyledImageView
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
AppCompatImageView(context, attrs, defStyleAttr) {
|
||||
@Inject lateinit var imageLoader: ImageLoader
|
||||
@Inject lateinit var uiSettings: UISettings
|
||||
|
||||
init {
|
||||
// Load view attributes
|
||||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
|
||||
val staticIcon =
|
||||
styledAttrs.getResourceId(
|
||||
R.styleable.StyledImageView_staticIcon, ResourcesCompat.ID_NULL)
|
||||
val cornerRadius = styledAttrs.getDimension(R.styleable.StyledImageView_cornerRadius, 0f)
|
||||
styledAttrs.recycle()
|
||||
|
||||
if (staticIcon != ResourcesCompat.ID_NULL) {
|
||||
// Use the static icon if specified for this image.
|
||||
setImageDrawable(StyledDrawable(context, context.getDrawableCompat(staticIcon)))
|
||||
}
|
||||
|
||||
// Use clipToOutline and a background drawable to crop images. While Coil's transformation
|
||||
// could theoretically be used to round corners, the corner radius is dependent on the
|
||||
// dimensions of the image, which will result in inconsistent corners across different
|
||||
// album covers unless we resize all covers to be the same size. clipToOutline is both
|
||||
// cheaper and more elegant. As a side-note, this also allows us to re-use the same
|
||||
// background for both the tonal background color and the corner rounding.
|
||||
clipToOutline = true
|
||||
background =
|
||||
MaterialShapeDrawable().apply {
|
||||
fillColor = context.getColorCompat(R.color.sel_cover_bg)
|
||||
if (uiSettings.roundMode) {
|
||||
// Only use the specified corner radius when round mode is enabled.
|
||||
setCornerSize(cornerRadius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a [Song]'s album cover to this view, also updating the content description.
|
||||
*
|
||||
* @param song The [Song] to bind.
|
||||
*/
|
||||
fun bind(song: Song) = bind(song.album)
|
||||
|
||||
/**
|
||||
* Bind an [Album]'s cover to this view, also updating the content description.
|
||||
*
|
||||
* @param album the [Album] to bind.
|
||||
*/
|
||||
fun bind(album: Album) = bind(album, R.drawable.ic_album_24, R.string.desc_album_cover)
|
||||
|
||||
/**
|
||||
* Bind an [Artist]'s image to this view, also updating the content description.
|
||||
*
|
||||
* @param artist the [Artist] to bind.
|
||||
*/
|
||||
fun bind(artist: Artist) = bind(artist, R.drawable.ic_artist_24, R.string.desc_artist_image)
|
||||
|
||||
/**
|
||||
* Bind an [Genre]'s image to this view, also updating the content description.
|
||||
*
|
||||
* @param genre the [Genre] to bind.
|
||||
*/
|
||||
fun bind(genre: Genre) = bind(genre, R.drawable.ic_genre_24, R.string.desc_genre_image)
|
||||
|
||||
/**
|
||||
* Bind a [Playlist]'s image to this view, also updating the content description.
|
||||
*
|
||||
* @param playlist The [Playlist] to bind.
|
||||
* @param songs [Song]s that can override the playlist image if it needs to differ for any
|
||||
* reason.
|
||||
*/
|
||||
fun bind(playlist: Playlist, songs: List<Song>? = null) =
|
||||
if (songs != null) {
|
||||
bind(
|
||||
songs,
|
||||
context.getString(R.string.desc_playlist_image, playlist.name.resolve(context)),
|
||||
R.drawable.ic_playlist_24)
|
||||
} else {
|
||||
bind(playlist, R.drawable.ic_playlist_24, R.string.desc_playlist_image)
|
||||
}
|
||||
|
||||
private fun bind(parent: MusicParent, @DrawableRes errorRes: Int, @StringRes descRes: Int) {
|
||||
bind(parent.songs, context.getString(descRes, parent.name.resolve(context)), errorRes)
|
||||
}
|
||||
|
||||
private fun bind(songs: List<Song>, desc: String, @DrawableRes errorRes: Int) {
|
||||
val request =
|
||||
ImageRequest.Builder(context)
|
||||
.data(songs)
|
||||
.error(StyledDrawable(context, context.getDrawableCompat(errorRes)))
|
||||
.transformations(SquareFrameTransform.INSTANCE)
|
||||
.target(this)
|
||||
.build()
|
||||
// Dispose of any previous image request and load a new image.
|
||||
CoilUtils.dispose(this)
|
||||
imageLoader.enqueue(request)
|
||||
contentDescription = desc
|
||||
}
|
||||
|
||||
/**
|
||||
* A [Drawable] wrapper that re-styles the drawable to better align with the style of
|
||||
* [StyledImageView].
|
||||
*
|
||||
* @param context [Context] required for initialization.
|
||||
* @param inner The [Drawable] to wrap.
|
||||
*/
|
||||
private class StyledDrawable(context: Context, private val inner: Drawable) : Drawable() {
|
||||
init {
|
||||
// Re-tint the drawable to use the analogous "on surface" color for
|
||||
// StyledImageView.
|
||||
DrawableCompat.setTintList(inner, context.getColorCompat(R.color.sel_on_cover_bg))
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
// Resize the drawable such that it's always 1/4 the size of the image and
|
||||
// centered in the middle of the canvas.
|
||||
val adjustWidth = bounds.width() / 4
|
||||
val adjustHeight = bounds.height() / 4
|
||||
inner.bounds.set(
|
||||
adjustWidth,
|
||||
adjustHeight,
|
||||
bounds.width() - adjustWidth,
|
||||
bounds.height() - adjustHeight)
|
||||
inner.draw(canvas)
|
||||
}
|
||||
|
||||
// Required drawable overrides. Just forward to the wrapped drawable.
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
inner.alpha = alpha
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
inner.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.min
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.guava.asDeferred
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -252,4 +253,21 @@ constructor(
|
|||
val size = pxOrElse { 512 }
|
||||
return if (size.mod(2) > 0) size + 1 else size
|
||||
}
|
||||
|
||||
private fun transform(input: Bitmap, size: Size): Bitmap {
|
||||
// Find the smaller dimension and then take a center portion of the image that
|
||||
// has that size.
|
||||
val dstSize = min(input.width, input.height)
|
||||
val x = (input.width - dstSize) / 2
|
||||
val y = (input.height - dstSize) / 2
|
||||
val dst = Bitmap.createBitmap(input, x, y, dstSize, dstSize)
|
||||
|
||||
val desiredWidth = size.width.pxOrElse { dstSize }
|
||||
val desiredHeight = size.height.pxOrElse { dstSize }
|
||||
if (dstSize != desiredWidth || dstSize != desiredHeight) {
|
||||
// Image is not the desired size, upscale it.
|
||||
return Bitmap.createScaledBitmap(dst, desiredWidth, desiredHeight, true)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
|
|||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.root.isSelected = isActive
|
||||
binding.songAlbumCover.isPlaying = isPlaying
|
||||
binding.songAlbumCover.setPlaying(isPlaying)
|
||||
}
|
||||
|
||||
override fun updateSelectionIndicator(isSelected: Boolean) {
|
||||
|
@ -114,7 +114,7 @@ class AlbumViewHolder private constructor(private val binding: ItemParentBinding
|
|||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.root.isSelected = isActive
|
||||
binding.parentImage.isPlaying = isPlaying
|
||||
binding.parentImage.setPlaying(isPlaying)
|
||||
}
|
||||
|
||||
override fun updateSelectionIndicator(isSelected: Boolean) {
|
||||
|
@ -174,7 +174,7 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
|
|||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.root.isSelected = isActive
|
||||
binding.parentImage.isPlaying = isPlaying
|
||||
binding.parentImage.setPlaying(isPlaying)
|
||||
}
|
||||
|
||||
override fun updateSelectionIndicator(isSelected: Boolean) {
|
||||
|
@ -231,7 +231,7 @@ class GenreViewHolder private constructor(private val binding: ItemParentBinding
|
|||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.root.isSelected = isActive
|
||||
binding.parentImage.isPlaying = isPlaying
|
||||
binding.parentImage.setPlaying(isPlaying)
|
||||
}
|
||||
|
||||
override fun updateSelectionIndicator(isSelected: Boolean) {
|
||||
|
@ -288,7 +288,7 @@ class PlaylistViewHolder private constructor(private val binding: ItemParentBind
|
|||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.root.isSelected = isActive
|
||||
binding.parentImage.isPlaying = isPlaying
|
||||
binding.parentImage.setPlaying(isPlaying)
|
||||
}
|
||||
|
||||
override fun updateSelectionIndicator(isSelected: Boolean) {
|
||||
|
|
|
@ -167,7 +167,7 @@ class QueueSongViewHolder private constructor(private val binding: ItemEditableS
|
|||
|
||||
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
|
||||
binding.interactBody.isSelected = isActive
|
||||
binding.songAlbumCover.isPlaying = isPlaying
|
||||
binding.songAlbumCover.setPlaying(isPlaying)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -16,15 +16,16 @@
|
|||
app:title="@string/lbl_playback"
|
||||
tools:subtitle="@string/lbl_all_songs" />
|
||||
|
||||
<org.oxycblt.auxio.image.StyledImageView
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/playback_cover"
|
||||
style="@style/Widget.Auxio.Image.Full"
|
||||
android:layout_margin="@dimen/spacing_medium"
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/playback_song"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playback_song"
|
||||
|
|
|
@ -8,14 +8,15 @@
|
|||
android:paddingStart="@dimen/spacing_medium"
|
||||
android:paddingEnd="@dimen/spacing_medium"
|
||||
android:paddingBottom="@dimen/spacing_mid_medium">
|
||||
<org.oxycblt.auxio.image.StyledImageView
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/detail_cover"
|
||||
style="@style/Widget.Auxio.Image.Huge"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_type"
|
||||
|
|
|
@ -9,13 +9,14 @@
|
|||
android:paddingEnd="@dimen/spacing_medium"
|
||||
android:paddingBottom="@dimen/spacing_mid_medium">
|
||||
|
||||
<org.oxycblt.auxio.image.StyledImageView
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/detail_cover"
|
||||
style="@style/Widget.Auxio.Image.Large"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_type"
|
||||
|
|
|
@ -16,15 +16,16 @@
|
|||
app:title="@string/lbl_playback"
|
||||
tools:subtitle="@string/lbl_all_songs" />
|
||||
|
||||
<org.oxycblt.auxio.image.StyledImageView
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/playback_cover"
|
||||
style="@style/Widget.Auxio.Image.Full"
|
||||
android:layout_margin="@dimen/spacing_medium"
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/playback_song"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playback_song"
|
||||
|
|
|
@ -7,14 +7,15 @@
|
|||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/spacing_medium">
|
||||
|
||||
<org.oxycblt.auxio.image.StyledImageView
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/detail_cover"
|
||||
style="@style/Widget.Auxio.Image.MidHuge"
|
||||
app:layout_constraintDimensionRatio="1"
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_type"
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/spacing_medium">
|
||||
|
||||
<org.oxycblt.auxio.image.StyledImageView
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/detail_cover"
|
||||
style="@style/Widget.Auxio.Image.Huge"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_type"
|
||||
|
|
|
@ -6,14 +6,15 @@
|
|||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<org.oxycblt.auxio.image.StyledImageView
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/playback_cover"
|
||||
style="@style/Widget.Auxio.Image.Small"
|
||||
android:layout_margin="@dimen/spacing_small"
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/playback_progress_container"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playback_song"
|
||||
|
|
|
@ -16,16 +16,17 @@
|
|||
app:title="@string/lbl_playback"
|
||||
tools:subtitle="@string/lbl_all_songs" />
|
||||
|
||||
<org.oxycblt.auxio.image.StyledImageView
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/playback_cover"
|
||||
style="@style/Widget.Auxio.Image.Full"
|
||||
android:layout_marginStart="@dimen/spacing_medium"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/playback_seek_bar"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
|
||||
<!-- Playback information is wrapped in a container so that marquee doesn't break -->
|
||||
|
|
|
@ -16,15 +16,25 @@
|
|||
with us only overlaying the track number (and other elements) onto it.
|
||||
-->
|
||||
|
||||
<org.oxycblt.auxio.image.ImageGroup
|
||||
android:id="@+id/song_track_bg"
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/song_track_cover"
|
||||
style="@style/Widget.Auxio.Image.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:staticIcon="@drawable/ic_song_24">
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/song_track_placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/ic_song_24"
|
||||
android:scaleType="center"
|
||||
android:contentDescription="@string/def_track"
|
||||
android:visibility="invisible"
|
||||
app:tint="@color/sel_on_cover_bg"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/song_track"
|
||||
android:id="@+id/song_track_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:ellipsize="end"
|
||||
|
@ -44,7 +54,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="16" />
|
||||
|
||||
</org.oxycblt.auxio.image.ImageGroup>
|
||||
</org.oxycblt.auxio.image.CoverView>
|
||||
|
||||
|
||||
<TextView
|
||||
|
@ -56,8 +66,8 @@
|
|||
android:textColor="@color/sel_selectable_text_primary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/song_duration"
|
||||
app:layout_constraintEnd_toStartOf="@+id/song_menu"
|
||||
app:layout_constraintStart_toEndOf="@+id/song_track_bg"
|
||||
app:layout_constraintTop_toTopOf="@+id/song_track_bg"
|
||||
app:layout_constraintStart_toEndOf="@+id/song_track_cover"
|
||||
app:layout_constraintTop_toTopOf="@+id/song_track_cover"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="Song Name" />
|
||||
|
||||
|
@ -68,9 +78,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/spacing_mid_medium"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/song_track_bg"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/song_track_cover"
|
||||
app:layout_constraintEnd_toStartOf="@+id/song_menu"
|
||||
app:layout_constraintStart_toEndOf="@+id/song_track_bg"
|
||||
app:layout_constraintStart_toEndOf="@+id/song_track_cover"
|
||||
app:layout_constraintTop_toBottomOf="@+id/song_name"
|
||||
tools:text="16:16" />
|
||||
|
||||
|
|
|
@ -10,14 +10,15 @@
|
|||
android:paddingEnd="@dimen/spacing_medium"
|
||||
android:paddingBottom="@dimen/spacing_mid_medium">
|
||||
|
||||
<org.oxycblt.auxio.image.StyledImageView
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/detail_cover"
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false"
|
||||
style="@style/Widget.Auxio.Image.MidHuge"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_type"
|
||||
|
|
|
@ -10,15 +10,27 @@
|
|||
android:paddingEnd="@dimen/spacing_medium"
|
||||
android:paddingBottom="@dimen/spacing_mid_medium">
|
||||
|
||||
<org.oxycblt.auxio.image.StyledImageView
|
||||
android:id="@+id/disc_icon"
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/disc_cover"
|
||||
style="@style/Widget.Auxio.Image.Small"
|
||||
android:scaleType="matrix"
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:staticIcon="@drawable/ic_album_24"
|
||||
tools:ignore="ContentDescription" />
|
||||
tools:ignore="ContentDescription">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/disc_icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/ic_add_24"
|
||||
android:scaleType="center"
|
||||
app:tint="@color/sel_on_cover_bg"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</org.oxycblt.auxio.image.CoverView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/disc_number"
|
||||
|
@ -29,7 +41,7 @@
|
|||
android:textColor="@color/sel_selectable_text_primary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/disc_name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/disc_icon"
|
||||
app:layout_constraintStart_toEndOf="@+id/disc_cover"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="Disc 1" />
|
||||
|
@ -43,7 +55,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:visibility="gone"
|
||||
app:layout_constraintStart_toEndOf="@+id/disc_icon"
|
||||
app:layout_constraintStart_toEndOf="@+id/disc_cover"
|
||||
app:layout_constraintTop_toBottomOf="@+id/disc_number"
|
||||
tools:text="Part 1" />
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ui_item_ripple">
|
||||
|
||||
<org.oxycblt.auxio.image.ImageGroup
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/song_album_cover"
|
||||
style="@style/Widget.Auxio.Image.Small"
|
||||
android:layout_marginStart="@dimen/spacing_medium"
|
||||
|
@ -43,8 +43,7 @@
|
|||
android:layout_marginBottom="@dimen/spacing_mid_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/song_name"
|
||||
|
|
|
@ -3,19 +3,33 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingStart="@dimen/spacing_large"
|
||||
android:paddingTop="@dimen/spacing_mid_medium"
|
||||
android:paddingEnd="@dimen/spacing_large"
|
||||
android:paddingBottom="@dimen/spacing_mid_medium">
|
||||
|
||||
<org.oxycblt.auxio.image.ImageGroup
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/picker_image"
|
||||
style="@style/Widget.Auxio.Image.Small"
|
||||
android:contentDescription="@string/lbl_new_playlist"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:staticIcon="@drawable/ic_add_24" />
|
||||
app:enablePlaybackIndicator="false"
|
||||
app:enableSelectionBadge="false">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/picker_icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/ic_add_24"
|
||||
android:scaleType="center"
|
||||
app:tint="@color/sel_on_cover_bg"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</org.oxycblt.auxio.image.CoverView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/picker_name"
|
||||
|
|
|
@ -10,13 +10,12 @@
|
|||
android:paddingEnd="@dimen/spacing_mid_medium"
|
||||
android:paddingBottom="@dimen/spacing_mid_medium">
|
||||
|
||||
<org.oxycblt.auxio.image.ImageGroup
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/parent_image"
|
||||
style="@style/Widget.Auxio.Image.Medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:staticIcon="@drawable/ic_artist_24" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/parent_name"
|
||||
|
|
|
@ -10,13 +10,12 @@
|
|||
android:paddingEnd="@dimen/spacing_large"
|
||||
android:paddingBottom="@dimen/spacing_mid_medium">
|
||||
|
||||
<org.oxycblt.auxio.image.ImageGroup
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/picker_image"
|
||||
style="@style/Widget.Auxio.Image.Small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/picker_name"
|
||||
|
|
|
@ -10,13 +10,12 @@
|
|||
android:paddingEnd="@dimen/spacing_mid_medium"
|
||||
android:paddingBottom="@dimen/spacing_mid_medium">
|
||||
|
||||
<org.oxycblt.auxio.image.ImageGroup
|
||||
<org.oxycblt.auxio.image.CoverView
|
||||
android:id="@+id/song_album_cover"
|
||||
style="@style/Widget.Auxio.Image.Small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:staticIcon="@drawable/ic_song_24" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/song_name"
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
<integer name="anim_fade_enter_duration">200</integer>
|
||||
<integer name="anim_fade_exit_duration">100</integer>
|
||||
|
||||
<declare-styleable name="StyledImageView">
|
||||
<declare-styleable name="CoverView">
|
||||
<attr name="cornerRadius" format="dimension" />
|
||||
<attr name="staticIcon" format="reference" />
|
||||
<attr name="useLargeIcon" format="boolean" />
|
||||
<attr name="enablePlaybackIndicator" format="boolean" />
|
||||
<attr name="enableSelectionBadge" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="IntListPreference">
|
||||
|
|
|
@ -47,21 +47,18 @@
|
|||
<item name="android:layout_width">@dimen/size_cover_large</item>
|
||||
<item name="android:layout_height">@dimen/size_cover_large</item>
|
||||
<item name="cornerRadius">@dimen/size_corners_medium</item>
|
||||
<item name="useLargeIcon">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Auxio.Image.MidHuge" parent="">
|
||||
<item name="android:layout_width">@dimen/size_cover_mid_huge</item>
|
||||
<item name="android:layout_height">@dimen/size_cover_mid_huge</item>
|
||||
<item name="cornerRadius">@dimen/size_corners_medium</item>
|
||||
<item name="useLargeIcon">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Auxio.Image.Huge" parent="">
|
||||
<item name="android:layout_width">@dimen/size_cover_huge</item>
|
||||
<item name="android:layout_height">@dimen/size_cover_huge</item>
|
||||
<item name="cornerRadius">@dimen/size_corners_medium</item>
|
||||
<item name="useLargeIcon">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Auxio.Image.Full" parent="">
|
||||
|
@ -69,7 +66,6 @@
|
|||
<item name="android:layout_height">0dp</item>
|
||||
<item name="layout_constraintDimensionRatio">1</item>
|
||||
<item name="cornerRadius">@dimen/size_corners_medium</item>
|
||||
<item name="useLargeIcon">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Auxio.RecyclerView.Linear" parent="">
|
||||
|
|
Loading…
Reference in a new issue