detail: reimplement song details

This commit is contained in:
Alexander Capehart 2024-12-17 11:25:55 -05:00
parent 50b7c24c03
commit 6850a3443f
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
6 changed files with 248 additions and 88 deletions

View file

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.detail package org.oxycblt.auxio.detail
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@ -30,6 +30,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.list.DiscDivider import org.oxycblt.auxio.detail.list.DiscDivider
import org.oxycblt.auxio.detail.list.DiscHeader import org.oxycblt.auxio.detail.list.DiscHeader
import org.oxycblt.auxio.detail.list.EditHeader import org.oxycblt.auxio.detail.list.EditHeader
import org.oxycblt.auxio.detail.list.SongProperty
import org.oxycblt.auxio.detail.list.SortHeader import org.oxycblt.auxio.detail.list.SortHeader
import org.oxycblt.auxio.list.BasicHeader import org.oxycblt.auxio.list.BasicHeader
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
@ -42,6 +43,7 @@ import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.playback.PlaySong import org.oxycblt.auxio.playback.PlaySong
import org.oxycblt.auxio.playback.PlaybackSettings import org.oxycblt.auxio.playback.PlaybackSettings
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.Event import org.oxycblt.auxio.util.Event
import org.oxycblt.auxio.util.MutableEvent import org.oxycblt.auxio.util.MutableEvent
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull
@ -70,6 +72,7 @@ constructor(
detailGeneratorFactory: DetailGenerator.Factory detailGeneratorFactory: DetailGenerator.Factory
) : ViewModel(), DetailGenerator.Invalidator { ) : ViewModel(), DetailGenerator.Invalidator {
private val _toShow = MutableEvent<Show>() private val _toShow = MutableEvent<Show>()
/** /**
* A [Show] command that is awaiting a view capable of responding to it. Null if none currently. * A [Show] command that is awaiting a view capable of responding to it. Null if none currently.
*/ */
@ -78,26 +81,34 @@ constructor(
// --- SONG --- // --- SONG ---
private var currentSongJob: Job? = null
private val _currentSong = MutableStateFlow<Song?>(null) private val _currentSong = MutableStateFlow<Song?>(null)
/** The current [Song] to display. Null if there is nothing to show. */ /** The current [Song] to display. Null if there is nothing to show. */
val currentSong: StateFlow<Song?> val currentSong: StateFlow<Song?>
get() = _currentSong get() = _currentSong
private val _currentSongProperties = MutableStateFlow<List<SongProperty>>(listOf())
/** The current properties of [currentSong]. Empty if nothing to show. */
val currentSongProperties: StateFlow<List<SongProperty>>
get() = _currentSongProperties
// --- ALBUM --- // --- ALBUM ---
private val _currentAlbum = MutableStateFlow<Album?>(null) private val _currentAlbum = MutableStateFlow<Album?>(null)
/** The current [Album] to display. Null if there is nothing to show. */ /** The current [Album] to display. Null if there is nothing to show. */
val currentAlbum: StateFlow<Album?> val currentAlbum: StateFlow<Album?>
get() = _currentAlbum get() = _currentAlbum
private val _albumSongList = MutableStateFlow(listOf<Item>()) private val _albumSongList = MutableStateFlow(listOf<Item>())
/** The current list data derived from [currentAlbum]. */ /** The current list data derived from [currentAlbum]. */
val albumSongList: StateFlow<List<Item>> val albumSongList: StateFlow<List<Item>>
get() = _albumSongList get() = _albumSongList
private val _albumSongInstructions = MutableEvent<UpdateInstructions>() private val _albumSongInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [albumSongList] in the UI. */ /** Instructions for updating [albumSongList] in the UI. */
val albumSongInstructions: Event<UpdateInstructions> val albumSongInstructions: Event<UpdateInstructions>
get() = _albumSongInstructions get() = _albumSongInstructions
@ -113,15 +124,18 @@ constructor(
// --- ARTIST --- // --- ARTIST ---
private val _currentArtist = MutableStateFlow<Artist?>(null) private val _currentArtist = MutableStateFlow<Artist?>(null)
/** The current [Artist] to display. Null if there is nothing to show. */ /** The current [Artist] to display. Null if there is nothing to show. */
val currentArtist: StateFlow<Artist?> val currentArtist: StateFlow<Artist?>
get() = _currentArtist get() = _currentArtist
private val _artistSongList = MutableStateFlow(listOf<Item>()) private val _artistSongList = MutableStateFlow(listOf<Item>())
/** The current list derived from [currentArtist]. */ /** The current list derived from [currentArtist]. */
val artistSongList: StateFlow<List<Item>> = _artistSongList val artistSongList: StateFlow<List<Item>> = _artistSongList
private val _artistSongInstructions = MutableEvent<UpdateInstructions>() private val _artistSongInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [artistSongList] in the UI. */ /** Instructions for updating [artistSongList] in the UI. */
val artistSongInstructions: Event<UpdateInstructions> val artistSongInstructions: Event<UpdateInstructions>
get() = _artistSongInstructions get() = _artistSongInstructions
@ -137,15 +151,18 @@ constructor(
// --- GENRE --- // --- GENRE ---
private val _currentGenre = MutableStateFlow<Genre?>(null) private val _currentGenre = MutableStateFlow<Genre?>(null)
/** The current [Genre] to display. Null if there is nothing to show. */ /** The current [Genre] to display. Null if there is nothing to show. */
val currentGenre: StateFlow<Genre?> val currentGenre: StateFlow<Genre?>
get() = _currentGenre get() = _currentGenre
private val _genreSongList = MutableStateFlow(listOf<Item>()) private val _genreSongList = MutableStateFlow(listOf<Item>())
/** The current list data derived from [currentGenre]. */ /** The current list data derived from [currentGenre]. */
val genreSongList: StateFlow<List<Item>> = _genreSongList val genreSongList: StateFlow<List<Item>> = _genreSongList
private val _genreSongInstructions = MutableEvent<UpdateInstructions>() private val _genreSongInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [artistSongList] in the UI. */ /** Instructions for updating [artistSongList] in the UI. */
val genreSongInstructions: Event<UpdateInstructions> val genreSongInstructions: Event<UpdateInstructions>
get() = _genreSongInstructions get() = _genreSongInstructions
@ -161,20 +178,24 @@ constructor(
// --- PLAYLIST --- // --- PLAYLIST ---
private val _currentPlaylist = MutableStateFlow<Playlist?>(null) private val _currentPlaylist = MutableStateFlow<Playlist?>(null)
/** The current [Playlist] to display. Null if there is nothing to do. */ /** The current [Playlist] to display. Null if there is nothing to do. */
val currentPlaylist: StateFlow<Playlist?> val currentPlaylist: StateFlow<Playlist?>
get() = _currentPlaylist get() = _currentPlaylist
private val _playlistSongList = MutableStateFlow(listOf<Item>()) private val _playlistSongList = MutableStateFlow(listOf<Item>())
/** The current list data derived from [currentPlaylist] */ /** The current list data derived from [currentPlaylist] */
val playlistSongList: StateFlow<List<Item>> = _playlistSongList val playlistSongList: StateFlow<List<Item>> = _playlistSongList
private val _playlistSongInstructions = MutableEvent<UpdateInstructions>() private val _playlistSongInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [playlistSongList] in the UI. */ /** Instructions for updating [playlistSongList] in the UI. */
val playlistSongInstructions: Event<UpdateInstructions> val playlistSongInstructions: Event<UpdateInstructions>
get() = _playlistSongInstructions get() = _playlistSongInstructions
private val _editedPlaylist = MutableStateFlow<List<Song>?>(null) private val _editedPlaylist = MutableStateFlow<List<Song>?>(null)
/** /**
* The new playlist songs created during the current editing session. Null if no editing session * The new playlist songs created during the current editing session. Null if no editing session
* is occurring. * is occurring.
@ -204,18 +225,23 @@ constructor(
val album = detailGenerator.album(currentAlbum.value?.uid ?: return) val album = detailGenerator.album(currentAlbum.value?.uid ?: return)
refreshDetail(album, _currentAlbum, _albumSongList, _albumSongInstructions, replace) refreshDetail(album, _currentAlbum, _albumSongList, _albumSongInstructions, replace)
} }
MusicType.ARTISTS -> { MusicType.ARTISTS -> {
val artist = detailGenerator.artist(currentArtist.value?.uid ?: return) val artist = detailGenerator.artist(currentArtist.value?.uid ?: return)
refreshDetail( refreshDetail(
artist, _currentArtist, _artistSongList, _artistSongInstructions, replace) artist, _currentArtist, _artistSongList, _artistSongInstructions, replace
)
} }
MusicType.GENRES -> { MusicType.GENRES -> {
val genre = detailGenerator.genre(currentGenre.value?.uid ?: return) val genre = detailGenerator.genre(currentGenre.value?.uid ?: return)
refreshDetail(genre, _currentGenre, _genreSongList, _genreSongInstructions, replace) refreshDetail(genre, _currentGenre, _genreSongList, _genreSongInstructions, replace)
} }
MusicType.PLAYLISTS -> { MusicType.PLAYLISTS -> {
refreshPlaylist(currentPlaylist.value?.uid ?: return) refreshPlaylist(currentPlaylist.value?.uid ?: return)
} }
else -> error("Unexpected music type $type") else -> error("Unexpected music type $type")
} }
} }
@ -253,7 +279,8 @@ constructor(
Show.SongArtistDecision(song) Show.SongArtistDecision(song)
} else { } else {
Show.ArtistDetails(song.artists.first()) Show.ArtistDetails(song.artists.first())
}) }
)
/** /**
* Navigate to the details of one of the [Artist]s of an [Album] using the corresponding choice * Navigate to the details of one of the [Artist]s of an [Album] using the corresponding choice
@ -267,7 +294,8 @@ constructor(
Show.AlbumArtistDecision(album) Show.AlbumArtistDecision(album)
} else { } else {
Show.ArtistDetails(album.artists.first()) Show.ArtistDetails(album.artists.first())
}) }
)
/** /**
* Navigate to the details of an [Artist]. * Navigate to the details of an [Artist].
@ -499,10 +527,104 @@ constructor(
} else { } else {
L.d("Playlist will be empty after removal, removing header") L.d("Playlist will be empty after removal, removing header")
UpdateInstructions.Remove(at - 1, 3) UpdateInstructions.Remove(at - 1, 3)
}) }
)
} }
private fun refreshAudioInfo(song: Song) {} private fun refreshAudioInfo(song: Song) {
_currentSongProperties.value =
buildList {
add(SongProperty(R.string.lbl_name, SongProperty.Value.MusicName(song)))
add(SongProperty(R.string.lbl_album, SongProperty.Value.MusicName(song.album)))
add(
SongProperty(
R.string.lbl_artists,
SongProperty.Value.MusicNames(song.artists)
)
)
add(
SongProperty(
R.string.lbl_genres,
SongProperty.Value.MusicNames(song.genres)
)
)
song.date?.let {
add(
SongProperty(
R.string.lbl_date,
SongProperty.Value.ItemDate(it)
)
)
}
song.track?.let {
add(
SongProperty(
R.string.lbl_track,
SongProperty.Value.Number(it, null)
)
)
}
song.disc?.let {
add(
SongProperty(
R.string.lbl_disc,
SongProperty.Value.Number(it.number, it.name)
)
)
}
add(
SongProperty(
R.string.lbl_path,
SongProperty.Value.ItemPath(song.path)
)
)
add(
SongProperty(
R.string.lbl_size, SongProperty.Value.Size(song.size)
)
)
add(
SongProperty(
R.string.lbl_duration,
SongProperty.Value.Duration(song.durationMs)
)
)
add(
SongProperty(
R.string.lbl_format,
SongProperty.Value.ItemFormat(song.format)
)
)
add(
SongProperty(
R.string.lbl_bitrate,
SongProperty.Value.Bitrate(song.bitrateKbps)
)
)
add(
SongProperty(
R.string.lbl_sample_rate,
SongProperty.Value.SampleRate(song.sampleRateHz)
)
)
song.replayGainAdjustment.track?.let {
add(
SongProperty(
R.string.lbl_replaygain_track,
SongProperty.Value.Decibels(it)
)
)
}
song.replayGainAdjustment.album?.let {
add(
SongProperty(
R.string.lbl_replaygain_album,
SongProperty.Value.Decibels(it)
)
)
}
}
}
private inline fun <T : MusicParent> refreshDetail( private inline fun <T : MusicParent> refreshDetail(
detail: Detail<T>?, detail: Detail<T>?,
@ -531,6 +653,7 @@ constructor(
newList.add(header) newList.add(header)
section.items section.items
} }
is DetailSection.Discs -> { is DetailSection.Discs -> {
val header = SortHeader(section.stringRes) val header = SortHeader(section.stringRes)
if (newList.isNotEmpty()) { if (newList.isNotEmpty()) {
@ -571,9 +694,10 @@ constructor(
if (edited == null) { if (edited == null) {
val playlist = detailGenerator.playlist(uid) val playlist = detailGenerator.playlist(uid)
refreshDetail( refreshDetail(
playlist, _currentPlaylist, _playlistSongList, _playlistSongInstructions, null) { playlist, _currentPlaylist, _playlistSongList, _playlistSongInstructions, null
EditHeader(it) ) {
} EditHeader(it)
}
return return
} }
val list = mutableListOf<Item>() val list = mutableListOf<Item>()

View file

@ -28,7 +28,9 @@ import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogSongDetailBinding import org.oxycblt.auxio.databinding.DialogSongDetailBinding
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.music.resolve 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
@ -66,84 +68,18 @@ class SongDetailDialog : ViewBindingMaterialDialogFragment<DialogSongDetailBindi
detailModel.setSong(args.songUid) detailModel.setSong(args.songUid)
detailModel.toShow.consume() detailModel.toShow.consume()
collectImmediately(detailModel.currentSong, ::updateSong) collectImmediately(detailModel.currentSong, ::updateSong)
collectImmediately(detailModel.currentSongProperties, ::updateSongProperties)
} }
private fun updateSong(song: Song?) { private fun updateSong(song: Song?) {
// if (song == null) {
L.d("No song to show, navigating away") L.d("No song to show, navigating away")
findNavController().navigateUp() if (song == null) {
return findNavController().navigateUp()
// } return
//
// if (info != null) {
// val context = requireContext()
// detailAdapter.update(
// buildList {
// add(SongProperty(R.string.lbl_name, song.zipName(context)))
// add(SongProperty(R.string.lbl_album, song.album.zipName(context)))
// add(SongProperty(R.string.lbl_artists,
// song.artists.zipNames(context)))
// add(SongProperty(R.string.lbl_genres,
// song.genres.resolveNames(context)))
// song.date?.let { add(SongProperty(R.string.lbl_date,
// it.resolve(context))) }
// song.track?.let {
// add(SongProperty(R.string.lbl_track,
// getString(R.string.fmt_number, it)))
// }
// song.disc?.let {
// val formattedNumber = getString(R.string.fmt_number, it.number)
// val zipped =
// if (it.name != null) {
// getString(R.string.fmt_zipped_names, formattedNumber,
// it.name)
// } else {
// formattedNumber
// }
// add(SongProperty(R.string.lbl_disc, zipped))
// }
// add(SongProperty(R.string.lbl_path, song.path.resolve(context)))
// // info.format.resolveName(context)?.let {
// // add(SongProperty(R.string.lbl_format, it))
// // }
// add(
// SongProperty(
// R.string.lbl_size, Formatter.formatFileSize(context,
// song.size)))
// add(SongProperty(R.string.lbl_duration,
// song.durationMs.formatDurationMs(true)))
// info.bitrateKbps?.let {
// add(SongProperty(R.string.lbl_bitrate,
// getString(R.string.fmt_bitrate, it)))
// }
// info.sampleRateHz?.let {
// add(
// SongProperty(
// R.string.lbl_sample_rate,
// getString(R.string.fmt_sample_rate, it)))
// }
// song.replayGainAdjustment.track?.let {
// add(SongProperty(R.string.lbl_replaygain_track,
// it.formatDb(context)))
// }
// song.replayGainAdjustment.album?.let {
// add(SongProperty(R.string.lbl_replaygain_album,
// it.formatDb(context)))
// }
// },
// UpdateInstructions.Replace(0))
// }
}
private fun <T : Music> T.zipName(context: Context): String {
val name = name
return if (name is Name.Known && name.sort != null) {
getString(R.string.fmt_zipped_names, name.resolve(context), name.sort)
} else {
name.resolve(context)
} }
} }
private fun <T : Music> List<T>.zipNames(context: Context) = private fun updateSongProperties(songProperties: List<SongProperty>) {
concatLocalized(context) { it.zipName(context) } detailAdapter.update(songProperties, UpdateInstructions.Replace(0))
}
} }

View file

@ -18,16 +18,28 @@
package org.oxycblt.auxio.detail.list package org.oxycblt.auxio.detail.list
import android.text.Editable
import android.text.format.Formatter
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemSongPropertyBinding import org.oxycblt.auxio.databinding.ItemSongPropertyBinding
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.music.resolveNames
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.replaygain.formatDb
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.Music
import org.oxycblt.musikr.fs.Format
import org.oxycblt.musikr.fs.Path
import org.oxycblt.musikr.tag.Date
import org.oxycblt.musikr.tag.Name
/** /**
* An adapter for [SongProperty] instances. * An adapter for [SongProperty] instances.
@ -52,7 +64,21 @@ class SongPropertyAdapter :
* @param value The value of the property. * @param value The value of the property.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
data class SongProperty(@StringRes val name: Int, val value: String) data class SongProperty(@StringRes val name: Int, val value: Value) {
sealed interface Value {
data class MusicName(val music: Music) : Value
data class MusicNames(val name: List<Music>) : Value
data class Number(val value: Int, val subtitle: String?) : Value
data class ItemDate(val date: Date) : Value
data class ItemPath(val path: Path) : Value
data class Size(val sizeBytes: Long) : Value
data class Duration(val durationMs: Long) : Value
data class ItemFormat(val format: Format) : Value
data class Bitrate(val kbps: Int) : Value
data class SampleRate(val hz: Int) : Value
data class Decibels(val value: Float) : Value
}
}
/** /**
* A [RecyclerView.ViewHolder] that displays a [SongProperty]. Use [from] to create an instance. * A [RecyclerView.ViewHolder] that displays a [SongProperty]. Use [from] to create an instance.
@ -64,7 +90,57 @@ class SongPropertyViewHolder private constructor(private val binding: ItemSongPr
fun bind(property: SongProperty) { fun bind(property: SongProperty) {
val context = binding.context val context = binding.context
binding.propertyName.hint = context.getString(property.name) binding.propertyName.hint = context.getString(property.name)
binding.propertyValue.setText(property.value) when (property.value) {
is SongProperty.Value.MusicName -> {
val music = property.value.music
binding.propertyValue.setText(music.name.resolve(context))
}
is SongProperty.Value.MusicNames -> {
val names = property.value.name.resolveNames(context)
binding.propertyValue.setText(names)
}
is SongProperty.Value.Number -> {
val value = context.getString(R.string.fmt_number, property.value.value)
val subtitle = property.value.subtitle
binding.propertyValue.setText(if (subtitle != null) {
context.getString(R.string.fmt_zipped_names, value, subtitle)
} else {
value
})
}
is SongProperty.Value.ItemDate -> {
val date = property.value.date
binding.propertyValue.setText(date.resolve(context))
}
is SongProperty.Value.ItemPath -> {
val path = property.value.path
binding.propertyValue.setText(path.resolve(context))
}
is SongProperty.Value.Size -> {
val size = property.value.sizeBytes
binding.propertyValue.setText(Formatter.formatFileSize(context, size))
}
is SongProperty.Value.Duration -> {
val duration = property.value.durationMs
binding.propertyValue.setText(duration.formatDurationMs(true))
}
is SongProperty.Value.ItemFormat -> {
val format = property.value.format
binding.propertyValue.setText(format.resolve(context))
}
is SongProperty.Value.Bitrate -> {
val kbps = property.value.kbps
binding.propertyValue.setText(context.getString(R.string.fmt_bitrate, kbps))
}
is SongProperty.Value.SampleRate -> {
val hz = property.value.hz
binding.propertyValue.setText(context.getString(R.string.fmt_sample_rate, hz))
}
is SongProperty.Value.Decibels -> {
val value = property.value.value
binding.propertyValue.setText(value.formatDb(context))
}
}
} }
companion object { companion object {

View file

@ -25,6 +25,7 @@ import kotlin.math.max
import org.oxycblt.auxio.R 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.fs.Format
import org.oxycblt.musikr.tag.Date import org.oxycblt.musikr.tag.Date
import org.oxycblt.musikr.tag.Disc import org.oxycblt.musikr.tag.Disc
import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.Name
@ -152,3 +153,19 @@ fun ReleaseType.resolve(context: Context) =
is ReleaseType.Mixtape -> context.getString(R.string.lbl_mixtape) is ReleaseType.Mixtape -> context.getString(R.string.lbl_mixtape)
is ReleaseType.Demo -> context.getString(R.string.lbl_demo) is ReleaseType.Demo -> context.getString(R.string.lbl_demo)
} }
fun Format.resolve(context: Context): String =
when (this) {
is Format.MPEG3 -> context.getString(R.string.cdc_mp3)
is Format.MPEG4 -> containing?.let { context.getString(R.string.cnt_mp4, it.resolve(context)) }
?: context.getString(R.string.cdc_mp4)
is Format.AAC -> context.getString(R.string.cdc_aac)
is Format.ALAC -> context.getString(R.string.cdc_alac)
is Format.Ogg -> containing?.let { context.getString(R.string.cnt_ogg, it.resolve(context)) }
?: context.getString(R.string.cdc_ogg)
is Format.Opus -> context.getString(R.string.cdc_opus)
is Format.Vorbis -> context.getString(R.string.cdc_vorbis)
is Format.FLAC -> context.getString(R.string.cdc_flac)
is Format.Wav -> context.getString(R.string.cdc_wav)
is Format.Unknown -> extension ?: context.getString(R.string.cdc_unknown)
}

View file

@ -14,6 +14,7 @@
<string name="cdc_vorbis">Vorbis</string> <string name="cdc_vorbis">Vorbis</string>
<string name="cdc_opus">Opus</string> <string name="cdc_opus">Opus</string>
<string name="cdc_wav">Microsoft WAVE</string> <string name="cdc_wav">Microsoft WAVE</string>
<string name="cnt_ogg">Ogg %s</string>
<!-- Supporter Namespace | Sponsor usernames --> <!-- Supporter Namespace | Sponsor usernames -->
</resources> </resources>

View file

@ -378,14 +378,20 @@
<string name="cdc_mp3">MPEG-1 audio</string> <string name="cdc_mp3">MPEG-1 audio</string>
<!-- "Audio" can optionally be translated --> <!-- "Audio" can optionally be translated -->
<string name="cdc_mp4">MPEG-4 audio</string> <string name="cdc_mp4">MPEG-4 audio</string>
<!-- i.e MPEG-4 containing some codec -->
<string name="cnt_mp4">MPEG-4 containing %s</string>
<!-- "Advanced Audio Coding" can optionally be translated -->
<string name="cdc_aac">Advanced Audio Coding (AAC)</string>
<!-- "Apple Lossless Audio Codec" can optionally be translated -->
<string name="cdc_alac">Apple Lossless Audio Codec (ALAC)</string>
<!-- "Audio" can optionally be translated --> <!-- "Audio" can optionally be translated -->
<string name="cdc_ogg">Ogg audio</string> <string name="cdc_ogg">Ogg audio</string>
<!-- "Audio" can optionally be translated --> <!-- "Audio" can optionally be translated -->
<string name="cdc_mka">Matroska audio</string> <string name="cdc_mka">Matroska audio</string>
<!-- "Advanced Audio Coding" can optionally be translated -->
<string name="cdc_aac">Advanced Audio Coding (AAC)</string>
<!-- "Free Lossless Audio Codec" can optionally be translated --> <!-- "Free Lossless Audio Codec" can optionally be translated -->
<string name="cdc_flac">Free Lossless Audio Codec (FLAC)</string> <string name="cdc_flac">Free Lossless Audio Codec (FLAC)</string>
<!-- As in unknown format -->
<string name="cdc_unknown">Unknown</string>
<!-- Color Label namespace | Accent names --> <!-- Color Label namespace | Accent names -->
<eat-comment /> <eat-comment />