musikr: decouple name from auxio
This commit is contained in:
parent
de1c091517
commit
c5cd404393
45 changed files with 330 additions and 241 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()) }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
32
app/src/main/java/org/oxycblt/auxio/home/list/ListUtil.kt
Normal file
32
app/src/main/java/org/oxycblt/auxio/home/list/ListUtil.kt
Normal 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 -> "?"
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
130
app/src/main/java/org/oxycblt/musikr/tag/interpret/Naming.kt
Normal file
130
app/src/main/java/org/oxycblt/musikr/tag/interpret/Naming.kt
Normal 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+)") }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
49
app/src/main/java/org/oxycblt/musikr/tag/interpret/Token.kt
Normal file
49
app/src/main/java/org/oxycblt/musikr/tag/interpret/Token.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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],
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue