music: make uids use music mode

Make UID structure dependent on the music mode.

This involves replacing the "tag" value with more structured fields and
appending the int code of the music mode to the UUID rather than making
it part of the UID's "tag". UIDs aren't quite a UUID, so this is
allowed.
This commit is contained in:
Alexander Capehart 2022-09-10 20:30:47 -06:00
parent c342fb364b
commit cdd4d5ebcd
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
23 changed files with 129 additions and 66 deletions

View file

@ -37,7 +37,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item

View file

@ -35,7 +35,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item

View file

@ -38,7 +38,7 @@ import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.ReleaseType
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.Item

View file

@ -36,7 +36,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item

View file

@ -20,6 +20,8 @@ package org.oxycblt.auxio.home
import android.content.Context
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.util.logD
/**
@ -36,18 +38,40 @@ class AdaptiveTabStrategy(context: Context, private val homeModel: HomeViewModel
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
val tabMode = homeModel.tabs[position]
val icon: Int
val string: Int
when (tabMode) {
MusicMode.SONGS -> {
icon = R.drawable.ic_song_24
string = R.string.lbl_songs
}
MusicMode.ALBUMS -> {
icon = R.drawable.ic_album_24
string = R.string.lbl_albums
}
MusicMode.ARTISTS -> {
icon = R.drawable.ic_artist_24
string = R.string.lbl_artists
}
MusicMode.GENRES -> {
icon = R.drawable.ic_genre_24
string = R.string.lbl_genres
}
}
when {
width < 370 -> {
logD("Using icon-only configuration")
tab.setIcon(tabMode.icon).setContentDescription(tabMode.string)
tab.setIcon(icon).setContentDescription(string)
}
width < 600 -> {
logD("Using text-only configuration")
tab.setText(tabMode.string)
tab.setText(string)
}
else -> {
logD("Using icon-and-text configuration")
tab.setIcon(tabMode.icon).setText(tabMode.string)
tab.setIcon(icon).setText(string)
}
}
}

View file

@ -47,11 +47,11 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.system.Indexer
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel

View file

@ -26,10 +26,10 @@ import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.application
import org.oxycblt.auxio.util.logD

View file

@ -24,11 +24,11 @@ import android.view.ViewGroup
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.music.secsToMs
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.ui.recycler.AlbumViewHolder
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item

View file

@ -23,10 +23,10 @@ import android.view.ViewGroup
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item

View file

@ -23,10 +23,10 @@ import android.view.ViewGroup
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.ui.recycler.GenreViewHolder
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item

View file

@ -23,12 +23,12 @@ import android.view.View
import android.view.ViewGroup
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.music.secsToMs
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item

View file

@ -21,7 +21,7 @@ import org.oxycblt.auxio.home.tabs.Tab.Companion.fromSequence
import org.oxycblt.auxio.home.tabs.Tab.Companion.toSequence
import org.oxycblt.auxio.home.tabs.Tab.Invisible
import org.oxycblt.auxio.home.tabs.Tab.Visible
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.util.logE
/**

View file

@ -22,8 +22,9 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemTabBinding
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.ui.recycler.DialogViewHolder
import org.oxycblt.auxio.util.inflater
@ -75,7 +76,14 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) :
binding.root.setOnClickListener { listener.onVisibilityToggled(item.mode) }
binding.tabIcon.apply {
setText(item.mode.string)
setText(
when (item.mode) {
MusicMode.SONGS -> R.string.lbl_songs
MusicMode.ALBUMS -> R.string.lbl_albums
MusicMode.ARTISTS -> R.string.lbl_artists
MusicMode.GENRES -> R.string.lbl_genres
}
)
isChecked = item is Tab.Visible
}

View file

@ -25,7 +25,7 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogTabsBinding
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
import org.oxycblt.auxio.util.context

View file

@ -34,7 +34,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.ui.Sort
import kotlin.math.min
/** A basic keyer for music data. */

View file

@ -26,8 +26,11 @@ import kotlinx.parcelize.Parcelize
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Date.Companion.from
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.inRangeOrNull
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.nonZeroOrNull
import org.oxycblt.auxio.util.unlikelyToBeNull
import java.security.MessageDigest
@ -36,7 +39,6 @@ import java.text.Collator
import java.util.UUID
import kotlin.math.max
import kotlin.math.min
import kotlin.reflect.KClass
// --- MUSIC MODELS ---
@ -98,17 +100,46 @@ sealed class Music : Item {
* @author OxygenCobalt
*/
@Parcelize
class UID private constructor(private val tag: String, private val uuid: UUID) : Parcelable {
class UID private constructor(
private val isMusicBrainz: Boolean,
private val mode: MusicMode,
private val uuid: UUID
) : Parcelable {
// Cache the hashCode for speed
@IgnoredOnParcel private val hashCode = 31 * tag.hashCode() + uuid.hashCode()
@IgnoredOnParcel private val hashCode: Int
init {
var result = isMusicBrainz.hashCode()
result = 31 * result + mode.hashCode()
result = 31 * result + uuid.hashCode()
hashCode = result
logD(this)
}
override fun hashCode() = hashCode
override fun equals(other: Any?) = other is UID && tag == other.tag && uuid == other.uuid
override fun equals(other: Any?) = other is UID &&
isMusicBrainz == other.isMusicBrainz &&
mode == other.mode && uuid == other.uuid
override fun toString() = "$tag:$uuid"
override fun toString(): String {
// Format comes first, delimited by a ":".
val format = if (isMusicBrainz) {
FORMAT_MUSICBRAINZ
} else {
FORMAT_AUXIO
}
// Instead of making new string values for the mode, just append it's intCode
// in front of the UUID. So I guess it's technically not a true UUID, but whatever.
return "$format:${mode.intCode.toString(16)}-$uuid"
}
companion object {
private const val FORMAT_AUXIO = "org.oxycblt.auxio"
private const val FORMAT_MUSICBRAINZ = "org.musicbrainz"
/** Parse a [UID] from the string [uid]. Returns null if not valid. */
fun fromString(uid: String): UID? {
val split = uid.split(':', limit = 2)
@ -116,7 +147,21 @@ sealed class Music : Item {
return null
}
return UID(tag = split[0], split[1].toUuid() ?: return null)
val isMusicBrainz = when (split[0]) {
FORMAT_MUSICBRAINZ -> true
FORMAT_AUXIO -> false
else -> return null
}
val ids = split[1].split('-', limit = 2)
if (ids.size != 2) {
return null
}
val mode = MusicMode.fromInt(ids[0].toIntOrNull(16) ?: return null) ?: return null
val uuid = UUID.fromString(ids[1])
return UID(isMusicBrainz, mode, uuid)
}
/**
@ -124,15 +169,14 @@ sealed class Music : Item {
*
* This is Auxio's UID format.
*/
fun hashed(clazz: KClass<*>, updates: MessageDigest.() -> Unit): UID {
fun hashed(mode: MusicMode, updates: MessageDigest.() -> Unit): UID {
// Auxio hashes consist of the MD5 hash of the non-subjective, consistent
// tags in a music item. For easier use with MusicBrainz IDs, we transform
// this into a UUID too.
val digest = MessageDigest.getInstance("MD5")
updates(digest)
val uuid = digest.digest().toUuid()
val tag = "auxio.${unlikelyToBeNull(clazz.simpleName).lowercase()}"
return UID(tag, uuid)
return UID(false, mode, uuid)
}
}
}
@ -276,7 +320,7 @@ class Song constructor(raw: Raw) : Music() {
// Generally, we calculate UIDs at the end since everything will definitely be initialized
// by now.
uid =
UID.hashed(this::class) {
UID.hashed(MusicMode.SONGS) {
update(rawName)
update(_rawAlbum.name)
update(_rawAlbum.date)
@ -368,7 +412,7 @@ class Album constructor(raw: Raw, override val songs: List<Song>) : MusicParent(
init {
uid =
UID.hashed(this::class) {
UID.hashed(MusicMode.ALBUMS) {
update(rawName)
update(_rawArtist.name)
update(date)
@ -420,7 +464,7 @@ constructor(
val durationMs = songs.sumOf { it.durationMs }
init {
uid = UID.hashed(this::class) { update(rawName) }
uid = UID.hashed(MusicMode.ARTISTS) { update(rawName) }
for (album in albums) {
album._link(this)
@ -460,7 +504,7 @@ class Genre constructor(raw: Raw, override val songs: List<Song>) : MusicParent(
val durationMs = songs.sumOf { it.durationMs }
init {
uid = UID.hashed(this::class) { update(rawName) }
uid = UID.hashed(MusicMode.GENRES) { update(rawName) }
for (song in songs) {
song._link(this)

View file

@ -32,12 +32,12 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.extractor.Api21MediaStoreLayer
import org.oxycblt.auxio.music.extractor.Api29MediaStoreLayer
import org.oxycblt.auxio.music.extractor.Api30MediaStoreLayer
import org.oxycblt.auxio.music.extractor.CacheLayer
import org.oxycblt.auxio.music.extractor.MetadataLayer
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.logW

View file

@ -15,10 +15,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music
package org.oxycblt.auxio.music.ui
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
enum class MusicMode {
SONGS,
@ -26,24 +25,6 @@ enum class MusicMode {
ARTISTS,
GENRES;
val string: Int
get() =
when (this) {
SONGS -> R.string.lbl_songs
ALBUMS -> R.string.lbl_albums
ARTISTS -> R.string.lbl_artists
GENRES -> R.string.lbl_genres
}
val icon: Int
get() =
when (this) {
SONGS -> R.drawable.ic_song_24
ALBUMS -> R.drawable.ic_album_24
ARTISTS -> R.drawable.ic_artist_24
GENRES -> R.drawable.ic_genre_24
}
val intCode: Int
get() =
when (this) {

View file

@ -15,12 +15,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music
package org.oxycblt.auxio.music.ui
import androidx.annotation.IdRes
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Sort.Mode
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Date
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.ui.Sort.Mode
/**
* Represents the sort modes used in Auxio.

View file

@ -28,11 +28,11 @@ import kotlinx.coroutines.launch
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.dsToMs
import org.oxycblt.auxio.music.msToDs
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.playback.state.InternalPlayer
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
import org.oxycblt.auxio.playback.state.PlaybackStateManager

View file

@ -35,9 +35,9 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item

View file

@ -33,10 +33,10 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.Item

View file

@ -28,9 +28,9 @@ import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.dirs.MusicDirs
import org.oxycblt.auxio.music.ui.MusicMode
import org.oxycblt.auxio.music.ui.Sort
import org.oxycblt.auxio.playback.BarAction
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp