musikr: decouple name from auxio

This commit is contained in:
Alexander Capehart 2024-12-14 13:41:38 -07:00
parent de1c091517
commit c5cd404393
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
45 changed files with 330 additions and 241 deletions

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.Menu import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.PlaylistDecision import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage import org.oxycblt.auxio.music.PlaylistMessage
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.PlaybackDecision import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.Menu import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.PlaylistDecision import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage import org.oxycblt.auxio.music.PlaylistMessage
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.PlaybackDecision import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.Menu import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.PlaylistDecision import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage import org.oxycblt.auxio.music.PlaylistMessage
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.playback.PlaybackDecision import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately

View file

@ -37,6 +37,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.Menu import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.PlaylistDecision import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage import org.oxycblt.auxio.music.PlaylistMessage
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.playback.PlaybackDecision import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.ui.DialogAwareNavigationListener import org.oxycblt.auxio.ui.DialogAwareNavigationListener

View file

@ -32,6 +32,7 @@ import org.oxycblt.auxio.databinding.DialogSongDetailBinding
import org.oxycblt.auxio.detail.list.SongProperty import org.oxycblt.auxio.detail.list.SongProperty
import org.oxycblt.auxio.detail.list.SongPropertyAdapter import org.oxycblt.auxio.detail.list.SongPropertyAdapter
import org.oxycblt.auxio.list.adapter.UpdateInstructions import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.replaygain.formatDb import org.oxycblt.auxio.playback.replaygain.formatDb

View file

@ -25,6 +25,7 @@ import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.DialogRecyclerView import org.oxycblt.auxio.list.recycler.DialogRecyclerView
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.Artist import org.oxycblt.musikr.Artist

View file

@ -35,6 +35,7 @@ import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getAttrColorCompat

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.Album import org.oxycblt.musikr.Album

View file

@ -40,6 +40,7 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.MaterialDragCallback import org.oxycblt.auxio.list.recycler.MaterialDragCallback
import org.oxycblt.auxio.list.recycler.SongViewHolder import org.oxycblt.auxio.list.recycler.SongViewHolder
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getAttrColorCompat

View file

@ -105,10 +105,10 @@ class AlbumListFragment :
// Change how we display the popup depending on the current sort mode. // Change how we display the popup depending on the current sort mode.
return when (homeModel.albumSort.mode) { return when (homeModel.albumSort.mode) {
// By Name -> Use Name // By Name -> Use Name
is Sort.Mode.ByName -> album.name.thumb is Sort.Mode.ByName -> album.name.thumb()
// By Artist -> Use name of first artist // By Artist -> Use name of first artist
is Sort.Mode.ByArtist -> album.artists[0].name.thumb is Sort.Mode.ByArtist -> album.artists[0].name.thumb()
// Date -> Use minimum date (Maximum dates are not sorted by, so showing them is odd) // Date -> Use minimum date (Maximum dates are not sorted by, so showing them is odd)
is Sort.Mode.ByDate -> album.dates?.run { min.resolve(requireContext()) } is Sort.Mode.ByDate -> album.dates?.run { min.resolve(requireContext()) }

View file

@ -100,7 +100,7 @@ class ArtistListFragment :
// Change how we display the popup depending on the current sort mode. // Change how we display the popup depending on the current sort mode.
return when (homeModel.artistSort.mode) { return when (homeModel.artistSort.mode) {
// By Name -> Use Name // By Name -> Use Name
is Sort.Mode.ByName -> artist.name.thumb is Sort.Mode.ByName -> artist.name.thumb()
// Duration -> Use formatted duration // Duration -> Use formatted duration
is Sort.Mode.ByDuration -> artist.durationMs?.formatDurationMs(false) is Sort.Mode.ByDuration -> artist.durationMs?.formatDurationMs(false)

View file

@ -99,7 +99,7 @@ class GenreListFragment :
// Change how we display the popup depending on the current sort mode. // Change how we display the popup depending on the current sort mode.
return when (homeModel.genreSort.mode) { return when (homeModel.genreSort.mode) {
// By Name -> Use Name // By Name -> Use Name
is Sort.Mode.ByName -> genre.name.thumb is Sort.Mode.ByName -> genre.name.thumb()
// Duration -> Use formatted duration // Duration -> Use formatted duration
is Sort.Mode.ByDuration -> genre.durationMs.formatDurationMs(false) is Sort.Mode.ByDuration -> genre.durationMs.formatDurationMs(false)

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2024 Auxio Project
* ListUtil.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.home.list
import androidx.core.text.isDigitsOnly
import org.oxycblt.musikr.tag.Name
fun Name.thumb() =
when (this) {
is Name.Known ->
tokens.firstOrNull()?.let {
val value = it.collationKey.sourceString
if (value.isDigitsOnly()) "#" else value
}
is Name.Unknown -> "?"
}

View file

@ -97,7 +97,7 @@ class PlaylistListFragment :
// Change how we display the popup depending on the current sort mode. // Change how we display the popup depending on the current sort mode.
return when (homeModel.playlistSort.mode) { return when (homeModel.playlistSort.mode) {
// By Name -> Use Name // By Name -> Use Name
is Sort.Mode.ByName -> playlist.name.thumb is Sort.Mode.ByName -> playlist.name.thumb()
// Duration -> Use formatted duration // Duration -> Use formatted duration
is Sort.Mode.ByDuration -> playlist.durationMs.formatDurationMs(false) is Sort.Mode.ByDuration -> playlist.durationMs.formatDurationMs(false)

View file

@ -105,13 +105,13 @@ class SongListFragment :
// based off the names of the parent objects and not the child objects. // based off the names of the parent objects and not the child objects.
return when (homeModel.songSort.mode) { return when (homeModel.songSort.mode) {
// Name -> Use name // Name -> Use name
is Sort.Mode.ByName -> song.name.thumb is Sort.Mode.ByName -> song.name.thumb()
// Artist -> Use name of first artist // Artist -> Use name of first artist
is Sort.Mode.ByArtist -> song.album.artists[0].name.thumb is Sort.Mode.ByArtist -> song.album.artists[0].name.thumb()
// Album -> Use Album Name // Album -> Use Album Name
is Sort.Mode.ByAlbum -> song.album.name.thumb is Sort.Mode.ByAlbum -> song.album.name.thumb()
// Year -> Use Full Year // Year -> Use Full Year
is Sort.Mode.ByDate -> song.album.dates?.resolveDate(requireContext()) is Sort.Mode.ByDate -> song.album.dates?.resolveDate(requireContext())

View file

@ -28,6 +28,7 @@ import org.oxycblt.auxio.databinding.DialogMenuBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ListViewModel import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.music.areNamesTheSame import org.oxycblt.auxio.music.areNamesTheSame
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.getPlural

View file

@ -39,7 +39,7 @@ import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.cache.Cache import org.oxycblt.musikr.cache.Cache
import org.oxycblt.musikr.cache.CacheDatabase import org.oxycblt.musikr.cache.CacheDatabase
import org.oxycblt.musikr.cover.StoredCovers import org.oxycblt.musikr.cover.StoredCovers
import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.interpret.Naming
import org.oxycblt.musikr.tag.interpret.Separators import org.oxycblt.musikr.tag.interpret.Separators
import timber.log.Timber as L import timber.log.Timber as L
@ -353,9 +353,9 @@ constructor(
val separators = Separators.from(musicSettings.separators) val separators = Separators.from(musicSettings.separators)
val nameFactory = val nameFactory =
if (musicSettings.intelligentSorting) { if (musicSettings.intelligentSorting) {
Name.Known.IntelligentFactory Naming.intelligent()
} else { } else {
Name.Known.SimpleFactory Naming.simple()
} }
val locations = musicSettings.musicLocations val locations = musicSettings.musicLocations

View file

@ -20,8 +20,22 @@ package org.oxycblt.auxio.music
import android.content.Context import android.content.Context
import kotlin.math.max import kotlin.math.max
import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.concatLocalized import org.oxycblt.auxio.util.concatLocalized
import org.oxycblt.musikr.Music import org.oxycblt.musikr.Music
import org.oxycblt.musikr.tag.Name
import org.oxycblt.musikr.tag.Placeholder
fun Name.resolve(context: Context) =
when (this) {
is Name.Known -> raw
is Name.Unknown ->
when (placeholder) {
Placeholder.ALBUM -> context.getString(R.string.def_album)
Placeholder.ARTIST -> context.getString(R.string.def_artist)
Placeholder.GENRE -> context.getString(R.string.def_genre)
}
}
/** /**
* Run [Name.resolve] on each instance in the given list and concatenate them into a [String] in a * Run [Name.resolve] on each instance in the given list and concatenate them into a [String] in a

View file

@ -29,6 +29,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogDeletePlaylistBinding import org.oxycblt.auxio.databinding.DialogDeletePlaylistBinding
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull

View file

@ -31,6 +31,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogPlaylistExportBinding import org.oxycblt.auxio.databinding.DialogPlaylistExportBinding
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull

View file

@ -25,6 +25,7 @@ import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.DialogRecyclerView import org.oxycblt.auxio.list.recycler.DialogRecyclerView
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater

View file

@ -28,6 +28,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.sort.Sort import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.PlaylistDecision import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.resolve
import org.oxycblt.musikr.Music import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Playlist import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song import org.oxycblt.musikr.Song

View file

@ -30,6 +30,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogPlaylistNameBinding import org.oxycblt.auxio.databinding.DialogPlaylistNameBinding
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull

View file

@ -27,6 +27,7 @@ import androidx.annotation.StringRes
import androidx.media.utils.MediaConstants import androidx.media.utils.MediaConstants
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.formatDurationDs import org.oxycblt.auxio.playback.formatDurationDs
import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.getPlural

View file

@ -26,6 +26,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment

View file

@ -35,6 +35,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ListViewModel import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.playback.ui.StyledSeekBar import org.oxycblt.auxio.playback.ui.StyledSeekBar

View file

@ -25,6 +25,7 @@ import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.DialogRecyclerView import org.oxycblt.auxio.list.recycler.DialogRecyclerView
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.Artist import org.oxycblt.musikr.Artist

View file

@ -25,6 +25,7 @@ import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.DialogRecyclerView import org.oxycblt.auxio.list.recycler.DialogRecyclerView
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.Genre import org.oxycblt.musikr.Genre

View file

@ -32,6 +32,7 @@ import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.PlayingIndicatorAdapter import org.oxycblt.auxio.list.adapter.PlayingIndicatorAdapter
import org.oxycblt.auxio.list.recycler.MaterialDragCallback import org.oxycblt.auxio.list.recycler.MaterialDragCallback
import org.oxycblt.auxio.list.recycler.SongViewHolder import org.oxycblt.auxio.list.recycler.SongViewHolder
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getAttrColorCompat

View file

@ -36,6 +36,7 @@ import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.image.ImageSettings import org.oxycblt.auxio.image.ImageSettings
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.music.service.MediaSessionUID import org.oxycblt.auxio.music.service.MediaSessionUID
import org.oxycblt.auxio.music.service.toMediaDescription import org.oxycblt.auxio.music.service.toMediaDescription

View file

@ -30,6 +30,7 @@ import javax.inject.Inject
import org.apache.commons.text.similarity.JaroWinklerSimilarity import org.apache.commons.text.similarity.JaroWinklerSimilarity
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.service.MediaSessionUID import org.oxycblt.auxio.music.service.MediaSessionUID
import org.oxycblt.auxio.music.service.MusicBrowser import org.oxycblt.auxio.music.service.MusicBrowser
import org.oxycblt.auxio.playback.state.PlaybackCommand import org.oxycblt.auxio.playback.state.PlaybackCommand

View file

@ -22,6 +22,7 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import java.text.Normalizer import java.text.Normalizer
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.auxio.music.resolve
import org.oxycblt.musikr.Album import org.oxycblt.musikr.Album
import org.oxycblt.musikr.Artist import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Genre import org.oxycblt.musikr.Genre

View file

@ -30,6 +30,7 @@ import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.service.PlaybackActions import org.oxycblt.auxio.playback.service.PlaybackActions
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode

View file

@ -20,9 +20,9 @@ package org.oxycblt.musikr
import org.oxycblt.musikr.cache.Cache import org.oxycblt.musikr.cache.Cache
import org.oxycblt.musikr.cover.StoredCovers import org.oxycblt.musikr.cover.StoredCovers
import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.interpret.Naming
import org.oxycblt.musikr.tag.interpret.Separators import org.oxycblt.musikr.tag.interpret.Separators
data class Storage(val cache: Cache, val storedCovers: StoredCovers) data class Storage(val cache: Cache, val storedCovers: StoredCovers)
data class Interpretation(val nameFactory: Name.Known.Factory, val separators: Separators) data class Interpretation(val naming: Naming, val separators: Separators)

View file

@ -115,7 +115,7 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder {
simplifyArtistCluster(cluster) simplifyArtistCluster(cluster)
} }
val albumClusters = albumVertices.values.groupBy { it.preAlbum.rawName.lowercase() } val albumClusters = albumVertices.values.groupBy { it.preAlbum.rawName?.lowercase() }
for (cluster in albumClusters.values) { for (cluster in albumClusters.values) {
simplifyAlbumCluster(cluster) simplifyAlbumCluster(cluster)
} }

View file

@ -25,6 +25,7 @@ import java.io.BufferedWriter
import java.io.InputStream import java.io.InputStream
import java.io.InputStreamReader import java.io.InputStreamReader
import java.io.OutputStream import java.io.OutputStream
import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.musikr.Playlist import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.fs.Components import org.oxycblt.musikr.fs.Components

View file

@ -18,11 +18,7 @@
package org.oxycblt.musikr.tag package org.oxycblt.musikr.tag
import android.content.Context import org.oxycblt.musikr.tag.interpret.Token
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import java.text.CollationKey
import java.text.Collator
/** /**
* The name of a music item. * The name of a music item.
@ -32,71 +28,26 @@ import java.text.Collator
* @author Alexander Capehart * @author Alexander Capehart
*/ */
sealed interface Name : Comparable<Name> { sealed interface Name : Comparable<Name> {
/**
* A logical first character that can be used to collate a sorted list of music.
*
* TODO: Move this to the home package
*/
val thumb: String
/**
* Get a human-readable string representation of this instance.
*
* @param context [Context] required.
*/
fun resolve(context: Context): String
/** A name that could be obtained for the music item. */ /** A name that could be obtained for the music item. */
sealed class Known : Name { abstract class Known : Name {
/** The raw name string obtained. Should be ignored in favor of [resolve]. */ /** The raw name string obtained. Should be ignored in favor of [resolve]. */
abstract val raw: String abstract val raw: String
/** The raw sort name string obtained. */ /** The raw sort name string obtained. */
abstract val sort: String? abstract val sort: String?
/** A tokenized version of the name that will be compared. */ /** A tokenized version of the name that will be compared. */
@VisibleForTesting(VisibleForTesting.PROTECTED) abstract val sortTokens: List<SortToken> abstract val tokens: List<Token>
final override val thumb: String
get() =
// TODO: Remove these safety checks once you have real unit testing
sortTokens
.firstOrNull()
?.run { collationKey.sourceString.firstOrNull() }
?.let { if (it.isDigit()) "#" else it.uppercase() } ?: "?"
final override fun resolve(context: Context) = raw
final override fun compareTo(other: Name) = final override fun compareTo(other: Name) =
when (other) { when (other) {
is Known -> { is Known -> {
val result = val result =
sortTokens.zip(other.sortTokens).fold(0) { acc, (token, otherToken) -> tokens.zip(other.tokens).fold(0) { acc, (token, otherToken) ->
acc.takeIf { it != 0 } ?: token.compareTo(otherToken) acc.takeIf { it != 0 } ?: token.compareTo(otherToken)
} }
if (result != 0) result else sortTokens.size.compareTo(other.sortTokens.size) if (result != 0) result else tokens.size.compareTo(other.tokens.size)
} }
is Unknown -> 1 is Unknown -> 1
} }
sealed interface Factory {
/**
* Create a new instance of [Name.Known]
*
* @param raw The raw name obtained from the music item
* @param sort The raw sort name obtained from the music item
*/
fun parse(raw: String, sort: String?): Known
}
/** Produces a simple [Known] with basic sorting heuristics that are locale-independent. */
data object SimpleFactory : Factory {
override fun parse(raw: String, sort: String?) = SimpleKnownName(raw, sort)
}
/** Produces an intelligent [Known] with advanced, but more fragile heuristics. */
data object IntelligentFactory : Factory {
override fun parse(raw: String, sort: String?) = IntelligentKnownName(raw, sort)
}
} }
/** /**
@ -104,11 +55,7 @@ sealed interface Name : Comparable<Name> {
* *
* @author Alexander Capehart * @author Alexander Capehart
*/ */
data class Unknown(@StringRes val stringRes: Int) : Name { data class Unknown(val placeholder: Placeholder) : Name {
override val thumb = "?"
override fun resolve(context: Context) = context.getString(stringRes)
override fun compareTo(other: Name) = override fun compareTo(other: Name) =
when (other) { when (other) {
// Unknown names do not need any direct comparison right now. // Unknown names do not need any direct comparison right now.
@ -119,111 +66,8 @@ sealed interface Name : Comparable<Name> {
} }
} }
private val collator: Collator = Collator.getInstance().apply { strength = Collator.PRIMARY } enum class Placeholder {
private val punctRegex by lazy { Regex("[\\p{Punct}+]") } ALBUM,
ARTIST,
// TODO: Consider how you want to handle whitespace and "gaps" in names. GENRE
/**
* Plain [Name.Known] implementation that is internationalization-safe.
*
* @author Alexander Capehart (OxygenCobalt)
*/
data class SimpleKnownName(override val raw: String, override val sort: String?) : Name.Known() {
override val sortTokens = listOf(parseToken(sort ?: raw))
private fun parseToken(name: String): SortToken {
// Remove excess punctuation from the string, as those usually aren't considered in sorting.
val stripped = name.replace(punctRegex, "").trim().ifEmpty { name }
val collationKey = collator.getCollationKey(stripped)
// Always use lexicographic mode since we aren't parsing any numeric components
return SortToken(collationKey, SortToken.Type.LEXICOGRAPHIC)
}
}
/**
* [Name.Known] implementation that adds advanced sorting behavior at the cost of localization.
*
* @author Alexander Capehart (OxygenCobalt)
*/
data class IntelligentKnownName(override val raw: String, override val sort: String?) :
Name.Known() {
override val sortTokens = parseTokens(sort ?: raw)
private fun parseTokens(name: String): List<SortToken> {
// TODO: This routine is consuming much of the song building runtime, find a way to
// optimize it
val stripped =
name
// Remove excess punctuation from the string, as those usually aren't
// considered in sorting.
.replace(punctRegex, "")
.ifEmpty { name }
.run {
// Strip any english articles like "the" or "an" from the start, as music
// sorting should ignore such when possible.
when {
length > 4 && startsWith("the ", ignoreCase = true) -> substring(4)
length > 3 && startsWith("an ", ignoreCase = true) -> substring(3)
length > 2 && startsWith("a ", ignoreCase = true) -> substring(2)
else -> this
}
}
// To properly compare numeric components in names, we have to split them up into
// individual lexicographic and numeric tokens and then individually compare them
// with special logic.
return TOKEN_REGEX.findAll(stripped).mapTo(mutableListOf()) { match ->
// Remove excess whitespace where possible
val token = match.value.trim().ifEmpty { match.value }
val collationKey: CollationKey
val type: SortToken.Type
// Separate each token into their numeric and lexicographic counterparts.
if (token.first().isDigit()) {
// The digit string comparison breaks with preceding zero digits, remove those
val digits =
token.trimStart { Character.getNumericValue(it) == 0 }.ifEmpty { token }
// Other languages have other types of digit strings, still use collation keys
collationKey = collator.getCollationKey(digits)
type = SortToken.Type.NUMERIC
} else {
collationKey = collator.getCollationKey(token)
type = SortToken.Type.LEXICOGRAPHIC
}
SortToken(collationKey, type)
}
}
companion object {
private val TOKEN_REGEX by lazy { Regex("(\\d+)|(\\D+)") }
}
}
/** An individual part of a name string that can be compared intelligently. */
@VisibleForTesting(VisibleForTesting.PROTECTED)
data class SortToken(val collationKey: CollationKey, val type: Type) : Comparable<SortToken> {
override fun compareTo(other: SortToken): Int {
// Numeric tokens should always be lower than lexicographic tokens.
val modeComp = type.compareTo(other.type)
if (modeComp != 0) {
return modeComp
}
// Numeric strings must be ordered by magnitude, thus immediately short-circuit
// the comparison if the lengths do not match.
if (type == Type.NUMERIC &&
collationKey.sourceString.length != other.collationKey.sourceString.length) {
return collationKey.sourceString.length - other.collationKey.sourceString.length
}
return collationKey.compareTo(other.collationKey)
}
/** Denotes the type of comparison to be performed with this token. */
enum class Type {
/** Compare as a digit string, like "65". */
NUMERIC,
/** Compare as a standard alphanumeric string, like "65daysofstatic" */
LEXICOGRAPHIC
}
} }

View file

@ -0,0 +1,130 @@
/*
* Copyright (c) 2024 Auxio Project
* Naming.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.musikr.tag.interpret
import java.text.CollationKey
import java.text.Collator
import org.oxycblt.musikr.tag.Name
import org.oxycblt.musikr.tag.Placeholder
abstract class Naming {
fun name(raw: String?, sort: String?, placeholder: Placeholder): Name =
if (raw != null) {
name(raw, sort)
} else {
Name.Unknown(placeholder)
}
abstract fun name(raw: String, sort: String?): Name.Known
companion object {
fun intelligent(): Naming = IntelligentNaming
fun simple(): Naming = SimpleNaming
}
}
private data object IntelligentNaming : Naming() {
override fun name(raw: String, sort: String?) = IntelligentKnownName(raw, sort)
}
private data object SimpleNaming : Naming() {
override fun name(raw: String, sort: String?) = SimpleKnownName(raw, sort)
}
private val collator: Collator = Collator.getInstance().apply { strength = Collator.PRIMARY }
private val punctRegex by lazy { Regex("[\\p{Punct}+]") }
// TODO: Consider how you want to handle whitespace and "gaps" in names.
/**
* Plain [Name.Known] implementation that is internationalization-safe.
*
* @author Alexander Capehart (OxygenCobalt)
*/
private data class SimpleKnownName(override val raw: String, override val sort: String?) :
Name.Known() {
override val tokens = listOf(parseToken(sort ?: raw))
private fun parseToken(name: String): Token {
// Remove excess punctuation from the string, as those usually aren't considered in sorting.
val stripped = name.replace(punctRegex, "").trim().ifEmpty { name }
val collationKey = collator.getCollationKey(stripped)
// Always use lexicographic mode since we aren't parsing any numeric components
return Token(collationKey, Token.Type.LEXICOGRAPHIC)
}
}
/**
* [Name.Known] implementation that adds advanced sorting behavior at the cost of localization.
*
* @author Alexander Capehart (OxygenCobalt)
*/
private data class IntelligentKnownName(override val raw: String, override val sort: String?) :
Name.Known() {
override val tokens = parseTokens(sort ?: raw)
private fun parseTokens(name: String): List<Token> {
// TODO: This routine is consuming much of the song building runtime, find a way to
// optimize it
val stripped =
name
// Remove excess punctuation from the string, as those usually aren't
// considered in sorting.
.replace(punctRegex, "")
.ifEmpty { name }
.run {
// Strip any english articles like "the" or "an" from the start, as music
// sorting should ignore such when possible.
when {
length > 4 && startsWith("the ", ignoreCase = true) -> substring(4)
length > 3 && startsWith("an ", ignoreCase = true) -> substring(3)
length > 2 && startsWith("a ", ignoreCase = true) -> substring(2)
else -> this
}
}
// To properly compare numeric components in names, we have to split them up into
// individual lexicographic and numeric tokens and then individually compare them
// with special logic.
return TOKEN_REGEX.findAll(stripped).mapTo(mutableListOf()) { match ->
// Remove excess whitespace where possible
val token = match.value.trim().ifEmpty { match.value }
val collationKey: CollationKey
val type: Token.Type
// Separate each token into their numeric and lexicographic counterparts.
if (token.first().isDigit()) {
// The digit string comparison breaks with preceding zero digits, remove those
val digits =
token.trimStart { Character.getNumericValue(it) == 0 }.ifEmpty { token }
// Other languages have other types of digit strings, still use collation keys
collationKey = collator.getCollationKey(digits)
type = Token.Type.NUMERIC
} else {
collationKey = collator.getCollationKey(token)
type = Token.Type.LEXICOGRAPHIC
}
Token(collationKey, type)
}
}
companion object {
private val TOKEN_REGEX by lazy { Regex("(\\d+)|(\\D+)") }
}
}

View file

@ -73,7 +73,7 @@ data class PreSong(
data class PreAlbum( data class PreAlbum(
val musicBrainzId: UUID?, val musicBrainzId: UUID?,
val name: Name, val name: Name,
val rawName: String, val rawName: String?,
val releaseType: ReleaseType, val releaseType: ReleaseType,
val preArtists: List<PreArtist> val preArtists: List<PreArtist>
) )

View file

@ -18,13 +18,12 @@
package org.oxycblt.musikr.tag.interpret package org.oxycblt.musikr.tag.interpret
import org.oxycblt.auxio.R
import org.oxycblt.musikr.Interpretation import org.oxycblt.musikr.Interpretation
import org.oxycblt.musikr.fs.Format import org.oxycblt.musikr.fs.Format
import org.oxycblt.musikr.fs.query.DeviceFile
import org.oxycblt.musikr.pipeline.RawSong import org.oxycblt.musikr.pipeline.RawSong
import org.oxycblt.musikr.tag.Disc import org.oxycblt.musikr.tag.Disc
import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.Name
import org.oxycblt.musikr.tag.Placeholder
import org.oxycblt.musikr.tag.ReleaseType import org.oxycblt.musikr.tag.ReleaseType
import org.oxycblt.musikr.tag.ReplayGainAdjustment import org.oxycblt.musikr.tag.ReplayGainAdjustment
import org.oxycblt.musikr.tag.parse.ParsedTags import org.oxycblt.musikr.tag.parse.ParsedTags
@ -54,8 +53,7 @@ private data object TagInterpreterImpl : TagInterpreter {
song.tags.albumArtistSortNames, song.tags.albumArtistSortNames,
interpretation) interpretation)
val preAlbum = val preAlbum =
makePreAlbum( makePreAlbum(song.tags, individualPreArtists, albumPreArtists, interpretation)
song.file, song.tags, individualPreArtists, albumPreArtists, interpretation)
val rawArtists = val rawArtists =
individualPreArtists.ifEmpty { albumPreArtists }.ifEmpty { listOf(unknownPreArtist()) } individualPreArtists.ifEmpty { albumPreArtists }.ifEmpty { listOf(unknownPreArtist()) }
val rawGenres = val rawGenres =
@ -70,7 +68,7 @@ private data object TagInterpreterImpl : TagInterpreter {
// TODO: Figure out what to do with date added // TODO: Figure out what to do with date added
dateAdded = song.file.lastModified, dateAdded = song.file.lastModified,
musicBrainzId = song.tags.musicBrainzId?.toUuidOrNull(), musicBrainzId = song.tags.musicBrainzId?.toUuidOrNull(),
name = interpretation.nameFactory.parse(song.tags.name, song.tags.sortName), name = interpretation.naming.name(song.tags.name, song.tags.sortName),
rawName = song.tags.name, rawName = song.tags.name,
track = song.tags.track, track = song.tags.track,
disc = song.tags.disc?.let { Disc(it, song.tags.subtitle) }, disc = song.tags.disc?.let { Disc(it, song.tags.subtitle) },
@ -88,18 +86,17 @@ private data object TagInterpreterImpl : TagInterpreter {
} }
private fun makePreAlbum( private fun makePreAlbum(
file: DeviceFile,
parsedTags: ParsedTags, parsedTags: ParsedTags,
individualPreArtists: List<PreArtist>, individualPreArtists: List<PreArtist>,
albumPreArtists: List<PreArtist>, albumPreArtists: List<PreArtist>,
interpretation: Interpretation interpretation: Interpretation
): PreAlbum { ): PreAlbum {
// TODO: Make fallbacks for this!
val rawAlbumName = requireNotNull(parsedTags.albumName)
return PreAlbum( return PreAlbum(
musicBrainzId = parsedTags.albumMusicBrainzId?.toUuidOrNull(), musicBrainzId = parsedTags.albumMusicBrainzId?.toUuidOrNull(),
name = interpretation.nameFactory.parse(rawAlbumName, parsedTags.albumSortName), name =
rawName = rawAlbumName, interpretation.naming.name(
parsedTags.albumName, parsedTags.albumSortName, Placeholder.ALBUM),
rawName = parsedTags.albumName,
releaseType = releaseType =
ReleaseType.parse(interpretation.separators.split(parsedTags.releaseTypes)) ReleaseType.parse(interpretation.separators.split(parsedTags.releaseTypes))
?: ReleaseType.Album(null), ?: ReleaseType.Album(null),
@ -129,14 +126,12 @@ private data object TagInterpreterImpl : TagInterpreter {
sortName: String?, sortName: String?,
interpretation: Interpretation interpretation: Interpretation
): PreArtist { ): PreArtist {
val name = val name = interpretation.naming.name(rawName, null, Placeholder.ARTIST)
rawName?.let { interpretation.nameFactory.parse(it, sortName) }
?: Name.Unknown(R.string.def_artist)
val musicBrainzId = musicBrainzId?.toUuidOrNull() val musicBrainzId = musicBrainzId?.toUuidOrNull()
return PreArtist(musicBrainzId, name, rawName) return PreArtist(musicBrainzId, name, rawName)
} }
private fun unknownPreArtist() = PreArtist(null, Name.Unknown(R.string.def_artist), null) private fun unknownPreArtist() = PreArtist(null, Name.Unknown(Placeholder.GENRE), null)
private fun makePreGenres( private fun makePreGenres(
parsedTags: ParsedTags, parsedTags: ParsedTags,
@ -149,10 +144,7 @@ private data object TagInterpreterImpl : TagInterpreter {
} }
private fun makePreGenre(rawName: String?, interpretation: Interpretation) = private fun makePreGenre(rawName: String?, interpretation: Interpretation) =
PreGenre( PreGenre(interpretation.naming.name(rawName, null, Placeholder.GENRE), rawName)
rawName?.let { interpretation.nameFactory.parse(it, null) }
?: Name.Unknown(R.string.def_genre),
rawName)
private fun unknownPreGenre() = PreGenre(Name.Unknown(R.string.def_genre), null) private fun unknownPreGenre() = PreGenre(Name.Unknown(Placeholder.GENRE), null)
} }

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024 Auxio Project
* Token.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.musikr.tag.interpret
import java.text.CollationKey
/** An individual part of a name string that can be compared intelligently. */
data class Token(val collationKey: CollationKey, val type: Type) : Comparable<Token> {
override fun compareTo(other: Token): Int {
// Numeric tokens should always be lower than lexicographic tokens.
val modeComp = type.compareTo(other.type)
if (modeComp != 0) {
return modeComp
}
// Numeric strings must be ordered by magnitude, thus immediately short-circuit
// the comparison if the lengths do not match.
if (type == Type.NUMERIC &&
collationKey.sourceString.length != other.collationKey.sourceString.length) {
return collationKey.sourceString.length - other.collationKey.sourceString.length
}
return collationKey.compareTo(other.collationKey)
}
/** Denotes the type of comparison to be performed with this token. */
enum class Type {
/** Compare as a digit string, like "65". */
NUMERIC,
/** Compare as a standard alphanumeric string, like "65daysofstatic" */
LEXICOGRAPHIC
}
}

View file

@ -20,9 +20,9 @@ package org.oxycblt.musikr.util
import java.security.MessageDigest import java.security.MessageDigest
import java.util.UUID import java.util.UUID
import kotlin.reflect.KClass
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.musikr.tag.Date import org.oxycblt.musikr.tag.Date
import kotlin.reflect.KClass
/** /**
* Sanitizes a value that is unlikely to be null. On debug builds, this aliases to [requireNotNull], * Sanitizes a value that is unlikely to be null. On debug builds, this aliases to [requireNotNull],

View file

@ -361,6 +361,7 @@
<!-- Default Namespace | Placeholder values --> <!-- Default Namespace | Placeholder values -->
<eat-comment /> <eat-comment />
<string name="def_album">Unknown album</string>
<string name="def_artist">Unknown artist</string> <string name="def_artist">Unknown artist</string>
<string name="def_genre">Unknown genre</string> <string name="def_genre">Unknown genre</string>
<string name="def_date">No date</string> <string name="def_date">No date</string>

View file

@ -31,7 +31,7 @@ class NameTest {
assertEquals("L", name.thumb) assertEquals("L", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("Loveless", only.collationKey.sourceString) assertEquals("Loveless", only.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
@ -42,7 +42,7 @@ class NameTest {
assertEquals("A", name.thumb) assertEquals("A", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("altJ", only.collationKey.sourceString) assertEquals("altJ", only.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
@ -53,7 +53,7 @@ class NameTest {
assertEquals("!", name.thumb) assertEquals("!", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("!!!", only.collationKey.sourceString) assertEquals("!!!", only.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
@ -64,7 +64,7 @@ class NameTest {
assertEquals("Y", name.thumb) assertEquals("Y", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Yet Yet", first.collationKey.sourceString) assertEquals("Yet Yet", first.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
} }
@Test @Test
@ -75,7 +75,7 @@ class NameTest {
assertEquals("S", name.thumb) assertEquals("S", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("Smile", only.collationKey.sourceString) assertEquals("Smile", only.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
@ -86,7 +86,7 @@ class NameTest {
assertEquals("L", name.thumb) assertEquals("L", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("Loveless", only.collationKey.sourceString) assertEquals("Loveless", only.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
@ -97,10 +97,10 @@ class NameTest {
assertEquals("#", name.thumb) assertEquals("#", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("15", first.collationKey.sourceString) assertEquals("15", first.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, first.type) assertEquals(Token.Type.NUMERIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("Step", second.collationKey.sourceString) assertEquals("Step", second.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type) assertEquals(Token.Type.LEXICOGRAPHIC, second.type)
} }
@Test @Test
@ -111,10 +111,10 @@ class NameTest {
assertEquals("#", name.thumb) assertEquals("#", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("23", first.collationKey.sourceString) assertEquals("23", first.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, first.type) assertEquals(Token.Type.NUMERIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("Kid", second.collationKey.sourceString) assertEquals("Kid", second.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type) assertEquals(Token.Type.LEXICOGRAPHIC, second.type)
} }
@Test @Test
@ -125,19 +125,19 @@ class NameTest {
assertEquals("F", name.thumb) assertEquals("F", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Foo", first.collationKey.sourceString) assertEquals("Foo", first.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("1", second.collationKey.sourceString) assertEquals("1", second.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, second.type) assertEquals(Token.Type.NUMERIC, second.type)
val third = name.sortTokens[2] val third = name.sortTokens[2]
assertEquals(" ", third.collationKey.sourceString) assertEquals(" ", third.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type) assertEquals(Token.Type.LEXICOGRAPHIC, third.type)
val fourth = name.sortTokens[3] val fourth = name.sortTokens[3]
assertEquals("2", fourth.collationKey.sourceString) assertEquals("2", fourth.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, fourth.type) assertEquals(Token.Type.NUMERIC, fourth.type)
val fifth = name.sortTokens[4] val fifth = name.sortTokens[4]
assertEquals("Bar", fifth.collationKey.sourceString) assertEquals("Bar", fifth.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, fifth.type) assertEquals(Token.Type.LEXICOGRAPHIC, fifth.type)
} }
@Test @Test
@ -148,13 +148,13 @@ class NameTest {
assertEquals("F", name.thumb) assertEquals("F", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Foo", first.collationKey.sourceString) assertEquals("Foo", first.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("12", second.collationKey.sourceString) assertEquals("12", second.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, second.type) assertEquals(Token.Type.NUMERIC, second.type)
val third = name.sortTokens[2] val third = name.sortTokens[2]
assertEquals("Bar", third.collationKey.sourceString) assertEquals("Bar", third.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type) assertEquals(Token.Type.LEXICOGRAPHIC, third.type)
} }
@Test @Test
@ -165,10 +165,10 @@ class NameTest {
assertEquals("F", name.thumb) assertEquals("F", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Foo", first.collationKey.sourceString) assertEquals("Foo", first.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("1", second.collationKey.sourceString) assertEquals("1", second.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, second.type) assertEquals(Token.Type.NUMERIC, second.type)
} }
@Test @Test
@ -179,10 +179,10 @@ class NameTest {
assertEquals("E", name.thumb) assertEquals("E", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Error", first.collationKey.sourceString) assertEquals("Error", first.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("404", second.collationKey.sourceString) assertEquals("404", second.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, second.type) assertEquals(Token.Type.NUMERIC, second.type)
} }
@Test @Test
@ -193,7 +193,7 @@ class NameTest {
assertEquals("N", name.thumb) assertEquals("N", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("National Anthem", first.collationKey.sourceString) assertEquals("National Anthem", first.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
} }
@Test @Test
@ -204,7 +204,7 @@ class NameTest {
assertEquals("E", name.thumb) assertEquals("E", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Eagle in Your Mind", first.collationKey.sourceString) assertEquals("Eagle in Your Mind", first.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
} }
@Test @Test
@ -215,7 +215,7 @@ class NameTest {
assertEquals("S", name.thumb) assertEquals("S", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Song For Our Fathers", first.collationKey.sourceString) assertEquals("Song For Our Fathers", first.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
} }
@Test @Test
@ -226,7 +226,7 @@ class NameTest {
assertEquals("A", name.thumb) assertEquals("A", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("altJ", only.collationKey.sourceString) assertEquals("altJ", only.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
@ -237,7 +237,7 @@ class NameTest {
assertEquals("!", name.thumb) assertEquals("!", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("!!!", only.collationKey.sourceString) assertEquals("!!!", only.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test
@ -248,7 +248,7 @@ class NameTest {
assertEquals("#", name.thumb) assertEquals("#", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("1", first.collationKey.sourceString) assertEquals("1", first.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, first.type) assertEquals(Token.Type.NUMERIC, first.type)
} }
@Test @Test
@ -259,7 +259,7 @@ class NameTest {
assertEquals("Y", name.thumb) assertEquals("Y", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Yet Yet", first.collationKey.sourceString) assertEquals("Yet Yet", first.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
} }
@Test @Test
@ -270,16 +270,16 @@ class NameTest {
assertEquals("D", name.thumb) assertEquals("D", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("Design", first.collationKey.sourceString) assertEquals("Design", first.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type) assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals("2", second.collationKey.sourceString) assertEquals("2", second.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, second.type) assertEquals(Token.Type.NUMERIC, second.type)
val third = name.sortTokens[2] val third = name.sortTokens[2]
assertEquals(" ", third.collationKey.sourceString) assertEquals(" ", third.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type) assertEquals(Token.Type.LEXICOGRAPHIC, third.type)
val fourth = name.sortTokens[3] val fourth = name.sortTokens[3]
assertEquals("3", fourth.collationKey.sourceString) assertEquals("3", fourth.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, fourth.type) assertEquals(Token.Type.NUMERIC, fourth.type)
} }
@Test @Test
@ -290,19 +290,19 @@ class NameTest {
assertEquals("#", name.thumb) assertEquals("#", name.thumb)
val first = name.sortTokens[0] val first = name.sortTokens[0]
assertEquals("2", first.collationKey.sourceString) assertEquals("2", first.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, first.type) assertEquals(Token.Type.NUMERIC, first.type)
val second = name.sortTokens[1] val second = name.sortTokens[1]
assertEquals(" ", second.collationKey.sourceString) assertEquals(" ", second.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type) assertEquals(Token.Type.LEXICOGRAPHIC, second.type)
val third = name.sortTokens[2] val third = name.sortTokens[2]
assertEquals("2", third.collationKey.sourceString) assertEquals("2", third.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, third.type) assertEquals(Token.Type.NUMERIC, third.type)
val fourth = name.sortTokens[3] val fourth = name.sortTokens[3]
assertEquals(" ", fourth.collationKey.sourceString) assertEquals(" ", fourth.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, fourth.type) assertEquals(Token.Type.LEXICOGRAPHIC, fourth.type)
val fifth = name.sortTokens[4] val fifth = name.sortTokens[4]
assertEquals("5", fifth.collationKey.sourceString) assertEquals("5", fifth.collationKey.sourceString)
assertEquals(SortToken.Type.NUMERIC, fifth.type) assertEquals(Token.Type.NUMERIC, fifth.type)
} }
@Test @Test
@ -313,7 +313,7 @@ class NameTest {
assertEquals("S", name.thumb) assertEquals("S", name.thumb)
val only = name.sortTokens.single() val only = name.sortTokens.single()
assertEquals("Smile", only.collationKey.sourceString) assertEquals("Smile", only.collationKey.sourceString)
assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type) assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
} }
@Test @Test