diff --git a/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt b/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt index b1d028463..c1fb348e8 100644 --- a/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt +++ b/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt @@ -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. diff --git a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt index 914ffbef8..6b13018e5 100644 --- a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt +++ b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt @@ -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. diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index 7549e4722..b35b28869 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -45,8 +45,6 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat * * TODO: Add multi-select * - * TODO: Remove asterisk imports - * * @author OxygenCobalt */ class MainActivity : AppCompatActivity() { diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index d9137f893..def7306ee 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 9dad5ccf9..7e4754e97 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index 0514fe3b1..c2ba91fe4 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index 1196c02ad..907899caf 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index f086aac08..13cad1809 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index a2f7bd10c..dd68dd14f 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -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(), 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(), 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(), 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(), 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(), 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(), 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() } } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 5d3154333..9238af08f 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -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> get() = _genres - var tabs: List = visibleTabs + var tabs: List = visibleTabs private set /** Internal getter for getting the visible library tabs */ - private val visibleTabs: List + private val visibleTabs: List get() = settings.libTabs.filterIsInstance().map { it.mode } private val _currentTab = MutableStateFlow(tabs[0]) - val currentTab: StateFlow = _currentTab + val currentTab: StateFlow = _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) } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt index c3c0a6a6a..3ff5a0058 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt @@ -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() { 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() } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt index b7cef8180..ec77ca008 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt @@ -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() { 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() } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt index 73d1d0c04..f64b19204 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt @@ -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() { 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() } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index 845592583..90a14cbaa 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -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() { // 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() } diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt index c63cb77f2..e0726e3ff 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt @@ -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. */ diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt index 7ab2c9df0..35285905b 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt @@ -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(), 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( diff --git a/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt b/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt index 330c423df..93faa7fcd 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt @@ -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 /** diff --git a/app/src/main/java/org/oxycblt/auxio/image/IndicatorView.kt b/app/src/main/java/org/oxycblt/auxio/image/IndicatorView.kt index 5a980cfaf..e86a30ad1 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/IndicatorView.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/IndicatorView.kt @@ -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) - } - } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt b/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt index 213117291..90fab0acd 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/image/BaseFetcher.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/BaseFetcher.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/image/BaseFetcher.kt rename to app/src/main/java/org/oxycblt/auxio/image/extractor/BaseFetcher.kt index 7e554ecae..921b11820 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/BaseFetcher.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/BaseFetcher.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.image +package org.oxycblt.auxio.image.extractor import android.content.Context import android.graphics.Bitmap diff --git a/app/src/main/java/org/oxycblt/auxio/image/Components.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/image/Components.kt rename to app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt index df7d8e265..c7ba9fddb 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/Components.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -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. */ diff --git a/app/src/main/java/org/oxycblt/auxio/image/CrossfadeTransitionFactory.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/CrossfadeTransitionFactory.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/image/CrossfadeTransitionFactory.kt rename to app/src/main/java/org/oxycblt/auxio/image/extractor/CrossfadeTransitionFactory.kt index 7d15697d6..6d364a1bd 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/CrossfadeTransitionFactory.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/CrossfadeTransitionFactory.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.image +package org.oxycblt.auxio.image.extractor import coil.decode.DataSource import coil.drawable.CrossfadeDrawable diff --git a/app/src/main/java/org/oxycblt/auxio/image/SquareFrameTransform.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/SquareFrameTransform.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/image/SquareFrameTransform.kt rename to app/src/main/java/org/oxycblt/auxio/image/extractor/SquareFrameTransform.kt index f81fd962f..69ad651c0 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/SquareFrameTransform.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/SquareFrameTransform.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.image +package org.oxycblt.auxio.image.extractor import android.graphics.Bitmap import coil.size.Size diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index 79c7918ce..b649cc3cc 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -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() diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicMode.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicMode.kt new file mode 100644 index 000000000..0054ce4e6 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicMode.kt @@ -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 . + */ + +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 + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt b/app/src/main/java/org/oxycblt/auxio/music/Sort.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/ui/Sort.kt rename to app/src/main/java/org/oxycblt/auxio/music/Sort.kt index 7047bc01c..35ba7c4c2 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Sort.kt @@ -15,18 +15,12 @@ * along with this program. If not, see . */ -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. diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheLayer.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheLayer.kt index 25bf864e0..75965476f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheLayer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheLayer.kt @@ -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) { - // STUB: Add cache database } /** diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreLayer.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreLayer.kt index 8c691f2a9..6e8fb70e8 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreLayer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreLayer.kt @@ -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) : diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataLayer.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataLayer.kt index f40debe8c..18496b517 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataLayer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataLayer.kt @@ -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() diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt index 0a03487c5..e97c28388 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt index 368eb4547..145df5c92 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt @@ -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) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt deleted file mode 100644 index 40d0ca545..000000000 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt +++ /dev/null @@ -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 . - */ - -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 - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index 54f75c23b..c1cfd7ed8 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 4fb2888db..9e1d4f26f 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -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) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/AnimatedMaterialButton.kt b/app/src/main/java/org/oxycblt/auxio/playback/ui/AnimatedMaterialButton.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/playback/AnimatedMaterialButton.kt rename to app/src/main/java/org/oxycblt/auxio/playback/ui/AnimatedMaterialButton.kt index 4531084ee..2ab20dbdd 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/AnimatedMaterialButton.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/ui/AnimatedMaterialButton.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.playback +package org.oxycblt.auxio.playback.ui import android.animation.ValueAnimator import android.content.Context diff --git a/app/src/main/java/org/oxycblt/auxio/playback/ForcedLTRFrameLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/ui/ForcedLTRFrameLayout.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/playback/ForcedLTRFrameLayout.kt rename to app/src/main/java/org/oxycblt/auxio/playback/ui/ForcedLTRFrameLayout.kt index 4cc33e656..9fb05e94c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/ForcedLTRFrameLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/ui/ForcedLTRFrameLayout.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.playback +package org.oxycblt.auxio.playback.ui import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt b/app/src/main/java/org/oxycblt/auxio/playback/ui/PlaybackSheetBehavior.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt rename to app/src/main/java/org/oxycblt/auxio/playback/ui/PlaybackSheetBehavior.kt index 04547a86b..de8dec70b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/ui/PlaybackSheetBehavior.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.playback +package org.oxycblt.auxio.playback.ui import android.content.Context import android.graphics.drawable.LayerDrawable diff --git a/app/src/main/java/org/oxycblt/auxio/playback/StyledSeekBar.kt b/app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/playback/StyledSeekBar.kt rename to app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt index 91f88ebd4..106c870f7 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/StyledSeekBar.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -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) { diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index 12e3020f7..a8814b368 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -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() diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index 34a5e3ee4..5eb802480 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -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> 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 } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt index 2345892b2..17e2a5558 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -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" } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/DisplayMode.kt b/app/src/main/java/org/oxycblt/auxio/ui/DisplayMode.kt deleted file mode 100644 index 2bdd1d94f..000000000 --- a/app/src/main/java/org/oxycblt/auxio/ui/DisplayMode.kt +++ /dev/null @@ -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 . - */ - -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 - } - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index 8bae68a1b..730ddb8c1 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -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 diff --git a/app/src/main/res/layout-h480dp/fragment_playback_panel.xml b/app/src/main/res/layout-h480dp/fragment_playback_panel.xml index 18a8eafa9..5a813afae 100644 --- a/app/src/main/res/layout-h480dp/fragment_playback_panel.xml +++ b/app/src/main/res/layout-h480dp/fragment_playback_panel.xml @@ -64,7 +64,7 @@ tools:text="Album Name" /> - - - - + diff --git a/app/src/main/res/layout-sw600dp/fragment_playback_panel.xml b/app/src/main/res/layout-sw600dp/fragment_playback_panel.xml index 26ef927c7..8e0b14205 100644 --- a/app/src/main/res/layout-sw600dp/fragment_playback_panel.xml +++ b/app/src/main/res/layout-sw600dp/fragment_playback_panel.xml @@ -64,7 +64,7 @@ tools:text="Album Name" /> - - - - + diff --git a/app/src/main/res/layout-w600dp-land/fragment_main.xml b/app/src/main/res/layout-w600dp-land/fragment_main.xml index a4a0fd849..c757e7f93 100644 --- a/app/src/main/res/layout-w600dp-land/fragment_main.xml +++ b/app/src/main/res/layout-w600dp-land/fragment_main.xml @@ -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"> + app:layout_behavior="org.oxycblt.auxio.playback.ui.PlaybackSheetBehavior"> - - + - - + diff --git a/app/src/main/res/layout/fragment_playback_panel.xml b/app/src/main/res/layout/fragment_playback_panel.xml index 0824b0b21..24700cb58 100644 --- a/app/src/main/res/layout/fragment_playback_panel.xml +++ b/app/src/main/res/layout/fragment_playback_panel.xml @@ -78,7 +78,7 @@ app:layout_constraintTop_toBottomOf="@+id/playback_artist" tools:text="Album Name" /> - - - - + diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index 7f518038c..3d5fcf691 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -20,8 +20,8 @@ auxio_pre_amp_with auxio_pre_amp_without - KEY_SONG_PLAY_MODE2 - auxio_detail_song_play_mode + auxio_library_playback_mode + auxio_detail_playback_mode KEY_KEEP_SHUFFLE KEY_PREV_REWIND KEY_LOOP_PAUSE @@ -127,10 +127,10 @@ 0xA11B -2147483648 - 0xA103 - 0xA104 - 0xA105 - 0xA106 + 0xA108 + 0xA109 + 0xA10A + 0xA10B 0xA111 0xA112 diff --git a/info/ARCHITECTURE.md b/info/ARCHITECTURE.md index 094af4a94..559f80bf0 100644 --- a/info/ARCHITECTURE.md +++ b/info/ARCHITECTURE.md @@ -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.