all: migrate to centralized constant table

Migrate to a centralized constant table for easier management.

Previously, Auxio would tie constants to the class itself, which
led to a largely disjointed system that relied on an internal table
so that it would stay sane. This commit moves all of those constants
to a single table for easier usage and management.
This commit is contained in:
OxygenCobalt 2022-03-19 19:57:57 -06:00
parent 6d9da35de0
commit 608112a7ac
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
23 changed files with 293 additions and 282 deletions

View file

@ -36,9 +36,6 @@ import org.oxycblt.auxio.settings.SettingsManager
* - Rework RecyclerView management and item dragging
* - Rework sealed classes to minimize whens and maximize overrides
* ```
*
* TODO: Dumpster int-codes for a 4-byte identifier (can still be in the form of an int) For
* example, instead of 0xA111 for ReplayGainMode.TRACK, you would instead have RTCK
*/
@Suppress("UNUSED")
class AuxioApp : Application(), ImageLoaderFactory {

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2022 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio
object IntegerTable {
/** SongViewHolder */
const val ITEM_TYPE_SONG = 0xA000
/** AlbumViewHolder */
const val ITEM_TYPE_ALBUM = 0xA001
/** ArtistViewHolder */
const val ITEM_TYPE_ARTIST = 0xA002
/** GenreViewHolder */
const val ITEM_TYPE_GENRE = 0xA003
/** HeaderViewHolder */
const val ITEM_TYPE_HEADER = 0xA004
/** ActionHeaderViewHolder */
const val ITEM_TYPE_ACTION_HEADER = 0xA005
/** AlbumDetailViewHolder */
const val ITEM_TYPE_ALBUM_DETAIL = 0xA006
/** AlbumSongViewHolder */
const val ITEM_TYPE_ALBUM_SONG = 0xA007
/** ArtistDetailViewHolder */
const val ITEM_TYPE_ARTIST_DETAIL = 0xA008
/** ArtistAlbumViewHolder */
const val ITEM_TYPE_ARTIST_ALBUM = 0xA009
/** ArtistSongViewHolder */
const val ITEM_TYPE_ARTIST_SONG = 0xA00A
/** GenreDetailViewHolder */
const val ITEM_TYPE_GENRE_DETAIL = 0xA00B
/** GenreSongViewHolder */
const val ITEM_TYPE_GENRE_SONG = 0xA00C
/** QueueSongViewHolder */
const val ITEM_TYPE_QUEUE_SONG = 0xA00D
/** "Music playback" Notification channel */
const val NOTIFICATION_CODE = 0xA0A0
/** Intent request code */
const val REQUEST_CODE = 0xA0C0
/** LoopMode.NONE */
const val LOOP_MODE_NONE = 0xA100
/** LoopMode.ALL */
const val LOOP_MODE_ALL = 0xA101
/** LoopMode.TRACK */
const val LOOP_MODE_TRACK = 0xA102
/** PlaybackMode.IN_GENRE */
const val PLAYBACK_MODE_IN_GENRE = 0xA103
/** PlaybackMode.IN_ARTIST */
const val PLAYBACK_MODE_IN_ARTIST = 0xA104
/** PlaybackMode.IN_ALBUM */
const val PLAYBACK_MODE_IN_ALBUM = 0xA105
/** PlaybackMode.ALL_SONGS */
const val PLAYBACK_MODE_ALL_SONGS = 0xA106
/** DisplayMode.NONE (No Longer used but still reserved) */
// const val DISPLAY_MODE_NONE = 0xA107
/** DisplayMode.SHOW_GENRES */
const val DISPLAY_MODE_SHOW_GENRES = 0xA108
/** DisplayMode.SHOW_ARTISTS */
const val DISPLAY_MODE_SHOW_ARTISTS = 0xA109
/** DisplayMode.SHOW_ALBUMS */
const val DISPLAY_MODE_SHOW_ALBUMS = 0xA10A
/** DisplayMode.SHOW_SONGS */
const val DISPLAY_MODE_SHOW_SONGS = 0xA10B
/** Sort.ByName */
const val SORT_BY_NAME = 0xA10C
/** Sort.ByArtist */
const val SORT_BY_ARTIST = 0xA10D
/** Sort.ByAlbum */
const val SORT_BY_ALBUM = 0xA10E
/** Sort.ByYear */
const val SORT_BY_YEAR = 0xA10F
/** ReplayGainMode.Off */
const val REPLAY_GAIN_MODE_OFF = 0xA110
/** ReplayGainMode.Track */
const val REPLAY_GAIN_MODE_TRACK = 0xA111
/** ReplayGainMode.Album */
const val REPLAY_GAIN_MODE_ALBUM = 0xA112
/** ReplayGainMode.Dynamic */
const val REPLAY_GAIN_MODE_DYNAMIC = 0xA113
}

View file

@ -22,6 +22,7 @@ import android.view.ViewGroup
import androidx.core.view.isInvisible
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.coil.bindAlbumArt
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
@ -53,20 +54,20 @@ class AlbumDetailAdapter(
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is Album -> ALBUM_DETAIL_ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
is Song -> ALBUM_SONG_ITEM_TYPE
is Album -> IntegerTable.ITEM_TYPE_ALBUM_DETAIL
is ActionHeader -> IntegerTable.ITEM_TYPE_ACTION_HEADER
is Song -> IntegerTable.ITEM_TYPE_ALBUM_SONG
else -> -1
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ALBUM_DETAIL_ITEM_TYPE ->
IntegerTable.ITEM_TYPE_ALBUM_DETAIL ->
AlbumDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater))
ALBUM_SONG_ITEM_TYPE ->
IntegerTable.ITEM_TYPE_ACTION_HEADER -> ActionHeaderViewHolder.from(parent.context)
IntegerTable.ITEM_TYPE_ALBUM_SONG ->
AlbumSongViewHolder(ItemAlbumSongBinding.inflate(parent.context.inflater))
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
else -> error("Invalid ViewHolder item type $viewType")
}
}
@ -172,9 +173,4 @@ class AlbumDetailAdapter(
binding.songTrackPlaceholder.isActivated = isHighlighted
}
}
companion object {
const val ALBUM_DETAIL_ITEM_TYPE = 0xA006
const val ALBUM_SONG_ITEM_TYPE = 0xA007
}
}

View file

@ -21,6 +21,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.coil.bindArtistImage
import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
@ -58,25 +59,25 @@ class ArtistDetailAdapter(
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is Artist -> ARTIST_DETAIL_ITEM_TYPE
is Album -> ARTIST_ALBUM_ITEM_TYPE
is Song -> ARTIST_SONG_ITEM_TYPE
is Header -> HeaderViewHolder.ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
is Artist -> IntegerTable.ITEM_TYPE_ARTIST_DETAIL
is Album -> IntegerTable.ITEM_TYPE_ARTIST_ALBUM
is Song -> IntegerTable.ITEM_TYPE_ARTIST_SONG
is Header -> IntegerTable.ITEM_TYPE_HEADER
is ActionHeader -> IntegerTable.ITEM_TYPE_ACTION_HEADER
else -> -1
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ARTIST_DETAIL_ITEM_TYPE ->
IntegerTable.ITEM_TYPE_ARTIST_DETAIL ->
ArtistDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater))
ARTIST_ALBUM_ITEM_TYPE ->
IntegerTable.ITEM_TYPE_ARTIST_ALBUM ->
ArtistAlbumViewHolder(ItemArtistAlbumBinding.inflate(parent.context.inflater))
ARTIST_SONG_ITEM_TYPE ->
IntegerTable.ITEM_TYPE_ARTIST_SONG ->
ArtistSongViewHolder(ItemArtistSongBinding.inflate(parent.context.inflater))
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
IntegerTable.ITEM_TYPE_HEADER -> HeaderViewHolder.from(parent.context)
IntegerTable.ITEM_TYPE_ACTION_HEADER -> ActionHeaderViewHolder.from(parent.context)
else -> error("Invalid ViewHolder item type $viewType")
}
}
@ -222,10 +223,4 @@ class ArtistDetailAdapter(
binding.songName.isActivated = isHighlighted
}
}
companion object {
const val ARTIST_DETAIL_ITEM_TYPE = 0xA008
const val ARTIST_ALBUM_ITEM_TYPE = 0xA009
const val ARTIST_SONG_ITEM_TYPE = 0xA00A
}
}

View file

@ -21,6 +21,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.coil.bindGenreImage
import org.oxycblt.auxio.databinding.ItemDetailBinding
@ -50,22 +51,22 @@ class GenreDetailAdapter(
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is Genre -> GENRE_DETAIL_ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
is Song -> GENRE_SONG_ITEM_TYPE
is Genre -> IntegerTable.ITEM_TYPE_GENRE_DETAIL
is ActionHeader -> IntegerTable.ITEM_TYPE_ACTION_HEADER
is Song -> IntegerTable.ITEM_TYPE_GENRE_SONG
else -> -1
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
GENRE_DETAIL_ITEM_TYPE ->
IntegerTable.ITEM_TYPE_GENRE_DETAIL ->
GenreDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater))
GENRE_SONG_ITEM_TYPE ->
IntegerTable.ITEM_TYPE_ACTION_HEADER -> ActionHeaderViewHolder.from(parent.context)
IntegerTable.ITEM_TYPE_GENRE_SONG ->
GenreSongViewHolder(
ItemGenreSongBinding.inflate(parent.context.inflater),
)
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
else -> error("Bad ViewHolder item type $viewType")
}
}
@ -154,9 +155,4 @@ class GenreDetailAdapter(
binding.songName.isActivated = isHighlighted
}
}
companion object {
const val GENRE_DETAIL_ITEM_TYPE = 0xA00B
const val GENRE_SONG_ITEM_TYPE = 0xA00C
}
}

View file

@ -156,7 +156,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0)
startAngle: Float,
sweepAngle: Float
) {
path.arcTo(
arcTo(
centerX - radius,
centerY - radius,
centerX + radius,

View file

@ -27,6 +27,7 @@ import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.Header
@ -55,19 +56,19 @@ class QueueAdapter(private val touchHelper: ItemTouchHelper) :
override fun getItemViewType(position: Int): Int {
return when (data[position]) {
is Song -> QUEUE_SONG_ITEM_TYPE
is Header -> HeaderViewHolder.ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
is Song -> IntegerTable.ITEM_TYPE_QUEUE_SONG
is Header -> IntegerTable.ITEM_TYPE_HEADER
is ActionHeader -> IntegerTable.ITEM_TYPE_ACTION_HEADER
else -> -1
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
QUEUE_SONG_ITEM_TYPE ->
IntegerTable.ITEM_TYPE_QUEUE_SONG ->
QueueSongViewHolder(ItemQueueSongBinding.inflate(parent.context.inflater))
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
IntegerTable.ITEM_TYPE_HEADER -> HeaderViewHolder.from(parent.context)
IntegerTable.ITEM_TYPE_ACTION_HEADER -> ActionHeaderViewHolder.from(parent.context)
else -> error("Invalid ViewHolder item type $viewType")
}
}
@ -146,8 +147,4 @@ class QueueAdapter(private val touchHelper: ItemTouchHelper) :
}
}
}
companion object {
const val QUEUE_SONG_ITEM_TYPE = 0xA00D
}
}

View file

@ -17,6 +17,8 @@
package org.oxycblt.auxio.playback.state
import org.oxycblt.auxio.IntegerTable
/**
* Enum that determines the playback repeat mode.
* @author OxygenCobalt
@ -39,25 +41,21 @@ enum class LoopMode {
* Convert the LoopMode to an int constant that is saved in PlaybackStateDatabase
* @return The int constant for this mode
*/
fun toInt(): Int {
return when (this) {
NONE -> INT_NONE
ALL -> INT_ALL
TRACK -> INT_TRACK
}
}
val intCode: Int
get() =
when (this) {
NONE -> IntegerTable.LOOP_MODE_NONE
ALL -> IntegerTable.LOOP_MODE_ALL
TRACK -> IntegerTable.LOOP_MODE_TRACK
}
companion object {
private const val INT_NONE = 0xA100
private const val INT_ALL = 0xA101
private const val INT_TRACK = 0xA102
/** Convert an int [constant] into a LoopMode, or null if it isn't valid. */
fun fromInt(constant: Int): LoopMode? {
fun fromIntCode(constant: Int): LoopMode? {
return when (constant) {
INT_NONE -> NONE
INT_ALL -> ALL
INT_TRACK -> TRACK
IntegerTable.LOOP_MODE_NONE -> NONE
IntegerTable.LOOP_MODE_ALL -> ALL
IntegerTable.LOOP_MODE_TRACK -> TRACK
else -> null
}
}

View file

@ -17,6 +17,8 @@
package org.oxycblt.auxio.playback.state
import org.oxycblt.auxio.IntegerTable
/**
* Enum that indicates how the queue should be constructed.
* @author OxygenCobalt
@ -35,32 +37,26 @@ enum class PlaybackMode {
* Convert the mode into an int constant, to be saved in PlaybackStateDatabase
* @return The constant for this mode,
*/
fun toInt(): Int {
return when (this) {
ALL_SONGS -> INT_ALL_SONGS
IN_ALBUM -> INT_IN_ALBUM
IN_ARTIST -> INT_IN_ARTIST
IN_GENRE -> INT_IN_GENRE
}
}
val intCode: Int
get() =
when (this) {
ALL_SONGS -> IntegerTable.PLAYBACK_MODE_ALL_SONGS
IN_ALBUM -> IntegerTable.PLAYBACK_MODE_IN_ALBUM
IN_ARTIST -> IntegerTable.PLAYBACK_MODE_IN_ARTIST
IN_GENRE -> IntegerTable.PLAYBACK_MODE_IN_GENRE
}
companion object {
// Kept in reverse order because of backwards compat, do not re-order these
private const val INT_ALL_SONGS = 0xA106
private const val INT_IN_ALBUM = 0xA105
private const val INT_IN_ARTIST = 0xA104
private const val INT_IN_GENRE = 0xA103
/**
* Get a [PlaybackMode] for an int [constant]
* @return The mode, null if there isn't one for this.
*/
fun fromInt(constant: Int): PlaybackMode? {
return when (constant) {
INT_ALL_SONGS -> ALL_SONGS
INT_IN_ALBUM -> IN_ALBUM
INT_IN_ARTIST -> IN_ARTIST
INT_IN_GENRE -> IN_GENRE
IntegerTable.PLAYBACK_MODE_ALL_SONGS -> ALL_SONGS
IntegerTable.PLAYBACK_MODE_IN_ALBUM -> IN_ALBUM
IntegerTable.PLAYBACK_MODE_IN_ARTIST -> IN_ARTIST
IntegerTable.PLAYBACK_MODE_IN_GENRE -> IN_GENRE
else -> null
}
}

View file

@ -34,6 +34,8 @@ import org.oxycblt.auxio.util.queryAll
* A SQLite database for managing the persistent playback state and queue. Yes. I know Room exists.
* But that would needlessly bloat my app and has crippling bugs.
* @author OxygenCobalt
*
* TODO: Rework to rely on queue indices more and only use specific items as fallbacks
*/
class PlaybackStateDatabase(context: Context) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
@ -144,7 +146,7 @@ class PlaybackStateDatabase(context: Context) :
queueIndex = cursor.getInt(indexIndex),
playbackMode = mode,
isShuffling = cursor.getInt(shuffleIndex) == 1,
loopMode = LoopMode.fromInt(cursor.getInt(loopModeIndex)) ?: LoopMode.NONE,
loopMode = LoopMode.fromIntCode(cursor.getInt(loopModeIndex)) ?: LoopMode.NONE,
)
logD("Successfully read playback state: $state")
@ -169,9 +171,9 @@ class PlaybackStateDatabase(context: Context) :
put(StateColumns.COLUMN_POSITION, state.position)
put(StateColumns.COLUMN_PARENT_HASH, state.parent?.id)
put(StateColumns.COLUMN_QUEUE_INDEX, state.queueIndex)
put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.toInt())
put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.intCode)
put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling)
put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.toInt())
put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.intCode)
}
insert(TABLE_NAME_STATE, null, stateData)

View file

@ -49,7 +49,6 @@ private constructor(private val context: Context, mediaToken: MediaSessionCompat
setCategory(NotificationCompat.CATEGORY_SERVICE)
setShowWhen(false)
setSilent(true)
setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
setContentIntent(context.newMainIntent())
setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
@ -159,7 +158,6 @@ private constructor(private val context: Context, mediaToken: MediaSessionCompat
companion object {
const val CHANNEL_ID = BuildConfig.APPLICATION_ID + ".channel.PLAYBACK"
const val NOTIFICATION_ID = 0xA0A0
/** Build a new instance of [PlaybackNotification]. */
fun from(

View file

@ -51,6 +51,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.LoopMode
@ -407,18 +408,17 @@ class PlaybackService :
// Specify that this is a media service, if supported.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
PlaybackNotification.NOTIFICATION_ID,
IntegerTable.NOTIFICATION_CODE,
notification.build(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
} else {
startForeground(PlaybackNotification.NOTIFICATION_ID, notification.build())
startForeground(IntegerTable.NOTIFICATION_CODE, notification.build())
}
isForeground = true
} else {
// If we are already in foreground just update the notification
notificationManager.notify(
PlaybackNotification.NOTIFICATION_ID, notification.build())
notificationManager.notify(IntegerTable.NOTIFICATION_CODE, notification.build())
}
}
}
@ -426,7 +426,7 @@ class PlaybackService :
/** Stop the foreground state and hide the notification */
private fun stopForegroundAndNotification() {
stopForeground(true)
notificationManager.cancel(PlaybackNotification.NOTIFICATION_ID)
notificationManager.cancel(IntegerTable.NOTIFICATION_CODE)
isForeground = false
}

View file

@ -17,6 +17,8 @@
package org.oxycblt.auxio.playback.system
import org.oxycblt.auxio.IntegerTable
/** Represents the current setting for ReplayGain. */
enum class ReplayGainMode {
/** Do not apply ReplayGain. */
@ -28,29 +30,13 @@ enum class ReplayGainMode {
/** Apply the album gain only when playing from an album, defaulting to track gain otherwise. */
DYNAMIC;
/** Converts this type to an integer constant. */
fun toInt(): Int {
return when (this) {
OFF -> INT_OFF
TRACK -> INT_TRACK
ALBUM -> INT_ALBUM
DYNAMIC -> INT_DYNAMIC
}
}
companion object {
private const val INT_OFF = 0xA110
private const val INT_TRACK = 0xA111
private const val INT_ALBUM = 0xA112
private const val INT_DYNAMIC = 0xA113
/** Converts an integer constant to this type. */
fun fromInt(value: Int): ReplayGainMode? {
fun fromIntCode(value: Int): ReplayGainMode? {
return when (value) {
INT_OFF -> OFF
INT_TRACK -> TRACK
INT_ALBUM -> ALBUM
INT_DYNAMIC -> DYNAMIC
IntegerTable.REPLAY_GAIN_MODE_OFF -> OFF
IntegerTable.REPLAY_GAIN_MODE_TRACK -> TRACK
IntegerTable.REPLAY_GAIN_MODE_ALBUM -> ALBUM
IntegerTable.REPLAY_GAIN_MODE_DYNAMIC -> DYNAMIC
else -> null
}
}

View file

@ -21,6 +21,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -46,26 +47,26 @@ class SearchAdapter(
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is Genre -> GenreViewHolder.ITEM_TYPE
is Artist -> ArtistViewHolder.ITEM_TYPE
is Album -> AlbumViewHolder.ITEM_TYPE
is Song -> SongViewHolder.ITEM_TYPE
is Header -> HeaderViewHolder.ITEM_TYPE
is Genre -> IntegerTable.ITEM_TYPE_GENRE
is Artist -> IntegerTable.ITEM_TYPE_ARTIST
is Album -> IntegerTable.ITEM_TYPE_ALBUM
is Song -> IntegerTable.ITEM_TYPE_SONG
is Header -> IntegerTable.ITEM_TYPE_HEADER
else -> -1
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
GenreViewHolder.ITEM_TYPE ->
IntegerTable.ITEM_TYPE_GENRE ->
GenreViewHolder.from(parent.context, doOnClick, doOnLongClick)
ArtistViewHolder.ITEM_TYPE ->
IntegerTable.ITEM_TYPE_ARTIST ->
ArtistViewHolder.from(parent.context, doOnClick, doOnLongClick)
AlbumViewHolder.ITEM_TYPE ->
IntegerTable.ITEM_TYPE_ALBUM ->
AlbumViewHolder.from(parent.context, doOnClick, doOnLongClick)
SongViewHolder.ITEM_TYPE ->
IntegerTable.ITEM_TYPE_SONG ->
SongViewHolder.from(parent.context, doOnClick, doOnLongClick)
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
IntegerTable.ITEM_TYPE_HEADER -> HeaderViewHolder.from(parent.context)
else -> error("Invalid ViewHolder item type")
}
}

View file

@ -104,7 +104,7 @@ class SettingsManager private constructor(context: Context) :
/** The current ReplayGain configuration */
val replayGainMode: ReplayGainMode
get() =
ReplayGainMode.fromInt(prefs.getInt(KEY_REPLAY_GAIN, Int.MIN_VALUE))
ReplayGainMode.fromIntCode(prefs.getInt(KEY_REPLAY_GAIN, Int.MIN_VALUE))
?: ReplayGainMode.OFF
/** What queue to create when a song is selected (ex. From All Songs or Search) */
@ -129,50 +129,54 @@ class SettingsManager private constructor(context: Context) :
/** The current filter mode of the search tab */
var searchFilterMode: DisplayMode?
get() = DisplayMode.fromFilterInt(prefs.getInt(KEY_SEARCH_FILTER_MODE, Int.MIN_VALUE))
get() = DisplayMode.fromInt(prefs.getInt(KEY_SEARCH_FILTER_MODE, Int.MIN_VALUE))
set(value) {
prefs.edit {
putInt(KEY_SEARCH_FILTER_MODE, DisplayMode.toFilterInt(value))
putInt(KEY_SEARCH_FILTER_MODE, value?.intCode ?: Int.MIN_VALUE)
apply()
}
}
/** The song sort mode on HomeFragment */
var libSongSort: Sort
get() = Sort.fromInt(prefs.getInt(KEY_LIB_SONGS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
get() =
Sort.fromIntCode(prefs.getInt(KEY_LIB_SONGS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_LIB_SONGS_SORT, value.toInt())
putInt(KEY_LIB_SONGS_SORT, value.intCode)
apply()
}
}
/** The album sort mode on HomeFragment */
var libAlbumSort: Sort
get() = Sort.fromInt(prefs.getInt(KEY_LIB_ALBUMS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
get() =
Sort.fromIntCode(prefs.getInt(KEY_LIB_ALBUMS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_LIB_ALBUMS_SORT, value.toInt())
putInt(KEY_LIB_ALBUMS_SORT, value.intCode)
apply()
}
}
/** The artist sort mode on HomeFragment */
var libArtistSort: Sort
get() = Sort.fromInt(prefs.getInt(KEY_LIB_ARTISTS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
get() =
Sort.fromIntCode(prefs.getInt(KEY_LIB_ARTISTS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_LIB_ARTISTS_SORT, value.toInt())
putInt(KEY_LIB_ARTISTS_SORT, value.intCode)
apply()
}
}
/** The genre sort mode on HomeFragment */
var libGenreSort: Sort
get() = Sort.fromInt(prefs.getInt(KEY_LIB_GENRES_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
get() =
Sort.fromIntCode(prefs.getInt(KEY_LIB_GENRES_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_LIB_GENRES_SORT, value.toInt())
putInt(KEY_LIB_GENRES_SORT, value.intCode)
apply()
}
}
@ -180,10 +184,11 @@ class SettingsManager private constructor(context: Context) :
/** The detail album sort mode */
var detailAlbumSort: Sort
get() =
Sort.fromInt(prefs.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
Sort.fromIntCode(prefs.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE))
?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_DETAIL_ALBUM_SORT, value.toInt())
putInt(KEY_DETAIL_ALBUM_SORT, value.intCode)
apply()
}
}
@ -191,10 +196,11 @@ class SettingsManager private constructor(context: Context) :
/** The detail artist sort mode */
var detailArtistSort: Sort
get() =
Sort.fromInt(prefs.getInt(KEY_DETAIL_ARTIST_SORT, Int.MIN_VALUE)) ?: Sort.ByYear(false)
Sort.fromIntCode(prefs.getInt(KEY_DETAIL_ARTIST_SORT, Int.MIN_VALUE))
?: Sort.ByYear(false)
set(value) {
prefs.edit {
putInt(KEY_DETAIL_ARTIST_SORT, value.toInt())
putInt(KEY_DETAIL_ARTIST_SORT, value.intCode)
apply()
}
}
@ -202,10 +208,11 @@ class SettingsManager private constructor(context: Context) :
/** The detail genre sort mode */
var detailGenreSort: Sort
get() =
Sort.fromInt(prefs.getInt(KEY_DETAIL_GENRE_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
Sort.fromIntCode(prefs.getInt(KEY_DETAIL_GENRE_SORT, Int.MIN_VALUE))
?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_DETAIL_GENRE_SORT, value.toInt())
putInt(KEY_DETAIL_GENRE_SORT, value.intCode)
apply()
}
}

View file

@ -17,6 +17,7 @@
package org.oxycblt.auxio.ui
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
/**
@ -49,39 +50,27 @@ enum class DisplayMode {
SHOW_GENRES -> R.drawable.ic_genre
}
companion object {
private const val INT_NULL = 0xA107
private const val INT_SHOW_GENRES = 0xA108
private const val INT_SHOW_ARTISTS = 0xA109
private const val INT_SHOW_ALBUMS = 0xA10A
private const val INT_SHOW_SONGS = 0xA10B
/**
* Convert this enum into an integer for filtering. In this context, a null value means to
* filter nothing.
* @return An integer constant for that display mode, or a constant for a null [DisplayMode]
*/
fun toFilterInt(value: DisplayMode?): Int {
return when (value) {
SHOW_SONGS -> INT_SHOW_SONGS
SHOW_ALBUMS -> INT_SHOW_ALBUMS
SHOW_ARTISTS -> INT_SHOW_ARTISTS
SHOW_GENRES -> INT_SHOW_GENRES
null -> INT_NULL
val intCode: Int
get() =
when (this) {
SHOW_SONGS -> IntegerTable.DISPLAY_MODE_SHOW_SONGS
SHOW_ALBUMS -> IntegerTable.DISPLAY_MODE_SHOW_ALBUMS
SHOW_ARTISTS -> IntegerTable.DISPLAY_MODE_SHOW_ARTISTS
SHOW_GENRES -> IntegerTable.DISPLAY_MODE_SHOW_GENRES
}
}
companion object {
/**
* Convert a filtering integer to a [DisplayMode]. In this context, a null value means to
* filter nothing.
* @return A [DisplayMode] for this constant (including null)
*/
fun fromFilterInt(value: Int): DisplayMode? {
fun fromInt(value: Int): DisplayMode? {
return when (value) {
INT_SHOW_SONGS -> SHOW_SONGS
INT_SHOW_ALBUMS -> SHOW_ALBUMS
INT_SHOW_ARTISTS -> SHOW_ARTISTS
INT_SHOW_GENRES -> SHOW_GENRES
IntegerTable.DISPLAY_MODE_SHOW_SONGS -> SHOW_SONGS
IntegerTable.DISPLAY_MODE_SHOW_ALBUMS -> SHOW_ALBUMS
IntegerTable.DISPLAY_MODE_SHOW_ARTISTS -> SHOW_ARTISTS
IntegerTable.DISPLAY_MODE_SHOW_GENRES -> SHOW_GENRES
else -> null
}
}

View file

@ -18,6 +18,7 @@
package org.oxycblt.auxio.ui
import androidx.annotation.IdRes
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
@ -43,6 +44,9 @@ import org.oxycblt.auxio.util.logW
* @author OxygenCobalt
*/
sealed class Sort(open val isAscending: Boolean) {
protected abstract val sortIntCode: Int
abstract val itemId: Int
open fun songs(songs: Collection<Song>): List<Song> {
logW("This sort is not supported for songs")
return songs.toList()
@ -63,8 +67,20 @@ sealed class Sort(open val isAscending: Boolean) {
return genres.toList()
}
/**
* Apply [newIsAscending] to the status of this sort.
* @return A new [Sort] with the value of [newIsAscending] applied.
*/
abstract fun ascending(newIsAscending: Boolean): Sort
/** Sort by the names of an item */
class ByName(override val isAscending: Boolean) : Sort(isAscending) {
override val sortIntCode: Int
get() = IntegerTable.SORT_BY_NAME
override val itemId: Int
get() = R.id.option_sort_name
override fun songs(songs: Collection<Song>): List<Song> {
return songs.sortedWith(compareByDynamic(NameComparator()) { it })
}
@ -80,10 +96,20 @@ sealed class Sort(open val isAscending: Boolean) {
override fun genres(genres: Collection<Genre>): List<Genre> {
return genres.sortedWith(compareByDynamic(NameComparator()) { it })
}
override fun ascending(newIsAscending: Boolean): Sort {
return ByName(isAscending)
}
}
/** Sort by the album of an item, only supported by [Song] */
class ByAlbum(override val isAscending: Boolean) : Sort(isAscending) {
override val sortIntCode: Int
get() = IntegerTable.SORT_BY_ALBUM
override val itemId: Int
get() = R.id.option_sort_album
override fun songs(songs: Collection<Song>): List<Song> {
return songs.sortedWith(
MultiComparator(
@ -91,10 +117,20 @@ sealed class Sort(open val isAscending: Boolean) {
compareBy(NullableComparator()) { it.track },
compareBy(NameComparator()) { it }))
}
override fun ascending(newIsAscending: Boolean): Sort {
return ByAlbum(newIsAscending)
}
}
/** Sort by the artist of an item, only supported by [Album] and [Song] */
class ByArtist(override val isAscending: Boolean) : Sort(isAscending) {
override val sortIntCode: Int
get() = IntegerTable.SORT_BY_ARTIST
override val itemId: Int
get() = R.id.option_sort_artist
override fun songs(songs: Collection<Song>): List<Song> {
return songs.sortedWith(
MultiComparator(
@ -112,10 +148,20 @@ sealed class Sort(open val isAscending: Boolean) {
compareByDescending(NullableComparator()) { it.year },
compareBy(NameComparator()) { it }))
}
override fun ascending(newIsAscending: Boolean): Sort {
return ByArtist(newIsAscending)
}
}
/** Sort by the year of an item, only supported by [Album] and [Song] */
class ByYear(override val isAscending: Boolean) : Sort(isAscending) {
override val sortIntCode: Int
get() = IntegerTable.SORT_BY_YEAR
override val itemId: Int
get() = R.id.option_sort_year
override fun songs(songs: Collection<Song>): List<Song> {
return songs.sortedWith(
MultiComparator(
@ -131,31 +177,15 @@ sealed class Sort(open val isAscending: Boolean) {
compareByDynamic(NullableComparator()) { it.year },
compareBy(NameComparator()) { it }))
}
}
/** Get the corresponding item id for this sort. */
val itemId: Int
get() =
when (this) {
is ByName -> R.id.option_sort_name
is ByArtist -> R.id.option_sort_artist
is ByAlbum -> R.id.option_sort_album
is ByYear -> R.id.option_sort_year
}
/**
* Apply [ascending] to the status of this sort.
* @return A new [Sort] with the value of [ascending] applied.
*/
fun ascending(ascending: Boolean): Sort {
return when (this) {
is ByName -> ByName(ascending)
is ByArtist -> ByArtist(ascending)
is ByAlbum -> ByAlbum(ascending)
is ByYear -> ByYear(ascending)
override fun ascending(newIsAscending: Boolean): Sort {
return ByYear(newIsAscending)
}
}
val intCode: Int
get() = sortIntCode.shl(1) or if (isAscending) 1 else 0
/**
* Assign a new [id] to this sort
* @return A new [Sort] corresponding to the [id] given, null if the ID has no analogue.
@ -197,16 +227,6 @@ sealed class Sort(open val isAscending: Boolean) {
return songs(genre.songs)
}
/** Convert this sort to it's integer representation. */
fun toInt(): Int {
return when (this) {
is ByName -> INT_NAME
is ByArtist -> INT_ARTIST
is ByAlbum -> INT_ALBUM
is ByYear -> INT_YEAR
}.shl(1) or if (isAscending) 1 else 0
}
protected inline fun <T : Music, K> compareByDynamic(
comparator: Comparator<in K>,
crossinline selector: (T) -> K
@ -244,8 +264,8 @@ sealed class Sort(open val isAscending: Boolean) {
*
* Sorts often need to compare multiple things at once across several hierarchies, with this
* class doing such in a more efficient manner than resorting at multiple intervals or grouping
* items up. Comparators are checked from first to last, with the first comparator that returns a
* non-equal result being propagated upwards.
* items up. Comparators are checked from first to last, with the first comparator that returns
* a non-equal result being propagated upwards.
*/
class MultiComparator<T>(vararg comparators: Comparator<T>) : Comparator<T> {
private val mComparators = comparators
@ -263,24 +283,19 @@ sealed class Sort(open val isAscending: Boolean) {
}
companion object {
private const val INT_NAME = 0xA10C
private const val INT_ARTIST = 0xA10D
private const val INT_ALBUM = 0xA10E
private const val INT_YEAR = 0xA10F
/**
* Convert a sort's integer representation into a [Sort] instance.
*
* @return A [Sort] instance, null if the data is malformed.
*/
fun fromInt(value: Int): Sort? {
fun fromIntCode(value: Int): Sort? {
val ascending = (value and 1) == 1
return when (value.shr(1)) {
INT_NAME -> ByName(ascending)
INT_ARTIST -> ByArtist(ascending)
INT_ALBUM -> ByAlbum(ascending)
INT_YEAR -> ByYear(ascending)
IntegerTable.SORT_BY_NAME -> ByName(ascending)
IntegerTable.SORT_BY_ARTIST -> ByArtist(ascending)
IntegerTable.SORT_BY_ALBUM -> ByAlbum(ascending)
IntegerTable.SORT_BY_YEAR -> ByYear(ascending)
else -> null
}
}

View file

@ -100,8 +100,6 @@ private constructor(
}
companion object {
const val ITEM_TYPE = 0xA000
/** Create an instance of [SongViewHolder] */
fun from(
context: Context,
@ -128,8 +126,6 @@ private constructor(
}
companion object {
const val ITEM_TYPE = 0xA001
/** Create an instance of [AlbumViewHolder] */
fun from(
context: Context,
@ -156,8 +152,6 @@ private constructor(
}
companion object {
const val ITEM_TYPE = 0xA002
/** Create an instance of [ArtistViewHolder] */
fun from(
context: Context,
@ -184,8 +178,6 @@ private constructor(
}
companion object {
const val ITEM_TYPE = 0xA003
/** Create an instance of [GenreViewHolder] */
fun from(
context: Context,
@ -207,8 +199,6 @@ class HeaderViewHolder private constructor(private val binding: ItemHeaderBindin
}
companion object {
const val ITEM_TYPE = 0xA004
/** Create an instance of [HeaderViewHolder] */
fun from(context: Context): HeaderViewHolder {
return HeaderViewHolder(ItemHeaderBinding.inflate(context.inflater))
@ -233,8 +223,6 @@ class ActionHeaderViewHolder private constructor(private val binding: ItemAction
}
companion object {
const val ITEM_TYPE = 0xA005
/** Create an instance of [ActionHeaderViewHolder] */
fun from(context: Context): ActionHeaderViewHolder {
return ActionHeaderViewHolder(ItemActionHeaderBinding.inflate(context.inflater))

View file

@ -40,10 +40,9 @@ import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import kotlin.reflect.KClass
import kotlin.system.exitProcess
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.MainActivity
const val INTENT_REQUEST_CODE = 0xA0A0
/** Shortcut to get a [LayoutInflater] from a [Context] */
val Context.inflater: LayoutInflater
get() = LayoutInflater.from(this)
@ -213,7 +212,7 @@ fun Context.showToast(@StringRes str: Int) {
fun Context.newMainIntent(): PendingIntent {
return PendingIntent.getActivity(
this,
INTENT_REQUEST_CODE,
IntegerTable.REQUEST_CODE,
Intent(this, MainActivity::class.java),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
}
@ -222,7 +221,7 @@ fun Context.newMainIntent(): PendingIntent {
fun Context.newBroadcastIntent(what: String): PendingIntent {
return PendingIntent.getBroadcast(
this,
INTENT_REQUEST_CODE,
IntegerTable.REQUEST_CODE,
Intent(what),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
}

View file

@ -26,6 +26,7 @@ import android.util.Log
import android.view.View
import android.view.WindowInsets
import androidx.annotation.ColorRes
import androidx.core.graphics.drawable.DrawableCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
@ -117,7 +118,7 @@ private fun isUnderImpl(
val View.isRtl: Boolean
get() = layoutDirection == View.LAYOUT_DIRECTION_RTL
val Drawable.isRtl: Boolean
get() = layoutDirection == View.LAYOUT_DIRECTION_RTL
get() = DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL
/**
* Resolve system bar insets in a version-aware manner. This can be used to apply padding to a view

View file

@ -172,12 +172,10 @@
<string name="fmt_songs_loaded">已加载 %d 首曲目</string>
<plurals name="fmt_song_count">
<item quantity="one">%d 首歌曲</item>
<item quantity="other">"%d 首歌曲"</item>
</plurals>
<plurals name="fmt_album_count">
<item quantity="one">%d 张专辑</item>
<item quantity="other">"%d 张专辑"</item>
</plurals>
</resources>

View file

@ -2,6 +2,7 @@
<resources>
<integer name="detail_app_bar_title_anim_duration">150</integer>
<!-- FIXME: This is really stupid, figure out how we can unify it with the C object-->
<!-- Preference values -->
<string-array name="entries_theme">
<item>@string/set_theme_auto</item>

View file

@ -150,58 +150,8 @@ system events, such as when a button is pressed on a headset. It should **never*
#### Data Integers
Integer representations of data/UI elements are used heavily in Auxio, primarily for efficiency.
To prevent any strange bugs, all integer representations must be unique. A table of all current integers used are shown below:
```
0xA0XX | UI Integer Space [Required by android]
0xA000 | SongViewHolder
0xA001 | AlbumViewHolder
0xA002 | ArtistViewHolder
0xA003 | GenreViewHolder
0xA004 | HeaderViewHolder
0xA005 | ActionHeaderViewHolder
0xA006 | AlbumDetailViewHolder
0xA007 | AlbumSongViewHolder
0xA008 | ArtistDetailViewHolder
0xA009 | ArtistAlbumViewHolder
0xA00A | ArtistSongViewHolder
0xA00B | GenreDetailViewHolder
0xA00C | GenreSongViewHolder
0xA00D | QueueSongViewHolder
0xA0A0 | Auxio notification code
0xA0C0 | Auxio request code
0xA1XX | Data Integer Space [Stored for IO efficency]
0xA100 | LoopMode.NONE
0xA101 | LoopMode.ALL
0xA102 | LoopMode.TRACK
0xA103 | PlaybackMode.IN_GENRE
0xA104 | PlaybackMode.IN_ARTIST
0xA105 | PlaybackMode.IN_ALBUM
0xA106 | PlaybackMode.ALL_SONGS
0xA107 | Null DisplayMode [Filter Nothing]
0xA108 | DisplayMode.SHOW_GENRES
0xA109 | DisplayMode.SHOW_ARTISTS
0xA10A | DisplayMode.SHOW_ALBUMS
0xA10B | DisplayMode.SHOW_SONGS
0xA10C | Sort.Name
0xA10D | Sort.Artist
0xA10E | Sort.Album
0xA10F | Sort.Year
0xA110 | ReplayGainMode.OFF
0xA111 | ReplayGainMode.TRACK
0xA112 | ReplayGainMode.ALBUM
0xA113 | ReplayGainMode.DYNAMIC
```
To prevent any strange bugs, all integer representations must be unique. To see a table of all current integers, see the `C` class within
the project.
Some datatypes [like `Tab` and `Sort`] have even more fine-grained integer representations for other data. More information can be found in
the documentation for those datatypes.