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.ImageLoader
import coil.ImageLoaderFactory import coil.ImageLoaderFactory
import coil.request.CachePolicy import coil.request.CachePolicy
import org.oxycblt.auxio.image.AlbumCoverFetcher import org.oxycblt.auxio.image.extractor.AlbumCoverFetcher
import org.oxycblt.auxio.image.ArtistImageFetcher import org.oxycblt.auxio.image.extractor.ArtistImageFetcher
import org.oxycblt.auxio.image.CrossfadeTransitionFactory import org.oxycblt.auxio.image.extractor.CrossfadeTransitionFactory
import org.oxycblt.auxio.image.GenreImageFetcher import org.oxycblt.auxio.image.extractor.GenreImageFetcher
import org.oxycblt.auxio.image.MusicKeyer import org.oxycblt.auxio.image.extractor.MusicKeyer
import org.oxycblt.auxio.settings.Settings
class AuxioApp : Application(), ImageLoaderFactory { class AuxioApp : Application(), ImageLoaderFactory {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Settings(this).migrate()
// Adding static shortcuts in a dynamic manner is better than declaring them // Adding static shortcuts in a dynamic manner is better than declaring them
// manually, as it will properly handle the difference between debug and release // manually, as it will properly handle the difference between debug and release
// Auxio instances. // Auxio instances.

View file

@ -90,17 +90,17 @@ object IntegerTable {
/** DisplayMode.NONE (No Longer used but still reserved) */ /** DisplayMode.NONE (No Longer used but still reserved) */
// const val DISPLAY_MODE_NONE = 0xA107 // const val DISPLAY_MODE_NONE = 0xA107
/** DisplayMode.SHOW_GENRES */ /** MusicMode._GENRES */
const val DISPLAY_MODE_SHOW_GENRES = 0xA108 const val MUSIC_MODE_GENRES = 0xA108
/** DisplayMode.SHOW_ARTISTS */ /** MusicMode._ARTISTS */
const val DISPLAY_MODE_SHOW_ARTISTS = 0xA109 const val MUSIC_MODE_ARTISTS = 0xA109
/** DisplayMode.SHOW_ALBUMS */ /** MusicMode._ALBUMS */
const val DISPLAY_MODE_SHOW_ALBUMS = 0xA10A const val MUSIC_MODE_ALBUMS = 0xA10A
/** DisplayMode.SHOW_SONGS */ /** MusicMode._SONGS */
const val DISPLAY_MODE_SHOW_SONGS = 0xA10B const val MUSIC_MODE_SONGS = 0xA10B
// Note: Sort integer codes are non-contiguous due to significant amounts of time // Note: Sort integer codes are non-contiguous due to significant amounts of time
// passing between the additions of new sort modes. // 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: Add multi-select
* *
* TODO: Remove asterisk imports
*
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class MainActivity : AppCompatActivity() { 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.databinding.FragmentMainBinding
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackSheetBehavior
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.queue.QueueSheetBehavior 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.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment 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.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.canScroll 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.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.collect 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.MusicStore
import org.oxycblt.auxio.music.ReleaseType import org.oxycblt.auxio.music.ReleaseType
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.settings.Settings 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.Header
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.application 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.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.collect 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.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.system.Indexer import org.oxycblt.auxio.music.system.Indexer
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collect
@ -198,7 +198,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
homeModel.updateCurrentSort( homeModel.updateCurrentSort(
homeModel homeModel
.getSortForDisplay(homeModel.currentTab.value) .getSortForTab(homeModel.currentTab.value)
.withAscending(item.isChecked) .withAscending(item.isChecked)
) )
} }
@ -207,7 +207,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
item.isChecked = true item.isChecked = true
homeModel.updateCurrentSort( homeModel.updateCurrentSort(
homeModel homeModel
.getSortForDisplay(homeModel.currentTab.value) .getSortForTab(homeModel.currentTab.value)
.withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId))) .withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId)))
) )
} }
@ -216,20 +216,20 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
return true 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 // Make sure that we update the scrolling view and allowed menu items whenever
// the tab changes. // the tab changes.
val binding = requireBinding() val binding = requireBinding()
when (tab) { when (tab) {
DisplayMode.SHOW_SONGS -> { MusicMode.SONGS -> {
updateSortMenu(tab) { id -> id != R.id.option_sort_count } updateSortMenu(tab) { id -> id != R.id.option_sort_count }
binding.homeAppbar.liftOnScrollTargetViewId = R.id.home_song_list binding.homeAppbar.liftOnScrollTargetViewId = R.id.home_song_list
} }
DisplayMode.SHOW_ALBUMS -> { MusicMode.ALBUMS -> {
updateSortMenu(tab) { id -> id != R.id.option_sort_album } updateSortMenu(tab) { id -> id != R.id.option_sort_album }
binding.homeAppbar.liftOnScrollTargetViewId = R.id.home_album_list binding.homeAppbar.liftOnScrollTargetViewId = R.id.home_album_list
} }
DisplayMode.SHOW_ARTISTS -> { MusicMode.ARTISTS -> {
updateSortMenu(tab) { id -> updateSortMenu(tab) { id ->
id == R.id.option_sort_asc || id == R.id.option_sort_asc ||
id == R.id.option_sort_name || id == R.id.option_sort_name ||
@ -238,7 +238,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
} }
binding.homeAppbar.liftOnScrollTargetViewId = R.id.home_artist_list binding.homeAppbar.liftOnScrollTargetViewId = R.id.home_artist_list
} }
DisplayMode.SHOW_GENRES -> { MusicMode.GENRES -> {
updateSortMenu(tab) { id -> updateSortMenu(tab) { id ->
id == R.id.option_sort_asc || id == R.id.option_sort_asc ||
id == R.id.option_sort_name || 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 sortMenu = requireNotNull(sortItem.subMenu)
val toHighlight = homeModel.getSortForDisplay(displayMode) val toHighlight = homeModel.getSortForTab(mode)
for (option in sortMenu) { for (option in sortMenu) {
if (option.itemId == toHighlight.mode.itemId) { if (option.itemId == toHighlight.mode.itemId) {
@ -434,10 +434,10 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return when (homeModel.tabs[position]) { return when (homeModel.tabs[position]) {
DisplayMode.SHOW_SONGS -> SongListFragment() MusicMode.SONGS -> SongListFragment()
DisplayMode.SHOW_ALBUMS -> AlbumListFragment() MusicMode.ALBUMS -> AlbumListFragment()
DisplayMode.SHOW_ARTISTS -> ArtistListFragment() MusicMode.ARTISTS -> ArtistListFragment()
DisplayMode.SHOW_GENRES -> GenreListFragment() 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.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.settings.Settings 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.application
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -59,15 +59,15 @@ class HomeViewModel(application: Application) :
val genres: StateFlow<List<Genre>> val genres: StateFlow<List<Genre>>
get() = _genres get() = _genres
var tabs: List<DisplayMode> = visibleTabs var tabs: List<MusicMode> = visibleTabs
private set private set
/** Internal getter for getting the visible library tabs */ /** 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 } get() = settings.libTabs.filterIsInstance<Tab.Visible>().map { it.mode }
private val _currentTab = MutableStateFlow(tabs[0]) 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 * 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 _shouldRecreateTabs.value = false
} }
/** Get the specific sort for the given [DisplayMode]. */ /** Get the specific sort for the given [MusicMode]. */
fun getSortForDisplay(displayMode: DisplayMode) = fun getSortForTab(tabMode: MusicMode): Sort =
when (displayMode) { when (tabMode) {
DisplayMode.SHOW_SONGS -> settings.libSongSort MusicMode.SONGS -> settings.libSongSort
DisplayMode.SHOW_ALBUMS -> settings.libAlbumSort MusicMode.ALBUMS -> settings.libAlbumSort
DisplayMode.SHOW_ARTISTS -> settings.libArtistSort MusicMode.ARTISTS -> settings.libArtistSort
DisplayMode.SHOW_GENRES -> settings.libGenreSort MusicMode.GENRES -> settings.libGenreSort
} }
/** Update the currently displayed item's [Sort]. */ /** Update the currently displayed item's [Sort]. */
fun updateCurrentSort(sort: Sort) { fun updateCurrentSort(sort: Sort) {
logD("Updating ${_currentTab.value} sort to $sort") logD("Updating ${_currentTab.value} sort to $sort")
when (_currentTab.value) { when (_currentTab.value) {
DisplayMode.SHOW_SONGS -> { MusicMode.SONGS -> {
settings.libSongSort = sort settings.libSongSort = sort
_songs.value = sort.songs(_songs.value) _songs.value = sort.songs(_songs.value)
} }
DisplayMode.SHOW_ALBUMS -> { MusicMode.ALBUMS -> {
settings.libAlbumSort = sort settings.libAlbumSort = sort
_albums.value = sort.albums(_albums.value) _albums.value = sort.albums(_albums.value)
} }
DisplayMode.SHOW_ARTISTS -> { MusicMode.ARTISTS -> {
settings.libArtistSort = sort settings.libArtistSort = sort
_artists.value = sort.artists(_artists.value) _artists.value = sort.artists(_artists.value)
} }
DisplayMode.SHOW_GENRES -> { MusicMode.GENRES -> {
settings.libGenreSort = sort settings.libGenreSort = sort
_genres.value = sort.genres(_genres.value) _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.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.music.secsToMs 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.AlbumViewHolder
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
@ -62,7 +62,7 @@ class AlbumListFragment : HomeListFragment<Album>() {
val album = homeModel.albums.value[pos] val album = homeModel.albums.value[pos]
// Change how we display the popup depending on the mode. // 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 // By Name -> Use Name
is Sort.Mode.ByName -> album.collationKey?.run { sourceString.first().uppercase() } 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.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.formatDurationMs 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.ArtistViewHolder
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
@ -57,7 +57,7 @@ class ArtistListFragment : HomeListFragment<Artist>() {
val artist = homeModel.artists.value[pos] val artist = homeModel.artists.value[pos]
// Change how we display the popup depending on the mode. // 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 // By Name -> Use Name
is Sort.Mode.ByName -> artist.collationKey?.run { sourceString.first().uppercase() } 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.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.formatDurationMs 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.GenreViewHolder
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
@ -57,7 +57,7 @@ class GenreListFragment : HomeListFragment<Genre>() {
val genre = homeModel.genres.value[pos] val genre = homeModel.genres.value[pos]
// Change how we display the popup depending on the mode. // 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 // By Name -> Use Name
is Sort.Mode.ByName -> genre.collationKey?.run { sourceString.first().uppercase() } 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 android.view.ViewGroup
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.music.secsToMs import org.oxycblt.auxio.music.secsToMs
import org.oxycblt.auxio.settings.Settings 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.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener 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. // 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 // 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. // 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 // Name -> Use name
is Sort.Mode.ByName -> song.collationKey?.run { sourceString.first().uppercase() } 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.Companion.toSequence
import org.oxycblt.auxio.home.tabs.Tab.Invisible import org.oxycblt.auxio.home.tabs.Tab.Invisible
import org.oxycblt.auxio.home.tabs.Tab.Visible 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 import org.oxycblt.auxio.util.logE
/** /**
@ -40,16 +40,16 @@ import org.oxycblt.auxio.util.logE
* VTTT * VTTT
* *
* Where V is a bit representing the visibility and T is a 3-bit integer representing the * 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 * To serialize and deserialize a tab sequence, [toSequence] and [fromSequence] can be used
* respectively. * respectively.
* *
* By default, the tab order will be SONGS, ALBUMS, ARTISTS, GENRES, PLAYLISTS * By default, the tab order will be SONGS, ALBUMS, ARTISTS, GENRES, PLAYLISTS
*/ */
sealed class Tab(open val mode: DisplayMode) { sealed class Tab(open val mode: MusicMode) {
data class Visible(override val mode: DisplayMode) : Tab(mode) data class Visible(override val mode: MusicMode) : Tab(mode)
data class Invisible(override val mode: DisplayMode) : Tab(mode) data class Invisible(override val mode: MusicMode) : Tab(mode)
companion object { companion object {
/** The length a well-formed tab sequence should be */ /** 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 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 = private val MODE_TABLE =
arrayOf( arrayOf(
DisplayMode.SHOW_SONGS, MusicMode.SONGS,
DisplayMode.SHOW_ALBUMS, MusicMode.ALBUMS,
DisplayMode.SHOW_ARTISTS, MusicMode.ARTISTS,
DisplayMode.SHOW_GENRES MusicMode.GENRES
) )
/** Convert an array [tabs] into a sequence of tabs. */ /** Convert an array [tabs] into a sequence of tabs. */

View file

@ -23,7 +23,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemTabBinding 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.ui.recycler.DialogViewHolder
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
@ -59,7 +59,7 @@ class TabAdapter(private val listener: Listener) : RecyclerView.Adapter<TabViewH
} }
interface Listener { interface Listener {
fun onVisibilityToggled(displayMode: DisplayMode) fun onVisibilityToggled(mode: MusicMode)
fun onPickUpTab(viewHolder: RecyclerView.ViewHolder) 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.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogTabsBinding import org.oxycblt.auxio.databinding.DialogTabsBinding
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -79,11 +79,8 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
binding.tabRecycler.adapter = null binding.tabRecycler.adapter = null
} }
override fun onVisibilityToggled(displayMode: DisplayMode) { override fun onVisibilityToggled(mode: MusicMode) {
// Tab viewholders bind with the initial tab state, which will drift from the actual val index = tabAdapter.tabs.indexOfFirst { it.mode == mode }
// 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 }
if (index > -1) { if (index > -1) {
val tab = tabAdapter.tabs[index] val tab = tabAdapter.tabs[index]
tabAdapter.setTab( tabAdapter.setTab(

View file

@ -24,6 +24,7 @@ import coil.imageLoader
import coil.request.Disposable import coil.request.Disposable
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Size import coil.size.Size
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
import org.oxycblt.auxio.music.Song 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 { init {
// Use clipToOutline and a background drawable to crop images. While Coil's transformation // 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 // 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) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec) super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// Emulate StyledDrawable scaling with matrix scaling.
val iconSize = max(measuredWidth, measuredHeight) / 2 val iconSize = max(measuredWidth, measuredHeight) / 2
imageMatrix = imageMatrix =
indicatorMatrix.apply { indicatorMatrix.apply {
reset() reset()
drawable?.let { drawable -> 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. // First scale the icon up to the desired size.
indicatorMatrixSrc.set( indicatorMatrixSrc.set(
0f, 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 coil.load
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre

View file

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

View file

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

View file

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

View file

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

View file

@ -26,7 +26,6 @@ import kotlinx.parcelize.Parcelize
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Date.Companion.from 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.ui.recycler.Item
import org.oxycblt.auxio.util.inRangeOrNull import org.oxycblt.auxio.util.inRangeOrNull
import org.oxycblt.auxio.util.nonZeroOrNull 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, * 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 * any (english) articles that prefix the names, and collation rules.
* since generating a collation key is non-trivial.
*/ */
val collationKey: CollationKey? by lazy { 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 { val sortName = (rawSortName ?: rawName)?.run {
when { when {
length > 5 && startsWith("the ", ignoreCase = true) -> substring(4) length > 5 && startsWith("the ", ignoreCase = true) -> substring(4)
@ -125,7 +126,8 @@ sealed class Music : Item {
*/ */
fun hashed(clazz: KClass<*>, updates: MessageDigest.() -> Unit): UID { fun hashed(clazz: KClass<*>, updates: MessageDigest.() -> Unit): UID {
// Auxio hashes consist of the MD5 hash of the non-subjective, consistent // 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") val digest = MessageDigest.getInstance("MD5")
updates(digest) updates(digest)
val uuid = digest.digest().toUuid() 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/>. * 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 androidx.annotation.IdRes
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Sort.Mode
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
/** /**
* Represents the sort modes used in Auxio. * 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 */ /** TODO: Stub class, not implemented yet */
class CacheLayer { class CacheLayer {
fun init() { fun init() {
// STUB: Add cache database
} }
/** /**
* Write a list of newly-indexed raw songs to the database. * Write a list of newly-indexed raw songs to the database.
*/ */
fun finalize(rawSongs: List<Song.Raw>) { 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 * 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 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) * 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 * 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 * 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! * 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 = raw.albumArtistNames =
cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings) cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings)
// Get the genre value we had to query for in initialization
raw.genreNames = genreNamesMap[raw.mediaStoreId] 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 * A [MediaStoreLayer] that completes the music loading process in a way compatible from
* API 21 onwards to API 29.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : 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["TORY"]?.run { get(0).toIntOrNull() }
?: tags["TYER"]?.run { get(0).toIntOrNull() } ?: return null ?: 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"] val tdat = tags["TDAT"]
return if (tdat != null && tdat[0].length == 4 && tdat[0].isDigitsOnly()) { return if (tdat != null && tdat[0].length == 4 && tdat[0].isDigitsOnly()) {
val mm = tdat[0].substring(0..1).toInt() 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.Genre
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song 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.Api21MediaStoreLayer
import org.oxycblt.auxio.music.extractor.Api29MediaStoreLayer import org.oxycblt.auxio.music.extractor.Api29MediaStoreLayer
import org.oxycblt.auxio.music.extractor.Api30MediaStoreLayer import org.oxycblt.auxio.music.extractor.Api30MediaStoreLayer
import org.oxycblt.auxio.music.extractor.CacheLayer import org.oxycblt.auxio.music.extractor.CacheLayer
import org.oxycblt.auxio.music.extractor.MetadataLayer import org.oxycblt.auxio.music.extractor.MetadataLayer
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW

View file

@ -64,7 +64,7 @@ class IndexingNotification(private val context: Context) :
lastUpdateTime = SystemClock.elapsedRealtime() 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") logD("Updating state to $indexing")
setContentText( setContentText(
context.getString(R.string.fmt_indexing, indexing.current, indexing.total) 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.Song
import org.oxycblt.auxio.music.msToDs import org.oxycblt.auxio.music.msToDs
import org.oxycblt.auxio.playback.state.RepeatMode 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.MainNavigationAction
import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.util.collectImmediately 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.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.dsToMs import org.oxycblt.auxio.music.dsToMs
@ -92,15 +93,15 @@ class PlaybackViewModel(application: Application) :
// --- PLAYING FUNCTIONS --- // --- PLAYING FUNCTIONS ---
/** Play a [song] with the [mode] specified, */ /** 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 // TODO: Remove this function when selection is implemented
val parent = val parent =
when (mode) { when (mode) {
PlaybackMode.IN_ALBUM -> song.album MusicMode.GENRES -> song.album
PlaybackMode.IN_ARTIST -> song.album.artist MusicMode.ARTISTS -> song.album.artist
PlaybackMode.IN_GENRE -> song.genres.maxBy { it.songs.size } MusicMode.ALBUMS -> song.genres.maxBy { it.songs.size }
PlaybackMode.ALL_SONGS -> null MusicMode.SONGS -> null
} }
playbackManager.play(song, parent, settings) playbackManager.play(song, parent, settings)

View file

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

View file

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

View file

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

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.playback package org.oxycblt.auxio.playback.ui
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
@ -67,7 +67,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
// this component. // this component.
val from = max(value, 0) 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 // the app, and that the user is not currently seeking (which would cause the SeekBar
// to jump around). // to jump around).
if (from <= durationDs && !isActivated) { 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.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
@ -78,7 +79,15 @@ class SearchFragment :
override fun onBindingCreated(binding: FragmentSearchBinding, savedInstanceState: Bundle?) { override fun onBindingCreated(binding: FragmentSearchBinding, savedInstanceState: Bundle?) {
binding.searchToolbar.apply { 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 { setNavigationOnClickListener {
imm.hide() 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.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.settings.Settings 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.Header
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.application import org.oxycblt.auxio.util.application
@ -59,7 +59,7 @@ class SearchViewModel(application: Application) :
val searchResults: StateFlow<List<Item>> val searchResults: StateFlow<List<Item>>
get() = _searchResults get() = _searchResults
val filterMode: DisplayMode? val filterMode: MusicMode?
get() = settings.searchFilterMode get() = settings.searchFilterMode
private var lastQuery: String? = null 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. // 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 -> library.artists.filterArtistsBy(query)?.let { artists ->
results.add(Header(R.string.lbl_artists)) results.add(Header(R.string.lbl_artists))
results.addAll(sort.artists(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 -> library.albums.filterAlbumsBy(query)?.let { albums ->
results.add(Header(R.string.lbl_albums)) results.add(Header(R.string.lbl_albums))
results.addAll(sort.albums(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 -> library.genres.filterGenresBy(query)?.let { genres ->
results.add(Header(R.string.lbl_genres)) results.add(Header(R.string.lbl_genres))
results.addAll(sort.genres(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 -> library.songs.filterSongsBy(query)?.let { songs ->
results.add(Header(R.string.lbl_songs)) results.add(Header(R.string.lbl_songs))
results.addAll(sort.songs(songs)) results.addAll(sort.songs(songs))
@ -128,10 +128,10 @@ class SearchViewModel(application: Application) :
fun updateFilterModeWithId(@IdRes id: Int) { fun updateFilterModeWithId(@IdRes id: Int) {
val newFilterMode = val newFilterMode =
when (id) { when (id) {
R.id.option_filter_songs -> DisplayMode.SHOW_SONGS R.id.option_filter_songs -> MusicMode.SONGS
R.id.option_filter_albums -> DisplayMode.SHOW_ALBUMS R.id.option_filter_albums -> MusicMode.ALBUMS
R.id.option_filter_artists -> DisplayMode.SHOW_ARTISTS R.id.option_filter_artists -> MusicMode.ARTISTS
R.id.option_filter_genres -> DisplayMode.SHOW_GENRES R.id.option_filter_genres -> MusicMode.GENRES
else -> null else -> null
} }

View file

@ -21,21 +21,21 @@ import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.os.storage.StorageManager import android.os.storage.StorageManager
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.dirs.MusicDirs import org.oxycblt.auxio.music.dirs.MusicDirs
import org.oxycblt.auxio.playback.BarAction 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.ReplayGainMode
import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp 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.ui.accent.Accent
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull 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() { fun release() {
inner.unregisterOnSharedPreferenceChangeListener(this) inner.unregisterOnSharedPreferenceChangeListener(this)
} }
@ -86,7 +147,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
/** The current accent. */ /** The current accent. */
var accent: Accent var accent: Accent
get() = handleAccentCompat(context, inner) get() = Accent.from(inner.getInt(context.getString(R.string.set_accent), Accent.DEFAULT))
set(value) { set(value) {
inner.edit { inner.edit {
putInt(context.getString(R.string.set_key_accent), value.index) 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 */ /** What queue to create when a song is selected from the library or search */
val libPlaybackMode: PlaybackMode val libPlaybackMode: MusicMode
get() = get() =
PlaybackMode.fromInt( MusicMode.fromInt(
inner.getInt( inner.getInt(
context.getString(R.string.set_key_library_song_playback_mode), context.getString(R.string.set_key_library_song_playback_mode),
Int.MIN_VALUE 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 * What queue t create when a song is selected from an album/artist/genre. Null means to default
* to the currently shown item. * to the currently shown item.
*/ */
val detailPlaybackMode: PlaybackMode? val detailPlaybackMode: MusicMode?
get() = get() =
PlaybackMode.fromInt( MusicMode.fromInt(
inner.getInt( inner.getInt(
context.getString(R.string.set_key_detail_song_playback_mode), context.getString(R.string.set_key_detail_song_playback_mode),
Int.MIN_VALUE 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 */ /** The current filter mode of the search tab */
var searchFilterMode: DisplayMode? var searchFilterMode: MusicMode?
get() = get() =
DisplayMode.fromInt( MusicMode.fromInt(
inner.getInt(context.getString(R.string.set_key_search_filter), Int.MIN_VALUE) inner.getInt(context.getString(R.string.set_key_search_filter), Int.MIN_VALUE)
) )
set(value) { set(value) {
@ -370,35 +431,11 @@ class Settings(private val context: Context, private val callback: Callback? = n
apply() apply()
} }
} }
}
// --- COMPAT --- /** Cache of the old keys used in Auxio. */
private object OldKeys {
fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent { const val KEY_ACCENT3 = "auxio_accent"
val currentKey = context.getString(R.string.set_key_accent) const val KEY_LIB_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2"
const val KEY_DETAIL_PLAYBACK_MODE = "auxio_detail_song_play_mode"
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()
}
} }
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 coil.transform.RoundedCornersTransformation
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider 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.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.InternalPlayer import org.oxycblt.auxio.playback.state.InternalPlayer

View file

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

View file

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

View file

@ -20,7 +20,7 @@
android:id="@+id/playback_sheet" android:id="@+id/playback_sheet"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <androidx.fragment.app.FragmentContainerView
android:id="@+id/playback_bar_fragment" android:id="@+id/playback_bar_fragment"

View file

@ -22,7 +22,7 @@
style="@style/Widget.Auxio.DisableDropShadows" style="@style/Widget.Auxio.DisableDropShadows"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <androidx.fragment.app.FragmentContainerView
android:id="@+id/playback_bar_fragment" android:id="@+id/playback_bar_fragment"

View file

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

View file

@ -78,7 +78,7 @@
app:layout_constraintTop_toBottomOf="@+id/playback_artist" app:layout_constraintTop_toBottomOf="@+id/playback_artist"
tools:text="Album Name" /> tools:text="Album Name" />
<org.oxycblt.auxio.playback.StyledSeekBar <org.oxycblt.auxio.playback.ui.StyledSeekBar
android:id="@+id/playback_seek_bar" android:id="@+id/playback_seek_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -87,7 +87,7 @@
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<org.oxycblt.auxio.playback.ForcedLTRFrameLayout <org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout
android:id="@+id/playback_controls_container" android:id="@+id/playback_controls_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -126,7 +126,7 @@
app:layout_constraintStart_toEndOf="@+id/playback_repeat" app:layout_constraintStart_toEndOf="@+id/playback_repeat"
app:layout_constraintTop_toTopOf="@+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" android:id="@+id/playback_play_pause"
style="@style/Widget.Auxio.Button.PlayPause" style="@style/Widget.Auxio.Button.PlayPause"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -166,7 +166,7 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</org.oxycblt.auxio.playback.ForcedLTRFrameLayout> </org.oxycblt.auxio.playback.ui.ForcedLTRFrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </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_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_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_library_song_playback_mode" translatable="false">auxio_library_playback_mode</string>
<string name="set_key_detail_song_playback_mode" translatable="false">auxio_detail_song_play_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_keep_shuffle" translatable="false">KEY_KEEP_SHUFFLE</string>
<string name="set_key_rewind_prev" translatable="false">KEY_PREV_REWIND</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> <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="bar_action_shuffle">0xA11B</integer>
<integer name="play_mode_none">-2147483648</integer> <integer name="play_mode_none">-2147483648</integer>
<integer name="play_mode_genre">0xA103</integer> <integer name="play_mode_genre">0xA108</integer>
<integer name="play_mode_artist">0xA104</integer> <integer name="play_mode_artist">0xA109</integer>
<integer name="play_mode_album">0xA105</integer> <integer name="play_mode_album">0xA10A</integer>
<integer name="play_mode_songs">0xA106</integer> <integer name="play_mode_songs">0xA10B</integer>
<integer name="replay_gain_track">0xA111</integer> <integer name="replay_gain_track">0xA111</integer>
<integer name="replay_gain_album">0xA112</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: Other data types represent a specific UI configuration or state:
- Data structures like `Sort` contain an ascending state that can be modified immutably. - 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: 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 - `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. library should use this.
- `Indexer`, which manages how music is loaded. This is only used by code that must manage or - `Indexer`, which manages how music is loaded. This is only used by code that must manage or
mirror the music loading state. 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: Internally, there are several other major systems:
- `IndexerService`, which does the indexer work in the background. - `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 - `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. 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: The music loading process is roughly as follows:
1. Something triggers `IndexerService` to start indexing, either by the UI or by the service itself 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` - Important `Fragment` superclasses like `ViewBindingFragment` and `MenuFragment`
- Customized views such as `AuxioAppBarLayout`, and others, which fix shortcomings with the - Customized views such as `AuxioAppBarLayout`, and others, which fix shortcomings with the
default implementations. 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 - `ForegroundManager` and `ServiceNotification`, which remove boilerplate regarding service
foreground instantiation. foreground instantiation.
- The `RecyclerView` adapter framework described previously. - The `RecyclerView` adapter framework described previously.