diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
index 2c378e158..4e03c910f 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
@@ -31,6 +31,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.playback.formatDurationMs
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
index ba66f34ee..533a1cd0f 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
@@ -31,6 +31,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.util.collect
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
index 5ec60d085..e94e9f18a 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
@@ -31,6 +31,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt
index d729ffa0e..1ac3e1b8d 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt
@@ -37,6 +37,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.ui.DialogAwareNavigationListener
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt
index cf6c1af92..76dfc2606 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt
@@ -32,6 +32,7 @@ import org.oxycblt.auxio.databinding.DialogSongDetailBinding
import org.oxycblt.auxio.detail.list.SongProperty
import org.oxycblt.auxio.detail.list.SongPropertyAdapter
import org.oxycblt.auxio.list.adapter.UpdateInstructions
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.replaygain.formatDb
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/decision/ArtistShowChoice.kt b/app/src/main/java/org/oxycblt/auxio/detail/decision/ArtistShowChoice.kt
index a1d86fae2..1749a22ab 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/decision/ArtistShowChoice.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/decision/ArtistShowChoice.kt
@@ -25,6 +25,7 @@ import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.DialogRecyclerView
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.Artist
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/list/AlbumDetailListAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/list/AlbumDetailListAdapter.kt
index de7fa331a..a7cc77d15 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/list/AlbumDetailListAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/list/AlbumDetailListAdapter.kt
@@ -35,6 +35,7 @@ import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/list/ArtistDetailListAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/list/ArtistDetailListAdapter.kt
index 5dc948707..79db903f3 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/list/ArtistDetailListAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/list/ArtistDetailListAdapter.kt
@@ -29,6 +29,7 @@ import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.Album
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/list/PlaylistDetailListAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/list/PlaylistDetailListAdapter.kt
index cb939a587..b125ed558 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/list/PlaylistDetailListAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/list/PlaylistDetailListAdapter.kt
@@ -40,6 +40,7 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.MaterialDragCallback
import org.oxycblt.auxio.list.recycler.SongViewHolder
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt
index e283e7fd5..4d97d355f 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt
@@ -105,10 +105,10 @@ class AlbumListFragment :
// Change how we display the popup depending on the current sort mode.
return when (homeModel.albumSort.mode) {
// 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
- 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)
is Sort.Mode.ByDate -> album.dates?.run { min.resolve(requireContext()) }
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt
index ecb1266a3..91cbbe86c 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt
@@ -100,7 +100,7 @@ class ArtistListFragment :
// Change how we display the popup depending on the current sort mode.
return when (homeModel.artistSort.mode) {
// By Name -> Use Name
- is Sort.Mode.ByName -> artist.name.thumb
+ is Sort.Mode.ByName -> artist.name.thumb()
// Duration -> Use formatted duration
is Sort.Mode.ByDuration -> artist.durationMs?.formatDurationMs(false)
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt
index 5d9c472d0..3a6483831 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt
@@ -99,7 +99,7 @@ class GenreListFragment :
// Change how we display the popup depending on the current sort mode.
return when (homeModel.genreSort.mode) {
// By Name -> Use Name
- is Sort.Mode.ByName -> genre.name.thumb
+ is Sort.Mode.ByName -> genre.name.thumb()
// Duration -> Use formatted duration
is Sort.Mode.ByDuration -> genre.durationMs.formatDurationMs(false)
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ListUtil.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ListUtil.kt
new file mode 100644
index 000000000..2e2b1a92d
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/ListUtil.kt
@@ -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 .
+ */
+
+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 -> "?"
+ }
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt
index 80b658812..6b176dc54 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt
@@ -97,7 +97,7 @@ class PlaylistListFragment :
// Change how we display the popup depending on the current sort mode.
return when (homeModel.playlistSort.mode) {
// By Name -> Use Name
- is Sort.Mode.ByName -> playlist.name.thumb
+ is Sort.Mode.ByName -> playlist.name.thumb()
// Duration -> Use formatted duration
is Sort.Mode.ByDuration -> playlist.durationMs.formatDurationMs(false)
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt
index 03d1b91dc..3fd75e808 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt
@@ -105,13 +105,13 @@ class SongListFragment :
// based off the names of the parent objects and not the child objects.
return when (homeModel.songSort.mode) {
// Name -> Use name
- is Sort.Mode.ByName -> song.name.thumb
+ is Sort.Mode.ByName -> song.name.thumb()
// 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
- is Sort.Mode.ByAlbum -> song.album.name.thumb
+ is Sort.Mode.ByAlbum -> song.album.name.thumb()
// Year -> Use Full Year
is Sort.Mode.ByDate -> song.album.dates?.resolveDate(requireContext())
diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt
index e3ee9b213..e847e88bf 100644
--- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt
+++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt
@@ -28,6 +28,7 @@ import org.oxycblt.auxio.databinding.DialogMenuBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.music.MusicViewModel
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs
diff --git a/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt
index 29e5ef101..51c247f85 100644
--- a/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt
+++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt
@@ -33,6 +33,7 @@ import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.music.areNamesTheSame
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getPlural
diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt
index d98a54c45..0ee54e513 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt
@@ -39,7 +39,7 @@ import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.cache.Cache
import org.oxycblt.musikr.cache.CacheDatabase
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 timber.log.Timber as L
@@ -353,9 +353,9 @@ constructor(
val separators = Separators.from(musicSettings.separators)
val nameFactory =
if (musicSettings.intelligentSorting) {
- Name.Known.IntelligentFactory
+ Naming.intelligent()
} else {
- Name.Known.SimpleFactory
+ Naming.simple()
}
val locations = musicSettings.musicLocations
diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt
index 9b316161c..f4ab3a905 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt
@@ -20,8 +20,22 @@ package org.oxycblt.auxio.music
import android.content.Context
import kotlin.math.max
+import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.concatLocalized
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
diff --git a/app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt
index 322c9cd60..1cb30b66f 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt
@@ -29,6 +29,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogDeletePlaylistBinding
import org.oxycblt.auxio.music.MusicViewModel
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.unlikelyToBeNull
diff --git a/app/src/main/java/org/oxycblt/auxio/music/decision/ExportPlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/ExportPlaylistDialog.kt
index 74f40c38a..f1da40031 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/decision/ExportPlaylistDialog.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/decision/ExportPlaylistDialog.kt
@@ -31,6 +31,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogPlaylistExportBinding
import org.oxycblt.auxio.music.MusicViewModel
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.unlikelyToBeNull
diff --git a/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistChoiceAdapter.kt
index b45df2cdf..f40137935 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistChoiceAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistChoiceAdapter.kt
@@ -25,6 +25,7 @@ import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.DialogRecyclerView
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater
diff --git a/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistPickerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistPickerViewModel.kt
index df2c13ee8..799ca1667 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistPickerViewModel.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/decision/PlaylistPickerViewModel.kt
@@ -28,6 +28,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.PlaylistDecision
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song
diff --git a/app/src/main/java/org/oxycblt/auxio/music/decision/RenamePlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/RenamePlaylistDialog.kt
index 8e103671b..bc0da2b65 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/decision/RenamePlaylistDialog.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/decision/RenamePlaylistDialog.kt
@@ -30,6 +30,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogPlaylistNameBinding
import org.oxycblt.auxio.music.MusicViewModel
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.unlikelyToBeNull
diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt b/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt
index a4bf9d5d6..b983d81aa 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt
@@ -27,6 +27,7 @@ import androidx.annotation.StringRes
import androidx.media.utils.MediaConstants
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.formatDurationDs
import org.oxycblt.auxio.util.getPlural
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt
index 36cb21d3f..6e488f6b5 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt
@@ -26,6 +26,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding
import org.oxycblt.auxio.detail.DetailViewModel
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.ui.ViewBindingFragment
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt
index ad918ff63..8df3ed148 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt
@@ -35,6 +35,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ListViewModel
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.playback.ui.StyledSeekBar
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/decision/ArtistPlaybackChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/decision/ArtistPlaybackChoiceAdapter.kt
index 96067a8b8..2d1f785f4 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/decision/ArtistPlaybackChoiceAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/decision/ArtistPlaybackChoiceAdapter.kt
@@ -25,6 +25,7 @@ import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.DialogRecyclerView
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.Artist
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/decision/GenrePlaybackChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/decision/GenrePlaybackChoiceAdapter.kt
index b79614d8f..7eb31fd06 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/decision/GenrePlaybackChoiceAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/decision/GenrePlaybackChoiceAdapter.kt
@@ -25,6 +25,7 @@ import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.DialogRecyclerView
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.Genre
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt
index b4ef78f7e..d1b630c7f 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt
@@ -32,6 +32,7 @@ import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.PlayingIndicatorAdapter
import org.oxycblt.auxio.list.recycler.MaterialDragCallback
import org.oxycblt.auxio.list.recycler.SongViewHolder
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionHolder.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionHolder.kt
index a7ed418e3..0cba44c5c 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionHolder.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionHolder.kt
@@ -36,6 +36,7 @@ import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.image.ImageSettings
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.music.service.MediaSessionUID
import org.oxycblt.auxio.music.service.toMediaDescription
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionInterface.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionInterface.kt
index 0620bd55a..f90db46a8 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionInterface.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionInterface.kt
@@ -30,6 +30,7 @@ import javax.inject.Inject
import org.apache.commons.text.similarity.JaroWinklerSimilarity
import org.oxycblt.auxio.BuildConfig
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.MusicBrowser
import org.oxycblt.auxio.playback.state.PlaybackCommand
diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt
index 5ff40a0c1..c107a5c99 100644
--- a/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt
+++ b/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt
@@ -22,6 +22,7 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import java.text.Normalizer
import javax.inject.Inject
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.musikr.Album
import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Genre
diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
index 9dc85edc8..54976c042 100644
--- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
@@ -30,6 +30,7 @@ import android.view.View
import android.widget.RemoteViews
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.service.PlaybackActions
import org.oxycblt.auxio.playback.state.RepeatMode
diff --git a/app/src/main/java/org/oxycblt/musikr/Config.kt b/app/src/main/java/org/oxycblt/musikr/Config.kt
index 741b29cfc..3885eb54c 100644
--- a/app/src/main/java/org/oxycblt/musikr/Config.kt
+++ b/app/src/main/java/org/oxycblt/musikr/Config.kt
@@ -20,9 +20,9 @@ package org.oxycblt.musikr
import org.oxycblt.musikr.cache.Cache
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
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)
diff --git a/app/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt b/app/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt
index b5dfea7ab..99df6f3e1 100644
--- a/app/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt
+++ b/app/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt
@@ -115,7 +115,7 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder {
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) {
simplifyAlbumCluster(cluster)
}
diff --git a/app/src/main/java/org/oxycblt/musikr/playlist/m3u/M3U.kt b/app/src/main/java/org/oxycblt/musikr/playlist/m3u/M3U.kt
index 2967d90fd..d11fc8f99 100644
--- a/app/src/main/java/org/oxycblt/musikr/playlist/m3u/M3U.kt
+++ b/app/src/main/java/org/oxycblt/musikr/playlist/m3u/M3U.kt
@@ -25,6 +25,7 @@ import java.io.BufferedWriter
import java.io.InputStream
import java.io.InputStreamReader
import java.io.OutputStream
+import org.oxycblt.auxio.music.resolve
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.fs.Components
diff --git a/app/src/main/java/org/oxycblt/musikr/tag/Name.kt b/app/src/main/java/org/oxycblt/musikr/tag/Name.kt
index 2b021be57..ee852d3da 100644
--- a/app/src/main/java/org/oxycblt/musikr/tag/Name.kt
+++ b/app/src/main/java/org/oxycblt/musikr/tag/Name.kt
@@ -18,11 +18,7 @@
package org.oxycblt.musikr.tag
-import android.content.Context
-import androidx.annotation.StringRes
-import androidx.annotation.VisibleForTesting
-import java.text.CollationKey
-import java.text.Collator
+import org.oxycblt.musikr.tag.interpret.Token
/**
* The name of a music item.
@@ -32,71 +28,26 @@ import java.text.Collator
* @author Alexander Capehart
*/
sealed interface Name : Comparable {
- /**
- * 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. */
- sealed class Known : Name {
+ abstract class Known : Name {
/** The raw name string obtained. Should be ignored in favor of [resolve]. */
abstract val raw: String
/** The raw sort name string obtained. */
abstract val sort: String?
-
/** A tokenized version of the name that will be compared. */
- @VisibleForTesting(VisibleForTesting.PROTECTED) abstract val sortTokens: List
-
- 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
+ abstract val tokens: List
final override fun compareTo(other: Name) =
when (other) {
is Known -> {
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)
}
- 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
}
-
- 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 {
*
* @author Alexander Capehart
*/
- data class Unknown(@StringRes val stringRes: Int) : Name {
- override val thumb = "?"
-
- override fun resolve(context: Context) = context.getString(stringRes)
-
+ data class Unknown(val placeholder: Placeholder) : Name {
override fun compareTo(other: Name) =
when (other) {
// Unknown names do not need any direct comparison right now.
@@ -119,111 +66,8 @@ sealed interface Name : Comparable {
}
}
-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)
- */
-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 {
- // 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 {
- 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
- }
+enum class Placeholder {
+ ALBUM,
+ ARTIST,
+ GENRE
}
diff --git a/app/src/main/java/org/oxycblt/musikr/tag/interpret/Naming.kt b/app/src/main/java/org/oxycblt/musikr/tag/interpret/Naming.kt
new file mode 100644
index 000000000..79300de45
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/musikr/tag/interpret/Naming.kt
@@ -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 .
+ */
+
+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 {
+ // 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+)") }
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt b/app/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt
index 0cbd2ad11..832668d6a 100644
--- a/app/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt
+++ b/app/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt
@@ -73,7 +73,7 @@ data class PreSong(
data class PreAlbum(
val musicBrainzId: UUID?,
val name: Name,
- val rawName: String,
+ val rawName: String?,
val releaseType: ReleaseType,
val preArtists: List
)
diff --git a/app/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt b/app/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt
index 997fe9310..de1e67abe 100644
--- a/app/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt
+++ b/app/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt
@@ -18,13 +18,12 @@
package org.oxycblt.musikr.tag.interpret
-import org.oxycblt.auxio.R
import org.oxycblt.musikr.Interpretation
import org.oxycblt.musikr.fs.Format
-import org.oxycblt.musikr.fs.query.DeviceFile
import org.oxycblt.musikr.pipeline.RawSong
import org.oxycblt.musikr.tag.Disc
import org.oxycblt.musikr.tag.Name
+import org.oxycblt.musikr.tag.Placeholder
import org.oxycblt.musikr.tag.ReleaseType
import org.oxycblt.musikr.tag.ReplayGainAdjustment
import org.oxycblt.musikr.tag.parse.ParsedTags
@@ -54,8 +53,7 @@ private data object TagInterpreterImpl : TagInterpreter {
song.tags.albumArtistSortNames,
interpretation)
val preAlbum =
- makePreAlbum(
- song.file, song.tags, individualPreArtists, albumPreArtists, interpretation)
+ makePreAlbum(song.tags, individualPreArtists, albumPreArtists, interpretation)
val rawArtists =
individualPreArtists.ifEmpty { albumPreArtists }.ifEmpty { listOf(unknownPreArtist()) }
val rawGenres =
@@ -70,7 +68,7 @@ private data object TagInterpreterImpl : TagInterpreter {
// TODO: Figure out what to do with date added
dateAdded = song.file.lastModified,
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,
track = song.tags.track,
disc = song.tags.disc?.let { Disc(it, song.tags.subtitle) },
@@ -88,18 +86,17 @@ private data object TagInterpreterImpl : TagInterpreter {
}
private fun makePreAlbum(
- file: DeviceFile,
parsedTags: ParsedTags,
individualPreArtists: List,
albumPreArtists: List,
interpretation: Interpretation
): PreAlbum {
- // TODO: Make fallbacks for this!
- val rawAlbumName = requireNotNull(parsedTags.albumName)
return PreAlbum(
musicBrainzId = parsedTags.albumMusicBrainzId?.toUuidOrNull(),
- name = interpretation.nameFactory.parse(rawAlbumName, parsedTags.albumSortName),
- rawName = rawAlbumName,
+ name =
+ interpretation.naming.name(
+ parsedTags.albumName, parsedTags.albumSortName, Placeholder.ALBUM),
+ rawName = parsedTags.albumName,
releaseType =
ReleaseType.parse(interpretation.separators.split(parsedTags.releaseTypes))
?: ReleaseType.Album(null),
@@ -129,14 +126,12 @@ private data object TagInterpreterImpl : TagInterpreter {
sortName: String?,
interpretation: Interpretation
): PreArtist {
- val name =
- rawName?.let { interpretation.nameFactory.parse(it, sortName) }
- ?: Name.Unknown(R.string.def_artist)
+ val name = interpretation.naming.name(rawName, null, Placeholder.ARTIST)
val musicBrainzId = musicBrainzId?.toUuidOrNull()
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(
parsedTags: ParsedTags,
@@ -149,10 +144,7 @@ private data object TagInterpreterImpl : TagInterpreter {
}
private fun makePreGenre(rawName: String?, interpretation: Interpretation) =
- PreGenre(
- rawName?.let { interpretation.nameFactory.parse(it, null) }
- ?: Name.Unknown(R.string.def_genre),
- rawName)
+ PreGenre(interpretation.naming.name(rawName, null, Placeholder.GENRE), rawName)
- private fun unknownPreGenre() = PreGenre(Name.Unknown(R.string.def_genre), null)
+ private fun unknownPreGenre() = PreGenre(Name.Unknown(Placeholder.GENRE), null)
}
diff --git a/app/src/main/java/org/oxycblt/musikr/tag/interpret/Token.kt b/app/src/main/java/org/oxycblt/musikr/tag/interpret/Token.kt
new file mode 100644
index 000000000..749b6c5e5
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/musikr/tag/interpret/Token.kt
@@ -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 .
+ */
+
+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 {
+ 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
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/musikr/util/LangUtil.kt b/app/src/main/java/org/oxycblt/musikr/util/LangUtil.kt
index 3ebd60c6c..dc804164f 100644
--- a/app/src/main/java/org/oxycblt/musikr/util/LangUtil.kt
+++ b/app/src/main/java/org/oxycblt/musikr/util/LangUtil.kt
@@ -20,9 +20,9 @@ package org.oxycblt.musikr.util
import java.security.MessageDigest
import java.util.UUID
+import kotlin.reflect.KClass
import org.oxycblt.auxio.BuildConfig
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],
@@ -130,4 +130,4 @@ fun lazyReflectedMethod(clazz: KClass<*>, method: String, vararg params: KClass<
clazz.java.getDeclaredMethod(method, *params.map { it.java }.toTypedArray()).also {
it.isAccessible = true
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 21ecb31a3..8ce492580 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -361,6 +361,7 @@
+ Unknown album
Unknown artist
Unknown genre
No date
diff --git a/app/src/test/java/org/oxycblt/musikr/tag/NameTest.kt b/app/src/test/java/org/oxycblt/musikr/tag/NameTest.kt
index d3c2e7bec..044c762f1 100644
--- a/app/src/test/java/org/oxycblt/musikr/tag/NameTest.kt
+++ b/app/src/test/java/org/oxycblt/musikr/tag/NameTest.kt
@@ -31,7 +31,7 @@ class NameTest {
assertEquals("L", name.thumb)
val only = name.sortTokens.single()
assertEquals("Loveless", only.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
}
@Test
@@ -42,7 +42,7 @@ class NameTest {
assertEquals("A", name.thumb)
val only = name.sortTokens.single()
assertEquals("altJ", only.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
}
@Test
@@ -53,7 +53,7 @@ class NameTest {
assertEquals("!", name.thumb)
val only = name.sortTokens.single()
assertEquals("!!!", only.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
}
@Test
@@ -64,7 +64,7 @@ class NameTest {
assertEquals("Y", name.thumb)
val first = name.sortTokens[0]
assertEquals("Yet Yet", first.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
}
@Test
@@ -75,7 +75,7 @@ class NameTest {
assertEquals("S", name.thumb)
val only = name.sortTokens.single()
assertEquals("Smile", only.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
}
@Test
@@ -86,7 +86,7 @@ class NameTest {
assertEquals("L", name.thumb)
val only = name.sortTokens.single()
assertEquals("Loveless", only.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
}
@Test
@@ -97,10 +97,10 @@ class NameTest {
assertEquals("#", name.thumb)
val first = name.sortTokens[0]
assertEquals("15", first.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, first.type)
+ assertEquals(Token.Type.NUMERIC, first.type)
val second = name.sortTokens[1]
assertEquals("Step", second.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, second.type)
}
@Test
@@ -111,10 +111,10 @@ class NameTest {
assertEquals("#", name.thumb)
val first = name.sortTokens[0]
assertEquals("23", first.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, first.type)
+ assertEquals(Token.Type.NUMERIC, first.type)
val second = name.sortTokens[1]
assertEquals("Kid", second.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, second.type)
}
@Test
@@ -125,19 +125,19 @@ class NameTest {
assertEquals("F", name.thumb)
val first = name.sortTokens[0]
assertEquals("Foo", first.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1]
assertEquals("1", second.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, second.type)
+ assertEquals(Token.Type.NUMERIC, second.type)
val third = name.sortTokens[2]
assertEquals(" ", third.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, third.type)
val fourth = name.sortTokens[3]
assertEquals("2", fourth.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, fourth.type)
+ assertEquals(Token.Type.NUMERIC, fourth.type)
val fifth = name.sortTokens[4]
assertEquals("Bar", fifth.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, fifth.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, fifth.type)
}
@Test
@@ -148,13 +148,13 @@ class NameTest {
assertEquals("F", name.thumb)
val first = name.sortTokens[0]
assertEquals("Foo", first.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1]
assertEquals("12", second.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, second.type)
+ assertEquals(Token.Type.NUMERIC, second.type)
val third = name.sortTokens[2]
assertEquals("Bar", third.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, third.type)
}
@Test
@@ -165,10 +165,10 @@ class NameTest {
assertEquals("F", name.thumb)
val first = name.sortTokens[0]
assertEquals("Foo", first.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1]
assertEquals("1", second.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, second.type)
+ assertEquals(Token.Type.NUMERIC, second.type)
}
@Test
@@ -179,10 +179,10 @@ class NameTest {
assertEquals("E", name.thumb)
val first = name.sortTokens[0]
assertEquals("Error", first.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1]
assertEquals("404", second.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, second.type)
+ assertEquals(Token.Type.NUMERIC, second.type)
}
@Test
@@ -193,7 +193,7 @@ class NameTest {
assertEquals("N", name.thumb)
val first = name.sortTokens[0]
assertEquals("National Anthem", first.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
}
@Test
@@ -204,7 +204,7 @@ class NameTest {
assertEquals("E", name.thumb)
val first = name.sortTokens[0]
assertEquals("Eagle in Your Mind", first.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
}
@Test
@@ -215,7 +215,7 @@ class NameTest {
assertEquals("S", name.thumb)
val first = name.sortTokens[0]
assertEquals("Song For Our Fathers", first.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
}
@Test
@@ -226,7 +226,7 @@ class NameTest {
assertEquals("A", name.thumb)
val only = name.sortTokens.single()
assertEquals("altJ", only.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
}
@Test
@@ -237,7 +237,7 @@ class NameTest {
assertEquals("!", name.thumb)
val only = name.sortTokens.single()
assertEquals("!!!", only.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
}
@Test
@@ -248,7 +248,7 @@ class NameTest {
assertEquals("#", name.thumb)
val first = name.sortTokens[0]
assertEquals("1", first.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, first.type)
+ assertEquals(Token.Type.NUMERIC, first.type)
}
@Test
@@ -259,7 +259,7 @@ class NameTest {
assertEquals("Y", name.thumb)
val first = name.sortTokens[0]
assertEquals("Yet Yet", first.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
}
@Test
@@ -270,16 +270,16 @@ class NameTest {
assertEquals("D", name.thumb)
val first = name.sortTokens[0]
assertEquals("Design", first.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, first.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, first.type)
val second = name.sortTokens[1]
assertEquals("2", second.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, second.type)
+ assertEquals(Token.Type.NUMERIC, second.type)
val third = name.sortTokens[2]
assertEquals(" ", third.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, third.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, third.type)
val fourth = name.sortTokens[3]
assertEquals("3", fourth.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, fourth.type)
+ assertEquals(Token.Type.NUMERIC, fourth.type)
}
@Test
@@ -290,19 +290,19 @@ class NameTest {
assertEquals("#", name.thumb)
val first = name.sortTokens[0]
assertEquals("2", first.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, first.type)
+ assertEquals(Token.Type.NUMERIC, first.type)
val second = name.sortTokens[1]
assertEquals(" ", second.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, second.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, second.type)
val third = name.sortTokens[2]
assertEquals("2", third.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, third.type)
+ assertEquals(Token.Type.NUMERIC, third.type)
val fourth = name.sortTokens[3]
assertEquals(" ", fourth.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, fourth.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, fourth.type)
val fifth = name.sortTokens[4]
assertEquals("5", fifth.collationKey.sourceString)
- assertEquals(SortToken.Type.NUMERIC, fifth.type)
+ assertEquals(Token.Type.NUMERIC, fifth.type)
}
@Test
@@ -313,7 +313,7 @@ class NameTest {
assertEquals("S", name.thumb)
val only = name.sortTokens.single()
assertEquals("Smile", only.collationKey.sourceString)
- assertEquals(SortToken.Type.LEXICOGRAPHIC, only.type)
+ assertEquals(Token.Type.LEXICOGRAPHIC, only.type)
}
@Test