all: merge display and playback modes

Merge DisplayMode and PlaybackMode into a new class called MusicMode.

Both of these datatypes represented similar things, and thus it's much
easier to make them the same datatype. Moreover, it makes the
forthcoming addition of the music selector much easier if the same
datatype was tied to the representation of music.

This commit also moves around things around the project to be slightly
more coherent.
This commit is contained in:
Alexander Capehart 2022-09-10 19:36:59 -06:00
parent 8aac87e02c
commit c342fb364b
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
52 changed files with 322 additions and 355 deletions

View file

@ -25,16 +25,19 @@ import androidx.core.graphics.drawable.IconCompat
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.request.CachePolicy
import org.oxycblt.auxio.image.AlbumCoverFetcher
import org.oxycblt.auxio.image.ArtistImageFetcher
import org.oxycblt.auxio.image.CrossfadeTransitionFactory
import org.oxycblt.auxio.image.GenreImageFetcher
import org.oxycblt.auxio.image.MusicKeyer
import org.oxycblt.auxio.image.extractor.AlbumCoverFetcher
import org.oxycblt.auxio.image.extractor.ArtistImageFetcher
import org.oxycblt.auxio.image.extractor.CrossfadeTransitionFactory
import org.oxycblt.auxio.image.extractor.GenreImageFetcher
import org.oxycblt.auxio.image.extractor.MusicKeyer
import org.oxycblt.auxio.settings.Settings
class AuxioApp : Application(), ImageLoaderFactory {
override fun onCreate() {
super.onCreate()
Settings(this).migrate()
// Adding static shortcuts in a dynamic manner is better than declaring them
// manually, as it will properly handle the difference between debug and release
// Auxio instances.

View file

@ -90,17 +90,17 @@ object IntegerTable {
/** DisplayMode.NONE (No Longer used but still reserved) */
// const val DISPLAY_MODE_NONE = 0xA107
/** DisplayMode.SHOW_GENRES */
const val DISPLAY_MODE_SHOW_GENRES = 0xA108
/** MusicMode._GENRES */
const val MUSIC_MODE_GENRES = 0xA108
/** DisplayMode.SHOW_ARTISTS */
const val DISPLAY_MODE_SHOW_ARTISTS = 0xA109
/** MusicMode._ARTISTS */
const val MUSIC_MODE_ARTISTS = 0xA109
/** DisplayMode.SHOW_ALBUMS */
const val DISPLAY_MODE_SHOW_ALBUMS = 0xA10A
/** MusicMode._ALBUMS */
const val MUSIC_MODE_ALBUMS = 0xA10A
/** DisplayMode.SHOW_SONGS */
const val DISPLAY_MODE_SHOW_SONGS = 0xA10B
/** MusicMode._SONGS */
const val MUSIC_MODE_SONGS = 0xA10B
// Note: Sort integer codes are non-contiguous due to significant amounts of time
// passing between the additions of new sort modes.

View file

@ -45,8 +45,6 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
*
* TODO: Add multi-select
*
* TODO: Remove asterisk imports
*
* @author OxygenCobalt
*/
class MainActivity : AppCompatActivity() {

View file

@ -34,9 +34,9 @@ import com.google.android.material.transition.MaterialFadeThrough
import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackSheetBehavior
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.queue.QueueSheetBehavior
import org.oxycblt.auxio.playback.ui.PlaybackSheetBehavior
import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment

View file

@ -37,8 +37,8 @@ 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.settings.Settings
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.canScroll

View file

@ -35,8 +35,8 @@ 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.settings.Settings
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.collect

View file

@ -38,8 +38,8 @@ 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.settings.Settings
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.application

View file

@ -36,8 +36,8 @@ 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.settings.Settings
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.collect

View file

@ -47,14 +47,14 @@ 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.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collect
@ -198,7 +198,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
item.isChecked = !item.isChecked
homeModel.updateCurrentSort(
homeModel
.getSortForDisplay(homeModel.currentTab.value)
.getSortForTab(homeModel.currentTab.value)
.withAscending(item.isChecked)
)
}
@ -207,7 +207,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
item.isChecked = true
homeModel.updateCurrentSort(
homeModel
.getSortForDisplay(homeModel.currentTab.value)
.getSortForTab(homeModel.currentTab.value)
.withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId)))
)
}
@ -216,20 +216,20 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
return true
}
private fun updateCurrentTab(tab: DisplayMode) {
private fun updateCurrentTab(tab: MusicMode) {
// Make sure that we update the scrolling view and allowed menu items whenever
// the tab changes.
val binding = requireBinding()
when (tab) {
DisplayMode.SHOW_SONGS -> {
MusicMode.SONGS -> {
updateSortMenu(tab) { id -> id != R.id.option_sort_count }
binding.homeAppbar.liftOnScrollTargetViewId = R.id.home_song_list
}
DisplayMode.SHOW_ALBUMS -> {
MusicMode.ALBUMS -> {
updateSortMenu(tab) { id -> id != R.id.option_sort_album }
binding.homeAppbar.liftOnScrollTargetViewId = R.id.home_album_list
}
DisplayMode.SHOW_ARTISTS -> {
MusicMode.ARTISTS -> {
updateSortMenu(tab) { id ->
id == R.id.option_sort_asc ||
id == R.id.option_sort_name ||
@ -238,7 +238,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
}
binding.homeAppbar.liftOnScrollTargetViewId = R.id.home_artist_list
}
DisplayMode.SHOW_GENRES -> {
MusicMode.GENRES -> {
updateSortMenu(tab) { id ->
id == R.id.option_sort_asc ||
id == R.id.option_sort_name ||
@ -250,9 +250,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
}
}
private fun updateSortMenu(displayMode: DisplayMode, isVisible: (Int) -> Boolean) {
private fun updateSortMenu(mode: MusicMode, isVisible: (Int) -> Boolean) {
val sortMenu = requireNotNull(sortItem.subMenu)
val toHighlight = homeModel.getSortForDisplay(displayMode)
val toHighlight = homeModel.getSortForTab(mode)
for (option in sortMenu) {
if (option.itemId == toHighlight.mode.itemId) {
@ -434,10 +434,10 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
override fun createFragment(position: Int): Fragment {
return when (homeModel.tabs[position]) {
DisplayMode.SHOW_SONGS -> SongListFragment()
DisplayMode.SHOW_ALBUMS -> AlbumListFragment()
DisplayMode.SHOW_ARTISTS -> ArtistListFragment()
DisplayMode.SHOW_GENRES -> GenreListFragment()
MusicMode.SONGS -> SongListFragment()
MusicMode.ALBUMS -> AlbumListFragment()
MusicMode.ARTISTS -> ArtistListFragment()
MusicMode.GENRES -> GenreListFragment()
}
}
}

View file

@ -26,11 +26,11 @@ 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.settings.Settings
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.util.application
import org.oxycblt.auxio.util.logD
@ -59,15 +59,15 @@ class HomeViewModel(application: Application) :
val genres: StateFlow<List<Genre>>
get() = _genres
var tabs: List<DisplayMode> = visibleTabs
var tabs: List<MusicMode> = visibleTabs
private set
/** Internal getter for getting the visible library tabs */
private val visibleTabs: List<DisplayMode>
private val visibleTabs: List<MusicMode>
get() = settings.libTabs.filterIsInstance<Tab.Visible>().map { it.mode }
private val _currentTab = MutableStateFlow(tabs[0])
val currentTab: StateFlow<DisplayMode> = _currentTab
val currentTab: StateFlow<MusicMode> = _currentTab
/**
* Marker to recreate all library tabs, usually initiated by a settings change. When this flag
@ -93,32 +93,32 @@ class HomeViewModel(application: Application) :
_shouldRecreateTabs.value = false
}
/** Get the specific sort for the given [DisplayMode]. */
fun getSortForDisplay(displayMode: DisplayMode) =
when (displayMode) {
DisplayMode.SHOW_SONGS -> settings.libSongSort
DisplayMode.SHOW_ALBUMS -> settings.libAlbumSort
DisplayMode.SHOW_ARTISTS -> settings.libArtistSort
DisplayMode.SHOW_GENRES -> settings.libGenreSort
/** Get the specific sort for the given [MusicMode]. */
fun getSortForTab(tabMode: MusicMode): Sort =
when (tabMode) {
MusicMode.SONGS -> settings.libSongSort
MusicMode.ALBUMS -> settings.libAlbumSort
MusicMode.ARTISTS -> settings.libArtistSort
MusicMode.GENRES -> settings.libGenreSort
}
/** Update the currently displayed item's [Sort]. */
fun updateCurrentSort(sort: Sort) {
logD("Updating ${_currentTab.value} sort to $sort")
when (_currentTab.value) {
DisplayMode.SHOW_SONGS -> {
MusicMode.SONGS -> {
settings.libSongSort = sort
_songs.value = sort.songs(_songs.value)
}
DisplayMode.SHOW_ALBUMS -> {
MusicMode.ALBUMS -> {
settings.libAlbumSort = sort
_albums.value = sort.albums(_albums.value)
}
DisplayMode.SHOW_ARTISTS -> {
MusicMode.ARTISTS -> {
settings.libArtistSort = sort
_artists.value = sort.artists(_artists.value)
}
DisplayMode.SHOW_GENRES -> {
MusicMode.GENRES -> {
settings.libGenreSort = sort
_genres.value = sort.genres(_genres.value)
}

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.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.AlbumViewHolder
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item
@ -62,7 +62,7 @@ class AlbumListFragment : HomeListFragment<Album>() {
val album = homeModel.albums.value[pos]
// Change how we display the popup depending on the mode.
return when (homeModel.getSortForDisplay(DisplayMode.SHOW_ALBUMS).mode) {
return when (homeModel.getSortForTab(MusicMode.ALBUMS).mode) {
// By Name -> Use Name
is Sort.Mode.ByName -> album.collationKey?.run { sourceString.first().uppercase() }

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.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item
@ -57,7 +57,7 @@ class ArtistListFragment : HomeListFragment<Artist>() {
val artist = homeModel.artists.value[pos]
// Change how we display the popup depending on the mode.
return when (homeModel.getSortForDisplay(DisplayMode.SHOW_ARTISTS).mode) {
return when (homeModel.getSortForTab(MusicMode.ARTISTS).mode) {
// By Name -> Use Name
is Sort.Mode.ByName -> artist.collationKey?.run { sourceString.first().uppercase() }

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.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.GenreViewHolder
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item
@ -57,7 +57,7 @@ class GenreListFragment : HomeListFragment<Genre>() {
val genre = homeModel.genres.value[pos]
// Change how we display the popup depending on the mode.
return when (homeModel.getSortForDisplay(DisplayMode.SHOW_GENRES).mode) {
return when (homeModel.getSortForTab(MusicMode.GENRES).mode) {
// By Name -> Use Name
is Sort.Mode.ByName -> genre.collationKey?.run { sourceString.first().uppercase() }

View file

@ -23,13 +23,13 @@ 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.settings.Settings
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener
@ -72,7 +72,7 @@ class SongListFragment : HomeListFragment<Song>() {
// Change how we display the popup depending on the mode.
// Note: We don't use the more correct individual artist name here, as sorts are largely
// based off the names of the parent objects and not the child objects.
return when (homeModel.getSortForDisplay(DisplayMode.SHOW_SONGS).mode) {
return when (homeModel.getSortForTab(MusicMode.SONGS).mode) {
// Name -> Use name
is Sort.Mode.ByName -> song.collationKey?.run { sourceString.first().uppercase() }

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.ui.DisplayMode
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.util.logE
/**
@ -40,16 +40,16 @@ import org.oxycblt.auxio.util.logE
* VTTT
*
* Where V is a bit representing the visibility and T is a 3-bit integer representing the
* [DisplayMode] ordinal for this tab.
* [MusicMode] ordinal for this tab.
*
* To serialize and deserialize a tab sequence, [toSequence] and [fromSequence] can be used
* respectively.
*
* By default, the tab order will be SONGS, ALBUMS, ARTISTS, GENRES, PLAYLISTS
*/
sealed class Tab(open val mode: DisplayMode) {
data class Visible(override val mode: DisplayMode) : Tab(mode)
data class Invisible(override val mode: DisplayMode) : Tab(mode)
sealed class Tab(open val mode: MusicMode) {
data class Visible(override val mode: MusicMode) : Tab(mode)
data class Invisible(override val mode: MusicMode) : Tab(mode)
companion object {
/** The length a well-formed tab sequence should be */
@ -59,14 +59,14 @@ sealed class Tab(open val mode: DisplayMode) {
const val SEQUENCE_DEFAULT = 0b1000_1001_1010_1011_0100
/**
* Maps between the integer code in the tab sequence and the actual [DisplayMode] instance.
* Maps between the integer code in the tab sequence and the actual [MusicMode] instance.
*/
private val MODE_TABLE =
arrayOf(
DisplayMode.SHOW_SONGS,
DisplayMode.SHOW_ALBUMS,
DisplayMode.SHOW_ARTISTS,
DisplayMode.SHOW_GENRES
MusicMode.SONGS,
MusicMode.ALBUMS,
MusicMode.ARTISTS,
MusicMode.GENRES
)
/** Convert an array [tabs] into a sequence of tabs. */

View file

@ -23,7 +23,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemTabBinding
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.ui.recycler.DialogViewHolder
import org.oxycblt.auxio.util.inflater
@ -59,7 +59,7 @@ class TabAdapter(private val listener: Listener) : RecyclerView.Adapter<TabViewH
}
interface Listener {
fun onVisibilityToggled(displayMode: DisplayMode)
fun onVisibilityToggled(mode: MusicMode)
fun onPickUpTab(viewHolder: RecyclerView.ViewHolder)
}

View file

@ -25,8 +25,8 @@ 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.settings.Settings
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.logD
@ -79,11 +79,8 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
binding.tabRecycler.adapter = null
}
override fun onVisibilityToggled(displayMode: DisplayMode) {
// Tab viewholders bind with the initial tab state, which will drift from the actual
// state of the tabs over editing. So, this callback simply provides the displayMode
// for us to locate within the data and then update.
val index = tabAdapter.tabs.indexOfFirst { it.mode == displayMode }
override fun onVisibilityToggled(mode: MusicMode) {
val index = tabAdapter.tabs.indexOfFirst { it.mode == mode }
if (index > -1) {
val tab = tabAdapter.tabs[index]
tabAdapter.setTab(

View file

@ -24,6 +24,7 @@ import coil.imageLoader
import coil.request.Disposable
import coil.request.ImageRequest
import coil.size.Size
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
import org.oxycblt.auxio.music.Song
/**

View file

@ -66,6 +66,18 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
}
}
var isPlaying: Boolean
get() = drawable == playingIndicatorDrawable
set(value) {
if (value) {
playingIndicatorDrawable.start()
setImageDrawable(playingIndicatorDrawable)
} else {
playingIndicatorDrawable.stop()
setImageDrawable(pausedIndicatorDrawable)
}
}
init {
// Use clipToOutline and a background drawable to crop images. While Coil's transformation
// could theoretically be used to round corners, the corner radius is dependent on the
@ -87,15 +99,13 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// Emulate StyledDrawable scaling with matrix scaling.
val iconSize = max(measuredWidth, measuredHeight) / 2
imageMatrix =
indicatorMatrix.apply {
reset()
drawable?.let { drawable ->
// Android is too good to allow us to set a fixed image size, so we instead need
// to define a matrix to scale an image directly.
// First scale the icon up to the desired size.
indicatorMatrixSrc.set(
0f,
@ -119,16 +129,4 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
}
}
}
var isPlaying: Boolean
get() = drawable == playingIndicatorDrawable
set(value) {
if (value) {
playingIndicatorDrawable.start()
setImageDrawable(playingIndicatorDrawable)
} else {
playingIndicatorDrawable.stop()
setImageDrawable(pausedIndicatorDrawable)
}
}
}

View file

@ -33,6 +33,7 @@ import coil.dispose
import coil.load
import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.image
package org.oxycblt.auxio.image.extractor
import android.content.Context
import android.graphics.Bitmap

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.image
package org.oxycblt.auxio.image.extractor
import android.content.Context
import coil.ImageLoader
@ -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.ui.Sort
import org.oxycblt.auxio.music.Sort
import kotlin.math.min
/** A basic keyer for music data. */

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.image
package org.oxycblt.auxio.image.extractor
import coil.decode.DataSource
import coil.drawable.CrossfadeDrawable

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.image
package org.oxycblt.auxio.image.extractor
import android.graphics.Bitmap
import coil.size.Size

View file

@ -26,7 +26,6 @@ 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.ui.Sort
import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.inRangeOrNull
import org.oxycblt.auxio.util.nonZeroOrNull
@ -59,10 +58,12 @@ sealed class Music : Item {
/**
* A key used by the sorting system that takes into account the sort tags of this item,
* any (english) articles that prefix the names, and collation rules. Lazily generated
* since generating a collation key is non-trivial.
* any (english) articles that prefix the names, and collation rules.
*/
val collationKey: CollationKey? by lazy {
// Ideally, we would generate this on creation, but this is an abstract class, which
// requires us to generate it lazily instead.
val sortName = (rawSortName ?: rawName)?.run {
when {
length > 5 && startsWith("the ", ignoreCase = true) -> substring(4)
@ -125,7 +126,8 @@ sealed class Music : Item {
*/
fun hashed(clazz: KClass<*>, 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
// 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()

View file

@ -0,0 +1,66 @@
/*
* 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.music
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
enum class MusicMode {
SONGS,
ALBUMS,
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) {
SONGS -> IntegerTable.MUSIC_MODE_SONGS
ALBUMS -> IntegerTable.MUSIC_MODE_ALBUMS
ARTISTS -> IntegerTable.MUSIC_MODE_ARTISTS
GENRES -> IntegerTable.MUSIC_MODE_GENRES
}
companion object {
fun fromInt(value: Int) =
when (value) {
IntegerTable.MUSIC_MODE_SONGS -> SONGS
IntegerTable.MUSIC_MODE_ALBUMS -> ALBUMS
IntegerTable.MUSIC_MODE_ARTISTS -> ARTISTS
IntegerTable.MUSIC_MODE_GENRES -> GENRES
else -> null
}
}
}

View file

@ -15,18 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.ui
package org.oxycblt.auxio.music
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
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.ui.Sort.Mode
import org.oxycblt.auxio.music.Sort.Mode
/**
* Represents the sort modes used in Auxio.

View file

@ -22,14 +22,12 @@ import org.oxycblt.auxio.music.Song
/** TODO: Stub class, not implemented yet */
class CacheLayer {
fun init() {
// STUB: Add cache database
}
/**
* Write a list of newly-indexed raw songs to the database.
*/
fun finalize(rawSongs: List<Song.Raw>) {
// STUB: Add cache database
}
/**

View file

@ -55,7 +55,7 @@ import java.io.File
* straightforward for this contract that was dropped on it's head as a baby. So instead, you have
* to query for each genre, query all the songs in each genre, and then iterate through those songs
* to link every song with their genre. This is not documented anywhere, and the O(mom im scared)
* algorithm you have to run to get it working single-handedly DOUBLES Auxio's loading times. At no
* algorithm you have to run to get it working single-handedly DOUBLES Auxio's query times. At no
* point have the devs considered that this system is absolutely insane, and instead focused on
* adding infuriat- I mean nice proprietary extensions to MediaStore for their own Google Play
* Music, and of course every Google Play Music user knew how great that turned out!
@ -347,6 +347,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
raw.albumArtistNames =
cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings)
// Get the genre value we had to query for in initialization
raw.genreNames = genreNamesMap[raw.mediaStoreId]
}
@ -381,6 +382,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
/**
* A [MediaStoreLayer] that completes the music loading process in a way compatible from
* API 21 onwards to API 29.
* @author OxygenCobalt
*/
class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :

View file

@ -247,6 +247,9 @@ class Task(context: Context, private val settings: Settings, private val raw: So
tags["TORY"]?.run { get(0).toIntOrNull() }
?: tags["TYER"]?.run { get(0).toIntOrNull() } ?: return null
// Assume that TDAT/TIME can refer to TYER or TORY depending on if TORY
// is present.
val tdat = tags["TDAT"]
return if (tdat != null && tdat[0].length == 4 && tdat[0].isDigitsOnly()) {
val mm = tdat[0].substring(0..1).toInt()

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.ui.Sort
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.logW

View file

@ -64,7 +64,7 @@ class IndexingNotification(private val context: Context) :
lastUpdateTime = SystemClock.elapsedRealtime()
// Only update the notification every two seconds to prevent rate-limiting.
// Only update the notification every 1.5s to prevent rate-limiting.
logD("Updating state to $indexing")
setContentText(
context.getString(R.string.fmt_indexing, indexing.current, indexing.total)

View file

@ -1,53 +0,0 @@
/*
* Copyright (c) 2021 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.playback
import org.oxycblt.auxio.IntegerTable
/**
* Enum that indicates how the queue should be constructed.
* @author OxygenCobalt
*/
enum class PlaybackMode {
/** Construct the queue from the genre's songs */
ALL_SONGS,
/** Construct the queue from the artist's songs */
IN_ALBUM,
/** Construct the queue from the album's songs */
IN_ARTIST,
/** Construct the queue from all songs */
IN_GENRE;
companion object {
/**
* Get a [PlaybackMode] for an int [constant]
* @return The mode, null if there isn't one for this.
*/
fun fromInt(constant: Int) =
when (constant) {
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

@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.msToDs
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.playback.ui.StyledSeekBar
import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.util.collectImmediately

View file

@ -28,6 +28,7 @@ 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
@ -92,15 +93,15 @@ class PlaybackViewModel(application: Application) :
// --- PLAYING FUNCTIONS ---
/** Play a [song] with the [mode] specified, */
fun play(song: Song, mode: PlaybackMode) {
fun play(song: Song, mode: MusicMode) {
// TODO: Remove this function when selection is implemented
val parent =
when (mode) {
PlaybackMode.IN_ALBUM -> song.album
PlaybackMode.IN_ARTIST -> song.album.artist
PlaybackMode.IN_GENRE -> song.genres.maxBy { it.songs.size }
PlaybackMode.ALL_SONGS -> null
MusicMode.GENRES -> song.album
MusicMode.ARTISTS -> song.album.artist
MusicMode.ALBUMS -> song.genres.maxBy { it.songs.size }
MusicMode.SONGS -> null
}
playbackManager.play(song, parent, settings)

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback
package org.oxycblt.auxio.playback.ui
import android.animation.ValueAnimator
import android.content.Context

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback
package org.oxycblt.auxio.playback.ui
import android.content.Context
import android.util.AttributeSet

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback
package org.oxycblt.auxio.playback.ui
import android.content.Context
import android.graphics.drawable.LayerDrawable

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback
package org.oxycblt.auxio.playback.ui
import android.content.Context
import android.util.AttributeSet
@ -67,7 +67,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
// this component.
val from = max(value, 0)
// Sanity check: Ensure that this value is within the duration and will not crash
// Sanity check 2: Ensure that this value is within the duration and will not crash
// the app, and that the user is not currently seeking (which would cause the SeekBar
// to jump around).
if (from <= durationDs && !isActivated) {

View file

@ -35,6 +35,7 @@ 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.settings.Settings
@ -78,7 +79,15 @@ class SearchFragment :
override fun onBindingCreated(binding: FragmentSearchBinding, savedInstanceState: Bundle?) {
binding.searchToolbar.apply {
menu.findItem(searchModel.filterMode?.itemId ?: R.id.option_filter_all).isChecked = true
val itemIdToSelect = when (searchModel.filterMode) {
MusicMode.SONGS -> R.id.option_filter_songs
MusicMode.ALBUMS -> R.id.option_filter_albums
MusicMode.ARTISTS -> R.id.option_filter_artists
MusicMode.GENRES -> R.id.option_filter_genres
null -> R.id.option_filter_all
}
menu.findItem(itemIdToSelect).isChecked = true
setNavigationOnClickListener {
imm.hide()

View file

@ -33,11 +33,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.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.application
@ -59,7 +59,7 @@ class SearchViewModel(application: Application) :
val searchResults: StateFlow<List<Item>>
get() = _searchResults
val filterMode: DisplayMode?
val filterMode: MusicMode?
get() = settings.searchFilterMode
private var lastQuery: String? = null
@ -89,28 +89,28 @@ class SearchViewModel(application: Application) :
// Note: a filter mode of null means to not filter at all.
if (filterMode == null || filterMode == DisplayMode.SHOW_ARTISTS) {
if (filterMode == null || filterMode == MusicMode.ARTISTS) {
library.artists.filterArtistsBy(query)?.let { artists ->
results.add(Header(R.string.lbl_artists))
results.addAll(sort.artists(artists))
}
}
if (filterMode == null || filterMode == DisplayMode.SHOW_ALBUMS) {
if (filterMode == null || filterMode == MusicMode.ALBUMS) {
library.albums.filterAlbumsBy(query)?.let { albums ->
results.add(Header(R.string.lbl_albums))
results.addAll(sort.albums(albums))
}
}
if (filterMode == null || filterMode == DisplayMode.SHOW_GENRES) {
if (filterMode == null || filterMode == MusicMode.GENRES) {
library.genres.filterGenresBy(query)?.let { genres ->
results.add(Header(R.string.lbl_genres))
results.addAll(sort.genres(genres))
}
}
if (filterMode == null || filterMode == DisplayMode.SHOW_SONGS) {
if (filterMode == null || filterMode == MusicMode.SONGS) {
library.songs.filterSongsBy(query)?.let { songs ->
results.add(Header(R.string.lbl_songs))
results.addAll(sort.songs(songs))
@ -128,10 +128,10 @@ class SearchViewModel(application: Application) :
fun updateFilterModeWithId(@IdRes id: Int) {
val newFilterMode =
when (id) {
R.id.option_filter_songs -> DisplayMode.SHOW_SONGS
R.id.option_filter_albums -> DisplayMode.SHOW_ALBUMS
R.id.option_filter_artists -> DisplayMode.SHOW_ARTISTS
R.id.option_filter_genres -> DisplayMode.SHOW_GENRES
R.id.option_filter_songs -> MusicMode.SONGS
R.id.option_filter_albums -> MusicMode.ALBUMS
R.id.option_filter_artists -> MusicMode.ARTISTS
R.id.option_filter_genres -> MusicMode.GENRES
else -> null
}

View file

@ -21,21 +21,21 @@ import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.os.storage.StorageManager
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import androidx.preference.PreferenceManager
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.playback.BarAction
import org.oxycblt.auxio.playback.PlaybackMode
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.accent.Accent
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull
/**
@ -57,6 +57,67 @@ class Settings(private val context: Context, private val callback: Callback? = n
}
}
/**
* Try to migrate shared preference keys to their new versions. Only intended for use by
* AuxioApp. Compat code will persist for 6 months before being removed.
*/
fun migrate() {
if (inner.contains(OldKeys.KEY_ACCENT3)) {
logD("Migrating ${OldKeys.KEY_ACCENT3}")
var accent = inner.getInt(OldKeys.KEY_ACCENT3, 5)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Accents were previously frozen as soon as the OS was updated to android twelve,
// as dynamic colors were enabled by default. This is no longer the case, so we need
// to re-update the setting to dynamic colors here.
accent = 16
}
inner.edit {
putInt(context.getString(R.string.set_accent), accent)
remove(OldKeys.KEY_ACCENT3)
apply()
}
}
fun Int.migratePlaybackMode() =
when (this) {
IntegerTable.PLAYBACK_MODE_ALL_SONGS -> MusicMode.SONGS
IntegerTable.PLAYBACK_MODE_IN_GENRE -> MusicMode.GENRES
IntegerTable.PLAYBACK_MODE_IN_ARTIST -> MusicMode.ARTISTS
IntegerTable.PLAYBACK_MODE_IN_ALBUM -> MusicMode.ALBUMS
else -> null
}
if (inner.contains(OldKeys.KEY_LIB_PLAYBACK_MODE)) {
logD("Migrating ${OldKeys.KEY_LIB_PLAYBACK_MODE}")
val mode = inner.getInt(
OldKeys.KEY_LIB_PLAYBACK_MODE,
IntegerTable.PLAYBACK_MODE_ALL_SONGS
).migratePlaybackMode() ?: MusicMode.SONGS
inner.edit {
putInt(context.getString(R.string.set_key_library_song_playback_mode), mode.intCode)
remove(OldKeys.KEY_LIB_PLAYBACK_MODE)
}
}
if (inner.contains(OldKeys.KEY_DETAIL_PLAYBACK_MODE)) {
logD("Migrating ${OldKeys.KEY_DETAIL_PLAYBACK_MODE}")
val mode = inner.getInt(OldKeys.KEY_DETAIL_PLAYBACK_MODE, Int.MIN_VALUE).migratePlaybackMode()
inner.edit {
putInt(
context.getString(R.string.set_key_detail_song_playback_mode),
mode?.intCode ?: Int.MIN_VALUE
)
remove(OldKeys.KEY_DETAIL_PLAYBACK_MODE)
}
}
}
fun release() {
inner.unregisterOnSharedPreferenceChangeListener(this)
}
@ -86,7 +147,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
/** The current accent. */
var accent: Accent
get() = handleAccentCompat(context, inner)
get() = Accent.from(inner.getInt(context.getString(R.string.set_accent), Accent.DEFAULT))
set(value) {
inner.edit {
putInt(context.getString(R.string.set_key_accent), value.index)
@ -163,23 +224,23 @@ class Settings(private val context: Context, private val callback: Callback? = n
}
/** What queue to create when a song is selected from the library or search */
val libPlaybackMode: PlaybackMode
val libPlaybackMode: MusicMode
get() =
PlaybackMode.fromInt(
MusicMode.fromInt(
inner.getInt(
context.getString(R.string.set_key_library_song_playback_mode),
Int.MIN_VALUE
)
)
?: PlaybackMode.ALL_SONGS
?: MusicMode.SONGS
/**
* What queue t create when a song is selected from an album/artist/genre. Null means to default
* to the currently shown item.
*/
val detailPlaybackMode: PlaybackMode?
val detailPlaybackMode: MusicMode?
get() =
PlaybackMode.fromInt(
MusicMode.fromInt(
inner.getInt(
context.getString(R.string.set_key_detail_song_playback_mode),
Int.MIN_VALUE
@ -246,9 +307,9 @@ class Settings(private val context: Context, private val callback: Callback? = n
}
/** The current filter mode of the search tab */
var searchFilterMode: DisplayMode?
var searchFilterMode: MusicMode?
get() =
DisplayMode.fromInt(
MusicMode.fromInt(
inner.getInt(context.getString(R.string.set_key_search_filter), Int.MIN_VALUE)
)
set(value) {
@ -370,35 +431,11 @@ class Settings(private val context: Context, private val callback: Callback? = n
apply()
}
}
}
// --- COMPAT ---
fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent {
val currentKey = context.getString(R.string.set_key_accent)
if (prefs.contains(OldKeys.KEY_ACCENT3)) {
Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT3}")
var accent = prefs.getInt(OldKeys.KEY_ACCENT3, 5)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Accents were previously frozen as soon as the OS was updated to android twelve,
// as dynamic colors were enabled by default. This is no longer the case, so we need
// to re-update the setting to dynamic colors here.
accent = 16
}
prefs.edit {
putInt(currentKey, accent)
remove(OldKeys.KEY_ACCENT3)
apply()
}
/** Cache of the old keys used in Auxio. */
private object OldKeys {
const val KEY_ACCENT3 = "auxio_accent"
const val KEY_LIB_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2"
const val KEY_DETAIL_PLAYBACK_MODE = "auxio_detail_song_play_mode"
}
return Accent.from(prefs.getInt(currentKey, Accent.DEFAULT))
}
/** Cache of the old keys used in Auxio. */
private object OldKeys {
const val KEY_ACCENT3 = "auxio_accent"
}

View file

@ -1,87 +0,0 @@
/*
* Copyright (c) 2021 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.ui
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
/**
* An enum for determining what items to show in a given list. Note: **DO NOT RE-ARRANGE THE ENUM**.
* The ordinals are used to store library tabs, so doing changing them would also change the meaning
* of tab instances.
* @author OxygenCobalt
*/
enum class DisplayMode {
SHOW_SONGS,
SHOW_ALBUMS,
SHOW_ARTISTS,
SHOW_GENRES;
val string: Int
get() =
when (this) {
SHOW_SONGS -> R.string.lbl_songs
SHOW_ALBUMS -> R.string.lbl_albums
SHOW_ARTISTS -> R.string.lbl_artists
SHOW_GENRES -> R.string.lbl_genres
}
val icon: Int
get() =
when (this) {
SHOW_SONGS -> R.drawable.ic_song_24
SHOW_ALBUMS -> R.drawable.ic_album_24
SHOW_ARTISTS -> R.drawable.ic_artist_24
SHOW_GENRES -> R.drawable.ic_genre_24
}
val itemId: Int
get() =
when (this) {
SHOW_SONGS -> R.id.option_filter_songs
SHOW_ALBUMS -> R.id.option_filter_albums
SHOW_ARTISTS -> R.id.option_filter_artists
SHOW_GENRES -> R.id.option_filter_genres
}
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 fromInt(value: Int): DisplayMode? {
return when (value) {
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

@ -24,7 +24,7 @@ import coil.request.ImageRequest
import coil.transform.RoundedCornersTransformation
import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.image.SquareFrameTransform
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.InternalPlayer

View file

@ -64,7 +64,7 @@
tools:text="Album Name" />
<org.oxycblt.auxio.playback.StyledSeekBar
<org.oxycblt.auxio.playback.ui.StyledSeekBar
android:id="@+id/playback_seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -73,7 +73,7 @@
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />
<org.oxycblt.auxio.playback.ForcedLTRFrameLayout
<org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout
android:id="@+id/playback_controls_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -111,7 +111,7 @@
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
<org.oxycblt.auxio.playback.AnimatedMaterialButton
<org.oxycblt.auxio.playback.ui.AnimatedMaterialButton
android:id="@+id/playback_play_pause"
style="@style/Widget.Auxio.Button.PlayPause"
android:layout_width="wrap_content"
@ -151,6 +151,6 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</org.oxycblt.auxio.playback.ForcedLTRFrameLayout>
</org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -64,7 +64,7 @@
tools:text="Album Name" />
<org.oxycblt.auxio.playback.StyledSeekBar
<org.oxycblt.auxio.playback.ui.StyledSeekBar
android:id="@+id/playback_seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -75,7 +75,7 @@
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />
<org.oxycblt.auxio.playback.ForcedLTRFrameLayout
<org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout
android:id="@+id/playback_controls_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -114,7 +114,7 @@
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
<org.oxycblt.auxio.playback.AnimatedMaterialButton
<org.oxycblt.auxio.playback.ui.AnimatedMaterialButton
android:id="@+id/playback_play_pause"
style="@style/Widget.Auxio.Button.PlayPause"
android:layout_width="wrap_content"
@ -154,6 +154,6 @@
app:tint="@color/sel_accented" />
</androidx.constraintlayout.widget.ConstraintLayout>
</org.oxycblt.auxio.playback.ForcedLTRFrameLayout>
</org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -20,7 +20,7 @@
android:id="@+id/playback_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="org.oxycblt.auxio.playback.PlaybackSheetBehavior">
app:layout_behavior="org.oxycblt.auxio.playback.ui.PlaybackSheetBehavior">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/playback_bar_fragment"

View file

@ -22,7 +22,7 @@
style="@style/Widget.Auxio.DisableDropShadows"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="org.oxycblt.auxio.playback.PlaybackSheetBehavior">
app:layout_behavior="org.oxycblt.auxio.playback.ui.PlaybackSheetBehavior">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/playback_bar_fragment"

View file

@ -43,7 +43,7 @@
app:layout_constraintTop_toBottomOf="@+id/playback_song"
tools:text="Artist Name / Album Name" />
<org.oxycblt.auxio.playback.ForcedLTRFrameLayout
<org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout
android:id="@+id/playback_controls_wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -73,9 +73,9 @@
tools:icon="@drawable/ic_skip_next_24" />
</LinearLayout>
</org.oxycblt.auxio.playback.ForcedLTRFrameLayout>
</org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout>
<org.oxycblt.auxio.playback.ForcedLTRFrameLayout
<org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout
android:id="@+id/playback_progress_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -91,6 +91,6 @@
android:layout_marginEnd="@dimen/spacing_small"
tools:progress="70" />
</org.oxycblt.auxio.playback.ForcedLTRFrameLayout>
</org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -78,7 +78,7 @@
app:layout_constraintTop_toBottomOf="@+id/playback_artist"
tools:text="Album Name" />
<org.oxycblt.auxio.playback.StyledSeekBar
<org.oxycblt.auxio.playback.ui.StyledSeekBar
android:id="@+id/playback_seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -87,7 +87,7 @@
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />
<org.oxycblt.auxio.playback.ForcedLTRFrameLayout
<org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout
android:id="@+id/playback_controls_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -126,7 +126,7 @@
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
<org.oxycblt.auxio.playback.AnimatedMaterialButton
<org.oxycblt.auxio.playback.ui.AnimatedMaterialButton
android:id="@+id/playback_play_pause"
style="@style/Widget.Auxio.Button.PlayPause"
android:layout_width="wrap_content"
@ -166,7 +166,7 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</org.oxycblt.auxio.playback.ForcedLTRFrameLayout>
</org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -20,8 +20,8 @@
<string name="set_key_pre_amp_with" translatable="false">auxio_pre_amp_with</string>
<string name="set_key_pre_amp_without" translatable="false">auxio_pre_amp_without</string>
<string name="set_key_library_song_playback_mode" translatable="false">KEY_SONG_PLAY_MODE2</string>
<string name="set_key_detail_song_playback_mode" translatable="false">auxio_detail_song_play_mode</string>
<string name="set_key_library_song_playback_mode" translatable="false">auxio_library_playback_mode</string>
<string name="set_key_detail_song_playback_mode" translatable="false">auxio_detail_playback_mode</string>
<string name="set_key_keep_shuffle" translatable="false">KEY_KEEP_SHUFFLE</string>
<string name="set_key_rewind_prev" translatable="false">KEY_PREV_REWIND</string>
<string name="set_key_repeat_pause" translatable="false">KEY_LOOP_PAUSE</string>
@ -127,10 +127,10 @@
<integer name="bar_action_shuffle">0xA11B</integer>
<integer name="play_mode_none">-2147483648</integer>
<integer name="play_mode_genre">0xA103</integer>
<integer name="play_mode_artist">0xA104</integer>
<integer name="play_mode_album">0xA105</integer>
<integer name="play_mode_songs">0xA106</integer>
<integer name="play_mode_genre">0xA108</integer>
<integer name="play_mode_artist">0xA109</integer>
<integer name="play_mode_album">0xA10A</integer>
<integer name="play_mode_songs">0xA10B</integer>
<integer name="replay_gain_track">0xA111</integer>
<integer name="replay_gain_album">0xA112</integer>

View file

@ -120,7 +120,7 @@ also the `detail`-specific `DiscHeader` and `SortHeader`, however these are larg
Other data types represent a specific UI configuration or state:
- Data structures like `Sort` contain an ascending state that can be modified immutably.
- Enums like `DisplayMode` and `RepeatMode` only contain static data, such as a string resource.
- Enums like `MusicMode` and `RepeatMode` only contain static data, such as a string resource.
Things to keep in mind while working with music data:
- `id` is not derived from the `MediaStore` ID of the music data. It is actually a hash of the
@ -253,17 +253,15 @@ The major classes are:
library should use this.
- `Indexer`, which manages how music is loaded. This is only used by code that must manage or
mirror the music loading state.
- The extractor system, which is Auxio's music parser. It's structured as several "Layer" classes
that build on eachother to implement version-specific functionality.
Internally, there are several other major systems:
- `IndexerService`, which does the indexer work in the background.
- `Indexer.Backend` implementations, which actually talk to the media database and load music.
As it stands, there are two classes of backend:
- Version-specific `MediaStoreBackend` implementations, which transform the (often insane)
music data from Android into a usable `Song`.
- `ExoPlayerBackend`, which mutates audio with extracted ID3v2 and Vorbis tags. This enables
some extra features and side-steps unfixable issues with `MediaStore`
- `StorageFramework`, which is a group of utilities that allows Auxio to be volume-aware and to
work with both extension-based and format-based mime types.
- Configuration models like `MusicMode` and `Sort`, which are tangentally related to operations
done on music
The music loading process is roughly as follows:
1. Something triggers `IndexerService` to start indexing, either by the UI or by the service itself
@ -313,8 +311,6 @@ Shared views and view configuration models. This contains:
- Important `Fragment` superclasses like `ViewBindingFragment` and `MenuFragment`
- Customized views such as `AuxioAppBarLayout`, and others, which fix shortcomings with the
default implementations.
- Configuration models like `DisplayMode` and `Sort`, which are used in many places but aren't tied
to a specific feature.
- `ForegroundManager` and `ServiceNotification`, which remove boilerplate regarding service
foreground instantiation.
- The `RecyclerView` adapter framework described previously.