about: add full library statistics

Split off the "songs loaded" about item into it's own card called
"library statistics"

This card includes the song, album, artist, and genre counts,
alongside a total duration of the music library. This is just more
informative and useful to the user.

Resolves #121.
This commit is contained in:
OxygenCobalt 2022-05-04 20:20:47 -06:00
parent ac6a471318
commit 1a9e55e73b
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 238 additions and 127 deletions

View file

@ -4,9 +4,11 @@
#### What's New #### What's New
- Added ReplayGain support for below-reference volume tracks [i.e positive ReplayGain values] - Added ReplayGain support for below-reference volume tracks [i.e positive ReplayGain values]
- About screen now shows counts for multiple types of library items, alongside a total duration
#### What's Fixed #### What's Fixed
- Fixed incorrect ellipsizing on song items - Fixed incorrect ellipsizing on song items
- Fixed a variety of esoteric crashes with queue state
#### What's Changed #### What's Changed
- Audio focus is no longer configurable - Audio focus is no longer configurable

View file

@ -112,7 +112,7 @@ private constructor(
private val genre: Genre, private val genre: Genre,
) : BaseFetcher() { ) : BaseFetcher() {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
// We don't need to sort here, as the way we // Don't sort here to preserve compatibility with previous variations 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) }

View file

@ -88,9 +88,6 @@ import org.oxycblt.auxio.util.logD
* I wish I was born in the neolithic. * I wish I was born in the neolithic.
* *
* @author OxygenCobalt * @author OxygenCobalt
*
* TODO: Experiment with making the exoplayer metadata system more effificent so that we could
* leverage it here (maybe)
*/ */
object Indexer { object Indexer {
/** /**

View file

@ -65,8 +65,8 @@ import org.oxycblt.auxio.widgets.WidgetProvider
* therefore there's no need to bind to it to deliver commands. * therefore there's no need to bind to it to deliver commands.
* @author OxygenCobalt * @author OxygenCobalt
* *
* TODO: Synchronize components in a less awful way (Fix issue where rapid-fire updates results * TODO: Synchronize components in a less awful way (Fix issue where rapid-fire updates results in a
* in a desynced notification) * desynced notification)
*/ */
class PlaybackService : class PlaybackService :
Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback { Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback {
@ -219,7 +219,9 @@ class PlaybackService :
newPosition: Player.PositionInfo, newPosition: Player.PositionInfo,
reason: Int reason: Int
) { ) {
playbackManager.synchronizePosition(player.currentPosition) if (reason == Player.DISCONTINUITY_REASON_SEEK) {
playbackManager.synchronizePosition(player.currentPosition)
}
} }
override fun onTracksInfoChanged(tracksInfo: TracksInfo) { override fun onTracksInfoChanged(tracksInfo: TracksInfo) {

View file

@ -32,6 +32,7 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentAboutBinding import org.oxycblt.auxio.databinding.FragmentAboutBinding
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.music.toDuration
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
@ -62,6 +63,20 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
homeModel.songs.observe(viewLifecycleOwner) { songs -> homeModel.songs.observe(viewLifecycleOwner) { songs ->
binding.aboutSongCount.textSafe = getString(R.string.fmt_songs_loaded, songs.size) binding.aboutSongCount.textSafe = getString(R.string.fmt_songs_loaded, songs.size)
binding.aboutTotalDuration.textSafe =
getString(R.string.fmt_total_duration, songs.sumOf { it.seconds }.toDuration(false))
}
homeModel.albums.observe(viewLifecycleOwner) { albums ->
binding.aboutAlbumCount.textSafe = getString(R.string.fmt_albums_loaded, albums.size)
}
homeModel.artists.observe(viewLifecycleOwner) { artists ->
binding.aboutArtistCount.textSafe = getString(R.string.fmt_artists_loaded, artists.size)
}
homeModel.genres.observe(viewLifecycleOwner) { genres ->
binding.aboutGenreCount.textSafe = getString(R.string.fmt_genres_loaded, genres.size)
} }
} }

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
</vector>

View file

@ -26,153 +26,229 @@
android:clipToPadding="false" android:clipToPadding="false"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<com.google.android.material.card.MaterialCardView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="@dimen/spacing_medium"> android:layout_margin="@dimen/spacing_medium">
<androidx.constraintlayout.widget.ConstraintLayout <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="wrap_content">
<ImageView
android:id="@+id/about_auxio_icon"
style="@style/Widget.Auxio.Image.Small"
android:layout_marginTop="@dimen/spacing_medium"
android:contentDescription="@string/desc_auxio_icon"
android:src="@mipmap/ic_launcher"
app:layout_constraintEnd_toStartOf="@+id/about_app_name"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/about_app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:text="@string/info_app_name"
android:textAppearance="@style/TextAppearance.Auxio.TitleLarge"
app:layout_constraintBottom_toBottomOf="@+id/about_auxio_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/about_auxio_icon"
app:layout_constraintTop_toTopOf="@+id/about_auxio_icon" />
<TextView
android:id="@+id/about_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_small"
android:gravity="center"
android:paddingStart="@dimen/spacing_medium"
android:paddingEnd="@dimen/spacing_medium"
android:text="@string/info_app_desc"
android:textAppearance="@style/TextAppearance.Auxio.BodyLarge"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_auxio_icon" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/version_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:padding="@dimen/spacing_medium"
app:layout_constraintTop_toBottomOf="@+id/about_desc"
app:layout_constraintVertical_chainStyle="packed">
<ImageView <ImageView
android:id="@+id/about_version_icon" android:id="@+id/about_auxio_icon"
android:layout_width="wrap_content" style="@style/Widget.Auxio.Image.Small"
android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_medium"
android:contentDescription="@string/lbl_version" android:contentDescription="@string/desc_auxio_icon"
android:src="@drawable/ic_about" android:src="@mipmap/ic_launcher"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/about_app_name"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/about_version_title" android:id="@+id/about_app_name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium" android:layout_marginStart="@dimen/spacing_medium"
android:text="@string/lbl_version" android:text="@string/info_app_name"
android:textAppearance="@style/TextAppearance.Auxio.BodyLarge" android:textAppearance="@style/TextAppearance.Auxio.TitleLarge"
app:layout_constraintBottom_toTopOf="@+id/about_version" app:layout_constraintBottom_toBottomOf="@+id/about_auxio_icon"
app:layout_constraintStart_toEndOf="@+id/about_version_icon" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/about_version_icon" /> app:layout_constraintStart_toEndOf="@+id/about_auxio_icon"
app:layout_constraintTop_toTopOf="@+id/about_auxio_icon" />
<TextView <TextView
android:id="@+id/about_version" android:id="@+id/about_desc"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium" android:layout_marginTop="@dimen/spacing_small"
android:textAppearance="@style/TextAppearance.Auxio.BodySmall" android:gravity="center"
app:layout_constraintBottom_toBottomOf="@+id/about_version_icon" android:paddingStart="@dimen/spacing_medium"
app:layout_constraintStart_toEndOf="@+id/about_version_icon" android:paddingEnd="@dimen/spacing_medium"
app:layout_constraintTop_toBottomOf="@+id/about_version_title" android:text="@string/info_app_desc"
tools:text="16.16.16" /> android:textAppearance="@style/TextAppearance.Auxio.BodyLarge"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_auxio_icon" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/version_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/spacing_medium"
app:layout_constraintTop_toBottomOf="@+id/about_desc"
app:layout_constraintVertical_chainStyle="packed">
<ImageView
android:id="@+id/about_version_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/lbl_version"
android:src="@drawable/ic_about"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/about_version_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:text="@string/lbl_version"
android:textAppearance="@style/TextAppearance.Auxio.BodyLarge"
app:layout_constraintBottom_toTopOf="@+id/about_version"
app:layout_constraintStart_toEndOf="@+id/about_version_icon"
app:layout_constraintTop_toTopOf="@+id/about_version_icon" />
<TextView
android:id="@+id/about_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="@style/TextAppearance.Auxio.BodySmall"
app:layout_constraintBottom_toBottomOf="@+id/about_version_icon"
app:layout_constraintStart_toEndOf="@+id/about_version_icon"
app:layout_constraintTop_toBottomOf="@+id/about_version_title"
tools:text="16.16.16" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/about_code"
style="@style/Widget.Auxio.TextView.Icon.Clickable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lbl_code"
app:drawableStartCompat="@drawable/ic_code"
app:layout_constraintBottom_toTopOf="@+id/about_faq"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/version_container" />
<TextView
android:id="@+id/about_faq"
style="@style/Widget.Auxio.TextView.Icon.Clickable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lbl_faq"
app:drawableStartCompat="@drawable/ic_faq"
app:layout_constraintBottom_toTopOf="@+id/about_licenses"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_code" />
<TextView
android:id="@+id/about_licenses"
style="@style/Widget.Auxio.TextView.Icon.Clickable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lbl_licenses"
app:drawableStartCompat="@drawable/ic_license"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_faq" />
<TextView
style="@style/Widget.Auxio.TextView.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lbl_author"
app:drawableStartCompat="@drawable/ic_artist"
app:drawableTint="?attr/colorControlNormal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_licenses" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<TextView <com.google.android.material.card.MaterialCardView
android:id="@+id/about_code" android:layout_width="match_parent"
style="@style/Widget.Auxio.TextView.Icon.Clickable" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_medium">
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:text="@string/lbl_code" android:orientation="vertical">
app:drawableStartCompat="@drawable/ic_code"
app:layout_constraintBottom_toTopOf="@+id/about_faq"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/version_container" />
<TextView <TextView
android:id="@+id/about_licenses" android:id="@+id/about_library_counts"
style="@style/Widget.Auxio.TextView.Icon.Clickable" style="@style/Widget.Auxio.TextView.Header"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/lbl_licenses" android:text="@string/lbl_library_counts"
app:drawableStartCompat="@drawable/ic_license" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_faq" />
<TextView <TextView
android:id="@+id/about_faq" android:id="@+id/about_song_count"
style="@style/Widget.Auxio.TextView.Icon.Clickable" style="@style/Widget.Auxio.TextView.Icon"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/lbl_faq" app:drawableStartCompat="@drawable/ic_song"
app:drawableStartCompat="@drawable/ic_faq" app:drawableTint="?attr/colorControlNormal"
app:layout_constraintBottom_toTopOf="@+id/about_licenses" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/about_licenses"
app:layout_constraintTop_toBottomOf="@+id/about_code" /> tools:text="Songs Loaded: 1616" />
<TextView <TextView
android:id="@+id/about_song_count" android:id="@+id/about_album_count"
style="@style/Widget.Auxio.TextView.Icon" style="@style/Widget.Auxio.TextView.Icon"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:drawableStartCompat="@drawable/ic_album" app:drawableStartCompat="@drawable/ic_album"
app:drawableTint="?attr/colorControlNormal" app:drawableTint="?attr/colorControlNormal"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_licenses" app:layout_constraintTop_toBottomOf="@+id/about_licenses"
tools:text="Songs Loaded: 1616" /> tools:text="Albums Loaded: 1616" />
<TextView <TextView
style="@style/Widget.Auxio.TextView.Icon" android:id="@+id/about_artist_count"
android:layout_width="match_parent" style="@style/Widget.Auxio.TextView.Icon"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:text="@string/lbl_author" android:layout_height="wrap_content"
app:drawableStartCompat="@drawable/ic_artist" app:drawableStartCompat="@drawable/ic_artist"
app:drawableTint="?attr/colorControlNormal" app:drawableTint="?attr/colorControlNormal"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_song_count" /> app:layout_constraintTop_toBottomOf="@+id/about_licenses"
tools:text="Artists Loaded: 1616" />
</androidx.constraintlayout.widget.ConstraintLayout> <TextView
</com.google.android.material.card.MaterialCardView> android:id="@+id/about_genre_count"
style="@style/Widget.Auxio.TextView.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:drawableStartCompat="@drawable/ic_genre"
app:drawableTint="?attr/colorControlNormal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_licenses"
tools:text="Genres Loaded: 1616" />
<TextView
android:id="@+id/about_total_duration"
style="@style/Widget.Auxio.TextView.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:drawableStartCompat="@drawable/ic_time"
app:drawableTint="?attr/colorControlNormal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_licenses"
tools:text="Total duration: 16:16:16" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</org.oxycblt.auxio.ui.EdgeCoordinatorLayout> </org.oxycblt.auxio.ui.EdgeCoordinatorLayout>

View file

@ -54,6 +54,7 @@
<string name="lbl_faq">FAQ</string> <string name="lbl_faq">FAQ</string>
<string name="lbl_licenses">Licenses</string> <string name="lbl_licenses">Licenses</string>
<string name="lbl_author">Developed by OxygenCobalt</string> <string name="lbl_author">Developed by OxygenCobalt</string>
<string name="lbl_library_counts">Library statistics</string>
<!-- Settings namespace | Settings-related labels --> <!-- Settings namespace | Settings-related labels -->
<string name="set_title">Settings</string> <string name="set_title">Settings</string>
@ -168,6 +169,10 @@
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<string name="fmt_songs_loaded">Songs loaded: %d</string> <string name="fmt_songs_loaded">Songs loaded: %d</string>
<string name="fmt_albums_loaded">Albums loaded: %d</string>
<string name="fmt_artists_loaded">Artists loaded: %d</string>
<string name="fmt_genres_loaded">Genres loaded: %d</string>
<string name="fmt_total_duration">Total duration: %s</string>
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">
<item quantity="one">%d Song</item> <item quantity="one">%d Song</item>