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:
parent
8aac87e02c
commit
c342fb364b
52 changed files with 322 additions and 355 deletions
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() }
|
||||||
|
|
||||||
|
|
|
@ -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() }
|
||||||
|
|
||||||
|
|
|
@ -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() }
|
||||||
|
|
||||||
|
|
|
@ -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() }
|
||||||
|
|
||||||
|
|
|
@ -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. */
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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. */
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
||||||
|
|
66
app/src/main/java/org/oxycblt/auxio/music/MusicMode.kt
Normal file
66
app/src/main/java/org/oxycblt/auxio/music/MusicMode.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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) :
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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) {
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue