ui: remove album name from song items

Remove the album name from the main song item in favor of a duration
value.

Auxio has traditionally used "Artist . Album" for song items. However,
this had some shortcomings:
1. The way the artist and album names are packed probably results in
truncation on small screens, which I doubt is very helpful.
2. All other items in Auxio have only one specific "subject" per line,
which makes the song items a bit of an outlier since it has two (the
artist and the album)
3. The empty space available could be used for another UI element,
such as a duration or maybe a menu button in the future.

For consistency, this also removes the song count from all album items
as well.
This commit is contained in:
OxygenCobalt 2022-02-25 07:59:33 -07:00
parent c5be39774a
commit d7babcba71
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
11 changed files with 39 additions and 25 deletions

View file

@ -7,10 +7,13 @@
- Shuffle and Repeat mode buttons now have more contrast when they are turned on - Shuffle and Repeat mode buttons now have more contrast when they are turned on
#### What's Fixed #### What's Fixed
- Fixed crash on certain devices running Android 10 and lower when a differing theme from the system theme was used. - Fixed crash on certain devices running Android 10 and lower when a differing theme
from the system theme was used.
#### What's Changed #### What's Changed
- All cover art is now cropped to a 1:1 aspect ratio - All cover art is now cropped to a 1:1 aspect ratio
- Song items no longer show an album in favor of a duration
- Album items no longer show a song count
#### Dev/Meta #### Dev/Meta
- Enabled elevation drop shadows below Android P for consistency - Enabled elevation drop shadows below Android P for consistency

View file

@ -24,7 +24,7 @@ import coil.ImageLoaderFactory
import coil.request.CachePolicy import coil.request.CachePolicy
import org.oxycblt.auxio.coil.AlbumArtFetcher import org.oxycblt.auxio.coil.AlbumArtFetcher
import org.oxycblt.auxio.coil.ArtistImageFetcher import org.oxycblt.auxio.coil.ArtistImageFetcher
import org.oxycblt.auxio.coil.ErrorCrossfadeFactory import org.oxycblt.auxio.coil.CrossfadeFactory
import org.oxycblt.auxio.coil.GenreImageFetcher import org.oxycblt.auxio.coil.GenreImageFetcher
import org.oxycblt.auxio.coil.MusicKeyer import org.oxycblt.auxio.coil.MusicKeyer
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
@ -48,7 +48,7 @@ class AuxioApp : Application(), ImageLoaderFactory {
add(GenreImageFetcher.Factory()) add(GenreImageFetcher.Factory())
add(MusicKeyer()) add(MusicKeyer())
} }
.transitionFactory(ErrorCrossfadeFactory()) .transitionFactory(CrossfadeFactory())
.diskCachePolicy(CachePolicy.DISABLED) // Not downloading anything, so no disk-caching .diskCachePolicy(CachePolicy.DISABLED) // Not downloading anything, so no disk-caching
.build() .build()
} }

View file

@ -37,7 +37,7 @@ import android.util.Size as AndroidSize
* @author OxygenCobalt * @author OxygenCobalt
* TODO: Artist images * TODO: Artist images
*/ */
abstract class AuxioFetcher : Fetcher { abstract class BaseFetcher : Fetcher {
private val settingsManager = SettingsManager.getInstance() private val settingsManager = SettingsManager.getInstance()
/** /**
@ -193,7 +193,6 @@ abstract class AuxioFetcher : Fetcher {
// In the case a front cover is not found, use the first image in the tag instead. // In the case a front cover is not found, use the first image in the tag instead.
// This can be corrected later on if a front cover frame is found. // This can be corrected later on if a front cover frame is found.
logW("No front cover image, using image of type $type instead") logW("No front cover image, using image of type $type instead")
stream = ByteArrayInputStream(pic) stream = ByteArrayInputStream(pic)
} }
} }

View file

@ -13,7 +13,7 @@ import coil.transition.TransitionTarget
* You know. Like they used to. * You know. Like they used to.
* @author Coil Team * @author Coil Team
*/ */
class ErrorCrossfadeFactory : Transition.Factory { class CrossfadeFactory : Transition.Factory {
override fun create(target: TransitionTarget, result: ImageResult): Transition { override fun create(target: TransitionTarget, result: ImageResult): Transition {
// Don't animate if the request was fulfilled by the memory cache. // Don't animate if the request was fulfilled by the memory cache.
if (result is SuccessResult && result.dataSource == DataSource.MEMORY_CACHE) { if (result is SuccessResult && result.dataSource == DataSource.MEMORY_CACHE) {

View file

@ -43,7 +43,7 @@ import kotlin.math.min
class AlbumArtFetcher private constructor( class AlbumArtFetcher private constructor(
private val context: Context, private val context: Context,
private val album: Album private val album: Album
) : AuxioFetcher() { ) : BaseFetcher() {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
return fetchArt(context, album)?.let { stream -> return fetchArt(context, album)?.let { stream ->
SourceResult( SourceResult(
@ -75,7 +75,7 @@ class ArtistImageFetcher private constructor(
private val context: Context, private val context: Context,
private val size: Size, private val size: Size,
private val artist: Artist, private val artist: Artist,
) : AuxioFetcher() { ) : BaseFetcher() {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
val albums = Sort.ByName(true) val albums = Sort.ByName(true)
.sortAlbums(artist.albums) .sortAlbums(artist.albums)
@ -101,7 +101,7 @@ class GenreImageFetcher private constructor(
private val context: Context, private val context: Context,
private val size: Size, private val size: Size,
private val genre: Genre, private val genre: Genre,
) : AuxioFetcher() { ) : BaseFetcher() {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
// We don't need to sort here, as the way we // We don't need to sort here, as the way we
val albums = genre.songs.groupBy { it.album }.keys val albums = genre.songs.groupBy { it.album }.keys

View file

@ -11,6 +11,7 @@ import org.oxycblt.auxio.music.Song
class MusicKeyer : Keyer<Music> { class MusicKeyer : Keyer<Music> {
override fun key(data: Music, options: Options): String { override fun key(data: Music, options: Options): String {
return if (data is Song) { return if (data is Song) {
// Group up song covers with album covers for better caching
key(data.album, options) key(data.album, options)
} else { } else {
"${data::class.simpleName}: ${data.id}" "${data::class.simpleName}: ${data.id}"

View file

@ -34,10 +34,11 @@ class RoundableImageView @JvmOverloads constructor(
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
super.onAttachedToWindow() super.onAttachedToWindow()
// As for why we use clipToOutline instead of coils RoundedCornersTransformation, the radii // Use clipToOutline and a background drawable to crop images. While Coil's transformation
// of an image's corners is dependent on the actual dimensions of the image, which would // could theoretically be used to round corners, the corner radius is dependent on the
// force us to resize all images to a fixed size. clipToOutline is pretty much always // dimensions of the image, which will result in inconsistent corners across different
// cheaper as long as we have a perfectly-square image. // album covers unless we resize all covers to be the same size. clipToOutline is both
// cheaper and more elegant.
val settingsManager = SettingsManager.getInstance() val settingsManager = SettingsManager.getInstance()
clipToOutline = settingsManager.roundCovers clipToOutline = settingsManager.roundCovers
} }

View file

@ -16,6 +16,8 @@ class SquareFrameTransform : Transformation {
get() = "SquareFrameTransform" get() = "SquareFrameTransform"
override suspend fun transform(input: Bitmap, size: Size): Bitmap { override suspend 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 dstSize = min(input.width, input.height)
val x = (input.width - dstSize) / 2 val x = (input.width - dstSize) / 2
val y = (input.height - dstSize) / 2 val y = (input.height - dstSize) / 2

View file

@ -131,11 +131,7 @@ fun TextView.bindSongInfo(song: Song?) {
return return
} }
text = context.getString( text = song.resolvedArtistName
R.string.fmt_two,
song.resolvedArtistName,
song.resolvedAlbumName
)
} }
@BindingAdapter("albumInfo") @BindingAdapter("albumInfo")
@ -145,10 +141,7 @@ fun TextView.bindAlbumInfo(album: Album?) {
return return
} }
text = context.getString( text = album.resolvedArtistName
R.string.fmt_two, album.resolvedArtistName,
context.getPluralSafe(R.plurals.fmt_song_count, album.songs.size)
)
} }
@BindingAdapter("artistInfo") @BindingAdapter("artistInfo")

View file

@ -30,8 +30,9 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{song.name}" android:text="@{song.name}"
android:layout_marginEnd="@dimen/spacing_small"
app:layout_constraintBottom_toTopOf="@+id/song_info" app:layout_constraintBottom_toTopOf="@+id/song_info"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@+id/song_duration"
app:layout_constraintStart_toEndOf="@+id/album_cover" app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
@ -43,11 +44,26 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:songInfo="@{song}" app:songInfo="@{song}"
android:layout_marginEnd="@dimen/spacing_small"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@+id/song_duration"
app:layout_constraintStart_toEndOf="@+id/album_cover" app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toBottomOf="@+id/song_name" app:layout_constraintTop_toBottomOf="@+id/song_name"
tools:text="Artist / Album" /> tools:text="Artist / Album" />
<TextView
android:id="@+id/song_duration"
style="@style/Widget.Auxio.TextView.Item.Tertiary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="none"
android:gravity="end"
android:text="@{song.formattedDuration}"
android:textAlignment="viewEnd"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="16:16" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -4,7 +4,6 @@
<string name="info_app_name" translatable="false">Auxio</string> <string name="info_app_name" translatable="false">Auxio</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<string name="fmt_two" translatable="false">%1$s • %2$s</string>
<string name="fmt_three" translatable="false">%1$s • %2$s • %3$s</string> <string name="fmt_three" translatable="false">%1$s • %2$s • %3$s</string>
<string name="fmt_counts" translatable="false">%1$s, %2$s</string> <string name="fmt_counts" translatable="false">%1$s, %2$s</string>
<string name="fmt_track" translatable="false">%d</string> <string name="fmt_track" translatable="false">%d</string>